summaryrefslogtreecommitdiffstats
path: root/devtools/shared
diff options
context:
space:
mode:
authorMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
committerMatt A. Tobin <mattatobin@localhost.localdomain>2018-02-02 04:16:08 -0500
commit5f8de423f190bbb79a62f804151bc24824fa32d8 (patch)
tree10027f336435511475e392454359edea8e25895d /devtools/shared
parent49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff)
downloadUXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz
UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip
Add m-esr52 at 52.6.0
Diffstat (limited to 'devtools/shared')
-rw-r--r--devtools/shared/DevToolsUtils.js672
-rw-r--r--devtools/shared/Loader.jsm244
-rw-r--r--devtools/shared/Parser.jsm2451
-rw-r--r--devtools/shared/ThreadSafeDevToolsUtils.js334
-rw-r--r--devtools/shared/acorn/LICENSE23
-rw-r--r--devtools/shared/acorn/UPGRADING.md31
-rw-r--r--devtools/shared/acorn/acorn.js3330
-rw-r--r--devtools/shared/acorn/acorn_loose.js1302
-rw-r--r--devtools/shared/acorn/moz.build13
-rw-r--r--devtools/shared/acorn/tests/unit/head_acorn.js75
-rw-r--r--devtools/shared/acorn/tests/unit/test_import_acorn.js18
-rw-r--r--devtools/shared/acorn/tests/unit/test_lenient_parser.js62
-rw-r--r--devtools/shared/acorn/tests/unit/test_same_ast.js37
-rw-r--r--devtools/shared/acorn/tests/unit/xpcshell.ini10
-rw-r--r--devtools/shared/acorn/walk.js377
-rw-r--r--devtools/shared/apps/Devices.jsm53
-rw-r--r--devtools/shared/apps/Simulator.jsm44
-rw-r--r--devtools/shared/apps/app-actor-front.js840
-rw-r--r--devtools/shared/apps/moz.build10
-rw-r--r--devtools/shared/async-storage.js188
-rw-r--r--devtools/shared/async-utils.js107
-rw-r--r--devtools/shared/builtin-modules.js288
-rw-r--r--devtools/shared/client/connection-manager.js382
-rw-r--r--devtools/shared/client/main.js3123
-rw-r--r--devtools/shared/client/moz.build10
-rw-r--r--devtools/shared/content-observer.js71
-rw-r--r--devtools/shared/css/color-db.js162
-rw-r--r--devtools/shared/css/color.js1117
-rw-r--r--devtools/shared/css/generated/generate-properties-db.js55
-rw-r--r--devtools/shared/css/generated/mach_commands.py91
-rw-r--r--devtools/shared/css/generated/moz.build9
-rw-r--r--devtools/shared/css/generated/properties-db.js9367
-rw-r--r--devtools/shared/css/generated/properties-db.js.in20
-rw-r--r--devtools/shared/css/lexer.js1240
-rw-r--r--devtools/shared/css/moz.build17
-rw-r--r--devtools/shared/css/parsing-utils.js1171
-rw-r--r--devtools/shared/css/properties-db.js100
-rw-r--r--devtools/shared/defer.js25
-rw-r--r--devtools/shared/deprecated-sync-thenables.js119
-rw-r--r--devtools/shared/discovery/discovery.js496
-rw-r--r--devtools/shared/discovery/moz.build11
-rw-r--r--devtools/shared/discovery/tests/unit/test_discovery.js161
-rw-r--r--devtools/shared/discovery/tests/unit/xpcshell.ini7
-rw-r--r--devtools/shared/dom-node-constants.js30
-rw-r--r--devtools/shared/dom-node-filter-constants.js21
-rw-r--r--devtools/shared/event-emitter.js250
-rw-r--r--devtools/shared/flags.js21
-rw-r--r--devtools/shared/fronts/actor-registry.js67
-rw-r--r--devtools/shared/fronts/addons.js17
-rw-r--r--devtools/shared/fronts/animation.js140
-rw-r--r--devtools/shared/fronts/call-watcher.js226
-rw-r--r--devtools/shared/fronts/canvas.js91
-rw-r--r--devtools/shared/fronts/css-properties.js323
-rw-r--r--devtools/shared/fronts/csscoverage.js125
-rw-r--r--devtools/shared/fronts/device.js54
-rw-r--r--devtools/shared/fronts/director-manager.js47
-rw-r--r--devtools/shared/fronts/director-registry.js21
-rw-r--r--devtools/shared/fronts/emulation.js24
-rw-r--r--devtools/shared/fronts/eventlooplag.js15
-rw-r--r--devtools/shared/fronts/framerate.js19
-rw-r--r--devtools/shared/fronts/gcli.js40
-rw-r--r--devtools/shared/fronts/highlighters.js34
-rw-r--r--devtools/shared/fronts/inspector.js1007
-rw-r--r--devtools/shared/fronts/layout.js30
-rw-r--r--devtools/shared/fronts/memory.js92
-rw-r--r--devtools/shared/fronts/moz.build41
-rw-r--r--devtools/shared/fronts/performance-entries.js17
-rw-r--r--devtools/shared/fronts/performance-recording.js152
-rw-r--r--devtools/shared/fronts/performance.js148
-rw-r--r--devtools/shared/fronts/preference.js31
-rw-r--r--devtools/shared/fronts/profiler.js80
-rw-r--r--devtools/shared/fronts/promises.js27
-rw-r--r--devtools/shared/fronts/reflow.js29
-rw-r--r--devtools/shared/fronts/settings.js29
-rw-r--r--devtools/shared/fronts/storage.js32
-rw-r--r--devtools/shared/fronts/string.js47
-rw-r--r--devtools/shared/fronts/styleeditor.js113
-rw-r--r--devtools/shared/fronts/styles.js421
-rw-r--r--devtools/shared/fronts/stylesheets.js184
-rw-r--r--devtools/shared/fronts/timeline.js25
-rw-r--r--devtools/shared/fronts/webaudio.js83
-rw-r--r--devtools/shared/fronts/webgl.js45
-rw-r--r--devtools/shared/gcli/commands/addon.js320
-rw-r--r--devtools/shared/gcli/commands/appcache.js186
-rw-r--r--devtools/shared/gcli/commands/calllog.js219
-rw-r--r--devtools/shared/gcli/commands/cmd.js178
-rw-r--r--devtools/shared/gcli/commands/cookie.js300
-rw-r--r--devtools/shared/gcli/commands/csscoverage.js201
-rw-r--r--devtools/shared/gcli/commands/folder.js77
-rw-r--r--devtools/shared/gcli/commands/highlight.js158
-rw-r--r--devtools/shared/gcli/commands/index.js179
-rw-r--r--devtools/shared/gcli/commands/inject.js86
-rw-r--r--devtools/shared/gcli/commands/jsb.js134
-rw-r--r--devtools/shared/gcli/commands/listen.js106
-rw-r--r--devtools/shared/gcli/commands/mdn.js83
-rw-r--r--devtools/shared/gcli/commands/measure.js112
-rw-r--r--devtools/shared/gcli/commands/media.js56
-rw-r--r--devtools/shared/gcli/commands/moz.build30
-rw-r--r--devtools/shared/gcli/commands/pagemod.js276
-rw-r--r--devtools/shared/gcli/commands/paintflashing.js201
-rw-r--r--devtools/shared/gcli/commands/qsa.js24
-rw-r--r--devtools/shared/gcli/commands/restart.js77
-rw-r--r--devtools/shared/gcli/commands/rulers.js110
-rw-r--r--devtools/shared/gcli/commands/screenshot.js579
-rw-r--r--devtools/shared/gcli/commands/security.js328
-rw-r--r--devtools/shared/gcli/moz.build23
-rw-r--r--devtools/shared/gcli/source/LICENSE202
-rw-r--r--devtools/shared/gcli/source/docs/design.md102
-rw-r--r--devtools/shared/gcli/source/docs/developing-gcli.md213
-rw-r--r--devtools/shared/gcli/source/docs/index.md150
-rw-r--r--devtools/shared/gcli/source/docs/running-tests.md60
-rw-r--r--devtools/shared/gcli/source/docs/writing-commands.md757
-rw-r--r--devtools/shared/gcli/source/docs/writing-tests.md20
-rw-r--r--devtools/shared/gcli/source/docs/writing-types.md106
-rw-r--r--devtools/shared/gcli/source/lib/gcli/cli.js2209
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/clear.js59
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/commands.js570
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/context.js62
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/help.js387
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/mocks.js68
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/moz.build16
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/pref.js93
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/preflist.js214
-rw-r--r--devtools/shared/gcli/source/lib/gcli/commands/test.js215
-rw-r--r--devtools/shared/gcli/source/lib/gcli/connectors/connectors.js157
-rw-r--r--devtools/shared/gcli/source/lib/gcli/connectors/moz.build9
-rw-r--r--devtools/shared/gcli/source/lib/gcli/converters/basic.js94
-rw-r--r--devtools/shared/gcli/source/lib/gcli/converters/converters.js280
-rw-r--r--devtools/shared/gcli/source/lib/gcli/converters/html.js47
-rw-r--r--devtools/shared/gcli/source/lib/gcli/converters/moz.build12
-rw-r--r--devtools/shared/gcli/source/lib/gcli/converters/terminal.js56
-rw-r--r--devtools/shared/gcli/source/lib/gcli/fields/delegate.js96
-rw-r--r--devtools/shared/gcli/source/lib/gcli/fields/fields.js245
-rw-r--r--devtools/shared/gcli/source/lib/gcli/fields/moz.build11
-rw-r--r--devtools/shared/gcli/source/lib/gcli/fields/selection.js124
-rw-r--r--devtools/shared/gcli/source/lib/gcli/index.js29
-rw-r--r--devtools/shared/gcli/source/lib/gcli/l10n.js74
-rw-r--r--devtools/shared/gcli/source/lib/gcli/languages/command.html14
-rw-r--r--devtools/shared/gcli/source/lib/gcli/languages/command.js563
-rw-r--r--devtools/shared/gcli/source/lib/gcli/languages/javascript.js86
-rw-r--r--devtools/shared/gcli/source/lib/gcli/languages/languages.js179
-rw-r--r--devtools/shared/gcli/source/lib/gcli/languages/moz.build12
-rw-r--r--devtools/shared/gcli/source/lib/gcli/moz.build13
-rw-r--r--devtools/shared/gcli/source/lib/gcli/mozui/completer.js151
-rw-r--r--devtools/shared/gcli/source/lib/gcli/mozui/inputter.js657
-rw-r--r--devtools/shared/gcli/source/lib/gcli/mozui/moz.build11
-rw-r--r--devtools/shared/gcli/source/lib/gcli/mozui/tooltip.js298
-rw-r--r--devtools/shared/gcli/source/lib/gcli/settings.js284
-rw-r--r--devtools/shared/gcli/source/lib/gcli/system.js370
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/array.js80
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/boolean.js62
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/command.js255
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/date.js248
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/delegate.js158
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/file.js96
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/fileparser.js19
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/javascript.js522
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/moz.build25
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/node.js201
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/number.js181
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/resource.js270
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/selection.js389
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/setting.js62
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/string.js92
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/types.js1146
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/union.js117
-rw-r--r--devtools/shared/gcli/source/lib/gcli/types/url.js86
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/focus.js403
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/history.js71
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/intro.js90
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/menu.css69
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/menu.html20
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/menu.js328
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/moz.build15
-rw-r--r--devtools/shared/gcli/source/lib/gcli/ui/view.js87
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/domtemplate.js20
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/fileparser.js281
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/filesystem.js130
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/host.js230
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/l10n.js80
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/legacy.js147
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/moz.build17
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/prism.js361
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/spell.js197
-rw-r--r--devtools/shared/gcli/source/lib/gcli/util/util.js685
-rw-r--r--devtools/shared/gcli/templater.js602
-rw-r--r--devtools/shared/heapsnapshot/.gitattributes1
-rw-r--r--devtools/shared/heapsnapshot/AutoMemMap.cpp64
-rw-r--r--devtools/shared/heapsnapshot/AutoMemMap.h75
-rw-r--r--devtools/shared/heapsnapshot/CensusUtils.js489
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.pb.cc2542
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.pb.h1893
-rw-r--r--devtools/shared/heapsnapshot/CoreDump.proto143
-rw-r--r--devtools/shared/heapsnapshot/DeserializedNode.cpp150
-rw-r--r--devtools/shared/heapsnapshot/DeserializedNode.h317
-rw-r--r--devtools/shared/heapsnapshot/DominatorTree.cpp140
-rw-r--r--devtools/shared/heapsnapshot/DominatorTree.h67
-rw-r--r--devtools/shared/heapsnapshot/DominatorTreeNode.js336
-rw-r--r--devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp86
-rw-r--r--devtools/shared/heapsnapshot/FileDescriptorOutputStream.h41
-rw-r--r--devtools/shared/heapsnapshot/HeapAnalysesClient.js277
-rw-r--r--devtools/shared/heapsnapshot/HeapAnalysesWorker.js303
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshot.cpp1652
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshot.h239
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js95
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h32
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp53
-rw-r--r--devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h35
-rw-r--r--devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl35
-rw-r--r--devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp100
-rw-r--r--devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h70
-rw-r--r--devtools/shared/heapsnapshot/census-tree-node.js748
-rwxr-xr-xdevtools/shared/heapsnapshot/generate-core-dump-sources.sh26
-rw-r--r--devtools/shared/heapsnapshot/moz.build62
-rw-r--r--devtools/shared/heapsnapshot/shortest-paths.js91
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp100
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp91
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DevTools.h276
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp73
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp64
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp53
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp37
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp30
-rw-r--r--devtools/shared/heapsnapshot/tests/gtest/moz.build31
-rw-r--r--devtools/shared/heapsnapshot/tests/mochitest/chrome.ini8
-rw-r--r--devtools/shared/heapsnapshot/tests/mochitest/mochitest.ini6
-rw-r--r--devtools/shared/heapsnapshot/tests/mochitest/test_DominatorTree_01.html37
-rw-r--r--devtools/shared/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html25
-rw-r--r--devtools/shared/heapsnapshot/tests/mochitest/test_saveHeapSnapshot_e10s_01.html82
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/Census.jsm165
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/Match.jsm190
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/dominator-tree-worker.js47
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js448
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/heap-snapshot-worker.js46
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js46
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js45
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js47
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js53
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_attachShortestPaths_01.js132
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_getNodeByIdAlongPath_01.js44
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_01.js112
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_02.js30
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_03.js117
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js164
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_01.js23
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_02.js40
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js13
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_04.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_05.js75
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_06.js56
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_01.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_02.js23
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_01.js59
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_02.js22
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js62
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js89
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCreationTime_01.js58
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js69
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_02.js31
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js81
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_readHeapSnapshot_01.js18
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js54
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js59
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js27
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js29
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_03.js48
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js118
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js44
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js109
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js52
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_01.js69
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_02.js47
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_creationTime_01.js30
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_deepStack_01.js70
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_describeNode_01.js42
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_01.js31
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_02.js57
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_03.js34
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_04.js36
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_05.js24
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_06.js125
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_07.js82
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_08.js82
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_09.js92
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_10.js68
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_11.js116
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_12.js50
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js20
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js36
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js40
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js82
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js76
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js136
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js96
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js159
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js145
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js200
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js200
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js142
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-09.js44
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js43
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_01.js74
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_02.js25
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js73
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_04.js63
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_05.js34
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js137
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js105
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js124
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js59
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js102
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_census_filtering_05.js71
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_countToBucketBreakdown_01.js37
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_deduplicatePaths_01.js113
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js60
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js114
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/test_saveHeapSnapshot_e10s_01.js8
-rw-r--r--devtools/shared/heapsnapshot/tests/unit/xpcshell.ini98
-rw-r--r--devtools/shared/indentation.js160
-rw-r--r--devtools/shared/inspector/css-logic.js325
-rw-r--r--devtools/shared/inspector/moz.build9
-rw-r--r--devtools/shared/jar.mn10
-rw-r--r--devtools/shared/jsbeautify/UPGRADING.md37
-rw-r--r--devtools/shared/jsbeautify/beautify.js7
-rw-r--r--devtools/shared/jsbeautify/lib/moz.build10
-rw-r--r--devtools/shared/jsbeautify/lib/sanitytest.js137
-rw-r--r--devtools/shared/jsbeautify/lib/urlencode_unpacker.js73
-rw-r--r--devtools/shared/jsbeautify/moz.build16
-rw-r--r--devtools/shared/jsbeautify/src/beautify-css.js367
-rw-r--r--devtools/shared/jsbeautify/src/beautify-html.js822
-rw-r--r--devtools/shared/jsbeautify/src/beautify-js.js1662
-rw-r--r--devtools/shared/jsbeautify/src/beautify-tests.js2096
-rw-r--r--devtools/shared/jsbeautify/src/moz.build12
-rw-r--r--devtools/shared/jsbeautify/tests/unit/head_jsbeautify.js17
-rw-r--r--devtools/shared/jsbeautify/tests/unit/test.js23
-rw-r--r--devtools/shared/jsbeautify/tests/unit/xpcshell.ini8
-rw-r--r--devtools/shared/l10n.js253
-rw-r--r--devtools/shared/layout/moz.build9
-rw-r--r--devtools/shared/layout/utils.js649
-rw-r--r--devtools/shared/loader-plugin-raw.jsm42
-rw-r--r--devtools/shared/locales/en-US/csscoverage.dtd47
-rw-r--r--devtools/shared/locales/en-US/csscoverage.properties32
-rw-r--r--devtools/shared/locales/en-US/debugger.properties59
-rw-r--r--devtools/shared/locales/en-US/gcli.properties318
-rw-r--r--devtools/shared/locales/en-US/gclicommands.properties1530
-rw-r--r--devtools/shared/locales/en-US/shared.properties6
-rw-r--r--devtools/shared/locales/en-US/styleinspector.properties188
-rw-r--r--devtools/shared/locales/jar.mn8
-rw-r--r--devtools/shared/locales/moz.build7
-rw-r--r--devtools/shared/moz.build67
-rw-r--r--devtools/shared/node-properties/UPGRADING.md12
-rw-r--r--devtools/shared/node-properties/moz.build9
-rw-r--r--devtools/shared/node-properties/node-properties.js776
-rw-r--r--devtools/shared/path.js28
-rw-r--r--devtools/shared/performance/moz.build12
-rw-r--r--devtools/shared/performance/recording-common.js97
-rw-r--r--devtools/shared/performance/recording-utils.js628
-rw-r--r--devtools/shared/performance/test/head.js7
-rw-r--r--devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js93
-rw-r--r--devtools/shared/performance/test/xpcshell.ini8
-rw-r--r--devtools/shared/platform/README.md13
-rw-r--r--devtools/shared/platform/chrome/clipboard.js28
-rw-r--r--devtools/shared/platform/chrome/moz.build10
-rw-r--r--devtools/shared/platform/chrome/stack.js75
-rw-r--r--devtools/shared/platform/content/.eslintrc.js12
-rw-r--r--devtools/shared/platform/content/clipboard.js34
-rw-r--r--devtools/shared/platform/content/moz.build16
-rw-r--r--devtools/shared/platform/content/stack.js49
-rw-r--r--devtools/shared/platform/content/test/.eslintrc.js6
-rw-r--r--devtools/shared/platform/content/test/mochitest.ini5
-rw-r--r--devtools/shared/platform/content/test/test_clipboard.html53
-rw-r--r--devtools/shared/platform/content/test/test_stack.js48
-rw-r--r--devtools/shared/platform/content/test/xpcshell.ini7
-rw-r--r--devtools/shared/platform/moz.build10
-rw-r--r--devtools/shared/plural-form.js196
-rw-r--r--devtools/shared/pretty-fast/UPGRADING.md11
-rw-r--r--devtools/shared/pretty-fast/moz.build11
-rw-r--r--devtools/shared/pretty-fast/pretty-fast.js873
-rw-r--r--devtools/shared/pretty-fast/tests/unit/head_pretty-fast.js49
-rw-r--r--devtools/shared/pretty-fast/tests/unit/test.js572
-rw-r--r--devtools/shared/pretty-fast/tests/unit/xpcshell.ini8
-rw-r--r--devtools/shared/protocol.js1517
-rw-r--r--devtools/shared/qrcode/decoder/LICENSE201
-rw-r--r--devtools/shared/qrcode/decoder/index.js2375
-rw-r--r--devtools/shared/qrcode/decoder/moz.build9
-rw-r--r--devtools/shared/qrcode/encoder/LICENSE19
-rw-r--r--devtools/shared/qrcode/encoder/index.js1674
-rw-r--r--devtools/shared/qrcode/encoder/moz.build9
-rw-r--r--devtools/shared/qrcode/index.js116
-rw-r--r--devtools/shared/qrcode/moz.build22
-rw-r--r--devtools/shared/qrcode/tests/mochitest/chrome.ini5
-rw-r--r--devtools/shared/qrcode/tests/mochitest/test_decode.html68
-rw-r--r--devtools/shared/qrcode/tests/unit/test_encode.js27
-rw-r--r--devtools/shared/qrcode/tests/unit/xpcshell.ini7
-rw-r--r--devtools/shared/security/auth.js653
-rw-r--r--devtools/shared/security/cert.js67
-rw-r--r--devtools/shared/security/docs/wifi.md154
-rw-r--r--devtools/shared/security/moz.build15
-rw-r--r--devtools/shared/security/prompt.js179
-rw-r--r--devtools/shared/security/socket.js793
-rw-r--r--devtools/shared/security/tests/chrome/chrome.ini4
-rw-r--r--devtools/shared/security/tests/chrome/test_websocket-transport.html76
-rw-r--r--devtools/shared/security/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/security/tests/unit/head_dbg.js96
-rw-r--r--devtools/shared/security/tests/unit/test_encryption.js110
-rw-r--r--devtools/shared/security/tests/unit/test_oob_cert_auth.js261
-rw-r--r--devtools/shared/security/tests/unit/testactors.js131
-rw-r--r--devtools/shared/security/tests/unit/xpcshell.ini12
-rw-r--r--devtools/shared/shims/Console.jsm35
-rw-r--r--devtools/shared/shims/Loader.jsm38
-rw-r--r--devtools/shared/shims/Simulator.jsm34
-rw-r--r--devtools/shared/shims/dbg-client.jsm43
-rw-r--r--devtools/shared/shims/event-emitter.js42
-rw-r--r--devtools/shared/shims/moz.build31
-rw-r--r--devtools/shared/sourcemap/UPGRADING.md13
-rw-r--r--devtools/shared/sourcemap/moz.build11
-rw-r--r--devtools/shared/sourcemap/source-map.js3006
-rw-r--r--devtools/shared/sourcemap/tests/unit/head_sourcemap.js18
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_api.js3026
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_array_set.js683
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_base64.js163
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_base64_vlq.js301
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_binary_search.js276
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_dog_fooding.js2985
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_quick_sort.js228
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_source_map_consumer.js4005
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_source_map_generator.js4039
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_source_node.js3908
-rw-r--r--devtools/shared/sourcemap/tests/unit/test_util.js651
-rw-r--r--devtools/shared/sourcemap/tests/unit/xpcshell.ini16
-rw-r--r--devtools/shared/specs/actor-registry.js43
-rw-r--r--devtools/shared/specs/addons.js19
-rw-r--r--devtools/shared/specs/animation.js151
-rw-r--r--devtools/shared/specs/breakpoint.js16
-rw-r--r--devtools/shared/specs/call-watcher.js79
-rw-r--r--devtools/shared/specs/canvas.js131
-rw-r--r--devtools/shared/specs/css-properties.js20
-rw-r--r--devtools/shared/specs/csscoverage.js44
-rw-r--r--devtools/shared/specs/device.js19
-rw-r--r--devtools/shared/specs/director-manager.js190
-rw-r--r--devtools/shared/specs/director-registry.js41
-rw-r--r--devtools/shared/specs/emulation.js106
-rw-r--r--devtools/shared/specs/environment.js27
-rw-r--r--devtools/shared/specs/eventlooplag.js31
-rw-r--r--devtools/shared/specs/frame.js14
-rw-r--r--devtools/shared/specs/framerate.js34
-rw-r--r--devtools/shared/specs/gcli.js86
-rw-r--r--devtools/shared/specs/heap-snapshot-file.js20
-rw-r--r--devtools/shared/specs/highlighters.js63
-rw-r--r--devtools/shared/specs/inspector.js445
-rw-r--r--devtools/shared/specs/layout.js33
-rw-r--r--devtools/shared/specs/memory.js124
-rw-r--r--devtools/shared/specs/moz.build50
-rw-r--r--devtools/shared/specs/node.js67
-rw-r--r--devtools/shared/specs/performance-entries.js25
-rw-r--r--devtools/shared/specs/performance-recording.js12
-rw-r--r--devtools/shared/specs/performance.js88
-rw-r--r--devtools/shared/specs/preference.js47
-rw-r--r--devtools/shared/specs/profiler.js121
-rw-r--r--devtools/shared/specs/promises.js56
-rw-r--r--devtools/shared/specs/reflow.js33
-rw-r--r--devtools/shared/specs/script.js14
-rw-r--r--devtools/shared/specs/settings.js31
-rw-r--r--devtools/shared/specs/source.js40
-rw-r--r--devtools/shared/specs/storage.js279
-rw-r--r--devtools/shared/specs/string.js87
-rw-r--r--devtools/shared/specs/styleeditor.js61
-rw-r--r--devtools/shared/specs/styles.js206
-rw-r--r--devtools/shared/specs/stylesheets.js120
-rw-r--r--devtools/shared/specs/timeline.js118
-rw-r--r--devtools/shared/specs/webaudio.js163
-rw-r--r--devtools/shared/specs/webgl.js101
-rw-r--r--devtools/shared/specs/worker.js78
-rw-r--r--devtools/shared/sprintfjs/UPGRADING.md12
-rw-r--r--devtools/shared/sprintfjs/moz.build9
-rw-r--r--devtools/shared/sprintfjs/sprintf.js274
-rw-r--r--devtools/shared/system.js339
-rw-r--r--devtools/shared/task.js515
-rw-r--r--devtools/shared/tests/browser/.eslintrc.js6
-rw-r--r--devtools/shared/tests/browser/browser.ini8
-rw-r--r--devtools/shared/tests/browser/browser_async_storage.js77
-rw-r--r--devtools/shared/tests/browser/browser_l10n_localizeMarkup.js54
-rw-r--r--devtools/shared/tests/mochitest/chrome.ini7
-rw-r--r--devtools/shared/tests/mochitest/test_devtools_extensions.html117
-rw-r--r--devtools/shared/tests/mochitest/test_eventemitter_basic.html194
-rw-r--r--devtools/shared/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/tests/unit/exposeLoader.js8
-rw-r--r--devtools/shared/tests/unit/head_devtools.js60
-rw-r--r--devtools/shared/tests/unit/test_assert.js36
-rw-r--r--devtools/shared/tests/unit/test_async-utils.js157
-rw-r--r--devtools/shared/tests/unit/test_console_filtering.js133
-rw-r--r--devtools/shared/tests/unit/test_css-properties-db.js136
-rw-r--r--devtools/shared/tests/unit/test_csslexer.js242
-rw-r--r--devtools/shared/tests/unit/test_defer.js32
-rw-r--r--devtools/shared/tests/unit/test_defineLazyPrototypeGetter.js68
-rw-r--r--devtools/shared/tests/unit/test_executeSoon.js48
-rw-r--r--devtools/shared/tests/unit/test_fetch-bom.js76
-rw-r--r--devtools/shared/tests/unit/test_fetch-chrome.js31
-rw-r--r--devtools/shared/tests/unit/test_fetch-file.js104
-rw-r--r--devtools/shared/tests/unit/test_fetch-http.js61
-rw-r--r--devtools/shared/tests/unit/test_fetch-resource.js31
-rw-r--r--devtools/shared/tests/unit/test_flatten.js24
-rw-r--r--devtools/shared/tests/unit/test_indentation.js133
-rw-r--r--devtools/shared/tests/unit/test_independent_loaders.js20
-rw-r--r--devtools/shared/tests/unit/test_invisible_loader.js60
-rw-r--r--devtools/shared/tests/unit/test_isSet.js25
-rw-r--r--devtools/shared/tests/unit/test_pluralForm-english.js29
-rw-r--r--devtools/shared/tests/unit/test_pluralForm-makeGetter.js38
-rw-r--r--devtools/shared/tests/unit/test_prettifyCSS.js68
-rw-r--r--devtools/shared/tests/unit/test_require.js20
-rw-r--r--devtools/shared/tests/unit/test_require_lazy.js32
-rw-r--r--devtools/shared/tests/unit/test_require_raw.js19
-rw-r--r--devtools/shared/tests/unit/test_safeErrorString.js58
-rw-r--r--devtools/shared/tests/unit/test_stack.js45
-rw-r--r--devtools/shared/tests/unit/xpcshell.ini40
-rw-r--r--devtools/shared/touch/moz.build11
-rw-r--r--devtools/shared/touch/simulator-content.js43
-rw-r--r--devtools/shared/touch/simulator-core.js366
-rw-r--r--devtools/shared/touch/simulator.js77
-rw-r--r--devtools/shared/transport/moz.build14
-rw-r--r--devtools/shared/transport/packets.js414
-rw-r--r--devtools/shared/transport/stream-utils.js249
-rw-r--r--devtools/shared/transport/tests/unit/.eslintrc.js6
-rw-r--r--devtools/shared/transport/tests/unit/head_dbg.js278
-rw-r--r--devtools/shared/transport/tests/unit/test_bulk_error.js92
-rw-r--r--devtools/shared/transport/tests/unit/test_client_server_bulk.js271
-rw-r--r--devtools/shared/transport/tests/unit/test_dbgsocket.js124
-rw-r--r--devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js81
-rw-r--r--devtools/shared/transport/tests/unit/test_delimited_read.js26
-rw-r--r--devtools/shared/transport/tests/unit/test_no_bulk.js38
-rw-r--r--devtools/shared/transport/tests/unit/test_packet.js21
-rw-r--r--devtools/shared/transport/tests/unit/test_queue.js177
-rw-r--r--devtools/shared/transport/tests/unit/test_transport_bulk.js148
-rw-r--r--devtools/shared/transport/tests/unit/test_transport_events.js75
-rw-r--r--devtools/shared/transport/tests/unit/testactors-no-bulk.js27
-rw-r--r--devtools/shared/transport/tests/unit/testactors.js131
-rw-r--r--devtools/shared/transport/tests/unit/xpcshell.ini21
-rw-r--r--devtools/shared/transport/transport.js908
-rw-r--r--devtools/shared/transport/websocket-transport.js79
-rw-r--r--devtools/shared/webconsole/client.js652
-rw-r--r--devtools/shared/webconsole/js-property-provider.js538
-rw-r--r--devtools/shared/webconsole/moz.build19
-rw-r--r--devtools/shared/webconsole/network-helper.js814
-rw-r--r--devtools/shared/webconsole/network-monitor.js2044
-rw-r--r--devtools/shared/webconsole/server-logger-monitor.js191
-rw-r--r--devtools/shared/webconsole/server-logger.js514
-rw-r--r--devtools/shared/webconsole/test/chrome.ini41
-rw-r--r--devtools/shared/webconsole/test/common.js345
-rw-r--r--devtools/shared/webconsole/test/console-test-worker.js16
-rw-r--r--devtools/shared/webconsole/test/data.json3
-rw-r--r--devtools/shared/webconsole/test/data.json^headers^3
-rw-r--r--devtools/shared/webconsole/test/helper_serviceworker.js19
-rw-r--r--devtools/shared/webconsole/test/network_requests_iframe.html61
-rw-r--r--devtools/shared/webconsole/test/sandboxed_iframe.html8
-rw-r--r--devtools/shared/webconsole/test/test_basics.html80
-rw-r--r--devtools/shared/webconsole/test/test_bug819670_getter_throws.html76
-rw-r--r--devtools/shared/webconsole/test/test_cached_messages.html230
-rw-r--r--devtools/shared/webconsole/test/test_commands_other.html83
-rw-r--r--devtools/shared/webconsole/test/test_commands_registration.html191
-rw-r--r--devtools/shared/webconsole/test/test_console_serviceworker.html157
-rw-r--r--devtools/shared/webconsole/test/test_console_serviceworker_cached.html117
-rw-r--r--devtools/shared/webconsole/test/test_console_styling.html126
-rw-r--r--devtools/shared/webconsole/test/test_consoleapi.html233
-rw-r--r--devtools/shared/webconsole/test/test_consoleapi_innerID.html164
-rw-r--r--devtools/shared/webconsole/test/test_file_uri.html106
-rw-r--r--devtools/shared/webconsole/test/test_jsterm.html309
-rw-r--r--devtools/shared/webconsole/test/test_jsterm_autocomplete.html183
-rw-r--r--devtools/shared/webconsole/test/test_jsterm_cd_iframe.html223
-rw-r--r--devtools/shared/webconsole/test/test_jsterm_last_result.html130
-rw-r--r--devtools/shared/webconsole/test/test_jsterm_queryselector.html134
-rw-r--r--devtools/shared/webconsole/test/test_network_get.html260
-rw-r--r--devtools/shared/webconsole/test/test_network_longstring.html293
-rw-r--r--devtools/shared/webconsole/test/test_network_post.html272
-rw-r--r--devtools/shared/webconsole/test/test_network_security-hpkp.html108
-rw-r--r--devtools/shared/webconsole/test/test_network_security-hsts.html100
-rw-r--r--devtools/shared/webconsole/test/test_nsiconsolemessage.html74
-rw-r--r--devtools/shared/webconsole/test/test_object_actor.html178
-rw-r--r--devtools/shared/webconsole/test/test_object_actor_native_getters.html106
-rw-r--r--devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html79
-rw-r--r--devtools/shared/webconsole/test/test_page_errors.html186
-rw-r--r--devtools/shared/webconsole/test/test_reflow.html94
-rw-r--r--devtools/shared/webconsole/test/test_throw.html93
-rw-r--r--devtools/shared/webconsole/test/unit/.eslintrc.js6
-rw-r--r--devtools/shared/webconsole/test/unit/test_js_property_provider.js170
-rw-r--r--devtools/shared/webconsole/test/unit/test_network_helper.js47
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-certificate.js68
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-parser.js64
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js54
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-state.js100
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js47
-rw-r--r--devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js47
-rw-r--r--devtools/shared/webconsole/test/unit/test_throttle.js140
-rw-r--r--devtools/shared/webconsole/test/unit/xpcshell.ini17
-rw-r--r--devtools/shared/webconsole/throttle.js418
-rw-r--r--devtools/shared/worker/helper.js133
-rw-r--r--devtools/shared/worker/loader.js517
-rw-r--r--devtools/shared/worker/moz.build13
-rw-r--r--devtools/shared/worker/tests/browser/.eslintrc.js6
-rw-r--r--devtools/shared/worker/tests/browser/browser.ini9
-rw-r--r--devtools/shared/worker/tests/browser/browser_worker-01.js45
-rw-r--r--devtools/shared/worker/tests/browser/browser_worker-02.js46
-rw-r--r--devtools/shared/worker/tests/browser/browser_worker-03.js52
-rw-r--r--devtools/shared/worker/worker.js171
604 files changed, 138967 insertions, 0 deletions
diff --git a/devtools/shared/DevToolsUtils.js b/devtools/shared/DevToolsUtils.js
new file mode 100644
index 000000000..d44184fd6
--- /dev/null
+++ b/devtools/shared/DevToolsUtils.js
@@ -0,0 +1,672 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* General utilities used throughout devtools. */
+
+var { Ci, Cu, Cc, components } = require("chrome");
+var Services = require("Services");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var flags = require("./flags");
+var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
+
+loader.lazyRequireGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm", true);
+
+// Re-export the thread-safe utils.
+const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js");
+for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
+ exports[key] = ThreadSafeDevToolsUtils[key];
+}
+
+/**
+ * Waits for the next tick in the event loop to execute a callback.
+ */
+exports.executeSoon = function executeSoon(aFn) {
+ if (isWorker) {
+ setImmediate(aFn);
+ } else {
+ let executor;
+ // Only enable async stack reporting when DEBUG_JS_MODULES is set
+ // (customized local builds) to avoid a performance penalty.
+ if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
+ let stack = getStack();
+ executor = () => {
+ callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
+ };
+ } else {
+ executor = aFn;
+ }
+ Services.tm.mainThread.dispatch({
+ run: exports.makeInfallible(executor)
+ }, Ci.nsIThread.DISPATCH_NORMAL);
+ }
+};
+
+/**
+ * Waits for the next tick in the event loop.
+ *
+ * @return Promise
+ * A promise that is resolved after the next tick in the event loop.
+ */
+exports.waitForTick = function waitForTick() {
+ let deferred = defer();
+ exports.executeSoon(deferred.resolve);
+ return deferred.promise;
+};
+
+/**
+ * Waits for the specified amount of time to pass.
+ *
+ * @param number aDelay
+ * The amount of time to wait, in milliseconds.
+ * @return Promise
+ * A promise that is resolved after the specified amount of time passes.
+ */
+exports.waitForTime = function waitForTime(aDelay) {
+ let deferred = defer();
+ setTimeout(deferred.resolve, aDelay);
+ return deferred.promise;
+};
+
+/**
+ * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
+ * very large arrays by yielding to the browser and continuing execution on the
+ * next tick.
+ *
+ * @param Array aArray
+ * The array being iterated over.
+ * @param Function aFn
+ * The function called on each item in the array. If a promise is
+ * returned by this function, iterating over the array will be paused
+ * until the respective promise is resolved.
+ * @returns Promise
+ * A promise that is resolved once the whole array has been iterated
+ * over, and all promises returned by the aFn callback are resolved.
+ */
+exports.yieldingEach = function yieldingEach(aArray, aFn) {
+ const deferred = defer();
+
+ let i = 0;
+ let len = aArray.length;
+ let outstanding = [deferred.promise];
+
+ (function loop() {
+ const start = Date.now();
+
+ while (i < len) {
+ // Don't block the main thread for longer than 16 ms at a time. To
+ // maintain 60fps, you have to render every frame in at least 16ms; we
+ // aren't including time spent in non-JS here, but this is Good
+ // Enough(tm).
+ if (Date.now() - start > 16) {
+ exports.executeSoon(loop);
+ return;
+ }
+
+ try {
+ outstanding.push(aFn(aArray[i], i++));
+ } catch (e) {
+ deferred.reject(e);
+ return;
+ }
+ }
+
+ deferred.resolve();
+ }());
+
+ return promise.all(outstanding);
+};
+
+/**
+ * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
+ * allows the lazy getter to be defined on a prototype and work correctly with
+ * instances.
+ *
+ * @param Object aObject
+ * The prototype object to define the lazy getter on.
+ * @param String aKey
+ * The key to define the lazy getter on.
+ * @param Function aCallback
+ * The callback that will be called to determine the value. Will be
+ * called with the |this| value of the current instance.
+ */
+exports.defineLazyPrototypeGetter =
+function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
+ Object.defineProperty(aObject, aKey, {
+ configurable: true,
+ get: function () {
+ const value = aCallback.call(this);
+
+ Object.defineProperty(this, aKey, {
+ configurable: true,
+ writable: true,
+ value: value
+ });
+
+ return value;
+ }
+ });
+};
+
+/**
+ * Safely get the property value from a Debugger.Object for a given key. Walks
+ * the prototype chain until the property is found.
+ *
+ * @param Debugger.Object aObject
+ * The Debugger.Object to get the value from.
+ * @param String aKey
+ * The key to look for.
+ * @return Any
+ */
+exports.getProperty = function getProperty(aObj, aKey) {
+ let root = aObj;
+ try {
+ do {
+ const desc = aObj.getOwnPropertyDescriptor(aKey);
+ if (desc) {
+ if ("value" in desc) {
+ return desc.value;
+ }
+ // Call the getter if it's safe.
+ return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
+ }
+ aObj = aObj.proto;
+ } while (aObj);
+ } catch (e) {
+ // If anything goes wrong report the error and return undefined.
+ exports.reportException("getProperty", e);
+ }
+ return undefined;
+};
+
+/**
+ * Determines if a descriptor has a getter which doesn't call into JavaScript.
+ *
+ * @param Object aDesc
+ * The descriptor to check for a safe getter.
+ * @return Boolean
+ * Whether a safe getter was found.
+ */
+exports.hasSafeGetter = function hasSafeGetter(aDesc) {
+ // Scripted functions that are CCWs will not appear scripted until after
+ // unwrapping.
+ try {
+ let fn = aDesc.get.unwrap();
+ return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
+ } catch (e) {
+ // Avoid exception 'Object in compartment marked as invisible to Debugger'
+ return false;
+ }
+};
+
+/**
+ * Check if it is safe to read properties and execute methods from the given JS
+ * object. Safety is defined as being protected from unintended code execution
+ * from content scripts (or cross-compartment code).
+ *
+ * See bugs 945920 and 946752 for discussion.
+ *
+ * @type Object aObj
+ * The object to check.
+ * @return Boolean
+ * True if it is safe to read properties from aObj, or false otherwise.
+ */
+exports.isSafeJSObject = function isSafeJSObject(aObj) {
+ // If we are running on a worker thread, Cu is not available. In this case,
+ // we always return false, just to be on the safe side.
+ if (isWorker) {
+ return false;
+ }
+
+ if (Cu.getGlobalForObject(aObj) ==
+ Cu.getGlobalForObject(exports.isSafeJSObject)) {
+ return true; // aObj is not a cross-compartment wrapper.
+ }
+
+ let principal = Cu.getObjectPrincipal(aObj);
+ if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
+ return true; // allow chrome objects
+ }
+
+ return Cu.isXrayWrapper(aObj);
+};
+
+exports.dumpn = function dumpn(str) {
+ if (flags.wantLogging) {
+ dump("DBG-SERVER: " + str + "\n");
+ }
+};
+
+/**
+ * A verbose logger for low-level tracing.
+ */
+exports.dumpv = function (msg) {
+ if (flags.wantVerbose) {
+ exports.dumpn(msg);
+ }
+};
+
+/**
+ * Defines a getter on a specified object that will be created upon first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject.
+ * @param aLambda
+ * A function that returns what the getter should return. This will
+ * only ever be called once.
+ */
+exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
+ Object.defineProperty(aObject, aName, {
+ get: function () {
+ delete aObject[aName];
+ return aObject[aName] = aLambda.apply(aObject);
+ },
+ configurable: true,
+ enumerable: true
+ });
+};
+
+exports.defineLazyGetter(this, "AppConstants", () => {
+ if (isWorker) {
+ return {};
+ }
+ const scope = {};
+ Cu.import("resource://gre/modules/AppConstants.jsm", scope);
+ return scope.AppConstants;
+});
+
+/**
+ * No operation. The empty function.
+ */
+exports.noop = function () { };
+
+let assertionFailureCount = 0;
+
+Object.defineProperty(exports, "assertionFailureCount", {
+ get() {
+ return assertionFailureCount;
+ }
+});
+
+function reallyAssert(condition, message) {
+ if (!condition) {
+ assertionFailureCount++;
+ const err = new Error("Assertion failure: " + message);
+ exports.reportException("DevToolsUtils.assert", err);
+ throw err;
+ }
+}
+
+/**
+ * DevToolsUtils.assert(condition, message)
+ *
+ * @param Boolean condition
+ * @param String message
+ *
+ * Assertions are enabled when any of the following are true:
+ * - This is a DEBUG_JS_MODULES build
+ * - This is a DEBUG build
+ * - flags.testing is set to true
+ *
+ * If assertions are enabled, then `condition` is checked and if false-y, the
+ * assertion failure is logged and then an error is thrown.
+ *
+ * If assertions are not enabled, then this function is a no-op.
+ */
+Object.defineProperty(exports, "assert", {
+ get: () => (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES || flags.testing)
+ ? reallyAssert
+ : exports.noop,
+});
+
+/**
+ * Defines a getter on a specified object for a module. The module will not
+ * be imported until first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject for the module.
+ * @param aResource
+ * The URL used to obtain the module.
+ * @param aSymbol
+ * The name of the symbol exported by the module.
+ * This parameter is optional and defaults to aName.
+ */
+exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName,
+ aResource,
+ aSymbol)
+{
+ this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
+ var temp = {};
+ Cu.import(aResource, temp);
+ return temp[aSymbol || aName];
+ });
+};
+
+exports.defineLazyGetter(this, "NetUtil", () => {
+ return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
+});
+
+exports.defineLazyGetter(this, "OS", () => {
+ return Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
+});
+
+exports.defineLazyGetter(this, "TextDecoder", () => {
+ return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder;
+});
+
+exports.defineLazyGetter(this, "NetworkHelper", () => {
+ return require("devtools/shared/webconsole/network-helper");
+});
+
+/**
+ * Performs a request to load the desired URL and returns a promise.
+ *
+ * @param aURL String
+ * The URL we will request.
+ * @param aOptions Object
+ * An object with the following optional properties:
+ * - loadFromCache: if false, will bypass the cache and
+ * always load fresh from the network (default: true)
+ * - policy: the nsIContentPolicy type to apply when fetching the URL
+ * (only works when loading from system principal)
+ * - window: the window to get the loadGroup from
+ * - charset: the charset to use if the channel doesn't provide one
+ * - principal: the principal to use, if omitted, the request is loaded
+ * with a codebase principal corresponding to the url being
+ * loaded, using the origin attributes of the window, if any.
+ * - cacheKey: when loading from cache, use this key to retrieve a cache
+ * specific to a given SHEntry. (Allows loading POST
+ * requests from cache)
+ * @returns Promise that resolves with an object with the following members on
+ * success:
+ * - content: the document at that URL, as a string,
+ * - contentType: the content type of the document
+ *
+ * If an error occurs, the promise is rejected with that error.
+ *
+ * XXX: It may be better to use nsITraceableChannel to get to the sources
+ * without relying on caching when we can (not for eval, etc.):
+ * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
+ */
+function mainThreadFetch(aURL, aOptions = { loadFromCache: true,
+ policy: Ci.nsIContentPolicy.TYPE_OTHER,
+ window: null,
+ charset: null,
+ principal: null,
+ cacheKey: null }) {
+ // Create a channel.
+ let url = aURL.split(" -> ").pop();
+ let channel;
+ try {
+ channel = newChannelForURL(url, aOptions);
+ } catch (ex) {
+ return promise.reject(ex);
+ }
+
+ // Set the channel options.
+ channel.loadFlags = aOptions.loadFromCache
+ ? channel.LOAD_FROM_CACHE
+ : channel.LOAD_BYPASS_CACHE;
+
+ // When loading from cache, the cacheKey allows us to target a specific
+ // SHEntry and offer ways to restore POST requests from cache.
+ if (aOptions.loadFromCache &&
+ aOptions.cacheKey && channel instanceof Ci.nsICacheInfoChannel) {
+ channel.cacheKey = aOptions.cacheKey;
+ }
+
+ if (aOptions.window) {
+ // Respect private browsing.
+ channel.loadGroup = aOptions.window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocumentLoader)
+ .loadGroup;
+ }
+
+ let deferred = defer();
+ let onResponse = (stream, status, request) => {
+ if (!components.isSuccessCode(status)) {
+ deferred.reject(new Error(`Failed to fetch ${url}. Code ${status}.`));
+ return;
+ }
+
+ try {
+ // We cannot use NetUtil to do the charset conversion as if charset
+ // information is not available and our default guess is wrong the method
+ // might fail and we lose the stream data. This means we can't fall back
+ // to using the locale default encoding (bug 1181345).
+
+ // Read and decode the data according to the locale default encoding.
+ let available = stream.available();
+ let source = NetUtil.readInputStreamToString(stream, available);
+ stream.close();
+
+ // We do our own BOM sniffing here because there's no convenient
+ // implementation of the "decode" algorithm
+ // (https://encoding.spec.whatwg.org/#decode) exposed to JS.
+ let bomCharset = null;
+ if (available >= 3 && source.codePointAt(0) == 0xef &&
+ source.codePointAt(1) == 0xbb && source.codePointAt(2) == 0xbf) {
+ bomCharset = "UTF-8";
+ source = source.slice(3);
+ } else if (available >= 2 && source.codePointAt(0) == 0xfe &&
+ source.codePointAt(1) == 0xff) {
+ bomCharset = "UTF-16BE";
+ source = source.slice(2);
+ } else if (available >= 2 && source.codePointAt(0) == 0xff &&
+ source.codePointAt(1) == 0xfe) {
+ bomCharset = "UTF-16LE";
+ source = source.slice(2);
+ }
+
+ // If the channel or the caller has correct charset information, the
+ // content will be decoded correctly. If we have to fall back to UTF-8 and
+ // the guess is wrong, the conversion fails and convertToUnicode returns
+ // the input unmodified. Essentially we try to decode the data as UTF-8
+ // and if that fails, we use the locale specific default encoding. This is
+ // the best we can do if the source does not provide charset info.
+ let charset = bomCharset || channel.contentCharset || aOptions.charset || "UTF-8";
+ let unicodeSource = NetworkHelper.convertToUnicode(source, charset);
+
+ deferred.resolve({
+ content: unicodeSource,
+ contentType: request.contentType
+ });
+ } catch (ex) {
+ let uri = request.originalURI;
+ if (ex.name === "NS_BASE_STREAM_CLOSED" && uri instanceof Ci.nsIFileURL) {
+ // Empty files cause NS_BASE_STREAM_CLOSED exception. Use OS.File to
+ // differentiate between empty files and other errors (bug 1170864).
+ // This can be removed when bug 982654 is fixed.
+
+ uri.QueryInterface(Ci.nsIFileURL);
+ let result = OS.File.read(uri.file.path).then(bytes => {
+ // Convert the bytearray to a String.
+ let decoder = new TextDecoder();
+ let content = decoder.decode(bytes);
+
+ // We can't detect the contentType without opening a channel
+ // and that failed already. This is the best we can do here.
+ return {
+ content,
+ contentType: "text/plain"
+ };
+ });
+
+ deferred.resolve(result);
+ } else {
+ deferred.reject(ex);
+ }
+ }
+ };
+
+ // Open the channel
+ try {
+ NetUtil.asyncFetch(channel, onResponse);
+ } catch (ex) {
+ return promise.reject(ex);
+ }
+
+ return deferred.promise;
+}
+
+/**
+ * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
+ *
+ * @param {String} url - The URL to open a channel for.
+ * @param {Object} options - The options object passed to @method fetch.
+ * @return {nsIChannel} - The newly created channel. Throws on failure.
+ */
+function newChannelForURL(url, { policy, window, principal }) {
+ var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+
+ let uri;
+ try {
+ uri = Services.io.newURI(url, null, null);
+ } catch (e) {
+ // In the xpcshell tests, the script url is the absolute path of the test
+ // file, which will make a malformed URI error be thrown. Add the file
+ // scheme to see if it helps.
+ uri = Services.io.newURI("file://" + url, null, null);
+ }
+ let channelOptions = {
+ contentPolicyType: policy,
+ securityFlags: securityFlags,
+ uri: uri
+ };
+ let prin = principal;
+ if (!prin) {
+ let oa = {};
+ if (window) {
+ oa = window.document.nodePrincipal.originAttributes;
+ }
+ prin = Services.scriptSecurityManager
+ .createCodebasePrincipal(uri, oa);
+ }
+ // contentPolicyType is required when specifying a principal
+ if (!channelOptions.contentPolicyType) {
+ channelOptions.contentPolicyType = Ci.nsIContentPolicy.TYPE_OTHER;
+ }
+ channelOptions.loadingPrincipal = prin;
+
+ try {
+ return NetUtil.newChannel(channelOptions);
+ } catch (e) {
+ // In xpcshell tests on Windows, nsExternalProtocolHandler::NewChannel()
+ // can throw NS_ERROR_UNKNOWN_PROTOCOL if the external protocol isn't
+ // supported by Windows, so we also need to handle the exception here if
+ // parsing the URL above doesn't throw.
+ return newChannelForURL("file://" + url, { policy, window, principal });
+ }
+}
+
+// Fetch is defined differently depending on whether we are on the main thread
+// or a worker thread.
+if (!this.isWorker) {
+ exports.fetch = mainThreadFetch;
+} else {
+ // Services is not available in worker threads, nor is there any other way
+ // to fetch a URL. We need to enlist the help from the main thread here, by
+ // issuing an rpc request, to fetch the URL on our behalf.
+ exports.fetch = function (url, options) {
+ return rpc("fetch", url, options);
+ };
+}
+
+/**
+ * Open the file at the given path for reading.
+ *
+ * @param {String} filePath
+ *
+ * @returns Promise<nsIInputStream>
+ */
+exports.openFileStream = function (filePath) {
+ return new Promise((resolve, reject) => {
+ const uri = NetUtil.newURI(new FileUtils.File(filePath));
+ NetUtil.asyncFetch(
+ { uri, loadUsingSystemPrincipal: true },
+ (stream, result) => {
+ if (!components.isSuccessCode(result)) {
+ reject(new Error(`Could not open "${filePath}": result = ${result}`));
+ return;
+ }
+
+ resolve(stream);
+ }
+ );
+ });
+};
+
+/*
+ * All of the flags have been moved to a different module. Make sure
+ * nobody is accessing them anymore, and don't write new code using
+ * them. We can remove this code after a while.
+ */
+function errorOnFlag(exports, name) {
+ Object.defineProperty(exports, name, {
+ get: () => {
+ const msg = `Cannot get the flag ${name}. ` +
+ `Use the "devtools/shared/flags" module instead`;
+ console.error(msg);
+ throw new Error(msg);
+ },
+ set: () => {
+ const msg = `Cannot set the flag ${name}. ` +
+ `Use the "devtools/shared/flags" module instead`;
+ console.error(msg);
+ throw new Error(msg);
+ }
+ });
+}
+
+errorOnFlag(exports, "testing");
+errorOnFlag(exports, "wantLogging");
+errorOnFlag(exports, "wantVerbose");
+
+// Calls the property with the given `name` on the given `object`, where
+// `name` is a string, and `object` a Debugger.Object instance.
+///
+// This function uses only the Debugger.Object API to call the property. It
+// avoids the use of unsafeDeference. This is useful for example in workers,
+// where unsafeDereference will return an opaque security wrapper to the
+// referent.
+function callPropertyOnObject(object, name) {
+ // Find the property.
+ let descriptor;
+ let proto = object;
+ do {
+ descriptor = proto.getOwnPropertyDescriptor(name);
+ if (descriptor !== undefined) {
+ break;
+ }
+ proto = proto.proto;
+ } while (proto !== null);
+ if (descriptor === undefined) {
+ throw new Error("No such property");
+ }
+ let value = descriptor.value;
+ if (typeof value !== "object" || value === null || !("callable" in value)) {
+ throw new Error("Not a callable object.");
+ }
+
+ // Call the property.
+ let result = value.call(object);
+ if (result === null) {
+ throw new Error("Code was terminated.");
+ }
+ if ("throw" in result) {
+ throw result.throw;
+ }
+ return result.return;
+}
+
+
+exports.callPropertyOnObject = callPropertyOnObject;
diff --git a/devtools/shared/Loader.jsm b/devtools/shared/Loader.jsm
new file mode 100644
index 000000000..6b31075ee
--- /dev/null
+++ b/devtools/shared/Loader.jsm
@@ -0,0 +1,244 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Manages the addon-sdk loader instance used to load the developer tools.
+ */
+
+var { utils: Cu } = Components;
+var { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+var { Loader, descriptor, resolveURI } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
+var { requireRawId } = Cu.import("resource://devtools/shared/loader-plugin-raw.jsm", {});
+
+this.EXPORTED_SYMBOLS = ["DevToolsLoader", "devtools", "BuiltinProvider",
+ "require", "loader"];
+
+/**
+ * Providers are different strategies for loading the devtools.
+ */
+
+var sharedGlobalBlocklist = ["sdk/indexed-db"];
+
+/**
+ * Used when the tools should be loaded from the Firefox package itself.
+ * This is the default case.
+ */
+function BuiltinProvider() {}
+BuiltinProvider.prototype = {
+ load: function () {
+ const paths = {
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "": "resource://gre/modules/commonjs/",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ // Modules here are intended to have one implementation for
+ // chrome, and a separate implementation for content. Here we
+ // map the directory to the chrome subdirectory, but the content
+ // loader will map to the content subdirectory. See the
+ // README.md in devtools/shared/platform.
+ "devtools/shared/platform": "resource://devtools/shared/platform/chrome",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "devtools": "resource://devtools",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "gcli": "resource://devtools/shared/gcli/source/lib/gcli",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "acorn": "resource://devtools/shared/acorn",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "acorn/util/walk": "resource://devtools/shared/acorn/walk.js",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "source-map": "resource://devtools/shared/sourcemap/source-map.js",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ // Allow access to xpcshell test items from the loader.
+ "xpcshell-test": "resource://test",
+
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ // Allow access to locale data using paths closer to what is
+ // used in the source tree.
+ "devtools/client/locales": "chrome://devtools/locale",
+ "devtools/shared/locales": "chrome://devtools-shared/locale",
+ "toolkit/locales": "chrome://global/locale",
+ };
+ // When creating a Loader invisible to the Debugger, we have to ensure
+ // using only modules and not depend on any JSM. As everything that is
+ // not loaded with Loader isn't going to respect `invisibleToDebugger`.
+ // But we have to keep using Promise.jsm for other loader to prevent
+ // breaking unhandled promise rejection in tests.
+ if (this.invisibleToDebugger) {
+ paths.promise = "resource://gre/modules/Promise-backend.js";
+ }
+ this.loader = new Loader.Loader({
+ id: "fx-devtools",
+ paths,
+ invisibleToDebugger: this.invisibleToDebugger,
+ sharedGlobal: true,
+ sharedGlobalBlocklist,
+ requireHook: (id, require) => {
+ if (id.startsWith("raw!")) {
+ return requireRawId(id, require);
+ }
+ return require(id);
+ },
+ });
+ },
+
+ unload: function (reason) {
+ Loader.unload(this.loader, reason);
+ delete this.loader;
+ },
+};
+
+var gNextLoaderID = 0;
+
+/**
+ * The main devtools API. The standard instance of this loader is exported as
+ * |devtools| below, but if a fresh copy of the loader is needed, then a new
+ * one can also be created.
+ */
+this.DevToolsLoader = function DevToolsLoader() {
+ this.require = this.require.bind(this);
+
+ Services.obs.addObserver(this, "devtools-unload", false);
+};
+
+DevToolsLoader.prototype = {
+ destroy: function (reason = "shutdown") {
+ Services.obs.removeObserver(this, "devtools-unload");
+
+ if (this._provider) {
+ this._provider.unload(reason);
+ delete this._provider;
+ }
+ },
+
+ get provider() {
+ if (!this._provider) {
+ this._loadProvider();
+ }
+ return this._provider;
+ },
+
+ _provider: null,
+
+ get id() {
+ if (this._id) {
+ return this._id;
+ }
+ this._id = ++gNextLoaderID;
+ return this._id;
+ },
+
+ /**
+ * A dummy version of require, in case a provider hasn't been chosen yet when
+ * this is first called. This will then be replaced by the real version.
+ * @see setProvider
+ */
+ require: function () {
+ if (!this._provider) {
+ this._loadProvider();
+ }
+ return this.require.apply(this, arguments);
+ },
+
+ /**
+ * Return true if |id| refers to something requiring help from a
+ * loader plugin.
+ */
+ isLoaderPluginId: function (id) {
+ return id.startsWith("raw!");
+ },
+
+ /**
+ * Override the provider used to load the tools.
+ */
+ setProvider: function (provider) {
+ if (provider === this._provider) {
+ return;
+ }
+
+ if (this._provider) {
+ delete this.require;
+ this._provider.unload("newprovider");
+ }
+ this._provider = provider;
+
+ // Pass through internal loader settings specific to this loader instance
+ this._provider.invisibleToDebugger = this.invisibleToDebugger;
+
+ this._provider.load();
+ this.require = Loader.Require(this._provider.loader, { id: "devtools" });
+
+ // Fetch custom pseudo modules and globals
+ let { modules, globals } = this.require("devtools/shared/builtin-modules");
+
+ // When creating a Loader for the browser toolbox, we have to use
+ // Promise-backend.js, as a Loader module. Instead of Promise.jsm which
+ // can't be flagged as invisible to debugger.
+ if (this.invisibleToDebugger) {
+ delete modules.promise;
+ }
+
+ // Register custom pseudo modules to the current loader instance
+ let loader = this._provider.loader;
+ for (let id in modules) {
+ let exports = modules[id];
+ let uri = resolveURI(id, loader.mapping);
+ loader.modules[uri] = { exports };
+ }
+
+ // Register custom globals to the current loader instance
+ globals.loader.id = this.id;
+ Object.defineProperties(loader.globals, descriptor(globals));
+
+ // Expose lazy helpers on loader
+ this.lazyGetter = globals.loader.lazyGetter;
+ this.lazyImporter = globals.loader.lazyImporter;
+ this.lazyServiceGetter = globals.loader.lazyServiceGetter;
+ this.lazyRequireGetter = globals.loader.lazyRequireGetter;
+ },
+
+ /**
+ * Choose a default tools provider based on the preferences.
+ */
+ _loadProvider: function () {
+ this.setProvider(new BuiltinProvider());
+ },
+
+ /**
+ * Handles "devtools-unload" event
+ *
+ * @param String data
+ * reason passed to modules when unloaded
+ */
+ observe: function (subject, topic, data) {
+ if (topic != "devtools-unload") {
+ return;
+ }
+ this.destroy(data);
+ },
+
+ /**
+ * Sets whether the compartments loaded by this instance should be invisible
+ * to the debugger. Invisibility is needed for loaders that support debugging
+ * of chrome code. This is true of remote target environments, like Fennec or
+ * B2G. It is not the default case for desktop Firefox because we offer the
+ * Browser Toolbox for chrome debugging there, which uses its own, separate
+ * loader instance.
+ * @see devtools/client/framework/ToolboxProcess.jsm
+ */
+ invisibleToDebugger: Services.appinfo.name !== "Firefox"
+};
+
+// Export the standard instance of DevToolsLoader used by the tools.
+this.devtools = this.loader = new DevToolsLoader();
+
+this.require = this.devtools.require.bind(this.devtools);
+
+// For compatibility reasons, expose these symbols on "devtools":
+Object.defineProperty(this.devtools, "Toolbox", {
+ get: () => this.require("devtools/client/framework/toolbox").Toolbox
+});
+Object.defineProperty(this.devtools, "TargetFactory", {
+ get: () => this.require("devtools/client/framework/target").TargetFactory
+});
diff --git a/devtools/shared/Parser.jsm b/devtools/shared/Parser.jsm
new file mode 100644
index 000000000..4bd635383
--- /dev/null
+++ b/devtools/shared/Parser.jsm
@@ -0,0 +1,2451 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const Cu = Components.utils;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+const { console } = require("resource://gre/modules/Console.jsm");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+XPCOMUtils.defineLazyModuleGetter(this,
+ "Reflect", "resource://gre/modules/reflect.jsm");
+
+this.EXPORTED_SYMBOLS = ["Parser", "ParserHelpers", "SyntaxTreeVisitor"];
+
+/**
+ * A JS parser using the reflection API.
+ */
+this.Parser = function Parser() {
+ this._cache = new Map();
+ this.errors = [];
+ this.logExceptions = true;
+};
+
+Parser.prototype = {
+ /**
+ * Gets a collection of parser methods for a specified source.
+ *
+ * @param string source
+ * The source text content.
+ * @param string url [optional]
+ * The source url. The AST nodes will be cached, so you can use this
+ * identifier to avoid parsing the whole source again.
+ */
+ get(source, url = "") {
+ // Try to use the cached AST nodes, to avoid useless parsing operations.
+ if (this._cache.has(url)) {
+ return this._cache.get(url);
+ }
+
+ // The source may not necessarily be JS, in which case we need to extract
+ // all the scripts. Fastest/easiest way is with a regular expression.
+ // Don't worry, the rules of using a <script> tag are really strict,
+ // this will work.
+ let regexp = /<script[^>]*?(?:>([^]*?)<\/script\s*>|\/>)/gim;
+ let syntaxTrees = [];
+ let scriptMatches = [];
+ let scriptMatch;
+
+ if (source.match(/^\s*</)) {
+ // First non whitespace character is &lt, so most definitely HTML.
+ while ((scriptMatch = regexp.exec(source))) {
+ // Contents are captured at index 1 or nothing: Self-closing scripts
+ // won't capture code content
+ scriptMatches.push(scriptMatch[1] || "");
+ }
+ }
+
+ // If there are no script matches, send the whole source directly to the
+ // reflection API to generate the AST nodes.
+ if (!scriptMatches.length) {
+ // Reflect.parse throws when encounters a syntax error.
+ try {
+ let nodes = Reflect.parse(source);
+ let length = source.length;
+ syntaxTrees.push(new SyntaxTree(nodes, url, length));
+ } catch (e) {
+ this.errors.push(e);
+ if (this.logExceptions) {
+ DevToolsUtils.reportException(url, e);
+ }
+ }
+ } else {
+ // Generate the AST nodes for each script.
+ for (let script of scriptMatches) {
+ // Reflect.parse throws when encounters a syntax error.
+ try {
+ let nodes = Reflect.parse(script);
+ let offset = source.indexOf(script);
+ let length = script.length;
+ syntaxTrees.push(new SyntaxTree(nodes, url, length, offset));
+ } catch (e) {
+ this.errors.push(e);
+ if (this.logExceptions) {
+ DevToolsUtils.reportException(url, e);
+ }
+ }
+ }
+ }
+
+ let pool = new SyntaxTreesPool(syntaxTrees, url);
+
+ // Cache the syntax trees pool by the specified url. This is entirely
+ // optional, but it's strongly encouraged to cache ASTs because
+ // generating them can be costly with big/complex sources.
+ if (url) {
+ this._cache.set(url, pool);
+ }
+
+ return pool;
+ },
+
+ /**
+ * Clears all the parsed sources from cache.
+ */
+ clearCache() {
+ this._cache.clear();
+ },
+
+ /**
+ * Clears the AST for a particular source.
+ *
+ * @param String url
+ * The URL of the source that is being cleared.
+ */
+ clearSource(url) {
+ this._cache.delete(url);
+ },
+
+ _cache: null,
+ errors: null
+};
+
+/**
+ * A pool handling a collection of AST nodes generated by the reflection API.
+ *
+ * @param object syntaxTrees
+ * A collection of AST nodes generated for a source.
+ * @param string url [optional]
+ * The source url.
+ */
+function SyntaxTreesPool(syntaxTrees, url = "<unknown>") {
+ this._trees = syntaxTrees;
+ this._url = url;
+ this._cache = new Map();
+}
+
+SyntaxTreesPool.prototype = {
+ /**
+ * @see SyntaxTree.prototype.getIdentifierAt
+ */
+ getIdentifierAt({ line, column, scriptIndex, ignoreLiterals }) {
+ return this._call("getIdentifierAt",
+ scriptIndex, line, column, ignoreLiterals)[0];
+ },
+
+ /**
+ * @see SyntaxTree.prototype.getNamedFunctionDefinitions
+ */
+ getNamedFunctionDefinitions(substring) {
+ return this._call("getNamedFunctionDefinitions", -1, substring);
+ },
+
+ /**
+ * @return SyntaxTree
+ * The last tree in this._trees
+ */
+ getLastSyntaxTree() {
+ return this._trees[this._trees.length - 1];
+ },
+
+ /**
+ * Gets the total number of scripts in the parent source.
+ * @return number
+ */
+ get scriptCount() {
+ return this._trees.length;
+ },
+
+ /**
+ * Finds the start and length of the script containing the specified offset
+ * relative to its parent source.
+ *
+ * @param number atOffset
+ * The offset relative to the parent source.
+ * @return object
+ * The offset and length relative to the enclosing script.
+ */
+ getScriptInfo(atOffset) {
+ let info = { start: -1, length: -1, index: -1 };
+
+ for (let { offset, length } of this._trees) {
+ info.index++;
+ if (offset <= atOffset && offset + length >= atOffset) {
+ info.start = offset;
+ info.length = length;
+ return info;
+ }
+ }
+
+ info.index = -1;
+ return info;
+ },
+
+ /**
+ * Handles a request for a specific or all known syntax trees.
+ *
+ * @param string functionName
+ * The function name to call on the SyntaxTree instances.
+ * @param number syntaxTreeIndex
+ * The syntax tree for which to handle the request. If the tree at
+ * the specified index isn't found, the accumulated results for all
+ * syntax trees are returned.
+ * @param any params
+ * Any kind params to pass to the request function.
+ * @return array
+ * The results given by all known syntax trees.
+ */
+ _call(functionName, syntaxTreeIndex, ...params) {
+ let results = [];
+ let requestId = [functionName, syntaxTreeIndex, params].toSource();
+
+ if (this._cache.has(requestId)) {
+ return this._cache.get(requestId);
+ }
+
+ let requestedTree = this._trees[syntaxTreeIndex];
+ let targettedTrees = requestedTree ? [requestedTree] : this._trees;
+
+ for (let syntaxTree of targettedTrees) {
+ try {
+ let parseResults = syntaxTree[functionName].apply(syntaxTree, params);
+ if (parseResults) {
+ parseResults.sourceUrl = syntaxTree.url;
+ parseResults.scriptLength = syntaxTree.length;
+ parseResults.scriptOffset = syntaxTree.offset;
+ results.push(parseResults);
+ }
+ } catch (e) {
+ // Can't guarantee that the tree traversal logic is forever perfect :)
+ // Language features may be added, in which case the recursive methods
+ // need to be updated. If an exception is thrown here, file a bug.
+ DevToolsUtils.reportException(
+ `Syntax tree visitor for ${this._url}`, e);
+ }
+ }
+ this._cache.set(requestId, results);
+ return results;
+ },
+
+ _trees: null,
+ _cache: null
+};
+
+/**
+ * A collection of AST nodes generated by the reflection API.
+ *
+ * @param object nodes
+ * The AST nodes.
+ * @param string url
+ * The source url.
+ * @param number length
+ * The total number of chars of the parsed script in the parent source.
+ * @param number offset [optional]
+ * The char offset of the parsed script in the parent source.
+ */
+function SyntaxTree(nodes, url, length, offset = 0) {
+ this.AST = nodes;
+ this.url = url;
+ this.length = length;
+ this.offset = offset;
+}
+
+SyntaxTree.prototype = {
+ /**
+ * Gets the identifier at the specified location.
+ *
+ * @param number line
+ * The line in the source.
+ * @param number column
+ * The column in the source.
+ * @param boolean ignoreLiterals
+ * Specifies if alone literals should be ignored.
+ * @return object
+ * An object containing identifier information as { name, location,
+ * evalString } properties, or null if nothing is found.
+ */
+ getIdentifierAt(line, column, ignoreLiterals) {
+ let info = null;
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each identifier node.
+ * @param Node node
+ */
+ onIdentifier(node) {
+ if (ParserHelpers.nodeContainsPoint(node, line, column)) {
+ info = {
+ name: node.name,
+ location: ParserHelpers.getNodeLocation(node),
+ evalString: ParserHelpers.getIdentifierEvalString(node)
+ };
+
+ // Abruptly halt walking the syntax tree.
+ SyntaxTreeVisitor.break = true;
+ }
+ },
+
+ /**
+ * Callback invoked for each literal node.
+ * @param Node node
+ */
+ onLiteral(node) {
+ if (!ignoreLiterals) {
+ this.onIdentifier(node);
+ }
+ },
+
+ /**
+ * Callback invoked for each 'this' node.
+ * @param Node node
+ */
+ onThisExpression(node) {
+ this.onIdentifier(node);
+ }
+ });
+
+ return info;
+ },
+
+ /**
+ * Searches for all function definitions (declarations and expressions)
+ * whose names (or inferred names) contain a string.
+ *
+ * @param string substring
+ * The string to be contained in the function name (or inferred name).
+ * Can be an empty string to match all functions.
+ * @return array
+ * All the matching function declarations and expressions, as
+ * { functionName, functionLocation ... } object hashes.
+ */
+ getNamedFunctionDefinitions(substring) {
+ let lowerCaseToken = substring.toLowerCase();
+ let store = [];
+
+ function includesToken(name) {
+ return name && name.toLowerCase().includes(lowerCaseToken);
+ }
+
+ SyntaxTreeVisitor.walk(this.AST, {
+ /**
+ * Callback invoked for each function declaration node.
+ * @param Node node
+ */
+ onFunctionDeclaration(node) {
+ let functionName = node.id.name;
+ if (includesToken(functionName)) {
+ store.push({
+ functionName: functionName,
+ functionLocation: ParserHelpers.getNodeLocation(node)
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each function expression node.
+ * @param Node node
+ */
+ onFunctionExpression(node) {
+ // Function expressions don't necessarily have a name.
+ let functionName = node.id ? node.id.name : "";
+ let functionLocation = ParserHelpers.getNodeLocation(node);
+
+ // Infer the function's name from an enclosing syntax tree node.
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(node);
+ let inferredName = inferredInfo.name;
+ let inferredChain = inferredInfo.chain;
+ let inferredLocation = inferredInfo.loc;
+
+ // Current node may be part of a larger assignment expression stack.
+ if (node._parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(node._parent);
+ }
+
+ if (includesToken(functionName) || includesToken(inferredName)) {
+ store.push({
+ functionName: functionName,
+ functionLocation: functionLocation,
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ },
+
+ /**
+ * Callback invoked for each arrow expression node.
+ * @param Node node
+ */
+ onArrowFunctionExpression(node) {
+ // Infer the function's name from an enclosing syntax tree node.
+ let inferredInfo = ParserHelpers.inferFunctionExpressionInfo(node);
+ let inferredName = inferredInfo.name;
+ let inferredChain = inferredInfo.chain;
+ let inferredLocation = inferredInfo.loc;
+
+ // Current node may be part of a larger assignment expression stack.
+ if (node._parent.type == "AssignmentExpression") {
+ this.onFunctionExpression(node._parent);
+ }
+
+ if (includesToken(inferredName)) {
+ store.push({
+ inferredName: inferredName,
+ inferredChain: inferredChain,
+ inferredLocation: inferredLocation
+ });
+ }
+ }
+ });
+
+ return store;
+ },
+
+ AST: null,
+ url: "",
+ length: 0,
+ offset: 0
+};
+
+/**
+ * Parser utility methods.
+ */
+var ParserHelpers = {
+ /**
+ * Gets the location information for a node. Not all nodes have a
+ * location property directly attached, or the location information
+ * is incorrect, in which cases it's accessible via the parent.
+ *
+ * @param Node node
+ * The node who's location needs to be retrieved.
+ * @return object
+ * An object containing { line, column } information.
+ */
+ getNodeLocation(node) {
+ if (node.type != "Identifier") {
+ return node.loc;
+ }
+ // Work around the fact that some identifier nodes don't have the
+ // correct location attached.
+ let { loc: parentLocation, type: parentType } = node._parent;
+ let { loc: nodeLocation } = node;
+ if (!nodeLocation) {
+ if (parentType == "FunctionDeclaration" ||
+ parentType == "FunctionExpression") {
+ // e.g. "function foo() {}" or "{ bar: function foo() {} }"
+ // The location is unavailable for the identifier node "foo".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ if (parentType == "MemberExpression") {
+ // e.g. "foo.bar"
+ // The location is unavailable for the identifier node "bar".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.start.line = loc.end.line;
+ loc.start.column = loc.end.column - node.name.length;
+ return loc;
+ }
+ if (parentType == "LabeledStatement") {
+ // e.g. label: ...
+ // The location is unavailable for the identifier node "label".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ if (parentType == "ContinueStatement" || parentType == "BreakStatement") {
+ // e.g. continue label; or break label;
+ // The location is unavailable for the identifier node "label".
+ let loc = Cu.cloneInto(parentLocation, {});
+ loc.start.line = loc.end.line;
+ loc.start.column = loc.end.column - node.name.length;
+ return loc;
+ }
+ } else if (parentType == "VariableDeclarator") {
+ // e.g. "let foo = 42"
+ // The location incorrectly spans across the whole variable declaration,
+ // not just the identifier node "foo".
+ let loc = Cu.cloneInto(nodeLocation, {});
+ loc.end.line = loc.start.line;
+ loc.end.column = loc.start.column + node.name.length;
+ return loc;
+ }
+ return node.loc;
+ },
+
+ /**
+ * Checks if a node's bounds contains a specified line.
+ *
+ * @param Node node
+ * The node's bounds used as reference.
+ * @param number line
+ * The line number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ nodeContainsLine(node, line) {
+ let { start: s, end: e } = this.getNodeLocation(node);
+ return s.line <= line && e.line >= line;
+ },
+
+ /**
+ * Checks if a node's bounds contains a specified line and column.
+ *
+ * @param Node node
+ * The node's bounds used as reference.
+ * @param number line
+ * The line number to check.
+ * @param number column
+ * The column number to check.
+ * @return boolean
+ * True if the line and column is contained in the node's bounds.
+ */
+ nodeContainsPoint(node, line, column) {
+ let { start: s, end: e } = this.getNodeLocation(node);
+ return s.line == line && e.line == line &&
+ s.column <= column && e.column >= column;
+ },
+
+ /**
+ * Try to infer a function expression's name & other details based on the
+ * enclosing VariableDeclarator, AssignmentExpression or ObjectExpression.
+ *
+ * @param Node node
+ * The function expression node to get the name for.
+ * @return object
+ * The inferred function name, or empty string can't infer the name,
+ * along with the chain (a generic "context", like a prototype chain)
+ * and location if available.
+ */
+ inferFunctionExpressionInfo(node) {
+ let parent = node._parent;
+
+ // A function expression may be defined in a variable declarator,
+ // e.g. var foo = function(){}, in which case it is possible to infer
+ // the variable name.
+ if (parent.type == "VariableDeclarator") {
+ return {
+ name: parent.id.name,
+ chain: null,
+ loc: this.getNodeLocation(parent.id)
+ };
+ }
+
+ // Function expressions can also be defined in assignment expressions,
+ // e.g. foo = function(){} or foo.bar = function(){}, in which case it is
+ // possible to infer the assignee name ("foo" and "bar" respectively).
+ if (parent.type == "AssignmentExpression") {
+ let propertyChain = this._getMemberExpressionPropertyChain(parent.left);
+ let propertyLeaf = propertyChain.pop();
+ return {
+ name: propertyLeaf,
+ chain: propertyChain,
+ loc: this.getNodeLocation(parent.left)
+ };
+ }
+
+ // If a function expression is defined in an object expression,
+ // e.g. { foo: function(){} }, then it is possible to infer the name
+ // from the corresponding property.
+ if (parent.type == "ObjectExpression") {
+ let propertyKey = this._getObjectExpressionPropertyKeyForValue(node);
+ let propertyChain = this._getObjectExpressionPropertyChain(parent);
+ let propertyLeaf = propertyKey.name;
+ return {
+ name: propertyLeaf,
+ chain: propertyChain,
+ loc: this.getNodeLocation(propertyKey)
+ };
+ }
+
+ // Can't infer the function expression's name.
+ return {
+ name: "",
+ chain: null,
+ loc: null
+ };
+ },
+
+ /**
+ * Gets the name of an object expression's property to which a specified
+ * value is assigned.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if "node" represents the "bar" identifier in a hypothetical
+ * "{ foo: bar }" object expression, the returned node is the "foo"
+ * identifier.
+ *
+ * @param Node node
+ * The value node in an object expression.
+ * @return object
+ * The key identifier node in the object expression.
+ */
+ _getObjectExpressionPropertyKeyForValue(node) {
+ let parent = node._parent;
+ if (parent.type != "ObjectExpression") {
+ return null;
+ }
+ for (let property of parent.properties) {
+ if (property.value == node) {
+ return property.key;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets an object expression's property chain to its parent
+ * variable declarator or assignment expression, if available.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if node represents the "baz: {}" object expression in a
+ * hypothetical "foo = { bar: { baz: {} } }" assignment expression, the
+ * returned chain is ["foo", "bar", "baz"].
+ *
+ * @param Node node
+ * The object expression node to begin the scan from.
+ * @param array aStore [optional]
+ * The chain to store the nodes into.
+ * @return array
+ * The chain to the parent variable declarator, as strings.
+ */
+ _getObjectExpressionPropertyChain(node, aStore = []) {
+ switch (node.type) {
+ case "ObjectExpression":
+ this._getObjectExpressionPropertyChain(node._parent, aStore);
+ let propertyKey = this._getObjectExpressionPropertyKeyForValue(node);
+ if (propertyKey) {
+ aStore.push(propertyKey.name);
+ }
+ break;
+ // Handle "var foo = { ... }" variable declarators.
+ case "VariableDeclarator":
+ aStore.push(node.id.name);
+ break;
+ // Handle "foo.bar = { ... }" assignment expressions, since they're
+ // commonly used when defining an object's prototype methods; e.g:
+ // "Foo.prototype = { ... }".
+ case "AssignmentExpression":
+ this._getMemberExpressionPropertyChain(node.left, aStore);
+ break;
+ // Additionally handle stuff like "foo = bar.baz({ ... })", because it's
+ // commonly used in prototype-based inheritance in many libraries; e.g:
+ // "Foo = Bar.extend({ ... })".
+ case "NewExpression":
+ case "CallExpression":
+ this._getObjectExpressionPropertyChain(node._parent, aStore);
+ break;
+ }
+ return aStore;
+ },
+
+ /**
+ * Gets a member expression's property chain.
+ *
+ * Used for inferring function expression information and retrieving
+ * an identifier evaluation string.
+ *
+ * For example, if node represents a hypothetical "foo.bar.baz"
+ * member expression, the returned chain ["foo", "bar", "baz"].
+ *
+ * More complex expressions like foo.bar().baz are intentionally not handled.
+ *
+ * @param Node node
+ * The member expression node to begin the scan from.
+ * @param array store [optional]
+ * The chain to store the nodes into.
+ * @return array
+ * The full member chain, as strings.
+ */
+ _getMemberExpressionPropertyChain(node, store = []) {
+ switch (node.type) {
+ case "MemberExpression":
+ this._getMemberExpressionPropertyChain(node.object, store);
+ this._getMemberExpressionPropertyChain(node.property, store);
+ break;
+ case "ThisExpression":
+ store.push("this");
+ break;
+ case "Identifier":
+ store.push(node.name);
+ break;
+ }
+ return store;
+ },
+
+ /**
+ * Returns an evaluation string which can be used to obtain the
+ * current value for the respective identifier.
+ *
+ * @param Node node
+ * The leaf node (e.g. Identifier, Literal) to begin the scan from.
+ * @return string
+ * The corresponding evaluation string, or empty string if
+ * the specified leaf node can't be used.
+ */
+ getIdentifierEvalString(node) {
+ switch (node._parent.type) {
+ case "ObjectExpression":
+ // If the identifier is the actual property value, it can be used
+ // directly as an evaluation string. Otherwise, construct the property
+ // access chain, since the value might have changed.
+ if (!this._getObjectExpressionPropertyKeyForValue(node)) {
+ let propertyChain =
+ this._getObjectExpressionPropertyChain(node._parent);
+ let propertyLeaf = node.name;
+ return [...propertyChain, propertyLeaf].join(".");
+ }
+ break;
+ case "MemberExpression":
+ // Make sure this is a property identifier, not the parent object.
+ if (node._parent.property == node) {
+ return this._getMemberExpressionPropertyChain(node._parent).join(".");
+ }
+ break;
+ }
+ switch (node.type) {
+ case "ThisExpression":
+ return "this";
+ case "Identifier":
+ return node.name;
+ case "Literal":
+ return uneval(node.value);
+ default:
+ return "";
+ }
+ }
+};
+
+/**
+ * A visitor for a syntax tree generated by the reflection API.
+ * See https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API.
+ *
+ * All node types implement the following interface:
+ * interface Node {
+ * type: string;
+ * loc: SourceLocation | null;
+ * }
+ */
+var SyntaxTreeVisitor = {
+ /**
+ * Walks a syntax tree.
+ *
+ * @param object tree
+ * The AST nodes generated by the reflection API
+ * @param object callbacks
+ * A map of all the callbacks to invoke when passing through certain
+ * types of noes (e.g: onFunctionDeclaration, onBlockStatement etc.).
+ */
+ walk(tree, callbacks) {
+ this.break = false;
+ this[tree.type](tree, callbacks);
+ },
+
+ /**
+ * Filters all the nodes in this syntax tree based on a predicate.
+ *
+ * @param object tree
+ * The AST nodes generated by the reflection API
+ * @param function predicate
+ * The predicate ran on each node.
+ * @return array
+ * An array of nodes validating the predicate.
+ */
+ filter(tree, predicate) {
+ let store = [];
+ this.walk(tree, {
+ onNode: e => {
+ if (predicate(e)) {
+ store.push(e);
+ }
+ }
+ });
+ return store;
+ },
+
+ /**
+ * A flag checked on each node in the syntax tree. If true, walking is
+ * abruptly halted.
+ */
+ break: false,
+
+ /**
+ * A complete program source tree.
+ *
+ * interface Program <: Node {
+ * type: "Program";
+ * body: [ Statement ];
+ * }
+ */
+ Program(node, callbacks) {
+ if (callbacks.onProgram) {
+ callbacks.onProgram(node);
+ }
+ for (let statement of node.body) {
+ this[statement.type](statement, node, callbacks);
+ }
+ },
+
+ /**
+ * Any statement.
+ *
+ * interface Statement <: Node { }
+ */
+ Statement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onStatement) {
+ callbacks.onStatement(node);
+ }
+ },
+
+ /**
+ * An empty statement, i.e., a solitary semicolon.
+ *
+ * interface EmptyStatement <: Statement {
+ * type: "EmptyStatement";
+ * }
+ */
+ EmptyStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onEmptyStatement) {
+ callbacks.onEmptyStatement(node);
+ }
+ },
+
+ /**
+ * A block statement, i.e., a sequence of statements surrounded by braces.
+ *
+ * interface BlockStatement <: Statement {
+ * type: "BlockStatement";
+ * body: [ Statement ];
+ * }
+ */
+ BlockStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBlockStatement) {
+ callbacks.onBlockStatement(node);
+ }
+ for (let statement of node.body) {
+ this[statement.type](statement, node, callbacks);
+ }
+ },
+
+ /**
+ * An expression statement, i.e., a statement consisting of a single
+ * expression.
+ *
+ * interface ExpressionStatement <: Statement {
+ * type: "ExpressionStatement";
+ * expression: Expression;
+ * }
+ */
+ ExpressionStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onExpressionStatement) {
+ callbacks.onExpressionStatement(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * An if statement.
+ *
+ * interface IfStatement <: Statement {
+ * type: "IfStatement";
+ * test: Expression;
+ * consequent: Statement;
+ * alternate: Statement | null;
+ * }
+ */
+ IfStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onIfStatement) {
+ callbacks.onIfStatement(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.consequent.type](node.consequent, node, callbacks);
+ if (node.alternate) {
+ this[node.alternate.type](node.alternate, node, callbacks);
+ }
+ },
+
+ /**
+ * A labeled statement, i.e., a statement prefixed by a break/continue label.
+ *
+ * interface LabeledStatement <: Statement {
+ * type: "LabeledStatement";
+ * label: Identifier;
+ * body: Statement;
+ * }
+ */
+ LabeledStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLabeledStatement) {
+ callbacks.onLabeledStatement(node);
+ }
+ this[node.label.type](node.label, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A break statement.
+ *
+ * interface BreakStatement <: Statement {
+ * type: "BreakStatement";
+ * label: Identifier | null;
+ * }
+ */
+ BreakStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBreakStatement) {
+ callbacks.onBreakStatement(node);
+ }
+ if (node.label) {
+ this[node.label.type](node.label, node, callbacks);
+ }
+ },
+
+ /**
+ * A continue statement.
+ *
+ * interface ContinueStatement <: Statement {
+ * type: "ContinueStatement";
+ * label: Identifier | null;
+ * }
+ */
+ ContinueStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onContinueStatement) {
+ callbacks.onContinueStatement(node);
+ }
+ if (node.label) {
+ this[node.label.type](node.label, node, callbacks);
+ }
+ },
+
+ /**
+ * A with statement.
+ *
+ * interface WithStatement <: Statement {
+ * type: "WithStatement";
+ * object: Expression;
+ * body: Statement;
+ * }
+ */
+ WithStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onWithStatement) {
+ callbacks.onWithStatement(node);
+ }
+ this[node.object.type](node.object, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A switch statement. The lexical flag is metadata indicating whether the
+ * switch statement contains any unnested let declarations (and therefore
+ * introduces a new lexical scope).
+ *
+ * interface SwitchStatement <: Statement {
+ * type: "SwitchStatement";
+ * discriminant: Expression;
+ * cases: [ SwitchCase ];
+ * lexical: boolean;
+ * }
+ */
+ SwitchStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSwitchStatement) {
+ callbacks.onSwitchStatement(node);
+ }
+ this[node.discriminant.type](node.discriminant, node, callbacks);
+ for (let _case of node.cases) {
+ this[_case.type](_case, node, callbacks);
+ }
+ },
+
+ /**
+ * A return statement.
+ *
+ * interface ReturnStatement <: Statement {
+ * type: "ReturnStatement";
+ * argument: Expression | null;
+ * }
+ */
+ ReturnStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onReturnStatement) {
+ callbacks.onReturnStatement(node);
+ }
+ if (node.argument) {
+ this[node.argument.type](node.argument, node, callbacks);
+ }
+ },
+
+ /**
+ * A throw statement.
+ *
+ * interface ThrowStatement <: Statement {
+ * type: "ThrowStatement";
+ * argument: Expression;
+ * }
+ */
+ ThrowStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onThrowStatement) {
+ callbacks.onThrowStatement(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A try statement.
+ *
+ * interface TryStatement <: Statement {
+ * type: "TryStatement";
+ * block: BlockStatement;
+ * handler: CatchClause | null;
+ * guardedHandlers: [ CatchClause ];
+ * finalizer: BlockStatement | null;
+ * }
+ */
+ TryStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onTryStatement) {
+ callbacks.onTryStatement(node);
+ }
+ this[node.block.type](node.block, node, callbacks);
+ if (node.handler) {
+ this[node.handler.type](node.handler, node, callbacks);
+ }
+ for (let guardedHandler of node.guardedHandlers) {
+ this[guardedHandler.type](guardedHandler, node, callbacks);
+ }
+ if (node.finalizer) {
+ this[node.finalizer.type](node.finalizer, node, callbacks);
+ }
+ },
+
+ /**
+ * A while statement.
+ *
+ * interface WhileStatement <: Statement {
+ * type: "WhileStatement";
+ * test: Expression;
+ * body: Statement;
+ * }
+ */
+ WhileStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onWhileStatement) {
+ callbacks.onWhileStatement(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A do/while statement.
+ *
+ * interface DoWhileStatement <: Statement {
+ * type: "DoWhileStatement";
+ * body: Statement;
+ * test: Expression;
+ * }
+ */
+ DoWhileStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDoWhileStatement) {
+ callbacks.onDoWhileStatement(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ this[node.test.type](node.test, node, callbacks);
+ },
+
+ /**
+ * A for statement.
+ *
+ * interface ForStatement <: Statement {
+ * type: "ForStatement";
+ * init: VariableDeclaration | Expression | null;
+ * test: Expression | null;
+ * update: Expression | null;
+ * body: Statement;
+ * }
+ */
+ ForStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForStatement) {
+ callbacks.onForStatement(node);
+ }
+ if (node.init) {
+ this[node.init.type](node.init, node, callbacks);
+ }
+ if (node.test) {
+ this[node.test.type](node.test, node, callbacks);
+ }
+ if (node.update) {
+ this[node.update.type](node.update, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for/in statement, or, if each is true, a for each/in statement.
+ *
+ * interface ForInStatement <: Statement {
+ * type: "ForInStatement";
+ * left: VariableDeclaration | Expression;
+ * right: Expression;
+ * body: Statement;
+ * each: boolean;
+ * }
+ */
+ ForInStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForInStatement) {
+ callbacks.onForInStatement(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for/of statement.
+ *
+ * interface ForOfStatement <: Statement {
+ * type: "ForOfStatement";
+ * left: VariableDeclaration | Expression;
+ * right: Expression;
+ * body: Statement;
+ * }
+ */
+ ForOfStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onForOfStatement) {
+ callbacks.onForOfStatement(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A let statement.
+ *
+ * interface LetStatement <: Statement {
+ * type: "LetStatement";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Statement;
+ * }
+ */
+ LetStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLetStatement) {
+ callbacks.onLetStatement(node);
+ }
+ for (let { id, init } of node.head) {
+ this[id.type](id, node, callbacks);
+ if (init) {
+ this[init.type](init, node, callbacks);
+ }
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A debugger statement.
+ *
+ * interface DebuggerStatement <: Statement {
+ * type: "DebuggerStatement";
+ * }
+ */
+ DebuggerStatement(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDebuggerStatement) {
+ callbacks.onDebuggerStatement(node);
+ }
+ },
+
+ /**
+ * Any declaration node. Note that declarations are considered statements;
+ * this is because declarations can appear in any statement context in the
+ * language recognized by the SpiderMonkey parser.
+ *
+ * interface Declaration <: Statement { }
+ */
+ Declaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onDeclaration) {
+ callbacks.onDeclaration(node);
+ }
+ },
+
+ /**
+ * A function declaration.
+ *
+ * interface FunctionDeclaration <: Function, Declaration {
+ * type: "FunctionDeclaration";
+ * id: Identifier;
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ FunctionDeclaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onFunctionDeclaration) {
+ callbacks.onFunctionDeclaration(node);
+ }
+ this[node.id.type](node.id, node, callbacks);
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A variable declaration, via one of var, let, or const.
+ *
+ * interface VariableDeclaration <: Declaration {
+ * type: "VariableDeclaration";
+ * declarations: [ VariableDeclarator ];
+ * kind: "var" | "let" | "const";
+ * }
+ */
+ VariableDeclaration(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onVariableDeclaration) {
+ callbacks.onVariableDeclaration(node);
+ }
+ for (let declaration of node.declarations) {
+ this[declaration.type](declaration, node, callbacks);
+ }
+ },
+
+ /**
+ * A variable declarator.
+ *
+ * interface VariableDeclarator <: Node {
+ * type: "VariableDeclarator";
+ * id: Pattern;
+ * init: Expression | null;
+ * }
+ */
+ VariableDeclarator(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onVariableDeclarator) {
+ callbacks.onVariableDeclarator(node);
+ }
+ this[node.id.type](node.id, node, callbacks);
+ if (node.init) {
+ this[node.init.type](node.init, node, callbacks);
+ }
+ },
+
+ /**
+ * Any expression node. Since the left-hand side of an assignment may be any
+ * expression in general, an expression can also be a pattern.
+ *
+ * interface Expression <: Node, Pattern { }
+ */
+ Expression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onExpression) {
+ callbacks.onExpression(node);
+ }
+ },
+
+ /**
+ * A this expression.
+ *
+ * interface ThisExpression <: Expression {
+ * type: "ThisExpression";
+ * }
+ */
+ ThisExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onThisExpression) {
+ callbacks.onThisExpression(node);
+ }
+ },
+
+ /**
+ * An array expression.
+ *
+ * interface ArrayExpression <: Expression {
+ * type: "ArrayExpression";
+ * elements: [ Expression | null ];
+ * }
+ */
+ ArrayExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrayExpression) {
+ callbacks.onArrayExpression(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A spread expression.
+ *
+ * interface SpreadExpression <: Expression {
+ * type: "SpreadExpression";
+ * expression: Expression;
+ * }
+ */
+ SpreadExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSpreadExpression) {
+ callbacks.onSpreadExpression(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * An object expression. A literal property in an object expression can have
+ * either a string or number as its value. Ordinary property initializers
+ * have a kind value "init"; getters and setters have the kind values "get"
+ * and "set", respectively.
+ *
+ * interface ObjectExpression <: Expression {
+ * type: "ObjectExpression";
+ * properties: [ { key: Literal | Identifier | ComputedName,
+ * value: Expression,
+ * kind: "init" | "get" | "set" } ];
+ * }
+ */
+ ObjectExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onObjectExpression) {
+ callbacks.onObjectExpression(node);
+ }
+ for (let { key, value } of node.properties) {
+ this[key.type](key, node, callbacks);
+ this[value.type](value, node, callbacks);
+ }
+ },
+
+ /**
+ * A computed property name in object expression, like in { [a]: b }
+ *
+ * interface ComputedName <: Node {
+ * type: "ComputedName";
+ * name: Expression;
+ * }
+ */
+ ComputedName(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComputedName) {
+ callbacks.onComputedName(node);
+ }
+ this[node.name.type](node.name, node, callbacks);
+ },
+
+ /**
+ * A function expression.
+ *
+ * interface FunctionExpression <: Function, Expression {
+ * type: "FunctionExpression";
+ * id: Identifier | null;
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ FunctionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onFunctionExpression) {
+ callbacks.onFunctionExpression(node);
+ }
+ if (node.id) {
+ this[node.id.type](node.id, node, callbacks);
+ }
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * An arrow expression.
+ *
+ * interface ArrowFunctionExpression <: Function, Expression {
+ * type: "ArrowFunctionExpression";
+ * params: [ Pattern ];
+ * defaults: [ Expression ];
+ * rest: Identifier | null;
+ * body: BlockStatement | Expression;
+ * generator: boolean;
+ * expression: boolean;
+ * }
+ */
+ ArrowFunctionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrowFunctionExpression) {
+ callbacks.onArrowFunctionExpression(node);
+ }
+ for (let param of node.params) {
+ this[param.type](param, node, callbacks);
+ }
+ for (let _default of node.defaults) {
+ if (_default) {
+ this[_default.type](_default, node, callbacks);
+ }
+ }
+ if (node.rest) {
+ this[node.rest.type](node.rest, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A sequence expression, i.e., a comma-separated sequence of expressions.
+ *
+ * interface SequenceExpression <: Expression {
+ * type: "SequenceExpression";
+ * expressions: [ Expression ];
+ * }
+ */
+ SequenceExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSequenceExpression) {
+ callbacks.onSequenceExpression(node);
+ }
+ for (let expression of node.expressions) {
+ this[expression.type](expression, node, callbacks);
+ }
+ },
+
+ /**
+ * A unary operator expression.
+ *
+ * interface UnaryExpression <: Expression {
+ * type: "UnaryExpression";
+ * operator: UnaryOperator;
+ * prefix: boolean;
+ * argument: Expression;
+ * }
+ */
+ UnaryExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onUnaryExpression) {
+ callbacks.onUnaryExpression(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A binary operator expression.
+ *
+ * interface BinaryExpression <: Expression {
+ * type: "BinaryExpression";
+ * operator: BinaryOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ BinaryExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onBinaryExpression) {
+ callbacks.onBinaryExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An assignment operator expression.
+ *
+ * interface AssignmentExpression <: Expression {
+ * type: "AssignmentExpression";
+ * operator: AssignmentOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ AssignmentExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onAssignmentExpression) {
+ callbacks.onAssignmentExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An update (increment or decrement) operator expression.
+ *
+ * interface UpdateExpression <: Expression {
+ * type: "UpdateExpression";
+ * operator: UpdateOperator;
+ * argument: Expression;
+ * prefix: boolean;
+ * }
+ */
+ UpdateExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onUpdateExpression) {
+ callbacks.onUpdateExpression(node);
+ }
+ this[node.argument.type](node.argument, node, callbacks);
+ },
+
+ /**
+ * A logical operator expression.
+ *
+ * interface LogicalExpression <: Expression {
+ * type: "LogicalExpression";
+ * operator: LogicalOperator;
+ * left: Expression;
+ * right: Expression;
+ * }
+ */
+ LogicalExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLogicalExpression) {
+ callbacks.onLogicalExpression(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * A conditional expression, i.e., a ternary ?/: expression.
+ *
+ * interface ConditionalExpression <: Expression {
+ * type: "ConditionalExpression";
+ * test: Expression;
+ * alternate: Expression;
+ * consequent: Expression;
+ * }
+ */
+ ConditionalExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onConditionalExpression) {
+ callbacks.onConditionalExpression(node);
+ }
+ this[node.test.type](node.test, node, callbacks);
+ this[node.alternate.type](node.alternate, node, callbacks);
+ this[node.consequent.type](node.consequent, node, callbacks);
+ },
+
+ /**
+ * A new expression.
+ *
+ * interface NewExpression <: Expression {
+ * type: "NewExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ NewExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onNewExpression) {
+ callbacks.onNewExpression(node);
+ }
+ this[node.callee.type](node.callee, node, callbacks);
+ for (let argument of node.arguments) {
+ if (argument) {
+ this[argument.type](argument, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A function or method call expression.
+ *
+ * interface CallExpression <: Expression {
+ * type: "CallExpression";
+ * callee: Expression;
+ * arguments: [ Expression | null ];
+ * }
+ */
+ CallExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onCallExpression) {
+ callbacks.onCallExpression(node);
+ }
+ this[node.callee.type](node.callee, node, callbacks);
+ for (let argument of node.arguments) {
+ if (argument) {
+ if (!this[argument.type]) {
+ console.error("Unknown parser object:", argument.type);
+ }
+ this[argument.type](argument, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A member expression. If computed is true, the node corresponds to a
+ * computed e1[e2] expression and property is an Expression. If computed is
+ * false, the node corresponds to a static e1.x expression and property is an
+ * Identifier.
+ *
+ * interface MemberExpression <: Expression {
+ * type: "MemberExpression";
+ * object: Expression;
+ * property: Identifier | Expression;
+ * computed: boolean;
+ * }
+ */
+ MemberExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onMemberExpression) {
+ callbacks.onMemberExpression(node);
+ }
+ this[node.object.type](node.object, node, callbacks);
+ this[node.property.type](node.property, node, callbacks);
+ },
+
+ /**
+ * A yield expression.
+ *
+ * interface YieldExpression <: Expression {
+ * argument: Expression | null;
+ * }
+ */
+ YieldExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onYieldExpression) {
+ callbacks.onYieldExpression(node);
+ }
+ if (node.argument) {
+ this[node.argument.type](node.argument, node, callbacks);
+ }
+ },
+
+ /**
+ * An array comprehension. The blocks array corresponds to the sequence of
+ * for and for each blocks. The optional filter expression corresponds to the
+ * final if clause, if present.
+ *
+ * interface ComprehensionExpression <: Expression {
+ * body: Expression;
+ * blocks: [ ComprehensionBlock ];
+ * filter: Expression | null;
+ * }
+ */
+ ComprehensionExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComprehensionExpression) {
+ callbacks.onComprehensionExpression(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ for (let block of node.blocks) {
+ this[block.type](block, node, callbacks);
+ }
+ if (node.filter) {
+ this[node.filter.type](node.filter, node, callbacks);
+ }
+ },
+
+ /**
+ * A generator expression. As with array comprehensions, the blocks array
+ * corresponds to the sequence of for and for each blocks, and the optional
+ * filter expression corresponds to the final if clause, if present.
+ *
+ * interface GeneratorExpression <: Expression {
+ * body: Expression;
+ * blocks: [ ComprehensionBlock ];
+ * filter: Expression | null;
+ * }
+ */
+ GeneratorExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGeneratorExpression) {
+ callbacks.onGeneratorExpression(node);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ for (let block of node.blocks) {
+ this[block.type](block, node, callbacks);
+ }
+ if (node.filter) {
+ this[node.filter.type](node.filter, node, callbacks);
+ }
+ },
+
+ /**
+ * A graph expression, aka "sharp literal," such as #1={ self: #1# }.
+ *
+ * interface GraphExpression <: Expression {
+ * index: uint32;
+ * expression: Literal;
+ * }
+ */
+ GraphExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGraphExpression) {
+ callbacks.onGraphExpression(node);
+ }
+ this[node.expression.type](node.expression, node, callbacks);
+ },
+
+ /**
+ * A graph index expression, aka "sharp variable," such as #1#.
+ *
+ * interface GraphIndexExpression <: Expression {
+ * index: uint32;
+ * }
+ */
+ GraphIndexExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onGraphIndexExpression) {
+ callbacks.onGraphIndexExpression(node);
+ }
+ },
+
+ /**
+ * A let expression.
+ *
+ * interface LetExpression <: Expression {
+ * type: "LetExpression";
+ * head: [ { id: Pattern, init: Expression | null } ];
+ * body: Expression;
+ * }
+ */
+ LetExpression(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLetExpression) {
+ callbacks.onLetExpression(node);
+ }
+ for (let { id, init } of node.head) {
+ this[id.type](id, node, callbacks);
+ if (init) {
+ this[init.type](init, node, callbacks);
+ }
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * Any pattern.
+ *
+ * interface Pattern <: Node { }
+ */
+ Pattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onPattern) {
+ callbacks.onPattern(node);
+ }
+ },
+
+ /**
+ * An object-destructuring pattern. A literal property in an object pattern
+ * can have either a string or number as its value.
+ *
+ * interface ObjectPattern <: Pattern {
+ * type: "ObjectPattern";
+ * properties: [ { key: Literal | Identifier, value: Pattern } ];
+ * }
+ */
+ ObjectPattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onObjectPattern) {
+ callbacks.onObjectPattern(node);
+ }
+ for (let { key, value } of node.properties) {
+ this[key.type](key, node, callbacks);
+ this[value.type](value, node, callbacks);
+ }
+ },
+
+ /**
+ * An array-destructuring pattern.
+ *
+ * interface ArrayPattern <: Pattern {
+ * type: "ArrayPattern";
+ * elements: [ Pattern | null ];
+ * }
+ */
+ ArrayPattern(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onArrayPattern) {
+ callbacks.onArrayPattern(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ },
+
+ /**
+ * A case (if test is an Expression) or default (if test is null) clause in
+ * the body of a switch statement.
+ *
+ * interface SwitchCase <: Node {
+ * type: "SwitchCase";
+ * test: Expression | null;
+ * consequent: [ Statement ];
+ * }
+ */
+ SwitchCase(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onSwitchCase) {
+ callbacks.onSwitchCase(node);
+ }
+ if (node.test) {
+ this[node.test.type](node.test, node, callbacks);
+ }
+ for (let consequent of node.consequent) {
+ this[consequent.type](consequent, node, callbacks);
+ }
+ },
+
+ /**
+ * A catch clause following a try block. The optional guard property
+ * corresponds to the optional expression guard on the bound variable.
+ *
+ * interface CatchClause <: Node {
+ * type: "CatchClause";
+ * param: Pattern;
+ * guard: Expression | null;
+ * body: BlockStatement;
+ * }
+ */
+ CatchClause(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onCatchClause) {
+ callbacks.onCatchClause(node);
+ }
+ this[node.param.type](node.param, node, callbacks);
+ if (node.guard) {
+ this[node.guard.type](node.guard, node, callbacks);
+ }
+ this[node.body.type](node.body, node, callbacks);
+ },
+
+ /**
+ * A for or for each block in an array comprehension or generator expression.
+ *
+ * interface ComprehensionBlock <: Node {
+ * left: Pattern;
+ * right: Expression;
+ * each: boolean;
+ * }
+ */
+ ComprehensionBlock(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onComprehensionBlock) {
+ callbacks.onComprehensionBlock(node);
+ }
+ this[node.left.type](node.left, node, callbacks);
+ this[node.right.type](node.right, node, callbacks);
+ },
+
+ /**
+ * An identifier. Note that an identifier may be an expression or a
+ * destructuring pattern.
+ *
+ * interface Identifier <: Node, Expression, Pattern {
+ * type: "Identifier";
+ * name: string;
+ * }
+ */
+ Identifier(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onIdentifier) {
+ callbacks.onIdentifier(node);
+ }
+ },
+
+ /**
+ * A literal token. Note that a literal can be an expression.
+ *
+ * interface Literal <: Node, Expression {
+ * type: "Literal";
+ * value: string | boolean | null | number | RegExp;
+ * }
+ */
+ Literal(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onLiteral) {
+ callbacks.onLiteral(node);
+ }
+ },
+
+ /**
+ * A template string literal.
+ *
+ * interface TemplateLiteral <: Node {
+ * type: "TemplateLiteral";
+ * elements: [ Expression ];
+ * }
+ */
+ TemplateLiteral(node, parent, callbacks) {
+ node._parent = parent;
+
+ if (this.break) {
+ return;
+ }
+ if (callbacks.onNode) {
+ if (callbacks.onNode(node, parent) === false) {
+ return;
+ }
+ }
+ if (callbacks.onTemplateLiteral) {
+ callbacks.onTemplateLiteral(node);
+ }
+ for (let element of node.elements) {
+ if (element) {
+ this[element.type](element, node, callbacks);
+ }
+ }
+ }
+};
+
+XPCOMUtils.defineLazyGetter(Parser, "reflectionAPI", () => Reflect);
diff --git a/devtools/shared/ThreadSafeDevToolsUtils.js b/devtools/shared/ThreadSafeDevToolsUtils.js
new file mode 100644
index 000000000..55a98cf35
--- /dev/null
+++ b/devtools/shared/ThreadSafeDevToolsUtils.js
@@ -0,0 +1,334 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * General utilities used throughout devtools that can also be used in
+ * workers.
+ */
+
+/**
+ * Immutably reduce the given `...objs` into one object. The reduction is
+ * applied from left to right, so `immutableUpdate({ a: 1 }, { a: 2 })` will
+ * result in `{ a: 2 }`. The resulting object is frozen.
+ *
+ * Example usage:
+ *
+ * const original = { foo: 1, bar: 2, baz: 3 };
+ * const modified = immutableUpdate(original, { baz: 0, bang: 4 });
+ *
+ * // We get the new object that we expect...
+ * assert(modified.baz === 0);
+ * assert(modified.bang === 4);
+ *
+ * // However, the original is not modified.
+ * assert(original.baz === 2);
+ * assert(original.bang === undefined);
+ *
+ * @param {...Object} ...objs
+ * @returns {Object}
+ */
+exports.immutableUpdate = function (...objs) {
+ return Object.freeze(Object.assign({}, ...objs));
+};
+
+/**
+ * Utility function for updating an object with the properties of
+ * other objects.
+ *
+ * DEPRECATED: Just use Object.assign() instead!
+ *
+ * @param aTarget Object
+ * The object being updated.
+ * @param aNewAttrs Object
+ * The rest params are objects to update aTarget with. You
+ * can pass as many as you like.
+ */
+exports.update = function update(target, ...args) {
+ for (let attrs of args) {
+ for (let key in attrs) {
+ let desc = Object.getOwnPropertyDescriptor(attrs, key);
+
+ if (desc) {
+ Object.defineProperty(target, key, desc);
+ }
+ }
+ }
+ return target;
+};
+
+/**
+ * Utility function for getting the values from an object as an array
+ *
+ * @param object Object
+ * The object to iterate over
+ */
+exports.values = function values(object) {
+ return Object.keys(object).map(k => object[k]);
+};
+
+/**
+ * Report that |who| threw an exception, |exception|.
+ */
+exports.reportException = function reportException(who, exception) {
+ const msg = `${who} threw an exception: ${exports.safeErrorString(exception)}`;
+ dump(msg + "\n");
+
+ if (typeof console !== "undefined" && console && console.error) {
+ console.error(msg);
+ }
+};
+
+/**
+ * Given a handler function that may throw, return an infallible handler
+ * function that calls the fallible handler, and logs any exceptions it
+ * throws.
+ *
+ * @param handler function
+ * A handler function, which may throw.
+ * @param aName string
+ * A name for handler, for use in error messages. If omitted, we use
+ * handler.name.
+ *
+ * (SpiderMonkey does generate good names for anonymous functions, but we
+ * don't have a way to get at them from JavaScript at the moment.)
+ */
+exports.makeInfallible = function (handler, name = handler.name) {
+ return function (/* arguments */) {
+ try {
+ return handler.apply(this, arguments);
+ } catch (ex) {
+ let who = "Handler function";
+ if (name) {
+ who += " " + name;
+ }
+ exports.reportException(who, ex);
+ return undefined;
+ }
+ };
+};
+
+/**
+ * Turn the |error| into a string, without fail.
+ *
+ * @param {Error|any} error
+ */
+exports.safeErrorString = function (error) {
+ try {
+ let errorString = error.toString();
+ if (typeof errorString == "string") {
+ // Attempt to attach a stack to |errorString|. If it throws an error, or
+ // isn't a string, don't use it.
+ try {
+ if (error.stack) {
+ let stack = error.stack.toString();
+ if (typeof stack == "string") {
+ errorString += "\nStack: " + stack;
+ }
+ }
+ } catch (ee) { }
+
+ // Append additional line and column number information to the output,
+ // since it might not be part of the stringified error.
+ if (typeof error.lineNumber == "number" && typeof error.columnNumber == "number") {
+ errorString += "Line: " + error.lineNumber + ", column: " + error.columnNumber;
+ }
+
+ return errorString;
+ }
+ } catch (ee) { }
+
+ // We failed to find a good error description, so do the next best thing.
+ return Object.prototype.toString.call(error);
+};
+
+/**
+ * Interleaves two arrays element by element, returning the combined array, like
+ * a zip. In the case of arrays with different sizes, undefined values will be
+ * interleaved at the end along with the extra values of the larger array.
+ *
+ * @param Array a
+ * @param Array b
+ * @returns Array
+ * The combined array, in the form [a1, b1, a2, b2, ...]
+ */
+exports.zip = function (a, b) {
+ if (!b) {
+ return a;
+ }
+ if (!a) {
+ return b;
+ }
+ const pairs = [];
+ for (let i = 0, aLength = a.length, bLength = b.length;
+ i < aLength || i < bLength;
+ i++) {
+ pairs.push([a[i], b[i]]);
+ }
+ return pairs;
+};
+
+/**
+ * Converts an object into an array with 2-element arrays as key/value
+ * pairs of the object. `{ foo: 1, bar: 2}` would become
+ * `[[foo, 1], [bar 2]]` (order not guaranteed).
+ *
+ * @param object obj
+ * @returns array
+ */
+exports.entries = function entries(obj) {
+ return Object.keys(obj).map(k => [k, obj[k]]);
+};
+
+/*
+ * Takes an array of 2-element arrays as key/values pairs and
+ * constructs an object using them.
+ */
+exports.toObject = function (arr) {
+ const obj = {};
+ for (let [k, v] of arr) {
+ obj[k] = v;
+ }
+ return obj;
+};
+
+/**
+ * Composes the given functions into a single function, which will
+ * apply the results of each function right-to-left, starting with
+ * applying the given arguments to the right-most function.
+ * `compose(foo, bar, baz)` === `args => foo(bar(baz(args)))`
+ *
+ * @param ...function funcs
+ * @returns function
+ */
+exports.compose = function compose(...funcs) {
+ return (...args) => {
+ const initialValue = funcs[funcs.length - 1](...args);
+ const leftFuncs = funcs.slice(0, -1);
+ return leftFuncs.reduceRight((composed, f) => f(composed),
+ initialValue);
+ };
+};
+
+/**
+ * Return true if `thing` is a generator function, false otherwise.
+ */
+exports.isGenerator = function (fn) {
+ if (typeof fn !== "function") {
+ return false;
+ }
+ let proto = Object.getPrototypeOf(fn);
+ if (!proto) {
+ return false;
+ }
+ let ctor = proto.constructor;
+ if (!ctor) {
+ return false;
+ }
+ return ctor.name == "GeneratorFunction";
+};
+
+/**
+ * Return true if `thing` is a Promise or then-able, false otherwise.
+ */
+exports.isPromise = function (p) {
+ return p && typeof p.then === "function";
+};
+
+/**
+ * Return true if `thing` is a SavedFrame, false otherwise.
+ */
+exports.isSavedFrame = function (thing) {
+ return Object.prototype.toString.call(thing) === "[object SavedFrame]";
+};
+
+/**
+ * Return true iff `thing` is a `Set` object (possibly from another global).
+ */
+exports.isSet = function (thing) {
+ return Object.prototype.toString.call(thing) === "[object Set]";
+};
+
+/**
+ * Given a list of lists, flatten it. Only flattens one level; does not
+ * recursively flatten all levels.
+ *
+ * @param {Array<Array<Any>>} lists
+ * @return {Array<Any>}
+ */
+exports.flatten = function (lists) {
+ return Array.prototype.concat.apply([], lists);
+};
+
+/**
+ * Returns a promise that is resolved or rejected when all promises have settled
+ * (resolved or rejected).
+ *
+ * This differs from Promise.all, which will reject immediately after the first
+ * rejection, instead of waiting for the remaining promises to settle.
+ *
+ * @param values
+ * Iterable of promises that may be pending, resolved, or rejected. When
+ * when all promises have settled (resolved or rejected), the returned
+ * promise will be resolved or rejected as well.
+ *
+ * @return A new promise that is fulfilled when all values have settled
+ * (resolved or rejected). Its resolution value will be an array of all
+ * resolved values in the given order, or undefined if values is an
+ * empty array. The reject reason will be forwarded from the first
+ * promise in the list of given promises to be rejected.
+ */
+exports.settleAll = values => {
+ if (values === null || typeof (values[Symbol.iterator]) != "function") {
+ throw new Error("settleAll() expects an iterable.");
+ }
+
+ return new Promise((resolve, reject) => {
+ values = Array.isArray(values) ? values : [...values];
+ let countdown = values.length;
+ let resolutionValues = new Array(countdown);
+ let rejectionValue;
+ let rejectionOccurred = false;
+
+ if (!countdown) {
+ resolve(resolutionValues);
+ return deferred.promise;
+ }
+
+ function checkForCompletion() {
+ if (--countdown > 0) {
+ return;
+ }
+ if (!rejectionOccurred) {
+ resolve(resolutionValues);
+ } else {
+ reject(rejectionValue);
+ }
+ }
+
+ for (let i = 0; i < values.length; i++) {
+ let index = i;
+ let value = values[i];
+ let resolver = result => {
+ resolutionValues[index] = result;
+ checkForCompletion();
+ };
+ let rejecter = error => {
+ if (!rejectionOccurred) {
+ rejectionValue = error;
+ rejectionOccurred = true;
+ }
+ checkForCompletion();
+ };
+
+ if (value && typeof (value.then) == "function") {
+ value.then(resolver, rejecter);
+ } else {
+ // Given value is not a promise, forward it as a resolution value.
+ resolver(value);
+ }
+ }
+ });
+};
diff --git a/devtools/shared/acorn/LICENSE b/devtools/shared/acorn/LICENSE
new file mode 100644
index 000000000..af7fab615
--- /dev/null
+++ b/devtools/shared/acorn/LICENSE
@@ -0,0 +1,23 @@
+Copyright (C) 2012 by Marijn Haverbeke <marijnh@gmail.com>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
+Please note that some subdirectories of the CodeMirror distribution
+include their own LICENSE files, and are released under different
+licences.
diff --git a/devtools/shared/acorn/UPGRADING.md b/devtools/shared/acorn/UPGRADING.md
new file mode 100644
index 000000000..f744ece7f
--- /dev/null
+++ b/devtools/shared/acorn/UPGRADING.md
@@ -0,0 +1,31 @@
+Assuming that acorn's dependencies have not changed, to upgrade our tree's
+acorn to a new version:
+
+1. Clone the acorn repository, and check out the version you want to upgrade
+to:
+
+ $ git clone https://github.com/marijnh/acorn.git
+ $ cd acorn
+ $ git checkout <version>
+
+2. Make sure that all tests pass:
+
+ $ npm install .
+ $ npm test
+
+ If there are any test failures, do not upgrade to that version of acorn!
+
+3. Copy acorn.js to our tree:
+
+ $ cp acorn.js /path/to/mozilla-central/devtools/shared/acorn/acorn.js
+
+4. Copy acorn_loose.js to our tree:
+
+ $ cp acorn_loose.js /path/to/mozilla-central/devtools/shared/acorn/acorn_loose.js
+
+5. Copy util/walk.js to our tree:
+
+ $ cp util/walk.js /path/to/mozilla-central/devtools/shared/acorn/walk.js
+
+6. Check and see if javascript pretty-printing and scratchpad work without any errors. As of version 2.6.4 we need to comment out lines in acorn_loose.js that attempt to extend the acorn object, like `acorn.parse_dammit = parse_dammit`.
+
diff --git a/devtools/shared/acorn/acorn.js b/devtools/shared/acorn/acorn.js
new file mode 100644
index 000000000..55d9a1968
--- /dev/null
+++ b/devtools/shared/acorn/acorn.js
@@ -0,0 +1,3330 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.acorn = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+// A recursive descent parser operates by defining functions for all
+// syntactic elements, and recursively calling those, each function
+// advancing the input stream and returning an AST node. Precedence
+// of constructs (for example, the fact that `!x[1]` means `!(x[1])`
+// instead of `(!x)[1]` is handled by the fact that the parser
+// function that parses unary prefix operators is called first, and
+// in turn calls the function that parses `[]` subscripts — that
+// way, it'll receive the node for `x[1]` already parsed, and wraps
+// *that* in the unary operator node.
+//
+// Acorn uses an [operator precedence parser][opp] to handle binary
+// operator precedence, because it is much more compact than using
+// the technique outlined above, which uses different, nesting
+// functions to specify precedence, for all of the ten binary
+// precedence levels that JavaScript defines.
+//
+// [opp]: http://en.wikipedia.org/wiki/Operator-precedence_parser
+
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var pp = _state.Parser.prototype;
+
+// Check if property name clashes with already added.
+// Object/class getters and setters are not allowed to clash —
+// either with each other or with an init property — and in
+// strict mode, init properties are also not allowed to be repeated.
+
+pp.checkPropClash = function (prop, propHash) {
+ if (this.options.ecmaVersion >= 6 && (prop.computed || prop.method || prop.shorthand)) return;
+ var key = prop.key;var name = undefined;
+ switch (key.type) {
+ case "Identifier":
+ name = key.name;break;
+ case "Literal":
+ name = String(key.value);break;
+ default:
+ return;
+ }
+ var kind = prop.kind;
+
+ if (this.options.ecmaVersion >= 6) {
+ if (name === "__proto__" && kind === "init") {
+ if (propHash.proto) this.raise(key.start, "Redefinition of __proto__ property");
+ propHash.proto = true;
+ }
+ return;
+ }
+ name = "$" + name;
+ var other = propHash[name];
+ if (other) {
+ var isGetSet = kind !== "init";
+ if ((this.strict || isGetSet) && other[kind] || !(isGetSet ^ other.init)) this.raise(key.start, "Redefinition of property");
+ } else {
+ other = propHash[name] = {
+ init: false,
+ get: false,
+ set: false
+ };
+ }
+ other[kind] = true;
+};
+
+// ### Expression parsing
+
+// These nest, from the most general expression type at the top to
+// 'atomic', nondivisible expression types at the bottom. Most of
+// the functions will simply let the function(s) below them parse,
+// and, *if* the syntactic construct they handle is present, wrap
+// the AST node that the inner parser gave them in another node.
+
+// Parse a full expression. The optional arguments are used to
+// forbid the `in` operator (in for loops initalization expressions)
+// and provide reference for storing '=' operator inside shorthand
+// property assignment in contexts where both object expression
+// and object pattern might appear (so it's possible to raise
+// delayed syntax error at correct position).
+
+pp.parseExpression = function (noIn, refDestructuringErrors) {
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var expr = this.parseMaybeAssign(noIn, refDestructuringErrors);
+ if (this.type === _tokentype.types.comma) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.expressions = [expr];
+ while (this.eat(_tokentype.types.comma)) node.expressions.push(this.parseMaybeAssign(noIn, refDestructuringErrors));
+ return this.finishNode(node, "SequenceExpression");
+ }
+ return expr;
+};
+
+// Parse an assignment expression. This includes applications of
+// operators like `+=`.
+
+pp.parseMaybeAssign = function (noIn, refDestructuringErrors, afterLeftParse) {
+ if (this.type == _tokentype.types._yield && this.inGenerator) return this.parseYield();
+
+ var validateDestructuring = false;
+ if (!refDestructuringErrors) {
+ refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 };
+ validateDestructuring = true;
+ }
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ if (this.type == _tokentype.types.parenL || this.type == _tokentype.types.name) this.potentialArrowAt = this.start;
+ var left = this.parseMaybeConditional(noIn, refDestructuringErrors);
+ if (afterLeftParse) left = afterLeftParse.call(this, left, startPos, startLoc);
+ if (this.type.isAssign) {
+ if (validateDestructuring) this.checkPatternErrors(refDestructuringErrors, true);
+ var node = this.startNodeAt(startPos, startLoc);
+ node.operator = this.value;
+ node.left = this.type === _tokentype.types.eq ? this.toAssignable(left) : left;
+ refDestructuringErrors.shorthandAssign = 0; // reset because shorthand default was used correctly
+ this.checkLVal(left);
+ this.next();
+ node.right = this.parseMaybeAssign(noIn);
+ return this.finishNode(node, "AssignmentExpression");
+ } else {
+ if (validateDestructuring) this.checkExpressionErrors(refDestructuringErrors, true);
+ }
+ return left;
+};
+
+// Parse a ternary conditional (`?:`) operator.
+
+pp.parseMaybeConditional = function (noIn, refDestructuringErrors) {
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var expr = this.parseExprOps(noIn, refDestructuringErrors);
+ if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+ if (this.eat(_tokentype.types.question)) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.test = expr;
+ node.consequent = this.parseMaybeAssign();
+ this.expect(_tokentype.types.colon);
+ node.alternate = this.parseMaybeAssign(noIn);
+ return this.finishNode(node, "ConditionalExpression");
+ }
+ return expr;
+};
+
+// Start the precedence parser.
+
+pp.parseExprOps = function (noIn, refDestructuringErrors) {
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var expr = this.parseMaybeUnary(refDestructuringErrors);
+ if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+ return this.parseExprOp(expr, startPos, startLoc, -1, noIn);
+};
+
+// Parse binary operators with the operator precedence parsing
+// algorithm. `left` is the left-hand side of the operator.
+// `minPrec` provides context that allows the function to stop and
+// defer further parser to one of its callers when it encounters an
+// operator that has a lower precedence than the set it is parsing.
+
+pp.parseExprOp = function (left, leftStartPos, leftStartLoc, minPrec, noIn) {
+ var prec = this.type.binop;
+ if (prec != null && (!noIn || this.type !== _tokentype.types._in)) {
+ if (prec > minPrec) {
+ var node = this.startNodeAt(leftStartPos, leftStartLoc);
+ node.left = left;
+ node.operator = this.value;
+ var op = this.type;
+ this.next();
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ node.right = this.parseExprOp(this.parseMaybeUnary(), startPos, startLoc, prec, noIn);
+ this.finishNode(node, op === _tokentype.types.logicalOR || op === _tokentype.types.logicalAND ? "LogicalExpression" : "BinaryExpression");
+ return this.parseExprOp(node, leftStartPos, leftStartLoc, minPrec, noIn);
+ }
+ }
+ return left;
+};
+
+// Parse unary operators, both prefix and postfix.
+
+pp.parseMaybeUnary = function (refDestructuringErrors) {
+ if (this.type.prefix) {
+ var node = this.startNode(),
+ update = this.type === _tokentype.types.incDec;
+ node.operator = this.value;
+ node.prefix = true;
+ this.next();
+ node.argument = this.parseMaybeUnary();
+ this.checkExpressionErrors(refDestructuringErrors, true);
+ if (update) this.checkLVal(node.argument);else if (this.strict && node.operator === "delete" && node.argument.type === "Identifier") this.raise(node.start, "Deleting local variable in strict mode");
+ return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
+ }
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var expr = this.parseExprSubscripts(refDestructuringErrors);
+ if (this.checkExpressionErrors(refDestructuringErrors)) return expr;
+ while (this.type.postfix && !this.canInsertSemicolon()) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.operator = this.value;
+ node.prefix = false;
+ node.argument = expr;
+ this.checkLVal(expr);
+ this.next();
+ expr = this.finishNode(node, "UpdateExpression");
+ }
+ return expr;
+};
+
+// Parse call, dot, and `[]`-subscript expressions.
+
+pp.parseExprSubscripts = function (refDestructuringErrors) {
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var expr = this.parseExprAtom(refDestructuringErrors);
+ var skipArrowSubscripts = expr.type === "ArrowFunctionExpression" && this.input.slice(this.lastTokStart, this.lastTokEnd) !== ")";
+ if (this.checkExpressionErrors(refDestructuringErrors) || skipArrowSubscripts) return expr;
+ return this.parseSubscripts(expr, startPos, startLoc);
+};
+
+pp.parseSubscripts = function (base, startPos, startLoc, noCalls) {
+ for (;;) {
+ if (this.eat(_tokentype.types.dot)) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.object = base;
+ node.property = this.parseIdent(true);
+ node.computed = false;
+ base = this.finishNode(node, "MemberExpression");
+ } else if (this.eat(_tokentype.types.bracketL)) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.object = base;
+ node.property = this.parseExpression();
+ node.computed = true;
+ this.expect(_tokentype.types.bracketR);
+ base = this.finishNode(node, "MemberExpression");
+ } else if (!noCalls && this.eat(_tokentype.types.parenL)) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.callee = base;
+ node.arguments = this.parseExprList(_tokentype.types.parenR, false);
+ base = this.finishNode(node, "CallExpression");
+ } else if (this.type === _tokentype.types.backQuote) {
+ var node = this.startNodeAt(startPos, startLoc);
+ node.tag = base;
+ node.quasi = this.parseTemplate();
+ base = this.finishNode(node, "TaggedTemplateExpression");
+ } else {
+ return base;
+ }
+ }
+};
+
+// Parse an atomic expression — either a single token that is an
+// expression, an expression started by a keyword like `function` or
+// `new`, or an expression wrapped in punctuation like `()`, `[]`,
+// or `{}`.
+
+pp.parseExprAtom = function (refDestructuringErrors) {
+ var node = undefined,
+ canBeArrow = this.potentialArrowAt == this.start;
+ switch (this.type) {
+ case _tokentype.types._super:
+ if (!this.inFunction) this.raise(this.start, "'super' outside of function or class");
+ case _tokentype.types._this:
+ var type = this.type === _tokentype.types._this ? "ThisExpression" : "Super";
+ node = this.startNode();
+ this.next();
+ return this.finishNode(node, type);
+
+ case _tokentype.types._yield:
+ if (this.inGenerator) this.unexpected();
+
+ case _tokentype.types.name:
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ var id = this.parseIdent(this.type !== _tokentype.types.name);
+ if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), [id]);
+ return id;
+
+ case _tokentype.types.regexp:
+ var value = this.value;
+ node = this.parseLiteral(value.value);
+ node.regex = { pattern: value.pattern, flags: value.flags };
+ return node;
+
+ case _tokentype.types.num:case _tokentype.types.string:
+ return this.parseLiteral(this.value);
+
+ case _tokentype.types._null:case _tokentype.types._true:case _tokentype.types._false:
+ node = this.startNode();
+ node.value = this.type === _tokentype.types._null ? null : this.type === _tokentype.types._true;
+ node.raw = this.type.keyword;
+ this.next();
+ return this.finishNode(node, "Literal");
+
+ case _tokentype.types.parenL:
+ return this.parseParenAndDistinguishExpression(canBeArrow);
+
+ case _tokentype.types.bracketL:
+ node = this.startNode();
+ this.next();
+ // check whether this is array comprehension or regular array
+ if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) {
+ return this.parseComprehension(node, false);
+ }
+ node.elements = this.parseExprList(_tokentype.types.bracketR, true, true, refDestructuringErrors);
+ return this.finishNode(node, "ArrayExpression");
+
+ case _tokentype.types.braceL:
+ return this.parseObj(false, refDestructuringErrors);
+
+ case _tokentype.types._function:
+ node = this.startNode();
+ this.next();
+ return this.parseFunction(node, false);
+
+ case _tokentype.types._class:
+ return this.parseClass(this.startNode(), false);
+
+ case _tokentype.types._new:
+ return this.parseNew();
+
+ case _tokentype.types.backQuote:
+ return this.parseTemplate();
+
+ default:
+ this.unexpected();
+ }
+};
+
+pp.parseLiteral = function (value) {
+ var node = this.startNode();
+ node.value = value;
+ node.raw = this.input.slice(this.start, this.end);
+ this.next();
+ return this.finishNode(node, "Literal");
+};
+
+pp.parseParenExpression = function () {
+ this.expect(_tokentype.types.parenL);
+ var val = this.parseExpression();
+ this.expect(_tokentype.types.parenR);
+ return val;
+};
+
+pp.parseParenAndDistinguishExpression = function (canBeArrow) {
+ var startPos = this.start,
+ startLoc = this.startLoc,
+ val = undefined;
+ if (this.options.ecmaVersion >= 6) {
+ this.next();
+
+ if (this.options.ecmaVersion >= 7 && this.type === _tokentype.types._for) {
+ return this.parseComprehension(this.startNodeAt(startPos, startLoc), true);
+ }
+
+ var innerStartPos = this.start,
+ innerStartLoc = this.startLoc;
+ var exprList = [],
+ first = true;
+ var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 },
+ spreadStart = undefined,
+ innerParenStart = undefined;
+ while (this.type !== _tokentype.types.parenR) {
+ first ? first = false : this.expect(_tokentype.types.comma);
+ if (this.type === _tokentype.types.ellipsis) {
+ spreadStart = this.start;
+ exprList.push(this.parseParenItem(this.parseRest()));
+ break;
+ } else {
+ if (this.type === _tokentype.types.parenL && !innerParenStart) {
+ innerParenStart = this.start;
+ }
+ exprList.push(this.parseMaybeAssign(false, refDestructuringErrors, this.parseParenItem));
+ }
+ }
+ var innerEndPos = this.start,
+ innerEndLoc = this.startLoc;
+ this.expect(_tokentype.types.parenR);
+
+ if (canBeArrow && !this.canInsertSemicolon() && this.eat(_tokentype.types.arrow)) {
+ this.checkPatternErrors(refDestructuringErrors, true);
+ if (innerParenStart) this.unexpected(innerParenStart);
+ return this.parseParenArrowList(startPos, startLoc, exprList);
+ }
+
+ if (!exprList.length) this.unexpected(this.lastTokStart);
+ if (spreadStart) this.unexpected(spreadStart);
+ this.checkExpressionErrors(refDestructuringErrors, true);
+
+ if (exprList.length > 1) {
+ val = this.startNodeAt(innerStartPos, innerStartLoc);
+ val.expressions = exprList;
+ this.finishNodeAt(val, "SequenceExpression", innerEndPos, innerEndLoc);
+ } else {
+ val = exprList[0];
+ }
+ } else {
+ val = this.parseParenExpression();
+ }
+
+ if (this.options.preserveParens) {
+ var par = this.startNodeAt(startPos, startLoc);
+ par.expression = val;
+ return this.finishNode(par, "ParenthesizedExpression");
+ } else {
+ return val;
+ }
+};
+
+pp.parseParenItem = function (item) {
+ return item;
+};
+
+pp.parseParenArrowList = function (startPos, startLoc, exprList) {
+ return this.parseArrowExpression(this.startNodeAt(startPos, startLoc), exprList);
+};
+
+// New's precedence is slightly tricky. It must allow its argument
+// to be a `[]` or dot subscript expression, but not a call — at
+// least, not without wrapping it in parentheses. Thus, it uses the
+
+var empty = [];
+
+pp.parseNew = function () {
+ var node = this.startNode();
+ var meta = this.parseIdent(true);
+ if (this.options.ecmaVersion >= 6 && this.eat(_tokentype.types.dot)) {
+ node.meta = meta;
+ node.property = this.parseIdent(true);
+ if (node.property.name !== "target") this.raise(node.property.start, "The only valid meta property for new is new.target");
+ if (!this.inFunction) this.raise(node.start, "new.target can only be used in functions");
+ return this.finishNode(node, "MetaProperty");
+ }
+ var startPos = this.start,
+ startLoc = this.startLoc;
+ node.callee = this.parseSubscripts(this.parseExprAtom(), startPos, startLoc, true);
+ if (this.eat(_tokentype.types.parenL)) node.arguments = this.parseExprList(_tokentype.types.parenR, false);else node.arguments = empty;
+ return this.finishNode(node, "NewExpression");
+};
+
+// Parse template expression.
+
+pp.parseTemplateElement = function () {
+ var elem = this.startNode();
+ elem.value = {
+ raw: this.input.slice(this.start, this.end).replace(/\r\n?/g, '\n'),
+ cooked: this.value
+ };
+ this.next();
+ elem.tail = this.type === _tokentype.types.backQuote;
+ return this.finishNode(elem, "TemplateElement");
+};
+
+pp.parseTemplate = function () {
+ var node = this.startNode();
+ this.next();
+ node.expressions = [];
+ var curElt = this.parseTemplateElement();
+ node.quasis = [curElt];
+ while (!curElt.tail) {
+ this.expect(_tokentype.types.dollarBraceL);
+ node.expressions.push(this.parseExpression());
+ this.expect(_tokentype.types.braceR);
+ node.quasis.push(curElt = this.parseTemplateElement());
+ }
+ this.next();
+ return this.finishNode(node, "TemplateLiteral");
+};
+
+// Parse an object literal or binding pattern.
+
+pp.parseObj = function (isPattern, refDestructuringErrors) {
+ var node = this.startNode(),
+ first = true,
+ propHash = {};
+ node.properties = [];
+ this.next();
+ while (!this.eat(_tokentype.types.braceR)) {
+ if (!first) {
+ this.expect(_tokentype.types.comma);
+ if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+ } else first = false;
+
+ var prop = this.startNode(),
+ isGenerator = undefined,
+ startPos = undefined,
+ startLoc = undefined;
+ if (this.options.ecmaVersion >= 6) {
+ prop.method = false;
+ prop.shorthand = false;
+ if (isPattern || refDestructuringErrors) {
+ startPos = this.start;
+ startLoc = this.startLoc;
+ }
+ if (!isPattern) isGenerator = this.eat(_tokentype.types.star);
+ }
+ this.parsePropertyName(prop);
+ this.parsePropertyValue(prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors);
+ this.checkPropClash(prop, propHash);
+ node.properties.push(this.finishNode(prop, "Property"));
+ }
+ return this.finishNode(node, isPattern ? "ObjectPattern" : "ObjectExpression");
+};
+
+pp.parsePropertyValue = function (prop, isPattern, isGenerator, startPos, startLoc, refDestructuringErrors) {
+ if (this.eat(_tokentype.types.colon)) {
+ prop.value = isPattern ? this.parseMaybeDefault(this.start, this.startLoc) : this.parseMaybeAssign(false, refDestructuringErrors);
+ prop.kind = "init";
+ } else if (this.options.ecmaVersion >= 6 && this.type === _tokentype.types.parenL) {
+ if (isPattern) this.unexpected();
+ prop.kind = "init";
+ prop.method = true;
+ prop.value = this.parseMethod(isGenerator);
+ } else if (this.options.ecmaVersion >= 5 && !prop.computed && prop.key.type === "Identifier" && (prop.key.name === "get" || prop.key.name === "set") && (this.type != _tokentype.types.comma && this.type != _tokentype.types.braceR)) {
+ if (isGenerator || isPattern) this.unexpected();
+ prop.kind = prop.key.name;
+ this.parsePropertyName(prop);
+ prop.value = this.parseMethod(false);
+ var paramCount = prop.kind === "get" ? 0 : 1;
+ if (prop.value.params.length !== paramCount) {
+ var start = prop.value.start;
+ if (prop.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param");
+ }
+ } else if (this.options.ecmaVersion >= 6 && !prop.computed && prop.key.type === "Identifier") {
+ prop.kind = "init";
+ if (isPattern) {
+ if (this.keywords.test(prop.key.name) || (this.strict ? this.reservedWordsStrictBind : this.reservedWords).test(prop.key.name)) this.raise(prop.key.start, "Binding " + prop.key.name);
+ prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
+ } else if (this.type === _tokentype.types.eq && refDestructuringErrors) {
+ if (!refDestructuringErrors.shorthandAssign) refDestructuringErrors.shorthandAssign = this.start;
+ prop.value = this.parseMaybeDefault(startPos, startLoc, prop.key);
+ } else {
+ prop.value = prop.key;
+ }
+ prop.shorthand = true;
+ } else this.unexpected();
+};
+
+pp.parsePropertyName = function (prop) {
+ if (this.options.ecmaVersion >= 6) {
+ if (this.eat(_tokentype.types.bracketL)) {
+ prop.computed = true;
+ prop.key = this.parseMaybeAssign();
+ this.expect(_tokentype.types.bracketR);
+ return prop.key;
+ } else {
+ prop.computed = false;
+ }
+ }
+ return prop.key = this.type === _tokentype.types.num || this.type === _tokentype.types.string ? this.parseExprAtom() : this.parseIdent(true);
+};
+
+// Initialize empty function node.
+
+pp.initFunction = function (node) {
+ node.id = null;
+ if (this.options.ecmaVersion >= 6) {
+ node.generator = false;
+ node.expression = false;
+ }
+};
+
+// Parse object or class method.
+
+pp.parseMethod = function (isGenerator) {
+ var node = this.startNode();
+ this.initFunction(node);
+ this.expect(_tokentype.types.parenL);
+ node.params = this.parseBindingList(_tokentype.types.parenR, false, false);
+ if (this.options.ecmaVersion >= 6) node.generator = isGenerator;
+ this.parseFunctionBody(node, false);
+ return this.finishNode(node, "FunctionExpression");
+};
+
+// Parse arrow function expression with given parameters.
+
+pp.parseArrowExpression = function (node, params) {
+ this.initFunction(node);
+ node.params = this.toAssignableList(params, true);
+ this.parseFunctionBody(node, true);
+ return this.finishNode(node, "ArrowFunctionExpression");
+};
+
+// Parse function body and check parameters.
+
+pp.parseFunctionBody = function (node, isArrowFunction) {
+ var isExpression = isArrowFunction && this.type !== _tokentype.types.braceL;
+
+ if (isExpression) {
+ node.body = this.parseMaybeAssign();
+ node.expression = true;
+ } else {
+ // Start a new scope with regard to labels and the `inFunction`
+ // flag (restore them to their old value afterwards).
+ var oldInFunc = this.inFunction,
+ oldInGen = this.inGenerator,
+ oldLabels = this.labels;
+ this.inFunction = true;this.inGenerator = node.generator;this.labels = [];
+ node.body = this.parseBlock(true);
+ node.expression = false;
+ this.inFunction = oldInFunc;this.inGenerator = oldInGen;this.labels = oldLabels;
+ }
+
+ // If this is a strict mode function, verify that argument names
+ // are not repeated, and it does not try to bind the words `eval`
+ // or `arguments`.
+ if (this.strict || !isExpression && node.body.body.length && this.isUseStrict(node.body.body[0])) {
+ var oldStrict = this.strict;
+ this.strict = true;
+ if (node.id) this.checkLVal(node.id, true);
+ this.checkParams(node);
+ this.strict = oldStrict;
+ } else if (isArrowFunction) {
+ this.checkParams(node);
+ }
+};
+
+// Checks function params for various disallowed patterns such as using "eval"
+// or "arguments" and duplicate parameters.
+
+pp.checkParams = function (node) {
+ var nameHash = {};
+ for (var i = 0; i < node.params.length; i++) {
+ this.checkLVal(node.params[i], true, nameHash);
+ }
+};
+
+// Parses a comma-separated list of expressions, and returns them as
+// an array. `close` is the token type that ends the list, and
+// `allowEmpty` can be turned on to allow subsequent commas with
+// nothing in between them to be parsed as `null` (which is needed
+// for array literals).
+
+pp.parseExprList = function (close, allowTrailingComma, allowEmpty, refDestructuringErrors) {
+ var elts = [],
+ first = true;
+ while (!this.eat(close)) {
+ if (!first) {
+ this.expect(_tokentype.types.comma);
+ if (this.type === close && refDestructuringErrors && !refDestructuringErrors.trailingComma) {
+ refDestructuringErrors.trailingComma = this.lastTokStart;
+ }
+ if (allowTrailingComma && this.afterTrailingComma(close)) break;
+ } else first = false;
+
+ var elt = undefined;
+ if (allowEmpty && this.type === _tokentype.types.comma) elt = null;else if (this.type === _tokentype.types.ellipsis) elt = this.parseSpread(refDestructuringErrors);else elt = this.parseMaybeAssign(false, refDestructuringErrors);
+ elts.push(elt);
+ }
+ return elts;
+};
+
+// Parse the next token as an identifier. If `liberal` is true (used
+// when parsing properties), it will also convert keywords into
+// identifiers.
+
+pp.parseIdent = function (liberal) {
+ var node = this.startNode();
+ if (liberal && this.options.allowReserved == "never") liberal = false;
+ if (this.type === _tokentype.types.name) {
+ if (!liberal && (this.strict ? this.reservedWordsStrict : this.reservedWords).test(this.value) && (this.options.ecmaVersion >= 6 || this.input.slice(this.start, this.end).indexOf("\\") == -1)) this.raise(this.start, "The keyword '" + this.value + "' is reserved");
+ node.name = this.value;
+ } else if (liberal && this.type.keyword) {
+ node.name = this.type.keyword;
+ } else {
+ this.unexpected();
+ }
+ this.next();
+ return this.finishNode(node, "Identifier");
+};
+
+// Parses yield expression inside generator.
+
+pp.parseYield = function () {
+ var node = this.startNode();
+ this.next();
+ if (this.type == _tokentype.types.semi || this.canInsertSemicolon() || this.type != _tokentype.types.star && !this.type.startsExpr) {
+ node.delegate = false;
+ node.argument = null;
+ } else {
+ node.delegate = this.eat(_tokentype.types.star);
+ node.argument = this.parseMaybeAssign();
+ }
+ return this.finishNode(node, "YieldExpression");
+};
+
+// Parses array and generator comprehensions.
+
+pp.parseComprehension = function (node, isGenerator) {
+ node.blocks = [];
+ while (this.type === _tokentype.types._for) {
+ var block = this.startNode();
+ this.next();
+ this.expect(_tokentype.types.parenL);
+ block.left = this.parseBindingAtom();
+ this.checkLVal(block.left, true);
+ this.expectContextual("of");
+ block.right = this.parseExpression();
+ this.expect(_tokentype.types.parenR);
+ node.blocks.push(this.finishNode(block, "ComprehensionBlock"));
+ }
+ node.filter = this.eat(_tokentype.types._if) ? this.parseParenExpression() : null;
+ node.body = this.parseExpression();
+ this.expect(isGenerator ? _tokentype.types.parenR : _tokentype.types.bracketR);
+ node.generator = isGenerator;
+ return this.finishNode(node, "ComprehensionExpression");
+};
+
+},{"./state":10,"./tokentype":14}],2:[function(_dereq_,module,exports){
+// This is a trick taken from Esprima. It turns out that, on
+// non-Chrome browsers, to check whether a string is in a set, a
+// predicate containing a big ugly `switch` statement is faster than
+// a regular expression, and on Chrome the two are about on par.
+// This function uses `eval` (non-lexical) to produce such a
+// predicate from a space-separated string of words.
+//
+// It starts by sorting the words by length.
+
+// Reserved word lists for various dialects of the language
+
+"use strict";
+
+exports.__esModule = true;
+exports.isIdentifierStart = isIdentifierStart;
+exports.isIdentifierChar = isIdentifierChar;
+var reservedWords = {
+ 3: "abstract boolean byte char class double enum export extends final float goto implements import int interface long native package private protected public short static super synchronized throws transient volatile",
+ 5: "class enum extends super const export import",
+ 6: "enum",
+ strict: "implements interface let package private protected public static yield",
+ strictBind: "eval arguments"
+};
+
+exports.reservedWords = reservedWords;
+// And the keywords
+
+var ecma5AndLessKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this";
+
+var keywords = {
+ 5: ecma5AndLessKeywords,
+ 6: ecma5AndLessKeywords + " let const class extends export import yield super"
+};
+
+exports.keywords = keywords;
+// ## Character categories
+
+// Big ugly regular expressions that match characters in the
+// whitespace, identifier, and identifier-start categories. These
+// are only applied when a character is found to actually have a
+// code point above 128.
+// Generated by `bin/generate-identifier-regex.js`.
+
+var nonASCIIidentifierStartChars = "ªµºÀ-ÖØ-öø-ˁˆ-ˑˠ-ˤˬˮͰ-ʹͶͷͺ-ͽͿΆΈ-ΊΌΎ-ΡΣ-ϵϷ-ҁҊ-ԯԱ-Ֆՙա-ևא-תװ-ײؠ-يٮٯٱ-ۓەۥۦۮۯۺ-ۼۿܐܒ-ܯݍ-ޥޱߊ-ߪߴߵߺࠀ-ࠕࠚࠤࠨࡀ-ࡘࢠ-ࢲऄ-हऽॐक़-ॡॱ-ঀঅ-ঌএঐও-নপ-রলশ-হঽৎড়ঢ়য়-ৡৰৱਅ-ਊਏਐਓ-ਨਪ-ਰਲਲ਼ਵਸ਼ਸਹਖ਼-ੜਫ਼ੲ-ੴઅ-ઍએ-ઑઓ-નપ-રલળવ-હઽૐૠૡଅ-ଌଏଐଓ-ନପ-ରଲଳଵ-ହଽଡ଼ଢ଼ୟ-ୡୱஃஅ-ஊஎ-ஐஒ-கஙசஜஞடணதந-பம-ஹௐఅ-ఌఎ-ఐఒ-నప-హఽౘౙౠౡಅ-ಌಎ-ಐಒ-ನಪ-ಳವ-ಹಽೞೠೡೱೲഅ-ഌഎ-ഐഒ-ഺഽൎൠൡൺ-ൿඅ-ඖක-නඳ-රලව-ෆก-ะาำเ-ๆກຂຄງຈຊຍດ-ທນ-ຟມ-ຣລວສຫອ-ະາຳຽເ-ໄໆໜ-ໟༀཀ-ཇཉ-ཬྈ-ྌက-ဪဿၐ-ၕၚ-ၝၡၥၦၮ-ၰၵ-ႁႎႠ-ჅჇჍა-ჺჼ-ቈቊ-ቍቐ-ቖቘቚ-ቝበ-ኈኊ-ኍነ-ኰኲ-ኵኸ-ኾዀዂ-ዅወ-ዖዘ-ጐጒ-ጕጘ-ፚᎀ-ᎏᎠ-Ᏼᐁ-ᙬᙯ-ᙿᚁ-ᚚᚠ-ᛪᛮ-ᛸᜀ-ᜌᜎ-ᜑᜠ-ᜱᝀ-ᝑᝠ-ᝬᝮ-ᝰក-ឳៗៜᠠ-ᡷᢀ-ᢨᢪᢰ-ᣵᤀ-ᤞᥐ-ᥭᥰ-ᥴᦀ-ᦫᧁ-ᧇᨀ-ᨖᨠ-ᩔᪧᬅ-ᬳᭅ-ᭋᮃ-ᮠᮮᮯᮺ-ᯥᰀ-ᰣᱍ-ᱏᱚ-ᱽᳩ-ᳬᳮ-ᳱᳵᳶᴀ-ᶿḀ-ἕἘ-Ἕἠ-ὅὈ-Ὅὐ-ὗὙὛὝὟ-ώᾀ-ᾴᾶ-ᾼιῂ-ῄῆ-ῌῐ-ΐῖ-Ίῠ-Ῥῲ-ῴῶ-ῼⁱⁿₐ-ₜℂℇℊ-ℓℕ℘-ℝℤΩℨK-ℹℼ-ℿⅅ-ⅉⅎⅠ-ↈⰀ-Ⱞⰰ-ⱞⱠ-ⳤⳫ-ⳮⳲⳳⴀ-ⴥⴧⴭⴰ-ⵧⵯⶀ-ⶖⶠ-ⶦⶨ-ⶮⶰ-ⶶⶸ-ⶾⷀ-ⷆⷈ-ⷎⷐ-ⷖⷘ-ⷞ々-〇〡-〩〱-〵〸-〼ぁ-ゖ゛-ゟァ-ヺー-ヿㄅ-ㄭㄱ-ㆎㆠ-ㆺㇰ-ㇿ㐀-䶵一-鿌ꀀ-ꒌꓐ-ꓽꔀ-ꘌꘐ-ꘟꘪꘫꙀ-ꙮꙿ-ꚝꚠ-ꛯꜗ-ꜟꜢ-ꞈꞋ-ꞎꞐ-ꞭꞰꞱꟷ-ꠁꠃ-ꠅꠇ-ꠊꠌ-ꠢꡀ-ꡳꢂ-ꢳꣲ-ꣷꣻꤊ-ꤥꤰ-ꥆꥠ-ꥼꦄ-ꦲꧏꧠ-ꧤꧦ-ꧯꧺ-ꧾꨀ-ꨨꩀ-ꩂꩄ-ꩋꩠ-ꩶꩺꩾ-ꪯꪱꪵꪶꪹ-ꪽꫀꫂꫛ-ꫝꫠ-ꫪꫲ-ꫴꬁ-ꬆꬉ-ꬎꬑ-ꬖꬠ-ꬦꬨ-ꬮꬰ-ꭚꭜ-ꭟꭤꭥꯀ-ꯢ가-힣ힰ-ퟆퟋ-ퟻ豈-舘並-龎ff-stﬓ-ﬗיִײַ-ﬨשׁ-זּטּ-לּמּנּסּףּפּצּ-ﮱﯓ-ﴽﵐ-ﶏﶒ-ﷇﷰ-ﷻﹰ-ﹴﹶ-ﻼA-Za-zヲ-하-ᅦᅧ-ᅬᅭ-ᅲᅳ-ᅵ";
+var nonASCIIidentifierChars = "‌‍·̀-ͯ·҃-֑҇-ׇֽֿׁׂׅׄؐ-ًؚ-٩ٰۖ-ۜ۟-۪ۤۧۨ-ۭ۰-۹ܑܰ-݊ަ-ް߀-߉߫-߳ࠖ-࠙ࠛ-ࠣࠥ-ࠧࠩ-࡙࠭-࡛ࣤ-ःऺ-़ा-ॏ॑-ॗॢॣ०-९ঁ-ঃ়া-ৄেৈো-্ৗৢৣ০-৯ਁ-ਃ਼ਾ-ੂੇੈੋ-੍ੑ੦-ੱੵઁ-ઃ઼ા-ૅે-ૉો-્ૢૣ૦-૯ଁ-ଃ଼ା-ୄେୈୋ-୍ୖୗୢୣ୦-୯ஂா-ூெ-ைொ-்ௗ௦-௯ఀ-ఃా-ౄె-ైొ-్ౕౖౢౣ౦-౯ಁ-ಃ಼ಾ-ೄೆ-ೈೊ-್ೕೖೢೣ೦-೯ഁ-ഃാ-ൄെ-ൈൊ-്ൗൢൣ൦-൯ංඃ්ා-ුූෘ-ෟ෦-෯ෲෳัิ-ฺ็-๎๐-๙ັິ-ູົຼ່-ໍ໐-໙༘༙༠-༩༹༵༷༾༿ཱ-྄྆྇ྍ-ྗྙ-ྼ࿆ါ-ှ၀-၉ၖ-ၙၞ-ၠၢ-ၤၧ-ၭၱ-ၴႂ-ႍႏ-ႝ፝-፟፩-፱ᜒ-᜔ᜲ-᜴ᝒᝓᝲᝳ឴-៓៝០-៩᠋-᠍᠐-᠙ᢩᤠ-ᤫᤰ-᤻᥆-᥏ᦰ-ᧀᧈᧉ᧐-᧚ᨗ-ᨛᩕ-ᩞ᩠-᩿᩼-᪉᪐-᪙᪰-᪽ᬀ-ᬄ᬴-᭄᭐-᭙᭫-᭳ᮀ-ᮂᮡ-ᮭ᮰-᮹᯦-᯳ᰤ-᰷᱀-᱉᱐-᱙᳐-᳔᳒-᳨᳭ᳲ-᳴᳸᳹᷀-᷵᷼-᷿‿⁀⁔⃐-⃥⃜⃡-⃰⳯-⵿⳱ⷠ-〪ⷿ-゙゚〯꘠-꘩꙯ꙴ-꙽ꚟ꛰꛱ꠂ꠆ꠋꠣ-ꠧꢀꢁꢴ-꣄꣐-꣙꣠-꣱꤀-꤉ꤦ-꤭ꥇ-꥓ꦀ-ꦃ꦳-꧀꧐-꧙ꧥ꧰-꧹ꨩ-ꨶꩃꩌꩍ꩐-꩙ꩻ-ꩽꪰꪲ-ꪴꪷꪸꪾ꪿꫁ꫫ-ꫯꫵ꫶ꯣ-ꯪ꯬꯭꯰-꯹ﬞ︀-️︠-︭︳︴﹍-﹏0-9_";
+
+var nonASCIIidentifierStart = new RegExp("[" + nonASCIIidentifierStartChars + "]");
+var nonASCIIidentifier = new RegExp("[" + nonASCIIidentifierStartChars + nonASCIIidentifierChars + "]");
+
+nonASCIIidentifierStartChars = nonASCIIidentifierChars = null;
+
+// These are a run-length and offset encoded representation of the
+// >0xffff code points that are a valid part of identifiers. The
+// offset starts at 0x10000, and each pair of numbers represents an
+// offset to the next range, and then a size of the range. They were
+// generated by tools/generate-identifier-regex.js
+var astralIdentifierStartCodes = [0, 11, 2, 25, 2, 18, 2, 1, 2, 14, 3, 13, 35, 122, 70, 52, 268, 28, 4, 48, 48, 31, 17, 26, 6, 37, 11, 29, 3, 35, 5, 7, 2, 4, 43, 157, 99, 39, 9, 51, 157, 310, 10, 21, 11, 7, 153, 5, 3, 0, 2, 43, 2, 1, 4, 0, 3, 22, 11, 22, 10, 30, 98, 21, 11, 25, 71, 55, 7, 1, 65, 0, 16, 3, 2, 2, 2, 26, 45, 28, 4, 28, 36, 7, 2, 27, 28, 53, 11, 21, 11, 18, 14, 17, 111, 72, 955, 52, 76, 44, 33, 24, 27, 35, 42, 34, 4, 0, 13, 47, 15, 3, 22, 0, 38, 17, 2, 24, 133, 46, 39, 7, 3, 1, 3, 21, 2, 6, 2, 1, 2, 4, 4, 0, 32, 4, 287, 47, 21, 1, 2, 0, 185, 46, 82, 47, 21, 0, 60, 42, 502, 63, 32, 0, 449, 56, 1288, 920, 104, 110, 2962, 1070, 13266, 568, 8, 30, 114, 29, 19, 47, 17, 3, 32, 20, 6, 18, 881, 68, 12, 0, 67, 12, 16481, 1, 3071, 106, 6, 12, 4, 8, 8, 9, 5991, 84, 2, 70, 2, 1, 3, 0, 3, 1, 3, 3, 2, 11, 2, 0, 2, 6, 2, 64, 2, 3, 3, 7, 2, 6, 2, 27, 2, 3, 2, 4, 2, 0, 4, 6, 2, 339, 3, 24, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 30, 2, 24, 2, 7, 4149, 196, 1340, 3, 2, 26, 2, 1, 2, 0, 3, 0, 2, 9, 2, 3, 2, 0, 2, 0, 7, 0, 5, 0, 2, 0, 2, 0, 2, 2, 2, 1, 2, 0, 3, 0, 2, 0, 2, 0, 2, 0, 2, 0, 2, 1, 2, 0, 3, 3, 2, 6, 2, 3, 2, 3, 2, 0, 2, 9, 2, 16, 6, 2, 2, 4, 2, 16, 4421, 42710, 42, 4148, 12, 221, 16355, 541];
+var astralIdentifierCodes = [509, 0, 227, 0, 150, 4, 294, 9, 1368, 2, 2, 1, 6, 3, 41, 2, 5, 0, 166, 1, 1306, 2, 54, 14, 32, 9, 16, 3, 46, 10, 54, 9, 7, 2, 37, 13, 2, 9, 52, 0, 13, 2, 49, 13, 16, 9, 83, 11, 168, 11, 6, 9, 8, 2, 57, 0, 2, 6, 3, 1, 3, 2, 10, 0, 11, 1, 3, 6, 4, 4, 316, 19, 13, 9, 214, 6, 3, 8, 112, 16, 16, 9, 82, 12, 9, 9, 535, 9, 20855, 9, 135, 4, 60, 6, 26, 9, 1016, 45, 17, 3, 19723, 1, 5319, 4, 4, 5, 9, 7, 3, 6, 31, 3, 149, 2, 1418, 49, 4305, 6, 792618, 239];
+
+// This has a complexity linear to the value of the code. The
+// assumption is that looking up astral identifier characters is
+// rare.
+function isInAstralSet(code, set) {
+ var pos = 0x10000;
+ for (var i = 0; i < set.length; i += 2) {
+ pos += set[i];
+ if (pos > code) return false;
+ pos += set[i + 1];
+ if (pos >= code) return true;
+ }
+}
+
+// Test whether a given character code starts an identifier.
+
+function isIdentifierStart(code, astral) {
+ if (code < 65) return code === 36;
+ if (code < 91) return true;
+ if (code < 97) return code === 95;
+ if (code < 123) return true;
+ if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifierStart.test(String.fromCharCode(code));
+ if (astral === false) return false;
+ return isInAstralSet(code, astralIdentifierStartCodes);
+}
+
+// Test whether a given character is part of an identifier.
+
+function isIdentifierChar(code, astral) {
+ if (code < 48) return code === 36;
+ if (code < 58) return true;
+ if (code < 65) return false;
+ if (code < 91) return true;
+ if (code < 97) return code === 95;
+ if (code < 123) return true;
+ if (code <= 0xffff) return code >= 0xaa && nonASCIIidentifier.test(String.fromCharCode(code));
+ if (astral === false) return false;
+ return isInAstralSet(code, astralIdentifierStartCodes) || isInAstralSet(code, astralIdentifierCodes);
+}
+
+},{}],3:[function(_dereq_,module,exports){
+// Acorn is a tiny, fast JavaScript parser written in JavaScript.
+//
+// Acorn was written by Marijn Haverbeke, Ingvar Stepanyan, and
+// various contributors and released under an MIT license.
+//
+// Git repositories for Acorn are available at
+//
+// http://marijnhaverbeke.nl/git/acorn
+// https://github.com/ternjs/acorn.git
+//
+// Please use the [github bug tracker][ghbt] to report issues.
+//
+// [ghbt]: https://github.com/ternjs/acorn/issues
+//
+// This file defines the main parser interface. The library also comes
+// with a [error-tolerant parser][dammit] and an
+// [abstract syntax tree walker][walk], defined in other files.
+//
+// [dammit]: acorn_loose.js
+// [walk]: util/walk.js
+
+"use strict";
+
+exports.__esModule = true;
+exports.parse = parse;
+exports.parseExpressionAt = parseExpressionAt;
+exports.tokenizer = tokenizer;
+
+var _state = _dereq_("./state");
+
+_dereq_("./parseutil");
+
+_dereq_("./statement");
+
+_dereq_("./lval");
+
+_dereq_("./expression");
+
+_dereq_("./location");
+
+exports.Parser = _state.Parser;
+exports.plugins = _state.plugins;
+
+var _options = _dereq_("./options");
+
+exports.defaultOptions = _options.defaultOptions;
+
+var _locutil = _dereq_("./locutil");
+
+exports.Position = _locutil.Position;
+exports.SourceLocation = _locutil.SourceLocation;
+exports.getLineInfo = _locutil.getLineInfo;
+
+var _node = _dereq_("./node");
+
+exports.Node = _node.Node;
+
+var _tokentype = _dereq_("./tokentype");
+
+exports.TokenType = _tokentype.TokenType;
+exports.tokTypes = _tokentype.types;
+
+var _tokencontext = _dereq_("./tokencontext");
+
+exports.TokContext = _tokencontext.TokContext;
+exports.tokContexts = _tokencontext.types;
+
+var _identifier = _dereq_("./identifier");
+
+exports.isIdentifierChar = _identifier.isIdentifierChar;
+exports.isIdentifierStart = _identifier.isIdentifierStart;
+
+var _tokenize = _dereq_("./tokenize");
+
+exports.Token = _tokenize.Token;
+
+var _whitespace = _dereq_("./whitespace");
+
+exports.isNewLine = _whitespace.isNewLine;
+exports.lineBreak = _whitespace.lineBreak;
+exports.lineBreakG = _whitespace.lineBreakG;
+var version = "2.6.4";
+
+exports.version = version;
+// The main exported interface (under `self.acorn` when in the
+// browser) is a `parse` function that takes a code string and
+// returns an abstract syntax tree as specified by [Mozilla parser
+// API][api].
+//
+// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+
+function parse(input, options) {
+ return new _state.Parser(options, input).parse();
+}
+
+// This function tries to parse a single expression at a given
+// offset in a string. Useful for parsing mixed-language formats
+// that embed JavaScript expressions.
+
+function parseExpressionAt(input, pos, options) {
+ var p = new _state.Parser(options, input, pos);
+ p.nextToken();
+ return p.parseExpression();
+}
+
+// Acorn is organized as a tokenizer and a recursive-descent parser.
+// The `tokenizer` export provides an interface to the tokenizer.
+
+function tokenizer(input, options) {
+ return new _state.Parser(options, input);
+}
+
+},{"./expression":1,"./identifier":2,"./location":4,"./locutil":5,"./lval":6,"./node":7,"./options":8,"./parseutil":9,"./state":10,"./statement":11,"./tokencontext":12,"./tokenize":13,"./tokentype":14,"./whitespace":16}],4:[function(_dereq_,module,exports){
+"use strict";
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var pp = _state.Parser.prototype;
+
+// This function is used to raise exceptions on parse errors. It
+// takes an offset integer (into the current `input`) to indicate
+// the location of the error, attaches the position to the end
+// of the error message, and then raises a `SyntaxError` with that
+// message.
+
+pp.raise = function (pos, message) {
+ var loc = _locutil.getLineInfo(this.input, pos);
+ message += " (" + loc.line + ":" + loc.column + ")";
+ var err = new SyntaxError(message);
+ err.pos = pos;err.loc = loc;err.raisedAt = this.pos;
+ throw err;
+};
+
+pp.curPosition = function () {
+ if (this.options.locations) {
+ return new _locutil.Position(this.curLine, this.pos - this.lineStart);
+ }
+};
+
+},{"./locutil":5,"./state":10}],5:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.getLineInfo = getLineInfo;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _whitespace = _dereq_("./whitespace");
+
+// These are used when `options.locations` is on, for the
+// `startLoc` and `endLoc` properties.
+
+var Position = (function () {
+ function Position(line, col) {
+ _classCallCheck(this, Position);
+
+ this.line = line;
+ this.column = col;
+ }
+
+ Position.prototype.offset = function offset(n) {
+ return new Position(this.line, this.column + n);
+ };
+
+ return Position;
+})();
+
+exports.Position = Position;
+
+var SourceLocation = function SourceLocation(p, start, end) {
+ _classCallCheck(this, SourceLocation);
+
+ this.start = start;
+ this.end = end;
+ if (p.sourceFile !== null) this.source = p.sourceFile;
+}
+
+// The `getLineInfo` function is mostly useful when the
+// `locations` option is off (for performance reasons) and you
+// want to find the line/column position for a given character
+// offset. `input` should be the code string that the offset refers
+// into.
+
+;
+
+exports.SourceLocation = SourceLocation;
+
+function getLineInfo(input, offset) {
+ for (var line = 1, cur = 0;;) {
+ _whitespace.lineBreakG.lastIndex = cur;
+ var match = _whitespace.lineBreakG.exec(input);
+ if (match && match.index < offset) {
+ ++line;
+ cur = match.index + match[0].length;
+ } else {
+ return new Position(line, offset - cur);
+ }
+ }
+}
+
+},{"./whitespace":16}],6:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _util = _dereq_("./util");
+
+var pp = _state.Parser.prototype;
+
+// Convert existing expression atom to assignable pattern
+// if possible.
+
+pp.toAssignable = function (node, isBinding) {
+ if (this.options.ecmaVersion >= 6 && node) {
+ switch (node.type) {
+ case "Identifier":
+ case "ObjectPattern":
+ case "ArrayPattern":
+ break;
+
+ case "ObjectExpression":
+ node.type = "ObjectPattern";
+ for (var i = 0; i < node.properties.length; i++) {
+ var prop = node.properties[i];
+ if (prop.kind !== "init") this.raise(prop.key.start, "Object pattern can't contain getter or setter");
+ this.toAssignable(prop.value, isBinding);
+ }
+ break;
+
+ case "ArrayExpression":
+ node.type = "ArrayPattern";
+ this.toAssignableList(node.elements, isBinding);
+ break;
+
+ case "AssignmentExpression":
+ if (node.operator === "=") {
+ node.type = "AssignmentPattern";
+ delete node.operator;
+ // falls through to AssignmentPattern
+ } else {
+ this.raise(node.left.end, "Only '=' operator can be used for specifying default value.");
+ break;
+ }
+
+ case "AssignmentPattern":
+ if (node.right.type === "YieldExpression") this.raise(node.right.start, "Yield expression cannot be a default value");
+ break;
+
+ case "ParenthesizedExpression":
+ node.expression = this.toAssignable(node.expression, isBinding);
+ break;
+
+ case "MemberExpression":
+ if (!isBinding) break;
+
+ default:
+ this.raise(node.start, "Assigning to rvalue");
+ }
+ }
+ return node;
+};
+
+// Convert list of expression atoms to binding list.
+
+pp.toAssignableList = function (exprList, isBinding) {
+ var end = exprList.length;
+ if (end) {
+ var last = exprList[end - 1];
+ if (last && last.type == "RestElement") {
+ --end;
+ } else if (last && last.type == "SpreadElement") {
+ last.type = "RestElement";
+ var arg = last.argument;
+ this.toAssignable(arg, isBinding);
+ if (arg.type !== "Identifier" && arg.type !== "MemberExpression" && arg.type !== "ArrayPattern") this.unexpected(arg.start);
+ --end;
+ }
+
+ if (isBinding && last.type === "RestElement" && last.argument.type !== "Identifier") this.unexpected(last.argument.start);
+ }
+ for (var i = 0; i < end; i++) {
+ var elt = exprList[i];
+ if (elt) this.toAssignable(elt, isBinding);
+ }
+ return exprList;
+};
+
+// Parses spread element.
+
+pp.parseSpread = function (refDestructuringErrors) {
+ var node = this.startNode();
+ this.next();
+ node.argument = this.parseMaybeAssign(refDestructuringErrors);
+ return this.finishNode(node, "SpreadElement");
+};
+
+pp.parseRest = function (allowNonIdent) {
+ var node = this.startNode();
+ this.next();
+
+ // RestElement inside of a function parameter must be an identifier
+ if (allowNonIdent) node.argument = this.type === _tokentype.types.name ? this.parseIdent() : this.unexpected();else node.argument = this.type === _tokentype.types.name || this.type === _tokentype.types.bracketL ? this.parseBindingAtom() : this.unexpected();
+
+ return this.finishNode(node, "RestElement");
+};
+
+// Parses lvalue (assignable) atom.
+
+pp.parseBindingAtom = function () {
+ if (this.options.ecmaVersion < 6) return this.parseIdent();
+ switch (this.type) {
+ case _tokentype.types.name:
+ return this.parseIdent();
+
+ case _tokentype.types.bracketL:
+ var node = this.startNode();
+ this.next();
+ node.elements = this.parseBindingList(_tokentype.types.bracketR, true, true);
+ return this.finishNode(node, "ArrayPattern");
+
+ case _tokentype.types.braceL:
+ return this.parseObj(true);
+
+ default:
+ this.unexpected();
+ }
+};
+
+pp.parseBindingList = function (close, allowEmpty, allowTrailingComma, allowNonIdent) {
+ var elts = [],
+ first = true;
+ while (!this.eat(close)) {
+ if (first) first = false;else this.expect(_tokentype.types.comma);
+ if (allowEmpty && this.type === _tokentype.types.comma) {
+ elts.push(null);
+ } else if (allowTrailingComma && this.afterTrailingComma(close)) {
+ break;
+ } else if (this.type === _tokentype.types.ellipsis) {
+ var rest = this.parseRest(allowNonIdent);
+ this.parseBindingListItem(rest);
+ elts.push(rest);
+ this.expect(close);
+ break;
+ } else {
+ var elem = this.parseMaybeDefault(this.start, this.startLoc);
+ this.parseBindingListItem(elem);
+ elts.push(elem);
+ }
+ }
+ return elts;
+};
+
+pp.parseBindingListItem = function (param) {
+ return param;
+};
+
+// Parses assignment pattern around given atom if possible.
+
+pp.parseMaybeDefault = function (startPos, startLoc, left) {
+ left = left || this.parseBindingAtom();
+ if (this.options.ecmaVersion < 6 || !this.eat(_tokentype.types.eq)) return left;
+ var node = this.startNodeAt(startPos, startLoc);
+ node.left = left;
+ node.right = this.parseMaybeAssign();
+ return this.finishNode(node, "AssignmentPattern");
+};
+
+// Verify that a node is an lval — something that can be assigned
+// to.
+
+pp.checkLVal = function (expr, isBinding, checkClashes) {
+ switch (expr.type) {
+ case "Identifier":
+ if (this.strict && this.reservedWordsStrictBind.test(expr.name)) this.raise(expr.start, (isBinding ? "Binding " : "Assigning to ") + expr.name + " in strict mode");
+ if (checkClashes) {
+ if (_util.has(checkClashes, expr.name)) this.raise(expr.start, "Argument name clash");
+ checkClashes[expr.name] = true;
+ }
+ break;
+
+ case "MemberExpression":
+ if (isBinding) this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " member expression");
+ break;
+
+ case "ObjectPattern":
+ for (var i = 0; i < expr.properties.length; i++) {
+ this.checkLVal(expr.properties[i].value, isBinding, checkClashes);
+ }break;
+
+ case "ArrayPattern":
+ for (var i = 0; i < expr.elements.length; i++) {
+ var elem = expr.elements[i];
+ if (elem) this.checkLVal(elem, isBinding, checkClashes);
+ }
+ break;
+
+ case "AssignmentPattern":
+ this.checkLVal(expr.left, isBinding, checkClashes);
+ break;
+
+ case "RestElement":
+ this.checkLVal(expr.argument, isBinding, checkClashes);
+ break;
+
+ case "ParenthesizedExpression":
+ this.checkLVal(expr.expression, isBinding, checkClashes);
+ break;
+
+ default:
+ this.raise(expr.start, (isBinding ? "Binding" : "Assigning to") + " rvalue");
+ }
+};
+
+},{"./state":10,"./tokentype":14,"./util":15}],7:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var Node = function Node(parser, pos, loc) {
+ _classCallCheck(this, Node);
+
+ this.type = "";
+ this.start = pos;
+ this.end = 0;
+ if (parser.options.locations) this.loc = new _locutil.SourceLocation(parser, loc);
+ if (parser.options.directSourceFile) this.sourceFile = parser.options.directSourceFile;
+ if (parser.options.ranges) this.range = [pos, 0];
+}
+
+// Start an AST node, attaching a start offset.
+
+;
+
+exports.Node = Node;
+var pp = _state.Parser.prototype;
+
+pp.startNode = function () {
+ return new Node(this, this.start, this.startLoc);
+};
+
+pp.startNodeAt = function (pos, loc) {
+ return new Node(this, pos, loc);
+};
+
+// Finish an AST node, adding `type` and `end` properties.
+
+function finishNodeAt(node, type, pos, loc) {
+ node.type = type;
+ node.end = pos;
+ if (this.options.locations) node.loc.end = loc;
+ if (this.options.ranges) node.range[1] = pos;
+ return node;
+}
+
+pp.finishNode = function (node, type) {
+ return finishNodeAt.call(this, node, type, this.lastTokEnd, this.lastTokEndLoc);
+};
+
+// Finish node at given position
+
+pp.finishNodeAt = function (node, type, pos, loc) {
+ return finishNodeAt.call(this, node, type, pos, loc);
+};
+
+},{"./locutil":5,"./state":10}],8:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.getOptions = getOptions;
+
+var _util = _dereq_("./util");
+
+var _locutil = _dereq_("./locutil");
+
+// A second optional argument can be given to further configure
+// the parser process. These options are recognized:
+
+var defaultOptions = {
+ // `ecmaVersion` indicates the ECMAScript version to parse. Must
+ // be either 3, or 5, or 6. This influences support for strict
+ // mode, the set of reserved words, support for getters and
+ // setters and other features.
+ ecmaVersion: 5,
+ // Source type ("script" or "module") for different semantics
+ sourceType: "script",
+ // `onInsertedSemicolon` can be a callback that will be called
+ // when a semicolon is automatically inserted. It will be passed
+ // th position of the comma as an offset, and if `locations` is
+ // enabled, it is given the location as a `{line, column}` object
+ // as second argument.
+ onInsertedSemicolon: null,
+ // `onTrailingComma` is similar to `onInsertedSemicolon`, but for
+ // trailing commas.
+ onTrailingComma: null,
+ // By default, reserved words are only enforced if ecmaVersion >= 5.
+ // Set `allowReserved` to a boolean value to explicitly turn this on
+ // an off. When this option has the value "never", reserved words
+ // and keywords can also not be used as property names.
+ allowReserved: null,
+ // When enabled, a return at the top level is not considered an
+ // error.
+ allowReturnOutsideFunction: false,
+ // When enabled, import/export statements are not constrained to
+ // appearing at the top of the program.
+ allowImportExportEverywhere: false,
+ // When enabled, hashbang directive in the beginning of file
+ // is allowed and treated as a line comment.
+ allowHashBang: false,
+ // When `locations` is on, `loc` properties holding objects with
+ // `start` and `end` properties in `{line, column}` form (with
+ // line being 1-based and column 0-based) will be attached to the
+ // nodes.
+ locations: false,
+ // A function can be passed as `onToken` option, which will
+ // cause Acorn to call that function with object in the same
+ // format as tokens returned from `tokenizer().getToken()`. Note
+ // that you are not allowed to call the parser from the
+ // callback—that will corrupt its internal state.
+ onToken: null,
+ // A function can be passed as `onComment` option, which will
+ // cause Acorn to call that function with `(block, text, start,
+ // end)` parameters whenever a comment is skipped. `block` is a
+ // boolean indicating whether this is a block (`/* */`) comment,
+ // `text` is the content of the comment, and `start` and `end` are
+ // character offsets that denote the start and end of the comment.
+ // When the `locations` option is on, two more parameters are
+ // passed, the full `{line, column}` locations of the start and
+ // end of the comments. Note that you are not allowed to call the
+ // parser from the callback—that will corrupt its internal state.
+ onComment: null,
+ // Nodes have their start and end characters offsets recorded in
+ // `start` and `end` properties (directly on the node, rather than
+ // the `loc` object, which holds line/column data. To also add a
+ // [semi-standardized][range] `range` property holding a `[start,
+ // end]` array with the same numbers, set the `ranges` option to
+ // `true`.
+ //
+ // [range]: https://bugzilla.mozilla.org/show_bug.cgi?id=745678
+ ranges: false,
+ // It is possible to parse multiple files into a single AST by
+ // passing the tree produced by parsing the first file as
+ // `program` option in subsequent parses. This will add the
+ // toplevel forms of the parsed file to the `Program` (top) node
+ // of an existing parse tree.
+ program: null,
+ // When `locations` is on, you can pass this to record the source
+ // file in every node's `loc` object.
+ sourceFile: null,
+ // This value, if given, is stored in every node, whether
+ // `locations` is on or off.
+ directSourceFile: null,
+ // When enabled, parenthesized expressions are represented by
+ // (non-standard) ParenthesizedExpression nodes
+ preserveParens: false,
+ plugins: {}
+};
+
+exports.defaultOptions = defaultOptions;
+// Interpret and default an options object
+
+function getOptions(opts) {
+ var options = {};
+ for (var opt in defaultOptions) {
+ options[opt] = opts && _util.has(opts, opt) ? opts[opt] : defaultOptions[opt];
+ }if (options.allowReserved == null) options.allowReserved = options.ecmaVersion < 5;
+
+ if (_util.isArray(options.onToken)) {
+ (function () {
+ var tokens = options.onToken;
+ options.onToken = function (token) {
+ return tokens.push(token);
+ };
+ })();
+ }
+ if (_util.isArray(options.onComment)) options.onComment = pushComment(options, options.onComment);
+
+ return options;
+}
+
+function pushComment(options, array) {
+ return function (block, text, start, end, startLoc, endLoc) {
+ var comment = {
+ type: block ? 'Block' : 'Line',
+ value: text,
+ start: start,
+ end: end
+ };
+ if (options.locations) comment.loc = new _locutil.SourceLocation(this, startLoc, endLoc);
+ if (options.ranges) comment.range = [start, end];
+ array.push(comment);
+ };
+}
+
+},{"./locutil":5,"./util":15}],9:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _whitespace = _dereq_("./whitespace");
+
+var pp = _state.Parser.prototype;
+
+// ## Parser utilities
+
+// Test whether a statement node is the string literal `"use strict"`.
+
+pp.isUseStrict = function (stmt) {
+ return this.options.ecmaVersion >= 5 && stmt.type === "ExpressionStatement" && stmt.expression.type === "Literal" && stmt.expression.raw.slice(1, -1) === "use strict";
+};
+
+// Predicate that tests whether the next token is of the given
+// type, and if yes, consumes it as a side effect.
+
+pp.eat = function (type) {
+ if (this.type === type) {
+ this.next();
+ return true;
+ } else {
+ return false;
+ }
+};
+
+// Tests whether parsed token is a contextual keyword.
+
+pp.isContextual = function (name) {
+ return this.type === _tokentype.types.name && this.value === name;
+};
+
+// Consumes contextual keyword if possible.
+
+pp.eatContextual = function (name) {
+ return this.value === name && this.eat(_tokentype.types.name);
+};
+
+// Asserts that following token is given contextual keyword.
+
+pp.expectContextual = function (name) {
+ if (!this.eatContextual(name)) this.unexpected();
+};
+
+// Test whether a semicolon can be inserted at the current position.
+
+pp.canInsertSemicolon = function () {
+ return this.type === _tokentype.types.eof || this.type === _tokentype.types.braceR || _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start));
+};
+
+pp.insertSemicolon = function () {
+ if (this.canInsertSemicolon()) {
+ if (this.options.onInsertedSemicolon) this.options.onInsertedSemicolon(this.lastTokEnd, this.lastTokEndLoc);
+ return true;
+ }
+};
+
+// Consume a semicolon, or, failing that, see if we are allowed to
+// pretend that there is a semicolon at this position.
+
+pp.semicolon = function () {
+ if (!this.eat(_tokentype.types.semi) && !this.insertSemicolon()) this.unexpected();
+};
+
+pp.afterTrailingComma = function (tokType) {
+ if (this.type == tokType) {
+ if (this.options.onTrailingComma) this.options.onTrailingComma(this.lastTokStart, this.lastTokStartLoc);
+ this.next();
+ return true;
+ }
+};
+
+// Expect a token of a given type. If found, consume it, otherwise,
+// raise an unexpected token error.
+
+pp.expect = function (type) {
+ this.eat(type) || this.unexpected();
+};
+
+// Raise an unexpected token error.
+
+pp.unexpected = function (pos) {
+ this.raise(pos != null ? pos : this.start, "Unexpected token");
+};
+
+pp.checkPatternErrors = function (refDestructuringErrors, andThrow) {
+ var pos = refDestructuringErrors && refDestructuringErrors.trailingComma;
+ if (!andThrow) return !!pos;
+ if (pos) this.raise(pos, "Trailing comma is not permitted in destructuring patterns");
+};
+
+pp.checkExpressionErrors = function (refDestructuringErrors, andThrow) {
+ var pos = refDestructuringErrors && refDestructuringErrors.shorthandAssign;
+ if (!andThrow) return !!pos;
+ if (pos) this.raise(pos, "Shorthand property assignments are valid only in destructuring patterns");
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],10:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _identifier = _dereq_("./identifier");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _whitespace = _dereq_("./whitespace");
+
+var _options = _dereq_("./options");
+
+// Registered plugins
+var plugins = {};
+
+exports.plugins = plugins;
+function keywordRegexp(words) {
+ return new RegExp("^(" + words.replace(/ /g, "|") + ")$");
+}
+
+var Parser = (function () {
+ function Parser(options, input, startPos) {
+ _classCallCheck(this, Parser);
+
+ this.options = options = _options.getOptions(options);
+ this.sourceFile = options.sourceFile;
+ this.keywords = keywordRegexp(_identifier.keywords[options.ecmaVersion >= 6 ? 6 : 5]);
+ var reserved = options.allowReserved ? "" : _identifier.reservedWords[options.ecmaVersion] + (options.sourceType == "module" ? " await" : "");
+ this.reservedWords = keywordRegexp(reserved);
+ var reservedStrict = (reserved ? reserved + " " : "") + _identifier.reservedWords.strict;
+ this.reservedWordsStrict = keywordRegexp(reservedStrict);
+ this.reservedWordsStrictBind = keywordRegexp(reservedStrict + " " + _identifier.reservedWords.strictBind);
+ this.input = String(input);
+
+ // Used to signal to callers of `readWord1` whether the word
+ // contained any escape sequences. This is needed because words with
+ // escape sequences must not be interpreted as keywords.
+ this.containsEsc = false;
+
+ // Load plugins
+ this.loadPlugins(options.plugins);
+
+ // Set up token state
+
+ // The current position of the tokenizer in the input.
+ if (startPos) {
+ this.pos = startPos;
+ this.lineStart = Math.max(0, this.input.lastIndexOf("\n", startPos));
+ this.curLine = this.input.slice(0, this.lineStart).split(_whitespace.lineBreak).length;
+ } else {
+ this.pos = this.lineStart = 0;
+ this.curLine = 1;
+ }
+
+ // Properties of the current token:
+ // Its type
+ this.type = _tokentype.types.eof;
+ // For tokens that include more information than their type, the value
+ this.value = null;
+ // Its start and end offset
+ this.start = this.end = this.pos;
+ // And, if locations are used, the {line, column} object
+ // corresponding to those offsets
+ this.startLoc = this.endLoc = this.curPosition();
+
+ // Position information for the previous token
+ this.lastTokEndLoc = this.lastTokStartLoc = null;
+ this.lastTokStart = this.lastTokEnd = this.pos;
+
+ // The context stack is used to superficially track syntactic
+ // context to predict whether a regular expression is allowed in a
+ // given position.
+ this.context = this.initialContext();
+ this.exprAllowed = true;
+
+ // Figure out if it's a module code.
+ this.strict = this.inModule = options.sourceType === "module";
+
+ // Used to signify the start of a potential arrow function
+ this.potentialArrowAt = -1;
+
+ // Flags to track whether we are in a function, a generator.
+ this.inFunction = this.inGenerator = false;
+ // Labels in scope.
+ this.labels = [];
+
+ // If enabled, skip leading hashbang line.
+ if (this.pos === 0 && options.allowHashBang && this.input.slice(0, 2) === '#!') this.skipLineComment(2);
+ }
+
+ // DEPRECATED Kept for backwards compatibility until 3.0 in case a plugin uses them
+
+ Parser.prototype.isKeyword = function isKeyword(word) {
+ return this.keywords.test(word);
+ };
+
+ Parser.prototype.isReservedWord = function isReservedWord(word) {
+ return this.reservedWords.test(word);
+ };
+
+ Parser.prototype.extend = function extend(name, f) {
+ this[name] = f(this[name]);
+ };
+
+ Parser.prototype.loadPlugins = function loadPlugins(pluginConfigs) {
+ for (var _name in pluginConfigs) {
+ var plugin = plugins[_name];
+ if (!plugin) throw new Error("Plugin '" + _name + "' not found");
+ plugin(this, pluginConfigs[_name]);
+ }
+ };
+
+ Parser.prototype.parse = function parse() {
+ var node = this.options.program || this.startNode();
+ this.nextToken();
+ return this.parseTopLevel(node);
+ };
+
+ return Parser;
+})();
+
+exports.Parser = Parser;
+
+},{"./identifier":2,"./options":8,"./tokentype":14,"./whitespace":16}],11:[function(_dereq_,module,exports){
+"use strict";
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _whitespace = _dereq_("./whitespace");
+
+var pp = _state.Parser.prototype;
+
+// ### Statement parsing
+
+// Parse a program. Initializes the parser, reads any number of
+// statements, and wraps them in a Program node. Optionally takes a
+// `program` argument. If present, the statements will be appended
+// to its body instead of creating a new node.
+
+pp.parseTopLevel = function (node) {
+ var first = true;
+ if (!node.body) node.body = [];
+ while (this.type !== _tokentype.types.eof) {
+ var stmt = this.parseStatement(true, true);
+ node.body.push(stmt);
+ if (first) {
+ if (this.isUseStrict(stmt)) this.setStrict(true);
+ first = false;
+ }
+ }
+ this.next();
+ if (this.options.ecmaVersion >= 6) {
+ node.sourceType = this.options.sourceType;
+ }
+ return this.finishNode(node, "Program");
+};
+
+var loopLabel = { kind: "loop" },
+ switchLabel = { kind: "switch" };
+
+// Parse a single statement.
+//
+// If expecting a statement and finding a slash operator, parse a
+// regular expression literal. This is to handle cases like
+// `if (foo) /blah/.exec(foo)`, where looking at the previous token
+// does not help.
+
+pp.parseStatement = function (declaration, topLevel) {
+ var starttype = this.type,
+ node = this.startNode();
+
+ // Most types of statements are recognized by the keyword they
+ // start with. Many are trivial to parse, some require a bit of
+ // complexity.
+
+ switch (starttype) {
+ case _tokentype.types._break:case _tokentype.types._continue:
+ return this.parseBreakContinueStatement(node, starttype.keyword);
+ case _tokentype.types._debugger:
+ return this.parseDebuggerStatement(node);
+ case _tokentype.types._do:
+ return this.parseDoStatement(node);
+ case _tokentype.types._for:
+ return this.parseForStatement(node);
+ case _tokentype.types._function:
+ if (!declaration && this.options.ecmaVersion >= 6) this.unexpected();
+ return this.parseFunctionStatement(node);
+ case _tokentype.types._class:
+ if (!declaration) this.unexpected();
+ return this.parseClass(node, true);
+ case _tokentype.types._if:
+ return this.parseIfStatement(node);
+ case _tokentype.types._return:
+ return this.parseReturnStatement(node);
+ case _tokentype.types._switch:
+ return this.parseSwitchStatement(node);
+ case _tokentype.types._throw:
+ return this.parseThrowStatement(node);
+ case _tokentype.types._try:
+ return this.parseTryStatement(node);
+ case _tokentype.types._let:case _tokentype.types._const:
+ if (!declaration) this.unexpected(); // NOTE: falls through to _var
+ case _tokentype.types._var:
+ return this.parseVarStatement(node, starttype);
+ case _tokentype.types._while:
+ return this.parseWhileStatement(node);
+ case _tokentype.types._with:
+ return this.parseWithStatement(node);
+ case _tokentype.types.braceL:
+ return this.parseBlock();
+ case _tokentype.types.semi:
+ return this.parseEmptyStatement(node);
+ case _tokentype.types._export:
+ case _tokentype.types._import:
+ if (!this.options.allowImportExportEverywhere) {
+ if (!topLevel) this.raise(this.start, "'import' and 'export' may only appear at the top level");
+ if (!this.inModule) this.raise(this.start, "'import' and 'export' may appear only with 'sourceType: module'");
+ }
+ return starttype === _tokentype.types._import ? this.parseImport(node) : this.parseExport(node);
+
+ // If the statement does not start with a statement keyword or a
+ // brace, it's an ExpressionStatement or LabeledStatement. We
+ // simply start parsing an expression, and afterwards, if the
+ // next token is a colon and the expression was a simple
+ // Identifier node, we switch to interpreting it as a label.
+ default:
+ var maybeName = this.value,
+ expr = this.parseExpression();
+ if (starttype === _tokentype.types.name && expr.type === "Identifier" && this.eat(_tokentype.types.colon)) return this.parseLabeledStatement(node, maybeName, expr);else return this.parseExpressionStatement(node, expr);
+ }
+};
+
+pp.parseBreakContinueStatement = function (node, keyword) {
+ var isBreak = keyword == "break";
+ this.next();
+ if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.label = null;else if (this.type !== _tokentype.types.name) this.unexpected();else {
+ node.label = this.parseIdent();
+ this.semicolon();
+ }
+
+ // Verify that there is an actual destination to break or
+ // continue to.
+ for (var i = 0; i < this.labels.length; ++i) {
+ var lab = this.labels[i];
+ if (node.label == null || lab.name === node.label.name) {
+ if (lab.kind != null && (isBreak || lab.kind === "loop")) break;
+ if (node.label && isBreak) break;
+ }
+ }
+ if (i === this.labels.length) this.raise(node.start, "Unsyntactic " + keyword);
+ return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+};
+
+pp.parseDebuggerStatement = function (node) {
+ this.next();
+ this.semicolon();
+ return this.finishNode(node, "DebuggerStatement");
+};
+
+pp.parseDoStatement = function (node) {
+ this.next();
+ this.labels.push(loopLabel);
+ node.body = this.parseStatement(false);
+ this.labels.pop();
+ this.expect(_tokentype.types._while);
+ node.test = this.parseParenExpression();
+ if (this.options.ecmaVersion >= 6) this.eat(_tokentype.types.semi);else this.semicolon();
+ return this.finishNode(node, "DoWhileStatement");
+};
+
+// Disambiguating between a `for` and a `for`/`in` or `for`/`of`
+// loop is non-trivial. Basically, we have to parse the init `var`
+// statement or expression, disallowing the `in` operator (see
+// the second parameter to `parseExpression`), and then check
+// whether the next token is `in` or `of`. When there is no init
+// part (semicolon immediately after the opening parenthesis), it
+// is a regular `for` loop.
+
+pp.parseForStatement = function (node) {
+ this.next();
+ this.labels.push(loopLabel);
+ this.expect(_tokentype.types.parenL);
+ if (this.type === _tokentype.types.semi) return this.parseFor(node, null);
+ if (this.type === _tokentype.types._var || this.type === _tokentype.types._let || this.type === _tokentype.types._const) {
+ var _init = this.startNode(),
+ varKind = this.type;
+ this.next();
+ this.parseVar(_init, true, varKind);
+ this.finishNode(_init, "VariableDeclaration");
+ if ((this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) && _init.declarations.length === 1 && !(varKind !== _tokentype.types._var && _init.declarations[0].init)) return this.parseForIn(node, _init);
+ return this.parseFor(node, _init);
+ }
+ var refDestructuringErrors = { shorthandAssign: 0, trailingComma: 0 };
+ var init = this.parseExpression(true, refDestructuringErrors);
+ if (this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of")) {
+ this.checkPatternErrors(refDestructuringErrors, true);
+ this.toAssignable(init);
+ this.checkLVal(init);
+ return this.parseForIn(node, init);
+ } else {
+ this.checkExpressionErrors(refDestructuringErrors, true);
+ }
+ return this.parseFor(node, init);
+};
+
+pp.parseFunctionStatement = function (node) {
+ this.next();
+ return this.parseFunction(node, true);
+};
+
+pp.parseIfStatement = function (node) {
+ this.next();
+ node.test = this.parseParenExpression();
+ node.consequent = this.parseStatement(false);
+ node.alternate = this.eat(_tokentype.types._else) ? this.parseStatement(false) : null;
+ return this.finishNode(node, "IfStatement");
+};
+
+pp.parseReturnStatement = function (node) {
+ if (!this.inFunction && !this.options.allowReturnOutsideFunction) this.raise(this.start, "'return' outside of function");
+ this.next();
+
+ // In `return` (and `break`/`continue`), the keywords with
+ // optional arguments, we eagerly look for a semicolon or the
+ // possibility to insert one.
+
+ if (this.eat(_tokentype.types.semi) || this.insertSemicolon()) node.argument = null;else {
+ node.argument = this.parseExpression();this.semicolon();
+ }
+ return this.finishNode(node, "ReturnStatement");
+};
+
+pp.parseSwitchStatement = function (node) {
+ this.next();
+ node.discriminant = this.parseParenExpression();
+ node.cases = [];
+ this.expect(_tokentype.types.braceL);
+ this.labels.push(switchLabel);
+
+ // Statements under must be grouped (by label) in SwitchCase
+ // nodes. `cur` is used to keep the node that we are currently
+ // adding statements to.
+
+ for (var cur, sawDefault = false; this.type != _tokentype.types.braceR;) {
+ if (this.type === _tokentype.types._case || this.type === _tokentype.types._default) {
+ var isCase = this.type === _tokentype.types._case;
+ if (cur) this.finishNode(cur, "SwitchCase");
+ node.cases.push(cur = this.startNode());
+ cur.consequent = [];
+ this.next();
+ if (isCase) {
+ cur.test = this.parseExpression();
+ } else {
+ if (sawDefault) this.raise(this.lastTokStart, "Multiple default clauses");
+ sawDefault = true;
+ cur.test = null;
+ }
+ this.expect(_tokentype.types.colon);
+ } else {
+ if (!cur) this.unexpected();
+ cur.consequent.push(this.parseStatement(true));
+ }
+ }
+ if (cur) this.finishNode(cur, "SwitchCase");
+ this.next(); // Closing brace
+ this.labels.pop();
+ return this.finishNode(node, "SwitchStatement");
+};
+
+pp.parseThrowStatement = function (node) {
+ this.next();
+ if (_whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) this.raise(this.lastTokEnd, "Illegal newline after throw");
+ node.argument = this.parseExpression();
+ this.semicolon();
+ return this.finishNode(node, "ThrowStatement");
+};
+
+// Reused empty array added for node fields that are always empty.
+
+var empty = [];
+
+pp.parseTryStatement = function (node) {
+ this.next();
+ node.block = this.parseBlock();
+ node.handler = null;
+ if (this.type === _tokentype.types._catch) {
+ var clause = this.startNode();
+ this.next();
+ this.expect(_tokentype.types.parenL);
+ clause.param = this.parseBindingAtom();
+ this.checkLVal(clause.param, true);
+ this.expect(_tokentype.types.parenR);
+ clause.body = this.parseBlock();
+ node.handler = this.finishNode(clause, "CatchClause");
+ }
+ node.finalizer = this.eat(_tokentype.types._finally) ? this.parseBlock() : null;
+ if (!node.handler && !node.finalizer) this.raise(node.start, "Missing catch or finally clause");
+ return this.finishNode(node, "TryStatement");
+};
+
+pp.parseVarStatement = function (node, kind) {
+ this.next();
+ this.parseVar(node, false, kind);
+ this.semicolon();
+ return this.finishNode(node, "VariableDeclaration");
+};
+
+pp.parseWhileStatement = function (node) {
+ this.next();
+ node.test = this.parseParenExpression();
+ this.labels.push(loopLabel);
+ node.body = this.parseStatement(false);
+ this.labels.pop();
+ return this.finishNode(node, "WhileStatement");
+};
+
+pp.parseWithStatement = function (node) {
+ if (this.strict) this.raise(this.start, "'with' in strict mode");
+ this.next();
+ node.object = this.parseParenExpression();
+ node.body = this.parseStatement(false);
+ return this.finishNode(node, "WithStatement");
+};
+
+pp.parseEmptyStatement = function (node) {
+ this.next();
+ return this.finishNode(node, "EmptyStatement");
+};
+
+pp.parseLabeledStatement = function (node, maybeName, expr) {
+ for (var i = 0; i < this.labels.length; ++i) {
+ if (this.labels[i].name === maybeName) this.raise(expr.start, "Label '" + maybeName + "' is already declared");
+ }var kind = this.type.isLoop ? "loop" : this.type === _tokentype.types._switch ? "switch" : null;
+ for (var i = this.labels.length - 1; i >= 0; i--) {
+ var label = this.labels[i];
+ if (label.statementStart == node.start) {
+ label.statementStart = this.start;
+ label.kind = kind;
+ } else break;
+ }
+ this.labels.push({ name: maybeName, kind: kind, statementStart: this.start });
+ node.body = this.parseStatement(true);
+ this.labels.pop();
+ node.label = expr;
+ return this.finishNode(node, "LabeledStatement");
+};
+
+pp.parseExpressionStatement = function (node, expr) {
+ node.expression = expr;
+ this.semicolon();
+ return this.finishNode(node, "ExpressionStatement");
+};
+
+// Parse a semicolon-enclosed block of statements, handling `"use
+// strict"` declarations when `allowStrict` is true (used for
+// function bodies).
+
+pp.parseBlock = function (allowStrict) {
+ var node = this.startNode(),
+ first = true,
+ oldStrict = undefined;
+ node.body = [];
+ this.expect(_tokentype.types.braceL);
+ while (!this.eat(_tokentype.types.braceR)) {
+ var stmt = this.parseStatement(true);
+ node.body.push(stmt);
+ if (first && allowStrict && this.isUseStrict(stmt)) {
+ oldStrict = this.strict;
+ this.setStrict(this.strict = true);
+ }
+ first = false;
+ }
+ if (oldStrict === false) this.setStrict(false);
+ return this.finishNode(node, "BlockStatement");
+};
+
+// Parse a regular `for` loop. The disambiguation code in
+// `parseStatement` will already have parsed the init statement or
+// expression.
+
+pp.parseFor = function (node, init) {
+ node.init = init;
+ this.expect(_tokentype.types.semi);
+ node.test = this.type === _tokentype.types.semi ? null : this.parseExpression();
+ this.expect(_tokentype.types.semi);
+ node.update = this.type === _tokentype.types.parenR ? null : this.parseExpression();
+ this.expect(_tokentype.types.parenR);
+ node.body = this.parseStatement(false);
+ this.labels.pop();
+ return this.finishNode(node, "ForStatement");
+};
+
+// Parse a `for`/`in` and `for`/`of` loop, which are almost
+// same from parser's perspective.
+
+pp.parseForIn = function (node, init) {
+ var type = this.type === _tokentype.types._in ? "ForInStatement" : "ForOfStatement";
+ this.next();
+ node.left = init;
+ node.right = this.parseExpression();
+ this.expect(_tokentype.types.parenR);
+ node.body = this.parseStatement(false);
+ this.labels.pop();
+ return this.finishNode(node, type);
+};
+
+// Parse a list of variable declarations.
+
+pp.parseVar = function (node, isFor, kind) {
+ node.declarations = [];
+ node.kind = kind.keyword;
+ for (;;) {
+ var decl = this.startNode();
+ this.parseVarId(decl);
+ if (this.eat(_tokentype.types.eq)) {
+ decl.init = this.parseMaybeAssign(isFor);
+ } else if (kind === _tokentype.types._const && !(this.type === _tokentype.types._in || this.options.ecmaVersion >= 6 && this.isContextual("of"))) {
+ this.unexpected();
+ } else if (decl.id.type != "Identifier" && !(isFor && (this.type === _tokentype.types._in || this.isContextual("of")))) {
+ this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value");
+ } else {
+ decl.init = null;
+ }
+ node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+ if (!this.eat(_tokentype.types.comma)) break;
+ }
+ return node;
+};
+
+pp.parseVarId = function (decl) {
+ decl.id = this.parseBindingAtom();
+ this.checkLVal(decl.id, true);
+};
+
+// Parse a function declaration or literal (depending on the
+// `isStatement` parameter).
+
+pp.parseFunction = function (node, isStatement, allowExpressionBody) {
+ this.initFunction(node);
+ if (this.options.ecmaVersion >= 6) node.generator = this.eat(_tokentype.types.star);
+ if (isStatement || this.type === _tokentype.types.name) node.id = this.parseIdent();
+ this.parseFunctionParams(node);
+ this.parseFunctionBody(node, allowExpressionBody);
+ return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+};
+
+pp.parseFunctionParams = function (node) {
+ this.expect(_tokentype.types.parenL);
+ node.params = this.parseBindingList(_tokentype.types.parenR, false, false, true);
+};
+
+// Parse a class declaration or literal (depending on the
+// `isStatement` parameter).
+
+pp.parseClass = function (node, isStatement) {
+ this.next();
+ this.parseClassId(node, isStatement);
+ this.parseClassSuper(node);
+ var classBody = this.startNode();
+ var hadConstructor = false;
+ classBody.body = [];
+ this.expect(_tokentype.types.braceL);
+ while (!this.eat(_tokentype.types.braceR)) {
+ if (this.eat(_tokentype.types.semi)) continue;
+ var method = this.startNode();
+ var isGenerator = this.eat(_tokentype.types.star);
+ var isMaybeStatic = this.type === _tokentype.types.name && this.value === "static";
+ this.parsePropertyName(method);
+ method["static"] = isMaybeStatic && this.type !== _tokentype.types.parenL;
+ if (method["static"]) {
+ if (isGenerator) this.unexpected();
+ isGenerator = this.eat(_tokentype.types.star);
+ this.parsePropertyName(method);
+ }
+ method.kind = "method";
+ var isGetSet = false;
+ if (!method.computed) {
+ var key = method.key;
+
+ if (!isGenerator && key.type === "Identifier" && this.type !== _tokentype.types.parenL && (key.name === "get" || key.name === "set")) {
+ isGetSet = true;
+ method.kind = key.name;
+ key = this.parsePropertyName(method);
+ }
+ if (!method["static"] && (key.type === "Identifier" && key.name === "constructor" || key.type === "Literal" && key.value === "constructor")) {
+ if (hadConstructor) this.raise(key.start, "Duplicate constructor in the same class");
+ if (isGetSet) this.raise(key.start, "Constructor can't have get/set modifier");
+ if (isGenerator) this.raise(key.start, "Constructor can't be a generator");
+ method.kind = "constructor";
+ hadConstructor = true;
+ }
+ }
+ this.parseClassMethod(classBody, method, isGenerator);
+ if (isGetSet) {
+ var paramCount = method.kind === "get" ? 0 : 1;
+ if (method.value.params.length !== paramCount) {
+ var start = method.value.start;
+ if (method.kind === "get") this.raise(start, "getter should have no params");else this.raise(start, "setter should have exactly one param");
+ }
+ }
+ }
+ node.body = this.finishNode(classBody, "ClassBody");
+ return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
+};
+
+pp.parseClassMethod = function (classBody, method, isGenerator) {
+ method.value = this.parseMethod(isGenerator);
+ classBody.body.push(this.finishNode(method, "MethodDefinition"));
+};
+
+pp.parseClassId = function (node, isStatement) {
+ node.id = this.type === _tokentype.types.name ? this.parseIdent() : isStatement ? this.unexpected() : null;
+};
+
+pp.parseClassSuper = function (node) {
+ node.superClass = this.eat(_tokentype.types._extends) ? this.parseExprSubscripts() : null;
+};
+
+// Parses module export declaration.
+
+pp.parseExport = function (node) {
+ this.next();
+ // export * from '...'
+ if (this.eat(_tokentype.types.star)) {
+ this.expectContextual("from");
+ node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+ this.semicolon();
+ return this.finishNode(node, "ExportAllDeclaration");
+ }
+ if (this.eat(_tokentype.types._default)) {
+ // export default ...
+ var expr = this.parseMaybeAssign();
+ var needsSemi = true;
+ if (expr.type == "FunctionExpression" || expr.type == "ClassExpression") {
+ needsSemi = false;
+ if (expr.id) {
+ expr.type = expr.type == "FunctionExpression" ? "FunctionDeclaration" : "ClassDeclaration";
+ }
+ }
+ node.declaration = expr;
+ if (needsSemi) this.semicolon();
+ return this.finishNode(node, "ExportDefaultDeclaration");
+ }
+ // export var|const|let|function|class ...
+ if (this.shouldParseExportStatement()) {
+ node.declaration = this.parseStatement(true);
+ node.specifiers = [];
+ node.source = null;
+ } else {
+ // export { x, y as z } [from '...']
+ node.declaration = null;
+ node.specifiers = this.parseExportSpecifiers();
+ if (this.eatContextual("from")) {
+ node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+ } else {
+ // check for keywords used as local names
+ for (var i = 0; i < node.specifiers.length; i++) {
+ if (this.keywords.test(node.specifiers[i].local.name) || this.reservedWords.test(node.specifiers[i].local.name)) {
+ this.unexpected(node.specifiers[i].local.start);
+ }
+ }
+
+ node.source = null;
+ }
+ this.semicolon();
+ }
+ return this.finishNode(node, "ExportNamedDeclaration");
+};
+
+pp.shouldParseExportStatement = function () {
+ return this.type.keyword;
+};
+
+// Parses a comma-separated list of module exports.
+
+pp.parseExportSpecifiers = function () {
+ var nodes = [],
+ first = true;
+ // export { x, y as z } [from '...']
+ this.expect(_tokentype.types.braceL);
+ while (!this.eat(_tokentype.types.braceR)) {
+ if (!first) {
+ this.expect(_tokentype.types.comma);
+ if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+ } else first = false;
+
+ var node = this.startNode();
+ node.local = this.parseIdent(this.type === _tokentype.types._default);
+ node.exported = this.eatContextual("as") ? this.parseIdent(true) : node.local;
+ nodes.push(this.finishNode(node, "ExportSpecifier"));
+ }
+ return nodes;
+};
+
+// Parses import declaration.
+
+pp.parseImport = function (node) {
+ this.next();
+ // import '...'
+ if (this.type === _tokentype.types.string) {
+ node.specifiers = empty;
+ node.source = this.parseExprAtom();
+ } else {
+ node.specifiers = this.parseImportSpecifiers();
+ this.expectContextual("from");
+ node.source = this.type === _tokentype.types.string ? this.parseExprAtom() : this.unexpected();
+ }
+ this.semicolon();
+ return this.finishNode(node, "ImportDeclaration");
+};
+
+// Parses a comma-separated list of module imports.
+
+pp.parseImportSpecifiers = function () {
+ var nodes = [],
+ first = true;
+ if (this.type === _tokentype.types.name) {
+ // import defaultObj, { x, y as z } from '...'
+ var node = this.startNode();
+ node.local = this.parseIdent();
+ this.checkLVal(node.local, true);
+ nodes.push(this.finishNode(node, "ImportDefaultSpecifier"));
+ if (!this.eat(_tokentype.types.comma)) return nodes;
+ }
+ if (this.type === _tokentype.types.star) {
+ var node = this.startNode();
+ this.next();
+ this.expectContextual("as");
+ node.local = this.parseIdent();
+ this.checkLVal(node.local, true);
+ nodes.push(this.finishNode(node, "ImportNamespaceSpecifier"));
+ return nodes;
+ }
+ this.expect(_tokentype.types.braceL);
+ while (!this.eat(_tokentype.types.braceR)) {
+ if (!first) {
+ this.expect(_tokentype.types.comma);
+ if (this.afterTrailingComma(_tokentype.types.braceR)) break;
+ } else first = false;
+
+ var node = this.startNode();
+ node.imported = this.parseIdent(true);
+ node.local = this.eatContextual("as") ? this.parseIdent() : node.imported;
+ this.checkLVal(node.local, true);
+ nodes.push(this.finishNode(node, "ImportSpecifier"));
+ }
+ return nodes;
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],12:[function(_dereq_,module,exports){
+// The algorithm used to determine whether a regexp can appear at a
+// given point in the program is loosely based on sweet.js' approach.
+// See https://github.com/mozilla/sweet.js/wiki/design
+
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _state = _dereq_("./state");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _whitespace = _dereq_("./whitespace");
+
+var TokContext = function TokContext(token, isExpr, preserveSpace, override) {
+ _classCallCheck(this, TokContext);
+
+ this.token = token;
+ this.isExpr = !!isExpr;
+ this.preserveSpace = !!preserveSpace;
+ this.override = override;
+};
+
+exports.TokContext = TokContext;
+var types = {
+ b_stat: new TokContext("{", false),
+ b_expr: new TokContext("{", true),
+ b_tmpl: new TokContext("${", true),
+ p_stat: new TokContext("(", false),
+ p_expr: new TokContext("(", true),
+ q_tmpl: new TokContext("`", true, true, function (p) {
+ return p.readTmplToken();
+ }),
+ f_expr: new TokContext("function", true)
+};
+
+exports.types = types;
+var pp = _state.Parser.prototype;
+
+pp.initialContext = function () {
+ return [types.b_stat];
+};
+
+pp.braceIsBlock = function (prevType) {
+ if (prevType === _tokentype.types.colon) {
+ var _parent = this.curContext();
+ if (_parent === types.b_stat || _parent === types.b_expr) return !_parent.isExpr;
+ }
+ if (prevType === _tokentype.types._return) return _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.start));
+ if (prevType === _tokentype.types._else || prevType === _tokentype.types.semi || prevType === _tokentype.types.eof || prevType === _tokentype.types.parenR) return true;
+ if (prevType == _tokentype.types.braceL) return this.curContext() === types.b_stat;
+ return !this.exprAllowed;
+};
+
+pp.updateContext = function (prevType) {
+ var update = undefined,
+ type = this.type;
+ if (type.keyword && prevType == _tokentype.types.dot) this.exprAllowed = false;else if (update = type.updateContext) update.call(this, prevType);else this.exprAllowed = type.beforeExpr;
+};
+
+// Token-specific context update code
+
+_tokentype.types.parenR.updateContext = _tokentype.types.braceR.updateContext = function () {
+ if (this.context.length == 1) {
+ this.exprAllowed = true;
+ return;
+ }
+ var out = this.context.pop();
+ if (out === types.b_stat && this.curContext() === types.f_expr) {
+ this.context.pop();
+ this.exprAllowed = false;
+ } else if (out === types.b_tmpl) {
+ this.exprAllowed = true;
+ } else {
+ this.exprAllowed = !out.isExpr;
+ }
+};
+
+_tokentype.types.braceL.updateContext = function (prevType) {
+ this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr);
+ this.exprAllowed = true;
+};
+
+_tokentype.types.dollarBraceL.updateContext = function () {
+ this.context.push(types.b_tmpl);
+ this.exprAllowed = true;
+};
+
+_tokentype.types.parenL.updateContext = function (prevType) {
+ var statementParens = prevType === _tokentype.types._if || prevType === _tokentype.types._for || prevType === _tokentype.types._with || prevType === _tokentype.types._while;
+ this.context.push(statementParens ? types.p_stat : types.p_expr);
+ this.exprAllowed = true;
+};
+
+_tokentype.types.incDec.updateContext = function () {
+ // tokExprAllowed stays unchanged
+};
+
+_tokentype.types._function.updateContext = function () {
+ if (this.curContext() !== types.b_stat) this.context.push(types.f_expr);
+ this.exprAllowed = false;
+};
+
+_tokentype.types.backQuote.updateContext = function () {
+ if (this.curContext() === types.q_tmpl) this.context.pop();else this.context.push(types.q_tmpl);
+ this.exprAllowed = false;
+};
+
+},{"./state":10,"./tokentype":14,"./whitespace":16}],13:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _identifier = _dereq_("./identifier");
+
+var _tokentype = _dereq_("./tokentype");
+
+var _state = _dereq_("./state");
+
+var _locutil = _dereq_("./locutil");
+
+var _whitespace = _dereq_("./whitespace");
+
+// Object type used to represent tokens. Note that normally, tokens
+// simply exist as properties on the parser object. This is only
+// used for the onToken callback and the external tokenizer.
+
+var Token = function Token(p) {
+ _classCallCheck(this, Token);
+
+ this.type = p.type;
+ this.value = p.value;
+ this.start = p.start;
+ this.end = p.end;
+ if (p.options.locations) this.loc = new _locutil.SourceLocation(p, p.startLoc, p.endLoc);
+ if (p.options.ranges) this.range = [p.start, p.end];
+}
+
+// ## Tokenizer
+
+;
+
+exports.Token = Token;
+var pp = _state.Parser.prototype;
+
+// Are we running under Rhino?
+var isRhino = typeof Packages == "object" && Object.prototype.toString.call(Packages) == "[object JavaPackage]";
+
+// Move to the next token
+
+pp.next = function () {
+ if (this.options.onToken) this.options.onToken(new Token(this));
+
+ this.lastTokEnd = this.end;
+ this.lastTokStart = this.start;
+ this.lastTokEndLoc = this.endLoc;
+ this.lastTokStartLoc = this.startLoc;
+ this.nextToken();
+};
+
+pp.getToken = function () {
+ this.next();
+ return new Token(this);
+};
+
+// If we're in an ES6 environment, make parsers iterable
+if (typeof Symbol !== "undefined") pp[Symbol.iterator] = function () {
+ var self = this;
+ return { next: function next() {
+ var token = self.getToken();
+ return {
+ done: token.type === _tokentype.types.eof,
+ value: token
+ };
+ } };
+};
+
+// Toggle strict mode. Re-reads the next number or string to please
+// pedantic tests (`"use strict"; 010;` should fail).
+
+pp.setStrict = function (strict) {
+ this.strict = strict;
+ if (this.type !== _tokentype.types.num && this.type !== _tokentype.types.string) return;
+ this.pos = this.start;
+ if (this.options.locations) {
+ while (this.pos < this.lineStart) {
+ this.lineStart = this.input.lastIndexOf("\n", this.lineStart - 2) + 1;
+ --this.curLine;
+ }
+ }
+ this.nextToken();
+};
+
+pp.curContext = function () {
+ return this.context[this.context.length - 1];
+};
+
+// Read a single token, updating the parser object's token-related
+// properties.
+
+pp.nextToken = function () {
+ var curContext = this.curContext();
+ if (!curContext || !curContext.preserveSpace) this.skipSpace();
+
+ this.start = this.pos;
+ if (this.options.locations) this.startLoc = this.curPosition();
+ if (this.pos >= this.input.length) return this.finishToken(_tokentype.types.eof);
+
+ if (curContext.override) return curContext.override(this);else this.readToken(this.fullCharCodeAtPos());
+};
+
+pp.readToken = function (code) {
+ // Identifier or keyword. '\uXXXX' sequences are allowed in
+ // identifiers, so '\' also dispatches to that.
+ if (_identifier.isIdentifierStart(code, this.options.ecmaVersion >= 6) || code === 92 /* '\' */) return this.readWord();
+
+ return this.getTokenFromCode(code);
+};
+
+pp.fullCharCodeAtPos = function () {
+ var code = this.input.charCodeAt(this.pos);
+ if (code <= 0xd7ff || code >= 0xe000) return code;
+ var next = this.input.charCodeAt(this.pos + 1);
+ return (code << 10) + next - 0x35fdc00;
+};
+
+pp.skipBlockComment = function () {
+ var startLoc = this.options.onComment && this.curPosition();
+ var start = this.pos,
+ end = this.input.indexOf("*/", this.pos += 2);
+ if (end === -1) this.raise(this.pos - 2, "Unterminated comment");
+ this.pos = end + 2;
+ if (this.options.locations) {
+ _whitespace.lineBreakG.lastIndex = start;
+ var match = undefined;
+ while ((match = _whitespace.lineBreakG.exec(this.input)) && match.index < this.pos) {
+ ++this.curLine;
+ this.lineStart = match.index + match[0].length;
+ }
+ }
+ if (this.options.onComment) this.options.onComment(true, this.input.slice(start + 2, end), start, this.pos, startLoc, this.curPosition());
+};
+
+pp.skipLineComment = function (startSkip) {
+ var start = this.pos;
+ var startLoc = this.options.onComment && this.curPosition();
+ var ch = this.input.charCodeAt(this.pos += startSkip);
+ while (this.pos < this.input.length && ch !== 10 && ch !== 13 && ch !== 8232 && ch !== 8233) {
+ ++this.pos;
+ ch = this.input.charCodeAt(this.pos);
+ }
+ if (this.options.onComment) this.options.onComment(false, this.input.slice(start + startSkip, this.pos), start, this.pos, startLoc, this.curPosition());
+};
+
+// Called at the start of the parse and after every token. Skips
+// whitespace and comments, and.
+
+pp.skipSpace = function () {
+ loop: while (this.pos < this.input.length) {
+ var ch = this.input.charCodeAt(this.pos);
+ switch (ch) {
+ case 32:case 160:
+ // ' '
+ ++this.pos;
+ break;
+ case 13:
+ if (this.input.charCodeAt(this.pos + 1) === 10) {
+ ++this.pos;
+ }
+ case 10:case 8232:case 8233:
+ ++this.pos;
+ if (this.options.locations) {
+ ++this.curLine;
+ this.lineStart = this.pos;
+ }
+ break;
+ case 47:
+ // '/'
+ switch (this.input.charCodeAt(this.pos + 1)) {
+ case 42:
+ // '*'
+ this.skipBlockComment();
+ break;
+ case 47:
+ this.skipLineComment(2);
+ break;
+ default:
+ break loop;
+ }
+ break;
+ default:
+ if (ch > 8 && ch < 14 || ch >= 5760 && _whitespace.nonASCIIwhitespace.test(String.fromCharCode(ch))) {
+ ++this.pos;
+ } else {
+ break loop;
+ }
+ }
+ }
+};
+
+// Called at the end of every token. Sets `end`, `val`, and
+// maintains `context` and `exprAllowed`, and skips the space after
+// the token, so that the next one's `start` will point at the
+// right position.
+
+pp.finishToken = function (type, val) {
+ this.end = this.pos;
+ if (this.options.locations) this.endLoc = this.curPosition();
+ var prevType = this.type;
+ this.type = type;
+ this.value = val;
+
+ this.updateContext(prevType);
+};
+
+// ### Token reading
+
+// This is the function that is called to fetch the next token. It
+// is somewhat obscure, because it works in character codes rather
+// than characters, and because operator parsing has been inlined
+// into it.
+//
+// All in the name of speed.
+//
+pp.readToken_dot = function () {
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next >= 48 && next <= 57) return this.readNumber(true);
+ var next2 = this.input.charCodeAt(this.pos + 2);
+ if (this.options.ecmaVersion >= 6 && next === 46 && next2 === 46) {
+ // 46 = dot '.'
+ this.pos += 3;
+ return this.finishToken(_tokentype.types.ellipsis);
+ } else {
+ ++this.pos;
+ return this.finishToken(_tokentype.types.dot);
+ }
+};
+
+pp.readToken_slash = function () {
+ // '/'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (this.exprAllowed) {
+ ++this.pos;return this.readRegexp();
+ }
+ if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+ return this.finishOp(_tokentype.types.slash, 1);
+};
+
+pp.readToken_mult_modulo = function (code) {
+ // '%*'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+ return this.finishOp(code === 42 ? _tokentype.types.star : _tokentype.types.modulo, 1);
+};
+
+pp.readToken_pipe_amp = function (code) {
+ // '|&'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === code) return this.finishOp(code === 124 ? _tokentype.types.logicalOR : _tokentype.types.logicalAND, 2);
+ if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+ return this.finishOp(code === 124 ? _tokentype.types.bitwiseOR : _tokentype.types.bitwiseAND, 1);
+};
+
+pp.readToken_caret = function () {
+ // '^'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+ return this.finishOp(_tokentype.types.bitwiseXOR, 1);
+};
+
+pp.readToken_plus_min = function (code) {
+ // '+-'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === code) {
+ if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 && _whitespace.lineBreak.test(this.input.slice(this.lastTokEnd, this.pos))) {
+ // A `-->` line comment
+ this.skipLineComment(3);
+ this.skipSpace();
+ return this.nextToken();
+ }
+ return this.finishOp(_tokentype.types.incDec, 2);
+ }
+ if (next === 61) return this.finishOp(_tokentype.types.assign, 2);
+ return this.finishOp(_tokentype.types.plusMin, 1);
+};
+
+pp.readToken_lt_gt = function (code) {
+ // '<>'
+ var next = this.input.charCodeAt(this.pos + 1);
+ var size = 1;
+ if (next === code) {
+ size = code === 62 && this.input.charCodeAt(this.pos + 2) === 62 ? 3 : 2;
+ if (this.input.charCodeAt(this.pos + size) === 61) return this.finishOp(_tokentype.types.assign, size + 1);
+ return this.finishOp(_tokentype.types.bitShift, size);
+ }
+ if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 && this.input.charCodeAt(this.pos + 3) == 45) {
+ if (this.inModule) this.unexpected();
+ // `<!--`, an XML-style comment that should be interpreted as a line comment
+ this.skipLineComment(4);
+ this.skipSpace();
+ return this.nextToken();
+ }
+ if (next === 61) size = this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2;
+ return this.finishOp(_tokentype.types.relational, size);
+};
+
+pp.readToken_eq_excl = function (code) {
+ // '=!'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === 61) return this.finishOp(_tokentype.types.equality, this.input.charCodeAt(this.pos + 2) === 61 ? 3 : 2);
+ if (code === 61 && next === 62 && this.options.ecmaVersion >= 6) {
+ // '=>'
+ this.pos += 2;
+ return this.finishToken(_tokentype.types.arrow);
+ }
+ return this.finishOp(code === 61 ? _tokentype.types.eq : _tokentype.types.prefix, 1);
+};
+
+pp.getTokenFromCode = function (code) {
+ switch (code) {
+ // The interpretation of a dot depends on whether it is followed
+ // by a digit or another two dots.
+ case 46:
+ // '.'
+ return this.readToken_dot();
+
+ // Punctuation tokens.
+ case 40:
+ ++this.pos;return this.finishToken(_tokentype.types.parenL);
+ case 41:
+ ++this.pos;return this.finishToken(_tokentype.types.parenR);
+ case 59:
+ ++this.pos;return this.finishToken(_tokentype.types.semi);
+ case 44:
+ ++this.pos;return this.finishToken(_tokentype.types.comma);
+ case 91:
+ ++this.pos;return this.finishToken(_tokentype.types.bracketL);
+ case 93:
+ ++this.pos;return this.finishToken(_tokentype.types.bracketR);
+ case 123:
+ ++this.pos;return this.finishToken(_tokentype.types.braceL);
+ case 125:
+ ++this.pos;return this.finishToken(_tokentype.types.braceR);
+ case 58:
+ ++this.pos;return this.finishToken(_tokentype.types.colon);
+ case 63:
+ ++this.pos;return this.finishToken(_tokentype.types.question);
+
+ case 96:
+ // '`'
+ if (this.options.ecmaVersion < 6) break;
+ ++this.pos;
+ return this.finishToken(_tokentype.types.backQuote);
+
+ case 48:
+ // '0'
+ var next = this.input.charCodeAt(this.pos + 1);
+ if (next === 120 || next === 88) return this.readRadixNumber(16); // '0x', '0X' - hex number
+ if (this.options.ecmaVersion >= 6) {
+ if (next === 111 || next === 79) return this.readRadixNumber(8); // '0o', '0O' - octal number
+ if (next === 98 || next === 66) return this.readRadixNumber(2); // '0b', '0B' - binary number
+ }
+ // Anything else beginning with a digit is an integer, octal
+ // number, or float.
+ case 49:case 50:case 51:case 52:case 53:case 54:case 55:case 56:case 57:
+ // 1-9
+ return this.readNumber(false);
+
+ // Quotes produce strings.
+ case 34:case 39:
+ // '"', "'"
+ return this.readString(code);
+
+ // Operators are parsed inline in tiny state machines. '=' (61) is
+ // often referred to. `finishOp` simply skips the amount of
+ // characters it is given as second argument, and returns a token
+ // of the type given by its first argument.
+
+ case 47:
+ // '/'
+ return this.readToken_slash();
+
+ case 37:case 42:
+ // '%*'
+ return this.readToken_mult_modulo(code);
+
+ case 124:case 38:
+ // '|&'
+ return this.readToken_pipe_amp(code);
+
+ case 94:
+ // '^'
+ return this.readToken_caret();
+
+ case 43:case 45:
+ // '+-'
+ return this.readToken_plus_min(code);
+
+ case 60:case 62:
+ // '<>'
+ return this.readToken_lt_gt(code);
+
+ case 61:case 33:
+ // '=!'
+ return this.readToken_eq_excl(code);
+
+ case 126:
+ // '~'
+ return this.finishOp(_tokentype.types.prefix, 1);
+ }
+
+ this.raise(this.pos, "Unexpected character '" + codePointToString(code) + "'");
+};
+
+pp.finishOp = function (type, size) {
+ var str = this.input.slice(this.pos, this.pos + size);
+ this.pos += size;
+ return this.finishToken(type, str);
+};
+
+// Parse a regular expression. Some context-awareness is necessary,
+// since a '/' inside a '[]' set does not end the expression.
+
+function tryCreateRegexp(src, flags, throwErrorAt, parser) {
+ try {
+ return new RegExp(src, flags);
+ } catch (e) {
+ if (throwErrorAt !== undefined) {
+ if (e instanceof SyntaxError) parser.raise(throwErrorAt, "Error parsing regular expression: " + e.message);
+ throw e;
+ }
+ }
+}
+
+var regexpUnicodeSupport = !!tryCreateRegexp("￿", "u");
+
+pp.readRegexp = function () {
+ var _this = this;
+
+ var escaped = undefined,
+ inClass = undefined,
+ start = this.pos;
+ for (;;) {
+ if (this.pos >= this.input.length) this.raise(start, "Unterminated regular expression");
+ var ch = this.input.charAt(this.pos);
+ if (_whitespace.lineBreak.test(ch)) this.raise(start, "Unterminated regular expression");
+ if (!escaped) {
+ if (ch === "[") inClass = true;else if (ch === "]" && inClass) inClass = false;else if (ch === "/" && !inClass) break;
+ escaped = ch === "\\";
+ } else escaped = false;
+ ++this.pos;
+ }
+ var content = this.input.slice(start, this.pos);
+ ++this.pos;
+ // Need to use `readWord1` because '\uXXXX' sequences are allowed
+ // here (don't ask).
+ var mods = this.readWord1();
+ var tmp = content;
+ if (mods) {
+ var validFlags = /^[gmsiy]*$/;
+ if (this.options.ecmaVersion >= 6) validFlags = /^[gmsiyu]*$/;
+ if (!validFlags.test(mods)) this.raise(start, "Invalid regular expression flag");
+ if (mods.indexOf('u') >= 0 && !regexpUnicodeSupport) {
+ // Replace each astral symbol and every Unicode escape sequence that
+ // possibly represents an astral symbol or a paired surrogate with a
+ // single ASCII symbol to avoid throwing on regular expressions that
+ // are only valid in combination with the `/u` flag.
+ // Note: replacing with the ASCII symbol `x` might cause false
+ // negatives in unlikely scenarios. For example, `[\u{61}-b]` is a
+ // perfectly valid pattern that is equivalent to `[a-b]`, but it would
+ // be replaced by `[x-b]` which throws an error.
+ tmp = tmp.replace(/\\u\{([0-9a-fA-F]+)\}/g, function (_match, code, offset) {
+ code = Number("0x" + code);
+ if (code > 0x10FFFF) _this.raise(start + offset + 3, "Code point out of bounds");
+ return "x";
+ });
+ tmp = tmp.replace(/\\u([a-fA-F0-9]{4})|[\uD800-\uDBFF][\uDC00-\uDFFF]/g, "x");
+ }
+ }
+ // Detect invalid regular expressions.
+ var value = null;
+ // Rhino's regular expression parser is flaky and throws uncatchable exceptions,
+ // so don't do detection if we are running under Rhino
+ if (!isRhino) {
+ tryCreateRegexp(tmp, undefined, start, this);
+ // Get a regular expression object for this pattern-flag pair, or `null` in
+ // case the current environment doesn't support the flags it uses.
+ value = tryCreateRegexp(content, mods);
+ }
+ return this.finishToken(_tokentype.types.regexp, { pattern: content, flags: mods, value: value });
+};
+
+// Read an integer in the given radix. Return null if zero digits
+// were read, the integer value otherwise. When `len` is given, this
+// will return `null` unless the integer has exactly `len` digits.
+
+pp.readInt = function (radix, len) {
+ var start = this.pos,
+ total = 0;
+ for (var i = 0, e = len == null ? Infinity : len; i < e; ++i) {
+ var code = this.input.charCodeAt(this.pos),
+ val = undefined;
+ if (code >= 97) val = code - 97 + 10; // a
+ else if (code >= 65) val = code - 65 + 10; // A
+ else if (code >= 48 && code <= 57) val = code - 48; // 0-9
+ else val = Infinity;
+ if (val >= radix) break;
+ ++this.pos;
+ total = total * radix + val;
+ }
+ if (this.pos === start || len != null && this.pos - start !== len) return null;
+
+ return total;
+};
+
+pp.readRadixNumber = function (radix) {
+ this.pos += 2; // 0x
+ var val = this.readInt(radix);
+ if (val == null) this.raise(this.start + 2, "Expected number in radix " + radix);
+ if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number");
+ return this.finishToken(_tokentype.types.num, val);
+};
+
+// Read an integer, octal integer, or floating-point number.
+
+pp.readNumber = function (startsWithDot) {
+ var start = this.pos,
+ isFloat = false,
+ octal = this.input.charCodeAt(this.pos) === 48;
+ if (!startsWithDot && this.readInt(10) === null) this.raise(start, "Invalid number");
+ var next = this.input.charCodeAt(this.pos);
+ if (next === 46) {
+ // '.'
+ ++this.pos;
+ this.readInt(10);
+ isFloat = true;
+ next = this.input.charCodeAt(this.pos);
+ }
+ if (next === 69 || next === 101) {
+ // 'eE'
+ next = this.input.charCodeAt(++this.pos);
+ if (next === 43 || next === 45) ++this.pos; // '+-'
+ if (this.readInt(10) === null) this.raise(start, "Invalid number");
+ isFloat = true;
+ }
+ if (_identifier.isIdentifierStart(this.fullCharCodeAtPos())) this.raise(this.pos, "Identifier directly after number");
+
+ var str = this.input.slice(start, this.pos),
+ val = undefined;
+ if (isFloat) val = parseFloat(str);else if (!octal || str.length === 1) val = parseInt(str, 10);else if (/[89]/.test(str) || this.strict) this.raise(start, "Invalid number");else val = parseInt(str, 8);
+ return this.finishToken(_tokentype.types.num, val);
+};
+
+// Read a string value, interpreting backslash-escapes.
+
+pp.readCodePoint = function () {
+ var ch = this.input.charCodeAt(this.pos),
+ code = undefined;
+
+ if (ch === 123) {
+ if (this.options.ecmaVersion < 6) this.unexpected();
+ var codePos = ++this.pos;
+ code = this.readHexChar(this.input.indexOf('}', this.pos) - this.pos);
+ ++this.pos;
+ if (code > 0x10FFFF) this.raise(codePos, "Code point out of bounds");
+ } else {
+ code = this.readHexChar(4);
+ }
+ return code;
+};
+
+function codePointToString(code) {
+ // UTF-16 Decoding
+ if (code <= 0xFFFF) return String.fromCharCode(code);
+ code -= 0x10000;
+ return String.fromCharCode((code >> 10) + 0xD800, (code & 1023) + 0xDC00);
+}
+
+pp.readString = function (quote) {
+ var out = "",
+ chunkStart = ++this.pos;
+ for (;;) {
+ if (this.pos >= this.input.length) this.raise(this.start, "Unterminated string constant");
+ var ch = this.input.charCodeAt(this.pos);
+ if (ch === quote) break;
+ if (ch === 92) {
+ // '\'
+ out += this.input.slice(chunkStart, this.pos);
+ out += this.readEscapedChar(false);
+ chunkStart = this.pos;
+ } else {
+ if (_whitespace.isNewLine(ch)) this.raise(this.start, "Unterminated string constant");
+ ++this.pos;
+ }
+ }
+ out += this.input.slice(chunkStart, this.pos++);
+ return this.finishToken(_tokentype.types.string, out);
+};
+
+// Reads template string tokens.
+
+pp.readTmplToken = function () {
+ var out = "",
+ chunkStart = this.pos;
+ for (;;) {
+ if (this.pos >= this.input.length) this.raise(this.start, "Unterminated template");
+ var ch = this.input.charCodeAt(this.pos);
+ if (ch === 96 || ch === 36 && this.input.charCodeAt(this.pos + 1) === 123) {
+ // '`', '${'
+ if (this.pos === this.start && this.type === _tokentype.types.template) {
+ if (ch === 36) {
+ this.pos += 2;
+ return this.finishToken(_tokentype.types.dollarBraceL);
+ } else {
+ ++this.pos;
+ return this.finishToken(_tokentype.types.backQuote);
+ }
+ }
+ out += this.input.slice(chunkStart, this.pos);
+ return this.finishToken(_tokentype.types.template, out);
+ }
+ if (ch === 92) {
+ // '\'
+ out += this.input.slice(chunkStart, this.pos);
+ out += this.readEscapedChar(true);
+ chunkStart = this.pos;
+ } else if (_whitespace.isNewLine(ch)) {
+ out += this.input.slice(chunkStart, this.pos);
+ ++this.pos;
+ switch (ch) {
+ case 13:
+ if (this.input.charCodeAt(this.pos) === 10) ++this.pos;
+ case 10:
+ out += "\n";
+ break;
+ default:
+ out += String.fromCharCode(ch);
+ break;
+ }
+ if (this.options.locations) {
+ ++this.curLine;
+ this.lineStart = this.pos;
+ }
+ chunkStart = this.pos;
+ } else {
+ ++this.pos;
+ }
+ }
+};
+
+// Used to read escaped characters
+
+pp.readEscapedChar = function (inTemplate) {
+ var ch = this.input.charCodeAt(++this.pos);
+ ++this.pos;
+ switch (ch) {
+ case 110:
+ return "\n"; // 'n' -> '\n'
+ case 114:
+ return "\r"; // 'r' -> '\r'
+ case 120:
+ return String.fromCharCode(this.readHexChar(2)); // 'x'
+ case 117:
+ return codePointToString(this.readCodePoint()); // 'u'
+ case 116:
+ return "\t"; // 't' -> '\t'
+ case 98:
+ return "\b"; // 'b' -> '\b'
+ case 118:
+ return "\u000b"; // 'v' -> '\u000b'
+ case 102:
+ return "\f"; // 'f' -> '\f'
+ case 13:
+ if (this.input.charCodeAt(this.pos) === 10) ++this.pos; // '\r\n'
+ case 10:
+ // ' \n'
+ if (this.options.locations) {
+ this.lineStart = this.pos;++this.curLine;
+ }
+ return "";
+ default:
+ if (ch >= 48 && ch <= 55) {
+ var octalStr = this.input.substr(this.pos - 1, 3).match(/^[0-7]+/)[0];
+ var octal = parseInt(octalStr, 8);
+ if (octal > 255) {
+ octalStr = octalStr.slice(0, -1);
+ octal = parseInt(octalStr, 8);
+ }
+ if (octal > 0 && (this.strict || inTemplate)) {
+ this.raise(this.pos - 2, "Octal literal in strict mode");
+ }
+ this.pos += octalStr.length - 1;
+ return String.fromCharCode(octal);
+ }
+ return String.fromCharCode(ch);
+ }
+};
+
+// Used to read character escape sequences ('\x', '\u', '\U').
+
+pp.readHexChar = function (len) {
+ var codePos = this.pos;
+ var n = this.readInt(16, len);
+ if (n === null) this.raise(codePos, "Bad character escape sequence");
+ return n;
+};
+
+// Read an identifier, and return it as a string. Sets `this.containsEsc`
+// to whether the word contained a '\u' escape.
+//
+// Incrementally adds only escaped chars, adding other chunks as-is
+// as a micro-optimization.
+
+pp.readWord1 = function () {
+ this.containsEsc = false;
+ var word = "",
+ first = true,
+ chunkStart = this.pos;
+ var astral = this.options.ecmaVersion >= 6;
+ while (this.pos < this.input.length) {
+ var ch = this.fullCharCodeAtPos();
+ if (_identifier.isIdentifierChar(ch, astral)) {
+ this.pos += ch <= 0xffff ? 1 : 2;
+ } else if (ch === 92) {
+ // "\"
+ this.containsEsc = true;
+ word += this.input.slice(chunkStart, this.pos);
+ var escStart = this.pos;
+ if (this.input.charCodeAt(++this.pos) != 117) // "u"
+ this.raise(this.pos, "Expecting Unicode escape sequence \\uXXXX");
+ ++this.pos;
+ var esc = this.readCodePoint();
+ if (!(first ? _identifier.isIdentifierStart : _identifier.isIdentifierChar)(esc, astral)) this.raise(escStart, "Invalid Unicode escape");
+ word += codePointToString(esc);
+ chunkStart = this.pos;
+ } else {
+ break;
+ }
+ first = false;
+ }
+ return word + this.input.slice(chunkStart, this.pos);
+};
+
+// Read an identifier or keyword token. Will check for reserved
+// words when necessary.
+
+pp.readWord = function () {
+ var word = this.readWord1();
+ var type = _tokentype.types.name;
+ if ((this.options.ecmaVersion >= 6 || !this.containsEsc) && this.keywords.test(word)) type = _tokentype.keywords[word];
+ return this.finishToken(type, word);
+};
+
+},{"./identifier":2,"./locutil":5,"./state":10,"./tokentype":14,"./whitespace":16}],14:[function(_dereq_,module,exports){
+// ## Token types
+
+// The assignment of fine-grained, information-carrying type objects
+// allows the tokenizer to store the information it has about a
+// token in a way that is very cheap for the parser to look up.
+
+// All token type variables start with an underscore, to make them
+// easy to recognize.
+
+// The `beforeExpr` property is used to disambiguate between regular
+// expressions and divisions. It is set on all token types that can
+// be followed by an expression (thus, a slash after them would be a
+// regular expression).
+//
+// The `startsExpr` property is used to check if the token ends a
+// `yield` expression. It is set on all token types that either can
+// directly start an expression (like a quotation mark) or can
+// continue an expression (like the body of a string).
+//
+// `isLoop` marks a keyword as starting a loop, which is important
+// to know when parsing a label, in order to allow or disallow
+// continue jumps to that label.
+
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var TokenType = function TokenType(label) {
+ var conf = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ _classCallCheck(this, TokenType);
+
+ this.label = label;
+ this.keyword = conf.keyword;
+ this.beforeExpr = !!conf.beforeExpr;
+ this.startsExpr = !!conf.startsExpr;
+ this.isLoop = !!conf.isLoop;
+ this.isAssign = !!conf.isAssign;
+ this.prefix = !!conf.prefix;
+ this.postfix = !!conf.postfix;
+ this.binop = conf.binop || null;
+ this.updateContext = null;
+};
+
+exports.TokenType = TokenType;
+
+function binop(name, prec) {
+ return new TokenType(name, { beforeExpr: true, binop: prec });
+}
+var beforeExpr = { beforeExpr: true },
+ startsExpr = { startsExpr: true };
+
+var types = {
+ num: new TokenType("num", startsExpr),
+ regexp: new TokenType("regexp", startsExpr),
+ string: new TokenType("string", startsExpr),
+ name: new TokenType("name", startsExpr),
+ eof: new TokenType("eof"),
+
+ // Punctuation token types.
+ bracketL: new TokenType("[", { beforeExpr: true, startsExpr: true }),
+ bracketR: new TokenType("]"),
+ braceL: new TokenType("{", { beforeExpr: true, startsExpr: true }),
+ braceR: new TokenType("}"),
+ parenL: new TokenType("(", { beforeExpr: true, startsExpr: true }),
+ parenR: new TokenType(")"),
+ comma: new TokenType(",", beforeExpr),
+ semi: new TokenType(";", beforeExpr),
+ colon: new TokenType(":", beforeExpr),
+ dot: new TokenType("."),
+ question: new TokenType("?", beforeExpr),
+ arrow: new TokenType("=>", beforeExpr),
+ template: new TokenType("template"),
+ ellipsis: new TokenType("...", beforeExpr),
+ backQuote: new TokenType("`", startsExpr),
+ dollarBraceL: new TokenType("${", { beforeExpr: true, startsExpr: true }),
+
+ // Operators. These carry several kinds of properties to help the
+ // parser use them properly (the presence of these properties is
+ // what categorizes them as operators).
+ //
+ // `binop`, when present, specifies that this operator is a binary
+ // operator, and will refer to its precedence.
+ //
+ // `prefix` and `postfix` mark the operator as a prefix or postfix
+ // unary operator.
+ //
+ // `isAssign` marks all of `=`, `+=`, `-=` etcetera, which act as
+ // binary operators with a very low precedence, that should result
+ // in AssignmentExpression nodes.
+
+ eq: new TokenType("=", { beforeExpr: true, isAssign: true }),
+ assign: new TokenType("_=", { beforeExpr: true, isAssign: true }),
+ incDec: new TokenType("++/--", { prefix: true, postfix: true, startsExpr: true }),
+ prefix: new TokenType("prefix", { beforeExpr: true, prefix: true, startsExpr: true }),
+ logicalOR: binop("||", 1),
+ logicalAND: binop("&&", 2),
+ bitwiseOR: binop("|", 3),
+ bitwiseXOR: binop("^", 4),
+ bitwiseAND: binop("&", 5),
+ equality: binop("==/!=", 6),
+ relational: binop("</>", 7),
+ bitShift: binop("<</>>", 8),
+ plusMin: new TokenType("+/-", { beforeExpr: true, binop: 9, prefix: true, startsExpr: true }),
+ modulo: binop("%", 10),
+ star: binop("*", 10),
+ slash: binop("/", 10)
+};
+
+exports.types = types;
+// Map keyword names to token types.
+
+var keywords = {};
+
+exports.keywords = keywords;
+// Succinct definitions of keyword token types
+function kw(name) {
+ var options = arguments.length <= 1 || arguments[1] === undefined ? {} : arguments[1];
+
+ options.keyword = name;
+ keywords[name] = types["_" + name] = new TokenType(name, options);
+}
+
+kw("break");
+kw("case", beforeExpr);
+kw("catch");
+kw("continue");
+kw("debugger");
+kw("default", beforeExpr);
+kw("do", { isLoop: true, beforeExpr: true });
+kw("else", beforeExpr);
+kw("finally");
+kw("for", { isLoop: true });
+kw("function", startsExpr);
+kw("if");
+kw("return", beforeExpr);
+kw("switch");
+kw("throw", beforeExpr);
+kw("try");
+kw("var");
+kw("let");
+kw("const");
+kw("while", { isLoop: true });
+kw("with");
+kw("new", { beforeExpr: true, startsExpr: true });
+kw("this", startsExpr);
+kw("super", startsExpr);
+kw("class");
+kw("extends", beforeExpr);
+kw("export");
+kw("import");
+kw("yield", { beforeExpr: true, startsExpr: true });
+kw("null", startsExpr);
+kw("true", startsExpr);
+kw("false", startsExpr);
+kw("in", { beforeExpr: true, binop: 7 });
+kw("instanceof", { beforeExpr: true, binop: 7 });
+kw("typeof", { beforeExpr: true, prefix: true, startsExpr: true });
+kw("void", { beforeExpr: true, prefix: true, startsExpr: true });
+kw("delete", { beforeExpr: true, prefix: true, startsExpr: true });
+
+},{}],15:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.isArray = isArray;
+exports.has = has;
+
+function isArray(obj) {
+ return Object.prototype.toString.call(obj) === "[object Array]";
+}
+
+// Checks if an object has a property.
+
+function has(obj, propName) {
+ return Object.prototype.hasOwnProperty.call(obj, propName);
+}
+
+},{}],16:[function(_dereq_,module,exports){
+// Matches a whole line break (where CRLF is considered a single
+// line break). Used to count lines.
+
+"use strict";
+
+exports.__esModule = true;
+exports.isNewLine = isNewLine;
+var lineBreak = /\r\n?|\n|\u2028|\u2029/;
+exports.lineBreak = lineBreak;
+var lineBreakG = new RegExp(lineBreak.source, "g");
+
+exports.lineBreakG = lineBreakG;
+
+function isNewLine(code) {
+ return code === 10 || code === 13 || code === 0x2028 || code == 0x2029;
+}
+
+var nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/;
+exports.nonASCIIwhitespace = nonASCIIwhitespace;
+
+},{}]},{},[3])(3)
+}); \ No newline at end of file
diff --git a/devtools/shared/acorn/acorn_loose.js b/devtools/shared/acorn/acorn_loose.js
new file mode 100644
index 000000000..52571e77c
--- /dev/null
+++ b/devtools/shared/acorn/acorn_loose.js
@@ -0,0 +1,1302 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).loose = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+"use strict";
+
+module.exports = typeof acorn != 'undefined' ? acorn : require("./acorn");
+
+},{}],2:[function(_dereq_,module,exports){
+"use strict";
+
+var _state = _dereq_("./state");
+
+var _parseutil = _dereq_("./parseutil");
+
+var _ = _dereq_("..");
+
+var lp = _state.LooseParser.prototype;
+
+lp.checkLVal = function (expr) {
+ if (!expr) return expr;
+ switch (expr.type) {
+ case "Identifier":
+ case "MemberExpression":
+ return expr;
+
+ case "ParenthesizedExpression":
+ expr.expression = this.checkLVal(expr.expression);
+ return expr;
+
+ default:
+ return this.dummyIdent();
+ }
+};
+
+lp.parseExpression = function (noIn) {
+ var start = this.storeCurrentPos();
+ var expr = this.parseMaybeAssign(noIn);
+ if (this.tok.type === _.tokTypes.comma) {
+ var node = this.startNodeAt(start);
+ node.expressions = [expr];
+ while (this.eat(_.tokTypes.comma)) node.expressions.push(this.parseMaybeAssign(noIn));
+ return this.finishNode(node, "SequenceExpression");
+ }
+ return expr;
+};
+
+lp.parseParenExpression = function () {
+ this.pushCx();
+ this.expect(_.tokTypes.parenL);
+ var val = this.parseExpression();
+ this.popCx();
+ this.expect(_.tokTypes.parenR);
+ return val;
+};
+
+lp.parseMaybeAssign = function (noIn) {
+ var start = this.storeCurrentPos();
+ var left = this.parseMaybeConditional(noIn);
+ if (this.tok.type.isAssign) {
+ var node = this.startNodeAt(start);
+ node.operator = this.tok.value;
+ node.left = this.tok.type === _.tokTypes.eq ? this.toAssignable(left) : this.checkLVal(left);
+ this.next();
+ node.right = this.parseMaybeAssign(noIn);
+ return this.finishNode(node, "AssignmentExpression");
+ }
+ return left;
+};
+
+lp.parseMaybeConditional = function (noIn) {
+ var start = this.storeCurrentPos();
+ var expr = this.parseExprOps(noIn);
+ if (this.eat(_.tokTypes.question)) {
+ var node = this.startNodeAt(start);
+ node.test = expr;
+ node.consequent = this.parseMaybeAssign();
+ node.alternate = this.expect(_.tokTypes.colon) ? this.parseMaybeAssign(noIn) : this.dummyIdent();
+ return this.finishNode(node, "ConditionalExpression");
+ }
+ return expr;
+};
+
+lp.parseExprOps = function (noIn) {
+ var start = this.storeCurrentPos();
+ var indent = this.curIndent,
+ line = this.curLineStart;
+ return this.parseExprOp(this.parseMaybeUnary(noIn), start, -1, noIn, indent, line);
+};
+
+lp.parseExprOp = function (left, start, minPrec, noIn, indent, line) {
+ if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) return left;
+ var prec = this.tok.type.binop;
+ if (prec != null && (!noIn || this.tok.type !== _.tokTypes._in)) {
+ if (prec > minPrec) {
+ var node = this.startNodeAt(start);
+ node.left = left;
+ node.operator = this.tok.value;
+ this.next();
+ if (this.curLineStart != line && this.curIndent < indent && this.tokenStartsLine()) {
+ node.right = this.dummyIdent();
+ } else {
+ var rightStart = this.storeCurrentPos();
+ node.right = this.parseExprOp(this.parseMaybeUnary(noIn), rightStart, prec, noIn, indent, line);
+ }
+ this.finishNode(node, /&&|\|\|/.test(node.operator) ? "LogicalExpression" : "BinaryExpression");
+ return this.parseExprOp(node, start, minPrec, noIn, indent, line);
+ }
+ }
+ return left;
+};
+
+lp.parseMaybeUnary = function (noIn) {
+ if (this.tok.type.prefix) {
+ var node = this.startNode(),
+ update = this.tok.type === _.tokTypes.incDec;
+ node.operator = this.tok.value;
+ node.prefix = true;
+ this.next();
+ node.argument = this.parseMaybeUnary(noIn);
+ if (update) node.argument = this.checkLVal(node.argument);
+ return this.finishNode(node, update ? "UpdateExpression" : "UnaryExpression");
+ } else if (this.tok.type === _.tokTypes.ellipsis) {
+ var node = this.startNode();
+ this.next();
+ node.argument = this.parseMaybeUnary(noIn);
+ return this.finishNode(node, "SpreadElement");
+ }
+ var start = this.storeCurrentPos();
+ var expr = this.parseExprSubscripts();
+ while (this.tok.type.postfix && !this.canInsertSemicolon()) {
+ var node = this.startNodeAt(start);
+ node.operator = this.tok.value;
+ node.prefix = false;
+ node.argument = this.checkLVal(expr);
+ this.next();
+ expr = this.finishNode(node, "UpdateExpression");
+ }
+ return expr;
+};
+
+lp.parseExprSubscripts = function () {
+ var start = this.storeCurrentPos();
+ return this.parseSubscripts(this.parseExprAtom(), start, false, this.curIndent, this.curLineStart);
+};
+
+lp.parseSubscripts = function (base, start, noCalls, startIndent, line) {
+ for (;;) {
+ if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) {
+ if (this.tok.type == _.tokTypes.dot && this.curIndent == startIndent) --startIndent;else return base;
+ }
+
+ if (this.eat(_.tokTypes.dot)) {
+ var node = this.startNodeAt(start);
+ node.object = base;
+ if (this.curLineStart != line && this.curIndent <= startIndent && this.tokenStartsLine()) node.property = this.dummyIdent();else node.property = this.parsePropertyAccessor() || this.dummyIdent();
+ node.computed = false;
+ base = this.finishNode(node, "MemberExpression");
+ } else if (this.tok.type == _.tokTypes.bracketL) {
+ this.pushCx();
+ this.next();
+ var node = this.startNodeAt(start);
+ node.object = base;
+ node.property = this.parseExpression();
+ node.computed = true;
+ this.popCx();
+ this.expect(_.tokTypes.bracketR);
+ base = this.finishNode(node, "MemberExpression");
+ } else if (!noCalls && this.tok.type == _.tokTypes.parenL) {
+ var node = this.startNodeAt(start);
+ node.callee = base;
+ node.arguments = this.parseExprList(_.tokTypes.parenR);
+ base = this.finishNode(node, "CallExpression");
+ } else if (this.tok.type == _.tokTypes.backQuote) {
+ var node = this.startNodeAt(start);
+ node.tag = base;
+ node.quasi = this.parseTemplate();
+ base = this.finishNode(node, "TaggedTemplateExpression");
+ } else {
+ return base;
+ }
+ }
+};
+
+lp.parseExprAtom = function () {
+ var node = undefined;
+ switch (this.tok.type) {
+ case _.tokTypes._this:
+ case _.tokTypes._super:
+ var type = this.tok.type === _.tokTypes._this ? "ThisExpression" : "Super";
+ node = this.startNode();
+ this.next();
+ return this.finishNode(node, type);
+
+ case _.tokTypes.name:
+ var start = this.storeCurrentPos();
+ var id = this.parseIdent();
+ return this.eat(_.tokTypes.arrow) ? this.parseArrowExpression(this.startNodeAt(start), [id]) : id;
+
+ case _.tokTypes.regexp:
+ node = this.startNode();
+ var val = this.tok.value;
+ node.regex = { pattern: val.pattern, flags: val.flags };
+ node.value = val.value;
+ node.raw = this.input.slice(this.tok.start, this.tok.end);
+ this.next();
+ return this.finishNode(node, "Literal");
+
+ case _.tokTypes.num:case _.tokTypes.string:
+ node = this.startNode();
+ node.value = this.tok.value;
+ node.raw = this.input.slice(this.tok.start, this.tok.end);
+ this.next();
+ return this.finishNode(node, "Literal");
+
+ case _.tokTypes._null:case _.tokTypes._true:case _.tokTypes._false:
+ node = this.startNode();
+ node.value = this.tok.type === _.tokTypes._null ? null : this.tok.type === _.tokTypes._true;
+ node.raw = this.tok.type.keyword;
+ this.next();
+ return this.finishNode(node, "Literal");
+
+ case _.tokTypes.parenL:
+ var parenStart = this.storeCurrentPos();
+ this.next();
+ var inner = this.parseExpression();
+ this.expect(_.tokTypes.parenR);
+ if (this.eat(_.tokTypes.arrow)) {
+ return this.parseArrowExpression(this.startNodeAt(parenStart), inner.expressions || (_parseutil.isDummy(inner) ? [] : [inner]));
+ }
+ if (this.options.preserveParens) {
+ var par = this.startNodeAt(parenStart);
+ par.expression = inner;
+ inner = this.finishNode(par, "ParenthesizedExpression");
+ }
+ return inner;
+
+ case _.tokTypes.bracketL:
+ node = this.startNode();
+ node.elements = this.parseExprList(_.tokTypes.bracketR, true);
+ return this.finishNode(node, "ArrayExpression");
+
+ case _.tokTypes.braceL:
+ return this.parseObj();
+
+ case _.tokTypes._class:
+ return this.parseClass();
+
+ case _.tokTypes._function:
+ node = this.startNode();
+ this.next();
+ return this.parseFunction(node, false);
+
+ case _.tokTypes._new:
+ return this.parseNew();
+
+ case _.tokTypes._yield:
+ node = this.startNode();
+ this.next();
+ if (this.semicolon() || this.canInsertSemicolon() || this.tok.type != _.tokTypes.star && !this.tok.type.startsExpr) {
+ node.delegate = false;
+ node.argument = null;
+ } else {
+ node.delegate = this.eat(_.tokTypes.star);
+ node.argument = this.parseMaybeAssign();
+ }
+ return this.finishNode(node, "YieldExpression");
+
+ case _.tokTypes.backQuote:
+ return this.parseTemplate();
+
+ default:
+ return this.dummyIdent();
+ }
+};
+
+lp.parseNew = function () {
+ var node = this.startNode(),
+ startIndent = this.curIndent,
+ line = this.curLineStart;
+ var meta = this.parseIdent(true);
+ if (this.options.ecmaVersion >= 6 && this.eat(_.tokTypes.dot)) {
+ node.meta = meta;
+ node.property = this.parseIdent(true);
+ return this.finishNode(node, "MetaProperty");
+ }
+ var start = this.storeCurrentPos();
+ node.callee = this.parseSubscripts(this.parseExprAtom(), start, true, startIndent, line);
+ if (this.tok.type == _.tokTypes.parenL) {
+ node.arguments = this.parseExprList(_.tokTypes.parenR);
+ } else {
+ node.arguments = [];
+ }
+ return this.finishNode(node, "NewExpression");
+};
+
+lp.parseTemplateElement = function () {
+ var elem = this.startNode();
+ elem.value = {
+ raw: this.input.slice(this.tok.start, this.tok.end).replace(/\r\n?/g, '\n'),
+ cooked: this.tok.value
+ };
+ this.next();
+ elem.tail = this.tok.type === _.tokTypes.backQuote;
+ return this.finishNode(elem, "TemplateElement");
+};
+
+lp.parseTemplate = function () {
+ var node = this.startNode();
+ this.next();
+ node.expressions = [];
+ var curElt = this.parseTemplateElement();
+ node.quasis = [curElt];
+ while (!curElt.tail) {
+ this.next();
+ node.expressions.push(this.parseExpression());
+ if (this.expect(_.tokTypes.braceR)) {
+ curElt = this.parseTemplateElement();
+ } else {
+ curElt = this.startNode();
+ curElt.value = { cooked: '', raw: '' };
+ curElt.tail = true;
+ }
+ node.quasis.push(curElt);
+ }
+ this.expect(_.tokTypes.backQuote);
+ return this.finishNode(node, "TemplateLiteral");
+};
+
+lp.parseObj = function () {
+ var node = this.startNode();
+ node.properties = [];
+ this.pushCx();
+ var indent = this.curIndent + 1,
+ line = this.curLineStart;
+ this.eat(_.tokTypes.braceL);
+ if (this.curIndent + 1 < indent) {
+ indent = this.curIndent;line = this.curLineStart;
+ }
+ while (!this.closes(_.tokTypes.braceR, indent, line)) {
+ var prop = this.startNode(),
+ isGenerator = undefined,
+ start = undefined;
+ if (this.options.ecmaVersion >= 6) {
+ start = this.storeCurrentPos();
+ prop.method = false;
+ prop.shorthand = false;
+ isGenerator = this.eat(_.tokTypes.star);
+ }
+ this.parsePropertyName(prop);
+ if (_parseutil.isDummy(prop.key)) {
+ if (_parseutil.isDummy(this.parseMaybeAssign())) this.next();this.eat(_.tokTypes.comma);continue;
+ }
+ if (this.eat(_.tokTypes.colon)) {
+ prop.kind = "init";
+ prop.value = this.parseMaybeAssign();
+ } else if (this.options.ecmaVersion >= 6 && (this.tok.type === _.tokTypes.parenL || this.tok.type === _.tokTypes.braceL)) {
+ prop.kind = "init";
+ prop.method = true;
+ prop.value = this.parseMethod(isGenerator);
+ } else if (this.options.ecmaVersion >= 5 && prop.key.type === "Identifier" && !prop.computed && (prop.key.name === "get" || prop.key.name === "set") && (this.tok.type != _.tokTypes.comma && this.tok.type != _.tokTypes.braceR)) {
+ prop.kind = prop.key.name;
+ this.parsePropertyName(prop);
+ prop.value = this.parseMethod(false);
+ } else {
+ prop.kind = "init";
+ if (this.options.ecmaVersion >= 6) {
+ if (this.eat(_.tokTypes.eq)) {
+ var assign = this.startNodeAt(start);
+ assign.operator = "=";
+ assign.left = prop.key;
+ assign.right = this.parseMaybeAssign();
+ prop.value = this.finishNode(assign, "AssignmentExpression");
+ } else {
+ prop.value = prop.key;
+ }
+ } else {
+ prop.value = this.dummyIdent();
+ }
+ prop.shorthand = true;
+ }
+ node.properties.push(this.finishNode(prop, "Property"));
+ this.eat(_.tokTypes.comma);
+ }
+ this.popCx();
+ if (!this.eat(_.tokTypes.braceR)) {
+ // If there is no closing brace, make the node span to the start
+ // of the next token (this is useful for Tern)
+ this.last.end = this.tok.start;
+ if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+ }
+ return this.finishNode(node, "ObjectExpression");
+};
+
+lp.parsePropertyName = function (prop) {
+ if (this.options.ecmaVersion >= 6) {
+ if (this.eat(_.tokTypes.bracketL)) {
+ prop.computed = true;
+ prop.key = this.parseExpression();
+ this.expect(_.tokTypes.bracketR);
+ return;
+ } else {
+ prop.computed = false;
+ }
+ }
+ var key = this.tok.type === _.tokTypes.num || this.tok.type === _.tokTypes.string ? this.parseExprAtom() : this.parseIdent();
+ prop.key = key || this.dummyIdent();
+};
+
+lp.parsePropertyAccessor = function () {
+ if (this.tok.type === _.tokTypes.name || this.tok.type.keyword) return this.parseIdent();
+};
+
+lp.parseIdent = function () {
+ var name = this.tok.type === _.tokTypes.name ? this.tok.value : this.tok.type.keyword;
+ if (!name) return this.dummyIdent();
+ var node = this.startNode();
+ this.next();
+ node.name = name;
+ return this.finishNode(node, "Identifier");
+};
+
+lp.initFunction = function (node) {
+ node.id = null;
+ node.params = [];
+ if (this.options.ecmaVersion >= 6) {
+ node.generator = false;
+ node.expression = false;
+ }
+};
+
+// Convert existing expression atom to assignable pattern
+// if possible.
+
+lp.toAssignable = function (node, binding) {
+ if (!node || node.type == "Identifier" || node.type == "MemberExpression" && !binding) {
+ // Okay
+ } else if (node.type == "ParenthesizedExpression") {
+ node.expression = this.toAssignable(node.expression, binding);
+ } else if (this.options.ecmaVersion < 6) {
+ return this.dummyIdent();
+ } else if (node.type == "ObjectExpression") {
+ node.type = "ObjectPattern";
+ var props = node.properties;
+ for (var i = 0; i < props.length; i++) {
+ props[i].value = this.toAssignable(props[i].value, binding);
+ }
+ } else if (node.type == "ArrayExpression") {
+ node.type = "ArrayPattern";
+ this.toAssignableList(node.elements, binding);
+ } else if (node.type == "SpreadElement") {
+ node.type = "RestElement";
+ node.argument = this.toAssignable(node.argument, binding);
+ } else if (node.type == "AssignmentExpression") {
+ node.type = "AssignmentPattern";
+ delete node.operator;
+ } else {
+ return this.dummyIdent();
+ }
+ return node;
+};
+
+lp.toAssignableList = function (exprList, binding) {
+ for (var i = 0; i < exprList.length; i++) {
+ exprList[i] = this.toAssignable(exprList[i], binding);
+ }return exprList;
+};
+
+lp.parseFunctionParams = function (params) {
+ params = this.parseExprList(_.tokTypes.parenR);
+ return this.toAssignableList(params, true);
+};
+
+lp.parseMethod = function (isGenerator) {
+ var node = this.startNode();
+ this.initFunction(node);
+ node.params = this.parseFunctionParams();
+ node.generator = isGenerator || false;
+ node.expression = this.options.ecmaVersion >= 6 && this.tok.type !== _.tokTypes.braceL;
+ node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock();
+ return this.finishNode(node, "FunctionExpression");
+};
+
+lp.parseArrowExpression = function (node, params) {
+ this.initFunction(node);
+ node.params = this.toAssignableList(params, true);
+ node.expression = this.tok.type !== _.tokTypes.braceL;
+ node.body = node.expression ? this.parseMaybeAssign() : this.parseBlock();
+ return this.finishNode(node, "ArrowFunctionExpression");
+};
+
+lp.parseExprList = function (close, allowEmpty) {
+ this.pushCx();
+ var indent = this.curIndent,
+ line = this.curLineStart,
+ elts = [];
+ this.next(); // Opening bracket
+ while (!this.closes(close, indent + 1, line)) {
+ if (this.eat(_.tokTypes.comma)) {
+ elts.push(allowEmpty ? null : this.dummyIdent());
+ continue;
+ }
+ var elt = this.parseMaybeAssign();
+ if (_parseutil.isDummy(elt)) {
+ if (this.closes(close, indent, line)) break;
+ this.next();
+ } else {
+ elts.push(elt);
+ }
+ this.eat(_.tokTypes.comma);
+ }
+ this.popCx();
+ if (!this.eat(close)) {
+ // If there is no closing brace, make the node span to the start
+ // of the next token (this is useful for Tern)
+ this.last.end = this.tok.start;
+ if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+ }
+ return elts;
+};
+
+},{"..":1,"./parseutil":4,"./state":5}],3:[function(_dereq_,module,exports){
+// Acorn: Loose parser
+//
+// This module provides an alternative parser (`parse_dammit`) that
+// exposes that same interface as `parse`, but will try to parse
+// anything as JavaScript, repairing syntax error the best it can.
+// There are circumstances in which it will raise an error and give
+// up, but they are very rare. The resulting AST will be a mostly
+// valid JavaScript AST (as per the [Mozilla parser API][api], except
+// that:
+//
+// - Return outside functions is allowed
+//
+// - Label consistency (no conflicts, break only to existing labels)
+// is not enforced.
+//
+// - Bogus Identifier nodes with a name of `"✖"` are inserted whenever
+// the parser got too confused to return anything meaningful.
+//
+// [api]: https://developer.mozilla.org/en-US/docs/SpiderMonkey/Parser_API
+//
+// The expected use for this is to *first* try `acorn.parse`, and only
+// if that fails switch to `parse_dammit`. The loose parser might
+// parse badly indented code incorrectly, so **don't** use it as
+// your default parser.
+//
+// Quite a lot of acorn.js is duplicated here. The alternative was to
+// add a *lot* of extra cruft to that file, making it less readable
+// and slower. Copying and editing the code allowed me to make
+// invasive changes and simplifications without creating a complicated
+// tangle.
+
+"use strict";
+
+exports.__esModule = true;
+exports.parse_dammit = parse_dammit;
+
+function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj["default"] = obj; return newObj; } }
+
+var _ = _dereq_("..");
+
+var acorn = _interopRequireWildcard(_);
+
+var _state = _dereq_("./state");
+
+_dereq_("./tokenize");
+
+_dereq_("./statement");
+
+_dereq_("./expression");
+
+exports.LooseParser = _state.LooseParser;
+exports.pluginsLoose = _state.pluginsLoose;
+
+acorn.defaultOptions.tabSize = 4;
+
+function parse_dammit(input, options) {
+ var p = new _state.LooseParser(input, options);
+ p.next();
+ return p.parseTopLevel();
+}
+
+// Don't define new properties on acorn because of:
+// TypeError: can't define property "parse_dammit": Object is not extensible
+// acorn.parse_dammit = parse_dammit;
+// acorn.LooseParser = _state.LooseParser;
+// acorn.pluginsLoose = _state.pluginsLoose;
+
+},{"..":1,"./expression":2,"./state":5,"./statement":6,"./tokenize":7}],4:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+exports.isDummy = isDummy;
+
+function isDummy(node) {
+ return node.name == "✖";
+}
+
+},{}],5:[function(_dereq_,module,exports){
+"use strict";
+
+exports.__esModule = true;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+var _ = _dereq_("..");
+
+// Registered plugins
+var pluginsLoose = {};
+
+exports.pluginsLoose = pluginsLoose;
+
+var LooseParser = (function () {
+ function LooseParser(input, options) {
+ _classCallCheck(this, LooseParser);
+
+ this.toks = _.tokenizer(input, options);
+ this.options = this.toks.options;
+ this.input = this.toks.input;
+ this.tok = this.last = { type: _.tokTypes.eof, start: 0, end: 0 };
+ if (this.options.locations) {
+ var here = this.toks.curPosition();
+ this.tok.loc = new _.SourceLocation(this.toks, here, here);
+ }
+ this.ahead = []; // Tokens ahead
+ this.context = []; // Indentation contexted
+ this.curIndent = 0;
+ this.curLineStart = 0;
+ this.nextLineStart = this.lineEnd(this.curLineStart) + 1;
+ // Load plugins
+ this.options.pluginsLoose = options.pluginsLoose || {};
+ this.loadPlugins(this.options.pluginsLoose);
+ }
+
+ LooseParser.prototype.startNode = function startNode() {
+ return new _.Node(this.toks, this.tok.start, this.options.locations ? this.tok.loc.start : null);
+ };
+
+ LooseParser.prototype.storeCurrentPos = function storeCurrentPos() {
+ return this.options.locations ? [this.tok.start, this.tok.loc.start] : this.tok.start;
+ };
+
+ LooseParser.prototype.startNodeAt = function startNodeAt(pos) {
+ if (this.options.locations) {
+ return new _.Node(this.toks, pos[0], pos[1]);
+ } else {
+ return new _.Node(this.toks, pos);
+ }
+ };
+
+ LooseParser.prototype.finishNode = function finishNode(node, type) {
+ node.type = type;
+ node.end = this.last.end;
+ if (this.options.locations) node.loc.end = this.last.loc.end;
+ if (this.options.ranges) node.range[1] = this.last.end;
+ return node;
+ };
+
+ LooseParser.prototype.dummyNode = function dummyNode(type) {
+ var dummy = this.startNode();
+ dummy.type = type;
+ dummy.end = dummy.start;
+ if (this.options.locations) dummy.loc.end = dummy.loc.start;
+ if (this.options.ranges) dummy.range[1] = dummy.start;
+ this.last = { type: _.tokTypes.name, start: dummy.start, end: dummy.start, loc: dummy.loc };
+ return dummy;
+ };
+
+ LooseParser.prototype.dummyIdent = function dummyIdent() {
+ var dummy = this.dummyNode("Identifier");
+ dummy.name = "✖";
+ return dummy;
+ };
+
+ LooseParser.prototype.dummyString = function dummyString() {
+ var dummy = this.dummyNode("Literal");
+ dummy.value = dummy.raw = "✖";
+ return dummy;
+ };
+
+ LooseParser.prototype.eat = function eat(type) {
+ if (this.tok.type === type) {
+ this.next();
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ LooseParser.prototype.isContextual = function isContextual(name) {
+ return this.tok.type === _.tokTypes.name && this.tok.value === name;
+ };
+
+ LooseParser.prototype.eatContextual = function eatContextual(name) {
+ return this.tok.value === name && this.eat(_.tokTypes.name);
+ };
+
+ LooseParser.prototype.canInsertSemicolon = function canInsertSemicolon() {
+ return this.tok.type === _.tokTypes.eof || this.tok.type === _.tokTypes.braceR || _.lineBreak.test(this.input.slice(this.last.end, this.tok.start));
+ };
+
+ LooseParser.prototype.semicolon = function semicolon() {
+ return this.eat(_.tokTypes.semi);
+ };
+
+ LooseParser.prototype.expect = function expect(type) {
+ if (this.eat(type)) return true;
+ for (var i = 1; i <= 2; i++) {
+ if (this.lookAhead(i).type == type) {
+ for (var j = 0; j < i; j++) {
+ this.next();
+ }return true;
+ }
+ }
+ };
+
+ LooseParser.prototype.pushCx = function pushCx() {
+ this.context.push(this.curIndent);
+ };
+
+ LooseParser.prototype.popCx = function popCx() {
+ this.curIndent = this.context.pop();
+ };
+
+ LooseParser.prototype.lineEnd = function lineEnd(pos) {
+ while (pos < this.input.length && !_.isNewLine(this.input.charCodeAt(pos))) ++pos;
+ return pos;
+ };
+
+ LooseParser.prototype.indentationAfter = function indentationAfter(pos) {
+ for (var count = 0;; ++pos) {
+ var ch = this.input.charCodeAt(pos);
+ if (ch === 32) ++count;else if (ch === 9) count += this.options.tabSize;else return count;
+ }
+ };
+
+ LooseParser.prototype.closes = function closes(closeTok, indent, line, blockHeuristic) {
+ if (this.tok.type === closeTok || this.tok.type === _.tokTypes.eof) return true;
+ return line != this.curLineStart && this.curIndent < indent && this.tokenStartsLine() && (!blockHeuristic || this.nextLineStart >= this.input.length || this.indentationAfter(this.nextLineStart) < indent);
+ };
+
+ LooseParser.prototype.tokenStartsLine = function tokenStartsLine() {
+ for (var p = this.tok.start - 1; p >= this.curLineStart; --p) {
+ var ch = this.input.charCodeAt(p);
+ if (ch !== 9 && ch !== 32) return false;
+ }
+ return true;
+ };
+
+ LooseParser.prototype.extend = function extend(name, f) {
+ this[name] = f(this[name]);
+ };
+
+ LooseParser.prototype.loadPlugins = function loadPlugins(pluginConfigs) {
+ for (var _name in pluginConfigs) {
+ var plugin = pluginsLoose[_name];
+ if (!plugin) throw new Error("Plugin '" + _name + "' not found");
+ plugin(this, pluginConfigs[_name]);
+ }
+ };
+
+ return LooseParser;
+})();
+
+exports.LooseParser = LooseParser;
+
+},{"..":1}],6:[function(_dereq_,module,exports){
+"use strict";
+
+var _state = _dereq_("./state");
+
+var _parseutil = _dereq_("./parseutil");
+
+var _ = _dereq_("..");
+
+var lp = _state.LooseParser.prototype;
+
+lp.parseTopLevel = function () {
+ var node = this.startNodeAt(this.options.locations ? [0, _.getLineInfo(this.input, 0)] : 0);
+ node.body = [];
+ while (this.tok.type !== _.tokTypes.eof) node.body.push(this.parseStatement());
+ this.last = this.tok;
+ if (this.options.ecmaVersion >= 6) {
+ node.sourceType = this.options.sourceType;
+ }
+ return this.finishNode(node, "Program");
+};
+
+lp.parseStatement = function () {
+ var starttype = this.tok.type,
+ node = this.startNode();
+
+ switch (starttype) {
+ case _.tokTypes._break:case _.tokTypes._continue:
+ this.next();
+ var isBreak = starttype === _.tokTypes._break;
+ if (this.semicolon() || this.canInsertSemicolon()) {
+ node.label = null;
+ } else {
+ node.label = this.tok.type === _.tokTypes.name ? this.parseIdent() : null;
+ this.semicolon();
+ }
+ return this.finishNode(node, isBreak ? "BreakStatement" : "ContinueStatement");
+
+ case _.tokTypes._debugger:
+ this.next();
+ this.semicolon();
+ return this.finishNode(node, "DebuggerStatement");
+
+ case _.tokTypes._do:
+ this.next();
+ node.body = this.parseStatement();
+ node.test = this.eat(_.tokTypes._while) ? this.parseParenExpression() : this.dummyIdent();
+ this.semicolon();
+ return this.finishNode(node, "DoWhileStatement");
+
+ case _.tokTypes._for:
+ this.next();
+ this.pushCx();
+ this.expect(_.tokTypes.parenL);
+ if (this.tok.type === _.tokTypes.semi) return this.parseFor(node, null);
+ if (this.tok.type === _.tokTypes._var || this.tok.type === _.tokTypes._let || this.tok.type === _.tokTypes._const) {
+ var _init = this.parseVar(true);
+ if (_init.declarations.length === 1 && (this.tok.type === _.tokTypes._in || this.isContextual("of"))) {
+ return this.parseForIn(node, _init);
+ }
+ return this.parseFor(node, _init);
+ }
+ var init = this.parseExpression(true);
+ if (this.tok.type === _.tokTypes._in || this.isContextual("of")) return this.parseForIn(node, this.toAssignable(init));
+ return this.parseFor(node, init);
+
+ case _.tokTypes._function:
+ this.next();
+ return this.parseFunction(node, true);
+
+ case _.tokTypes._if:
+ this.next();
+ node.test = this.parseParenExpression();
+ node.consequent = this.parseStatement();
+ node.alternate = this.eat(_.tokTypes._else) ? this.parseStatement() : null;
+ return this.finishNode(node, "IfStatement");
+
+ case _.tokTypes._return:
+ this.next();
+ if (this.eat(_.tokTypes.semi) || this.canInsertSemicolon()) node.argument = null;else {
+ node.argument = this.parseExpression();this.semicolon();
+ }
+ return this.finishNode(node, "ReturnStatement");
+
+ case _.tokTypes._switch:
+ var blockIndent = this.curIndent,
+ line = this.curLineStart;
+ this.next();
+ node.discriminant = this.parseParenExpression();
+ node.cases = [];
+ this.pushCx();
+ this.expect(_.tokTypes.braceL);
+
+ var cur = undefined;
+ while (!this.closes(_.tokTypes.braceR, blockIndent, line, true)) {
+ if (this.tok.type === _.tokTypes._case || this.tok.type === _.tokTypes._default) {
+ var isCase = this.tok.type === _.tokTypes._case;
+ if (cur) this.finishNode(cur, "SwitchCase");
+ node.cases.push(cur = this.startNode());
+ cur.consequent = [];
+ this.next();
+ if (isCase) cur.test = this.parseExpression();else cur.test = null;
+ this.expect(_.tokTypes.colon);
+ } else {
+ if (!cur) {
+ node.cases.push(cur = this.startNode());
+ cur.consequent = [];
+ cur.test = null;
+ }
+ cur.consequent.push(this.parseStatement());
+ }
+ }
+ if (cur) this.finishNode(cur, "SwitchCase");
+ this.popCx();
+ this.eat(_.tokTypes.braceR);
+ return this.finishNode(node, "SwitchStatement");
+
+ case _.tokTypes._throw:
+ this.next();
+ node.argument = this.parseExpression();
+ this.semicolon();
+ return this.finishNode(node, "ThrowStatement");
+
+ case _.tokTypes._try:
+ this.next();
+ node.block = this.parseBlock();
+ node.handler = null;
+ if (this.tok.type === _.tokTypes._catch) {
+ var clause = this.startNode();
+ this.next();
+ this.expect(_.tokTypes.parenL);
+ clause.param = this.toAssignable(this.parseExprAtom(), true);
+ this.expect(_.tokTypes.parenR);
+ clause.body = this.parseBlock();
+ node.handler = this.finishNode(clause, "CatchClause");
+ }
+ node.finalizer = this.eat(_.tokTypes._finally) ? this.parseBlock() : null;
+ if (!node.handler && !node.finalizer) return node.block;
+ return this.finishNode(node, "TryStatement");
+
+ case _.tokTypes._var:
+ case _.tokTypes._let:
+ case _.tokTypes._const:
+ return this.parseVar();
+
+ case _.tokTypes._while:
+ this.next();
+ node.test = this.parseParenExpression();
+ node.body = this.parseStatement();
+ return this.finishNode(node, "WhileStatement");
+
+ case _.tokTypes._with:
+ this.next();
+ node.object = this.parseParenExpression();
+ node.body = this.parseStatement();
+ return this.finishNode(node, "WithStatement");
+
+ case _.tokTypes.braceL:
+ return this.parseBlock();
+
+ case _.tokTypes.semi:
+ this.next();
+ return this.finishNode(node, "EmptyStatement");
+
+ case _.tokTypes._class:
+ return this.parseClass(true);
+
+ case _.tokTypes._import:
+ return this.parseImport();
+
+ case _.tokTypes._export:
+ return this.parseExport();
+
+ default:
+ var expr = this.parseExpression();
+ if (_parseutil.isDummy(expr)) {
+ this.next();
+ if (this.tok.type === _.tokTypes.eof) return this.finishNode(node, "EmptyStatement");
+ return this.parseStatement();
+ } else if (starttype === _.tokTypes.name && expr.type === "Identifier" && this.eat(_.tokTypes.colon)) {
+ node.body = this.parseStatement();
+ node.label = expr;
+ return this.finishNode(node, "LabeledStatement");
+ } else {
+ node.expression = expr;
+ this.semicolon();
+ return this.finishNode(node, "ExpressionStatement");
+ }
+ }
+};
+
+lp.parseBlock = function () {
+ var node = this.startNode();
+ this.pushCx();
+ this.expect(_.tokTypes.braceL);
+ var blockIndent = this.curIndent,
+ line = this.curLineStart;
+ node.body = [];
+ while (!this.closes(_.tokTypes.braceR, blockIndent, line, true)) node.body.push(this.parseStatement());
+ this.popCx();
+ this.eat(_.tokTypes.braceR);
+ return this.finishNode(node, "BlockStatement");
+};
+
+lp.parseFor = function (node, init) {
+ node.init = init;
+ node.test = node.update = null;
+ if (this.eat(_.tokTypes.semi) && this.tok.type !== _.tokTypes.semi) node.test = this.parseExpression();
+ if (this.eat(_.tokTypes.semi) && this.tok.type !== _.tokTypes.parenR) node.update = this.parseExpression();
+ this.popCx();
+ this.expect(_.tokTypes.parenR);
+ node.body = this.parseStatement();
+ return this.finishNode(node, "ForStatement");
+};
+
+lp.parseForIn = function (node, init) {
+ var type = this.tok.type === _.tokTypes._in ? "ForInStatement" : "ForOfStatement";
+ this.next();
+ node.left = init;
+ node.right = this.parseExpression();
+ this.popCx();
+ this.expect(_.tokTypes.parenR);
+ node.body = this.parseStatement();
+ return this.finishNode(node, type);
+};
+
+lp.parseVar = function (noIn) {
+ var node = this.startNode();
+ node.kind = this.tok.type.keyword;
+ this.next();
+ node.declarations = [];
+ do {
+ var decl = this.startNode();
+ decl.id = this.options.ecmaVersion >= 6 ? this.toAssignable(this.parseExprAtom(), true) : this.parseIdent();
+ decl.init = this.eat(_.tokTypes.eq) ? this.parseMaybeAssign(noIn) : null;
+ node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+ } while (this.eat(_.tokTypes.comma));
+ if (!node.declarations.length) {
+ var decl = this.startNode();
+ decl.id = this.dummyIdent();
+ node.declarations.push(this.finishNode(decl, "VariableDeclarator"));
+ }
+ if (!noIn) this.semicolon();
+ return this.finishNode(node, "VariableDeclaration");
+};
+
+lp.parseClass = function (isStatement) {
+ var node = this.startNode();
+ this.next();
+ if (this.tok.type === _.tokTypes.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent();else node.id = null;
+ node.superClass = this.eat(_.tokTypes._extends) ? this.parseExpression() : null;
+ node.body = this.startNode();
+ node.body.body = [];
+ this.pushCx();
+ var indent = this.curIndent + 1,
+ line = this.curLineStart;
+ this.eat(_.tokTypes.braceL);
+ if (this.curIndent + 1 < indent) {
+ indent = this.curIndent;line = this.curLineStart;
+ }
+ while (!this.closes(_.tokTypes.braceR, indent, line)) {
+ if (this.semicolon()) continue;
+ var method = this.startNode(),
+ isGenerator = undefined;
+ if (this.options.ecmaVersion >= 6) {
+ method["static"] = false;
+ isGenerator = this.eat(_.tokTypes.star);
+ }
+ this.parsePropertyName(method);
+ if (_parseutil.isDummy(method.key)) {
+ if (_parseutil.isDummy(this.parseMaybeAssign())) this.next();this.eat(_.tokTypes.comma);continue;
+ }
+ if (method.key.type === "Identifier" && !method.computed && method.key.name === "static" && (this.tok.type != _.tokTypes.parenL && this.tok.type != _.tokTypes.braceL)) {
+ method["static"] = true;
+ isGenerator = this.eat(_.tokTypes.star);
+ this.parsePropertyName(method);
+ } else {
+ method["static"] = false;
+ }
+ if (this.options.ecmaVersion >= 5 && method.key.type === "Identifier" && !method.computed && (method.key.name === "get" || method.key.name === "set") && this.tok.type !== _.tokTypes.parenL && this.tok.type !== _.tokTypes.braceL) {
+ method.kind = method.key.name;
+ this.parsePropertyName(method);
+ method.value = this.parseMethod(false);
+ } else {
+ if (!method.computed && !method["static"] && !isGenerator && (method.key.type === "Identifier" && method.key.name === "constructor" || method.key.type === "Literal" && method.key.value === "constructor")) {
+ method.kind = "constructor";
+ } else {
+ method.kind = "method";
+ }
+ method.value = this.parseMethod(isGenerator);
+ }
+ node.body.body.push(this.finishNode(method, "MethodDefinition"));
+ }
+ this.popCx();
+ if (!this.eat(_.tokTypes.braceR)) {
+ // If there is no closing brace, make the node span to the start
+ // of the next token (this is useful for Tern)
+ this.last.end = this.tok.start;
+ if (this.options.locations) this.last.loc.end = this.tok.loc.start;
+ }
+ this.semicolon();
+ this.finishNode(node.body, "ClassBody");
+ return this.finishNode(node, isStatement ? "ClassDeclaration" : "ClassExpression");
+};
+
+lp.parseFunction = function (node, isStatement) {
+ this.initFunction(node);
+ if (this.options.ecmaVersion >= 6) {
+ node.generator = this.eat(_.tokTypes.star);
+ }
+ if (this.tok.type === _.tokTypes.name) node.id = this.parseIdent();else if (isStatement) node.id = this.dummyIdent();
+ node.params = this.parseFunctionParams();
+ node.body = this.parseBlock();
+ return this.finishNode(node, isStatement ? "FunctionDeclaration" : "FunctionExpression");
+};
+
+lp.parseExport = function () {
+ var node = this.startNode();
+ this.next();
+ if (this.eat(_.tokTypes.star)) {
+ node.source = this.eatContextual("from") ? this.parseExprAtom() : null;
+ return this.finishNode(node, "ExportAllDeclaration");
+ }
+ if (this.eat(_.tokTypes._default)) {
+ var expr = this.parseMaybeAssign();
+ if (expr.id) {
+ switch (expr.type) {
+ case "FunctionExpression":
+ expr.type = "FunctionDeclaration";break;
+ case "ClassExpression":
+ expr.type = "ClassDeclaration";break;
+ }
+ }
+ node.declaration = expr;
+ this.semicolon();
+ return this.finishNode(node, "ExportDefaultDeclaration");
+ }
+ if (this.tok.type.keyword) {
+ node.declaration = this.parseStatement();
+ node.specifiers = [];
+ node.source = null;
+ } else {
+ node.declaration = null;
+ node.specifiers = this.parseExportSpecifierList();
+ node.source = this.eatContextual("from") ? this.parseExprAtom() : null;
+ this.semicolon();
+ }
+ return this.finishNode(node, "ExportNamedDeclaration");
+};
+
+lp.parseImport = function () {
+ var node = this.startNode();
+ this.next();
+ if (this.tok.type === _.tokTypes.string) {
+ node.specifiers = [];
+ node.source = this.parseExprAtom();
+ node.kind = '';
+ } else {
+ var elt = undefined;
+ if (this.tok.type === _.tokTypes.name && this.tok.value !== "from") {
+ elt = this.startNode();
+ elt.local = this.parseIdent();
+ this.finishNode(elt, "ImportDefaultSpecifier");
+ this.eat(_.tokTypes.comma);
+ }
+ node.specifiers = this.parseImportSpecifierList();
+ node.source = this.eatContextual("from") && this.tok.type == _.tokTypes.string ? this.parseExprAtom() : this.dummyString();
+ if (elt) node.specifiers.unshift(elt);
+ }
+ this.semicolon();
+ return this.finishNode(node, "ImportDeclaration");
+};
+
+lp.parseImportSpecifierList = function () {
+ var elts = [];
+ if (this.tok.type === _.tokTypes.star) {
+ var elt = this.startNode();
+ this.next();
+ if (this.eatContextual("as")) elt.local = this.parseIdent();
+ elts.push(this.finishNode(elt, "ImportNamespaceSpecifier"));
+ } else {
+ var indent = this.curIndent,
+ line = this.curLineStart,
+ continuedLine = this.nextLineStart;
+ this.pushCx();
+ this.eat(_.tokTypes.braceL);
+ if (this.curLineStart > continuedLine) continuedLine = this.curLineStart;
+ while (!this.closes(_.tokTypes.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
+ var elt = this.startNode();
+ if (this.eat(_.tokTypes.star)) {
+ if (this.eatContextual("as")) elt.local = this.parseIdent();
+ this.finishNode(elt, "ImportNamespaceSpecifier");
+ } else {
+ if (this.isContextual("from")) break;
+ elt.imported = this.parseIdent();
+ if (_parseutil.isDummy(elt.imported)) break;
+ elt.local = this.eatContextual("as") ? this.parseIdent() : elt.imported;
+ this.finishNode(elt, "ImportSpecifier");
+ }
+ elts.push(elt);
+ this.eat(_.tokTypes.comma);
+ }
+ this.eat(_.tokTypes.braceR);
+ this.popCx();
+ }
+ return elts;
+};
+
+lp.parseExportSpecifierList = function () {
+ var elts = [];
+ var indent = this.curIndent,
+ line = this.curLineStart,
+ continuedLine = this.nextLineStart;
+ this.pushCx();
+ this.eat(_.tokTypes.braceL);
+ if (this.curLineStart > continuedLine) continuedLine = this.curLineStart;
+ while (!this.closes(_.tokTypes.braceR, indent + (this.curLineStart <= continuedLine ? 1 : 0), line)) {
+ if (this.isContextual("from")) break;
+ var elt = this.startNode();
+ elt.local = this.parseIdent();
+ if (_parseutil.isDummy(elt.local)) break;
+ elt.exported = this.eatContextual("as") ? this.parseIdent() : elt.local;
+ this.finishNode(elt, "ExportSpecifier");
+ elts.push(elt);
+ this.eat(_.tokTypes.comma);
+ }
+ this.eat(_.tokTypes.braceR);
+ this.popCx();
+ return elts;
+};
+
+},{"..":1,"./parseutil":4,"./state":5}],7:[function(_dereq_,module,exports){
+"use strict";
+
+var _ = _dereq_("..");
+
+var _state = _dereq_("./state");
+
+var lp = _state.LooseParser.prototype;
+
+function isSpace(ch) {
+ return ch < 14 && ch > 8 || ch === 32 || ch === 160 || _.isNewLine(ch);
+}
+
+lp.next = function () {
+ this.last = this.tok;
+ if (this.ahead.length) this.tok = this.ahead.shift();else this.tok = this.readToken();
+
+ if (this.tok.start >= this.nextLineStart) {
+ while (this.tok.start >= this.nextLineStart) {
+ this.curLineStart = this.nextLineStart;
+ this.nextLineStart = this.lineEnd(this.curLineStart) + 1;
+ }
+ this.curIndent = this.indentationAfter(this.curLineStart);
+ }
+};
+
+lp.readToken = function () {
+ for (;;) {
+ try {
+ this.toks.next();
+ if (this.toks.type === _.tokTypes.dot && this.input.substr(this.toks.end, 1) === "." && this.options.ecmaVersion >= 6) {
+ this.toks.end++;
+ this.toks.type = _.tokTypes.ellipsis;
+ }
+ return new _.Token(this.toks);
+ } catch (e) {
+ if (!(e instanceof SyntaxError)) throw e;
+
+ // Try to skip some text, based on the error message, and then continue
+ var msg = e.message,
+ pos = e.raisedAt,
+ replace = true;
+ if (/unterminated/i.test(msg)) {
+ pos = this.lineEnd(e.pos + 1);
+ if (/string/.test(msg)) {
+ replace = { start: e.pos, end: pos, type: _.tokTypes.string, value: this.input.slice(e.pos + 1, pos) };
+ } else if (/regular expr/i.test(msg)) {
+ var re = this.input.slice(e.pos, pos);
+ try {
+ re = new RegExp(re);
+ } catch (e) {}
+ replace = { start: e.pos, end: pos, type: _.tokTypes.regexp, value: re };
+ } else if (/template/.test(msg)) {
+ replace = { start: e.pos, end: pos,
+ type: _.tokTypes.template,
+ value: this.input.slice(e.pos, pos) };
+ } else {
+ replace = false;
+ }
+ } else if (/invalid (unicode|regexp|number)|expecting unicode|octal literal|is reserved|directly after number|expected number in radix/i.test(msg)) {
+ while (pos < this.input.length && !isSpace(this.input.charCodeAt(pos))) ++pos;
+ } else if (/character escape|expected hexadecimal/i.test(msg)) {
+ while (pos < this.input.length) {
+ var ch = this.input.charCodeAt(pos++);
+ if (ch === 34 || ch === 39 || _.isNewLine(ch)) break;
+ }
+ } else if (/unexpected character/i.test(msg)) {
+ pos++;
+ replace = false;
+ } else if (/regular expression/i.test(msg)) {
+ replace = true;
+ } else {
+ throw e;
+ }
+ this.resetTo(pos);
+ if (replace === true) replace = { start: pos, end: pos, type: _.tokTypes.name, value: "✖" };
+ if (replace) {
+ if (this.options.locations) replace.loc = new _.SourceLocation(this.toks, _.getLineInfo(this.input, replace.start), _.getLineInfo(this.input, replace.end));
+ return replace;
+ }
+ }
+ }
+};
+
+lp.resetTo = function (pos) {
+ this.toks.pos = pos;
+ var ch = this.input.charAt(pos - 1);
+ this.toks.exprAllowed = !ch || /[\[\{\(,;:?\/*=+\-~!|&%^<>]/.test(ch) || /[enwfd]/.test(ch) && /\b(keywords|case|else|return|throw|new|in|(instance|type)of|delete|void)$/.test(this.input.slice(pos - 10, pos));
+
+ if (this.options.locations) {
+ this.toks.curLine = 1;
+ this.toks.lineStart = _.lineBreakG.lastIndex = 0;
+ var match = undefined;
+ while ((match = _.lineBreakG.exec(this.input)) && match.index < pos) {
+ ++this.toks.curLine;
+ this.toks.lineStart = match.index + match[0].length;
+ }
+ }
+};
+
+lp.lookAhead = function (n) {
+ while (n > this.ahead.length) this.ahead.push(this.readToken());
+ return this.ahead[n - 1];
+};
+
+},{"..":1,"./state":5}]},{},[3])(3)
+}); \ No newline at end of file
diff --git a/devtools/shared/acorn/moz.build b/devtools/shared/acorn/moz.build
new file mode 100644
index 000000000..8fd5375ef
--- /dev/null
+++ b/devtools/shared/acorn/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'acorn.js',
+ 'acorn_loose.js',
+ 'walk.js',
+)
diff --git a/devtools/shared/acorn/tests/unit/head_acorn.js b/devtools/shared/acorn/tests/unit/head_acorn.js
new file mode 100644
index 000000000..3e06ca3c1
--- /dev/null
+++ b/devtools/shared/acorn/tests/unit/head_acorn.js
@@ -0,0 +1,75 @@
+"use strict";
+var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+
+function isObject(value) {
+ return typeof value === "object" && value !== null;
+}
+
+function intersect(a, b) {
+ const seen = new Set(a);
+ return b.filter(value => seen.has(value));
+}
+
+function checkEquivalentASTs(expected, actual, prop = []) {
+ do_print("Checking: " + prop.join(" "));
+
+ if (!isObject(expected)) {
+ return void do_check_eq(expected, actual);
+ }
+
+ do_check_true(isObject(actual));
+
+ if (Array.isArray(expected)) {
+ do_check_true(Array.isArray(actual));
+ do_check_eq(expected.length, actual.length);
+ for (let i = 0; i < expected.length; i++) {
+ checkEquivalentASTs(expected[i], actual[i], prop.concat(i));
+ }
+ } else {
+ // We must intersect the keys since acorn and Reflect have different
+ // extraneous properties on their AST nodes.
+ const keys = intersect(Object.keys(expected), Object.keys(actual));
+ for (let key of keys) {
+ checkEquivalentASTs(expected[key], actual[key], prop.concat(key));
+ }
+ }
+}
+
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Ignored until they are fixed in bug 1242968.
+ if (string.includes("JavaScript Warning")) {
+ return;
+ }
+
+ do_throw("head_acorn.js got console message: " + string + "\n");
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
diff --git a/devtools/shared/acorn/tests/unit/test_import_acorn.js b/devtools/shared/acorn/tests/unit/test_import_acorn.js
new file mode 100644
index 000000000..49b2b50cb
--- /dev/null
+++ b/devtools/shared/acorn/tests/unit/test_import_acorn.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we can require acorn.
+ */
+
+function run_test() {
+ const acorn = require("acorn/acorn");
+ const acorn_loose = require("acorn/acorn_loose");
+ const walk = require("acorn/util/walk");
+ do_check_true(isObject(acorn));
+ do_check_true(isObject(acorn_loose));
+ do_check_true(isObject(walk));
+ do_check_eq(typeof acorn.parse, "function");
+ do_check_eq(typeof acorn_loose.parse_dammit, "function");
+ do_check_eq(typeof walk.simple, "function");
+}
diff --git a/devtools/shared/acorn/tests/unit/test_lenient_parser.js b/devtools/shared/acorn/tests/unit/test_lenient_parser.js
new file mode 100644
index 000000000..7586eb8a3
--- /dev/null
+++ b/devtools/shared/acorn/tests/unit/test_lenient_parser.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that acorn's lenient parser gives something usable.
+ */
+
+const acorn_loose = require("acorn/acorn_loose");
+
+function run_test() {
+ let actualAST = acorn_loose.parse_dammit("let x = 10", {});
+
+ do_print("Actual AST:");
+ do_print(JSON.stringify(actualAST, null, 2));
+ do_print("Expected AST:");
+ do_print(JSON.stringify(expectedAST, null, 2));
+
+ checkEquivalentASTs(expectedAST, actualAST);
+}
+
+const expectedAST = {
+ "type": "Program",
+ "start": 0,
+ "end": 10,
+ "body": [
+ {
+ "type": "ExpressionStatement",
+ "start": 0,
+ "end": 3,
+ "expression": {
+ "type": "Identifier",
+ "start": 0,
+ "end": 3,
+ "name": "let"
+ }
+ },
+ {
+ "type": "ExpressionStatement",
+ "start": 4,
+ "end": 10,
+ "expression": {
+ "type": "AssignmentExpression",
+ "start": 4,
+ "end": 10,
+ "operator": "=",
+ "left": {
+ "type": "Identifier",
+ "start": 4,
+ "end": 5,
+ "name": "x"
+ },
+ "right": {
+ "type": "Literal",
+ "start": 8,
+ "end": 10,
+ "value": 10,
+ "raw": "10"
+ }
+ }
+ }
+ ]
+};
diff --git a/devtools/shared/acorn/tests/unit/test_same_ast.js b/devtools/shared/acorn/tests/unit/test_same_ast.js
new file mode 100644
index 000000000..d1d76bc50
--- /dev/null
+++ b/devtools/shared/acorn/tests/unit/test_same_ast.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that Reflect and acorn create the same AST for ES5.
+ */
+
+const acorn = require("acorn/acorn");
+const { Reflect } = require("resource://gre/modules/reflect.jsm");
+
+const testCode = "" + function main () {
+ function makeAcc(n) {
+ return function () {
+ return ++n;
+ };
+ }
+
+ var acc = makeAcc(10);
+
+ for (var i = 0; i < 10; i++) {
+ acc();
+ }
+
+ console.log(acc());
+};
+
+function run_test() {
+ const reflectAST = Reflect.parse(testCode);
+ const acornAST = acorn.parse(testCode);
+
+ do_print("Reflect AST:");
+ do_print(JSON.stringify(reflectAST, null, 2));
+ do_print("acorn AST:");
+ do_print(JSON.stringify(acornAST, null, 2));
+
+ checkEquivalentASTs(reflectAST, acornAST);
+}
diff --git a/devtools/shared/acorn/tests/unit/xpcshell.ini b/devtools/shared/acorn/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..414dfe567
--- /dev/null
+++ b/devtools/shared/acorn/tests/unit/xpcshell.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+tags = devtools
+head = head_acorn.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_import_acorn.js]
+[test_same_ast.js]
+[test_lenient_parser.js]
diff --git a/devtools/shared/acorn/walk.js b/devtools/shared/acorn/walk.js
new file mode 100644
index 000000000..9429e0b34
--- /dev/null
+++ b/devtools/shared/acorn/walk.js
@@ -0,0 +1,377 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}(g.acorn || (g.acorn = {})).walk = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(_dereq_,module,exports){
+// AST walker module for Mozilla Parser API compatible trees
+
+// A simple walk is one where you simply specify callbacks to be
+// called on specific nodes. The last two arguments are optional. A
+// simple use would be
+//
+// walk.simple(myTree, {
+// Expression: function(node) { ... }
+// });
+//
+// to do something with all expressions. All Parser API node types
+// can be used to identify node types, as well as Expression,
+// Statement, and ScopeBody, which denote categories of nodes.
+//
+// The base argument can be used to pass a custom (recursive)
+// walker, and state can be used to give this walked an initial
+// state.
+
+"use strict";
+
+exports.__esModule = true;
+exports.simple = simple;
+exports.ancestor = ancestor;
+exports.recursive = recursive;
+exports.findNodeAt = findNodeAt;
+exports.findNodeAround = findNodeAround;
+exports.findNodeAfter = findNodeAfter;
+exports.findNodeBefore = findNodeBefore;
+exports.make = make;
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+function simple(node, visitors, base, state, override) {
+ if (!base) base = exports.base;(function c(node, st, override) {
+ var type = override || node.type,
+ found = visitors[type];
+ base[type](node, st, c);
+ if (found) found(node, st);
+ })(node, state, override);
+}
+
+// An ancestor walk builds up an array of ancestor nodes (including
+// the current node) and passes them to the callback as the state parameter.
+
+function ancestor(node, visitors, base, state) {
+ if (!base) base = exports.base;
+ if (!state) state = [];(function c(node, st, override) {
+ var type = override || node.type,
+ found = visitors[type];
+ if (node != st[st.length - 1]) {
+ st = st.slice();
+ st.push(node);
+ }
+ base[type](node, st, c);
+ if (found) found(node, st);
+ })(node, state);
+}
+
+// A recursive walk is one where your functions override the default
+// walkers. They can modify and replace the state parameter that's
+// threaded through the walk, and can opt how and whether to walk
+// their child nodes (by calling their third argument on these
+// nodes).
+
+function recursive(node, state, funcs, base, override) {
+ var visitor = funcs ? exports.make(funcs, base) : base;(function c(node, st, override) {
+ visitor[override || node.type](node, st, c);
+ })(node, state, override);
+}
+
+function makeTest(test) {
+ if (typeof test == "string") return function (type) {
+ return type == test;
+ };else if (!test) return function () {
+ return true;
+ };else return test;
+}
+
+var Found = function Found(node, state) {
+ _classCallCheck(this, Found);
+
+ this.node = node;this.state = state;
+}
+
+// Find a node with a given start, end, and type (all are optional,
+// null can be used as wildcard). Returns a {node, state} object, or
+// undefined when it doesn't find a matching node.
+;
+
+function findNodeAt(node, start, end, test, base, state) {
+ test = makeTest(test);
+ if (!base) base = exports.base;
+ try {
+ ;(function c(node, st, override) {
+ var type = override || node.type;
+ if ((start == null || node.start <= start) && (end == null || node.end >= end)) base[type](node, st, c);
+ if ((start == null || node.start == start) && (end == null || node.end == end) && test(type, node)) throw new Found(node, st);
+ })(node, state);
+ } catch (e) {
+ if (e instanceof Found) return e;
+ throw e;
+ }
+}
+
+// Find the innermost node of a given type that contains the given
+// position. Interface similar to findNodeAt.
+
+function findNodeAround(node, pos, test, base, state) {
+ test = makeTest(test);
+ if (!base) base = exports.base;
+ try {
+ ;(function c(node, st, override) {
+ var type = override || node.type;
+ if (node.start > pos || node.end < pos) return;
+ base[type](node, st, c);
+ if (test(type, node)) throw new Found(node, st);
+ })(node, state);
+ } catch (e) {
+ if (e instanceof Found) return e;
+ throw e;
+ }
+}
+
+// Find the outermost matching node after a given position.
+
+function findNodeAfter(node, pos, test, base, state) {
+ test = makeTest(test);
+ if (!base) base = exports.base;
+ try {
+ ;(function c(node, st, override) {
+ if (node.end < pos) return;
+ var type = override || node.type;
+ if (node.start >= pos && test(type, node)) throw new Found(node, st);
+ base[type](node, st, c);
+ })(node, state);
+ } catch (e) {
+ if (e instanceof Found) return e;
+ throw e;
+ }
+}
+
+// Find the outermost matching node before a given position.
+
+function findNodeBefore(node, pos, test, base, state) {
+ test = makeTest(test);
+ if (!base) base = exports.base;
+ var max = undefined;(function c(node, st, override) {
+ if (node.start > pos) return;
+ var type = override || node.type;
+ if (node.end <= pos && (!max || max.node.end < node.end) && test(type, node)) max = new Found(node, st);
+ base[type](node, st, c);
+ })(node, state);
+ return max;
+}
+
+// Used to create a custom walker. Will fill in all missing node
+// type properties with the defaults.
+
+function make(funcs, base) {
+ if (!base) base = exports.base;
+ var visitor = {};
+ for (var type in base) visitor[type] = base[type];
+ for (var type in funcs) visitor[type] = funcs[type];
+ return visitor;
+}
+
+function skipThrough(node, st, c) {
+ c(node, st);
+}
+function ignore(_node, _st, _c) {}
+
+// Node walkers.
+
+var base = {};
+
+exports.base = base;
+base.Program = base.BlockStatement = function (node, st, c) {
+ for (var i = 0; i < node.body.length; ++i) {
+ c(node.body[i], st, "Statement");
+ }
+};
+base.Statement = skipThrough;
+base.EmptyStatement = ignore;
+base.ExpressionStatement = base.ParenthesizedExpression = function (node, st, c) {
+ return c(node.expression, st, "Expression");
+};
+base.IfStatement = function (node, st, c) {
+ c(node.test, st, "Expression");
+ c(node.consequent, st, "Statement");
+ if (node.alternate) c(node.alternate, st, "Statement");
+};
+base.LabeledStatement = function (node, st, c) {
+ return c(node.body, st, "Statement");
+};
+base.BreakStatement = base.ContinueStatement = ignore;
+base.WithStatement = function (node, st, c) {
+ c(node.object, st, "Expression");
+ c(node.body, st, "Statement");
+};
+base.SwitchStatement = function (node, st, c) {
+ c(node.discriminant, st, "Expression");
+ for (var i = 0; i < node.cases.length; ++i) {
+ var cs = node.cases[i];
+ if (cs.test) c(cs.test, st, "Expression");
+ for (var j = 0; j < cs.consequent.length; ++j) {
+ c(cs.consequent[j], st, "Statement");
+ }
+ }
+};
+base.ReturnStatement = base.YieldExpression = function (node, st, c) {
+ if (node.argument) c(node.argument, st, "Expression");
+};
+base.ThrowStatement = base.SpreadElement = function (node, st, c) {
+ return c(node.argument, st, "Expression");
+};
+base.TryStatement = function (node, st, c) {
+ c(node.block, st, "Statement");
+ if (node.handler) {
+ c(node.handler.param, st, "Pattern");
+ c(node.handler.body, st, "ScopeBody");
+ }
+ if (node.finalizer) c(node.finalizer, st, "Statement");
+};
+base.WhileStatement = base.DoWhileStatement = function (node, st, c) {
+ c(node.test, st, "Expression");
+ c(node.body, st, "Statement");
+};
+base.ForStatement = function (node, st, c) {
+ if (node.init) c(node.init, st, "ForInit");
+ if (node.test) c(node.test, st, "Expression");
+ if (node.update) c(node.update, st, "Expression");
+ c(node.body, st, "Statement");
+};
+base.ForInStatement = base.ForOfStatement = function (node, st, c) {
+ c(node.left, st, "ForInit");
+ c(node.right, st, "Expression");
+ c(node.body, st, "Statement");
+};
+base.ForInit = function (node, st, c) {
+ if (node.type == "VariableDeclaration") c(node, st);else c(node, st, "Expression");
+};
+base.DebuggerStatement = ignore;
+
+base.FunctionDeclaration = function (node, st, c) {
+ return c(node, st, "Function");
+};
+base.VariableDeclaration = function (node, st, c) {
+ for (var i = 0; i < node.declarations.length; ++i) {
+ c(node.declarations[i], st);
+ }
+};
+base.VariableDeclarator = function (node, st, c) {
+ c(node.id, st, "Pattern");
+ if (node.init) c(node.init, st, "Expression");
+};
+
+base.Function = function (node, st, c) {
+ if (node.id) c(node.id, st, "Pattern");
+ for (var i = 0; i < node.params.length; i++) {
+ c(node.params[i], st, "Pattern");
+ }c(node.body, st, node.expression ? "ScopeExpression" : "ScopeBody");
+};
+// FIXME drop these node types in next major version
+// (They are awkward, and in ES6 every block can be a scope.)
+base.ScopeBody = function (node, st, c) {
+ return c(node, st, "Statement");
+};
+base.ScopeExpression = function (node, st, c) {
+ return c(node, st, "Expression");
+};
+
+base.Pattern = function (node, st, c) {
+ if (node.type == "Identifier") c(node, st, "VariablePattern");else if (node.type == "MemberExpression") c(node, st, "MemberPattern");else c(node, st);
+};
+base.VariablePattern = ignore;
+base.MemberPattern = skipThrough;
+base.RestElement = function (node, st, c) {
+ return c(node.argument, st, "Pattern");
+};
+base.ArrayPattern = function (node, st, c) {
+ for (var i = 0; i < node.elements.length; ++i) {
+ var elt = node.elements[i];
+ if (elt) c(elt, st, "Pattern");
+ }
+};
+base.ObjectPattern = function (node, st, c) {
+ for (var i = 0; i < node.properties.length; ++i) {
+ c(node.properties[i].value, st, "Pattern");
+ }
+};
+
+base.Expression = skipThrough;
+base.ThisExpression = base.Super = base.MetaProperty = ignore;
+base.ArrayExpression = function (node, st, c) {
+ for (var i = 0; i < node.elements.length; ++i) {
+ var elt = node.elements[i];
+ if (elt) c(elt, st, "Expression");
+ }
+};
+base.ObjectExpression = function (node, st, c) {
+ for (var i = 0; i < node.properties.length; ++i) {
+ c(node.properties[i], st);
+ }
+};
+base.FunctionExpression = base.ArrowFunctionExpression = base.FunctionDeclaration;
+base.SequenceExpression = base.TemplateLiteral = function (node, st, c) {
+ for (var i = 0; i < node.expressions.length; ++i) {
+ c(node.expressions[i], st, "Expression");
+ }
+};
+base.UnaryExpression = base.UpdateExpression = function (node, st, c) {
+ c(node.argument, st, "Expression");
+};
+base.BinaryExpression = base.LogicalExpression = function (node, st, c) {
+ c(node.left, st, "Expression");
+ c(node.right, st, "Expression");
+};
+base.AssignmentExpression = base.AssignmentPattern = function (node, st, c) {
+ c(node.left, st, "Pattern");
+ c(node.right, st, "Expression");
+};
+base.ConditionalExpression = function (node, st, c) {
+ c(node.test, st, "Expression");
+ c(node.consequent, st, "Expression");
+ c(node.alternate, st, "Expression");
+};
+base.NewExpression = base.CallExpression = function (node, st, c) {
+ c(node.callee, st, "Expression");
+ if (node.arguments) for (var i = 0; i < node.arguments.length; ++i) {
+ c(node.arguments[i], st, "Expression");
+ }
+};
+base.MemberExpression = function (node, st, c) {
+ c(node.object, st, "Expression");
+ if (node.computed) c(node.property, st, "Expression");
+};
+base.ExportNamedDeclaration = base.ExportDefaultDeclaration = function (node, st, c) {
+ if (node.declaration) c(node.declaration, st, node.type == "ExportNamedDeclaration" || node.declaration.id ? "Statement" : "Expression");
+ if (node.source) c(node.source, st, "Expression");
+};
+base.ExportAllDeclaration = function (node, st, c) {
+ c(node.source, st, "Expression");
+};
+base.ImportDeclaration = function (node, st, c) {
+ for (var i = 0; i < node.specifiers.length; i++) {
+ c(node.specifiers[i], st);
+ }c(node.source, st, "Expression");
+};
+base.ImportSpecifier = base.ImportDefaultSpecifier = base.ImportNamespaceSpecifier = base.Identifier = base.Literal = ignore;
+
+base.TaggedTemplateExpression = function (node, st, c) {
+ c(node.tag, st, "Expression");
+ c(node.quasi, st);
+};
+base.ClassDeclaration = base.ClassExpression = function (node, st, c) {
+ return c(node, st, "Class");
+};
+base.Class = function (node, st, c) {
+ if (node.id) c(node.id, st, "Pattern");
+ if (node.superClass) c(node.superClass, st, "Expression");
+ for (var i = 0; i < node.body.body.length; i++) {
+ c(node.body.body[i], st);
+ }
+};
+base.MethodDefinition = base.Property = function (node, st, c) {
+ if (node.computed) c(node.key, st, "Expression");
+ c(node.value, st, "Expression");
+};
+base.ComprehensionExpression = function (node, st, c) {
+ for (var i = 0; i < node.blocks.length; i++) {
+ c(node.blocks[i].right, st, "Expression");
+ }c(node.body, st, "Expression");
+};
+
+},{}]},{},[1])(1)
+}); \ No newline at end of file
diff --git a/devtools/shared/apps/Devices.jsm b/devtools/shared/apps/Devices.jsm
new file mode 100644
index 000000000..d3390b34b
--- /dev/null
+++ b/devtools/shared/apps/Devices.jsm
@@ -0,0 +1,53 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://devtools/shared/event-emitter.js");
+
+const EXPORTED_SYMBOLS = ["Devices"];
+
+var addonInstalled = false;
+
+const Devices = {
+ _devices: {},
+
+ get helperAddonInstalled() {
+ return addonInstalled;
+ },
+ set helperAddonInstalled(v) {
+ addonInstalled = v;
+ if (!addonInstalled) {
+ for (let name in this._devices) {
+ this.unregister(name);
+ }
+ }
+ this.emit("addon-status-updated", v);
+ },
+
+ register: function (name, device) {
+ this._devices[name] = device;
+ this.emit("register");
+ },
+
+ unregister: function (name) {
+ delete this._devices[name];
+ this.emit("unregister");
+ },
+
+ available: function () {
+ return Object.keys(this._devices).sort();
+ },
+
+ getByName: function (name) {
+ return this._devices[name];
+ }
+};
+Object.defineProperty(this, "Devices", {
+ value: Devices,
+ enumerable: true,
+ writable: false
+});
+
+EventEmitter.decorate(Devices);
diff --git a/devtools/shared/apps/Simulator.jsm b/devtools/shared/apps/Simulator.jsm
new file mode 100644
index 000000000..d7d70842a
--- /dev/null
+++ b/devtools/shared/apps/Simulator.jsm
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://devtools/shared/event-emitter.js");
+
+/**
+ * TODO (Bug 1132453) The `Simulator` module is deprecated, and should be
+ * removed once all simulator addons stop using it (see bug 1132452).
+ *
+ * If you want to register, unregister, or otherwise deal with installed
+ * simulators, please use the `Simulators` module defined in:
+ *
+ * devtools/client/webide/modules/simulators.js
+ */
+
+this.EXPORTED_SYMBOLS = ["Simulator"];
+
+let Simulator = this.Simulator = {
+ _simulators: {},
+
+ register: function (name, simulator) {
+ // simulators register themselves as "Firefox OS X.Y"
+ this._simulators[name] = simulator;
+ this.emit("register", name);
+ },
+
+ unregister: function (name) {
+ delete this._simulators[name];
+ this.emit("unregister", name);
+ },
+
+ availableNames: function () {
+ return Object.keys(this._simulators).sort();
+ },
+
+ getByName: function (name) {
+ return this._simulators[name];
+ },
+};
+
+EventEmitter.decorate(Simulator);
diff --git a/devtools/shared/apps/app-actor-front.js b/devtools/shared/apps/app-actor-front.js
new file mode 100644
index 000000000..6cd793679
--- /dev/null
+++ b/devtools/shared/apps/app-actor-front.js
@@ -0,0 +1,840 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Ci, Cc, Cr} = require("chrome");
+const {OS} = require("resource://gre/modules/osfile.jsm");
+const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
+const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+// Bug 1188401: When loaded from xpcshell tests, we do not have browser/ files
+// and can't load target.js. Should be fixed by bug 912121.
+loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
+
+// XXX: bug 912476 make this module a real protocol.js front
+// by converting webapps actor to protocol.js
+
+const PR_USEC_PER_MSEC = 1000;
+const PR_RDWR = 0x04;
+const PR_CREATE_FILE = 0x08;
+const PR_TRUNCATE = 0x20;
+
+const CHUNK_SIZE = 10000;
+
+const appTargets = new Map();
+
+function addDirToZip(writer, dir, basePath) {
+ let files = dir.directoryEntries;
+
+ while (files.hasMoreElements()) {
+ let file = files.getNext().QueryInterface(Ci.nsIFile);
+
+ if (file.isHidden() ||
+ file.isSpecial() ||
+ file.equals(writer.file))
+ {
+ continue;
+ }
+
+ if (file.isDirectory()) {
+ writer.addEntryDirectory(basePath + file.leafName + "/",
+ file.lastModifiedTime * PR_USEC_PER_MSEC,
+ true);
+ addDirToZip(writer, file, basePath + file.leafName + "/");
+ } else {
+ writer.addEntryFile(basePath + file.leafName,
+ Ci.nsIZipWriter.COMPRESSION_DEFAULT,
+ file,
+ true);
+ }
+ }
+}
+
+/**
+ * Convert an XPConnect result code to its name and message.
+ * We have to extract them from an exception per bug 637307 comment 5.
+ */
+function getResultText(code) {
+ let regexp =
+ /^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/;
+ let ex = Cc["@mozilla.org/js/xpc/Exception;1"].
+ createInstance(Ci.nsIXPCException);
+ ex.initialize(null, code, null, null, null, null);
+ let [, message, name] = regexp.exec(ex.toString());
+ return { name: name, message: message };
+}
+
+function zipDirectory(zipFile, dirToArchive) {
+ let deferred = defer();
+ let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+ writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+ this.addDirToZip(writer, dirToArchive, "");
+
+ writer.processQueue({
+ onStartRequest: function onStartRequest(request, context) {},
+ onStopRequest: (request, context, status) => {
+ if (status == Cr.NS_OK) {
+ writer.close();
+ deferred.resolve(zipFile);
+ }
+ else {
+ let { name, message } = getResultText(status);
+ deferred.reject(name + ": " + message);
+ }
+ }
+ }, null);
+
+ return deferred.promise;
+}
+
+function uploadPackage(client, webappsActor, packageFile, progressCallback) {
+ if (client.traits.bulk) {
+ return uploadPackageBulk(client, webappsActor, packageFile, progressCallback);
+ } else {
+ return uploadPackageJSON(client, webappsActor, packageFile, progressCallback);
+ }
+}
+
+function uploadPackageJSON(client, webappsActor, packageFile, progressCallback) {
+ let deferred = defer();
+
+ let request = {
+ to: webappsActor,
+ type: "uploadPackage"
+ };
+ client.request(request, (res) => {
+ openFile(res.actor);
+ });
+
+ let fileSize;
+ let bytesRead = 0;
+
+ function emitProgress() {
+ progressCallback({
+ bytesSent: bytesRead,
+ totalBytes: fileSize
+ });
+ }
+
+ function openFile(actor) {
+ let openedFile;
+ OS.File.open(packageFile.path)
+ .then(file => {
+ openedFile = file;
+ return openedFile.stat();
+ })
+ .then(fileInfo => {
+ fileSize = fileInfo.size;
+ emitProgress();
+ uploadChunk(actor, openedFile);
+ });
+ }
+ function uploadChunk(actor, file) {
+ file.read(CHUNK_SIZE)
+ .then(function (bytes) {
+ bytesRead += bytes.length;
+ emitProgress();
+ // To work around the fact that JSON.stringify translates the typed
+ // array to object, we are encoding the typed array here into a string
+ let chunk = String.fromCharCode.apply(null, bytes);
+
+ let request = {
+ to: actor,
+ type: "chunk",
+ chunk: chunk
+ };
+ client.request(request, (res) => {
+ if (bytes.length == CHUNK_SIZE) {
+ uploadChunk(actor, file);
+ } else {
+ file.close().then(function () {
+ endsUpload(actor);
+ });
+ }
+ });
+ });
+ }
+ function endsUpload(actor) {
+ let request = {
+ to: actor,
+ type: "done"
+ };
+ client.request(request, (res) => {
+ deferred.resolve(actor);
+ });
+ }
+ return deferred.promise;
+}
+
+function uploadPackageBulk(client, webappsActor, packageFile, progressCallback) {
+ let deferred = defer();
+
+ let request = {
+ to: webappsActor,
+ type: "uploadPackage",
+ bulk: true
+ };
+ client.request(request, (res) => {
+ startBulkUpload(res.actor);
+ });
+
+ function startBulkUpload(actor) {
+ console.log("Starting bulk upload");
+ let fileSize = packageFile.fileSize;
+ console.log("File size: " + fileSize);
+
+ let request = client.startBulkRequest({
+ actor: actor,
+ type: "stream",
+ length: fileSize
+ });
+
+ request.on("bulk-send-ready", ({copyFrom}) => {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(packageFile),
+ loadUsingSystemPrincipal: true
+ }, function (inputStream) {
+ let copying = copyFrom(inputStream);
+ copying.on("progress", (e, progress) => {
+ progressCallback(progress);
+ });
+ copying.then(() => {
+ console.log("Bulk upload done");
+ inputStream.close();
+ deferred.resolve(actor);
+ });
+ });
+ });
+ }
+
+ return deferred.promise;
+}
+
+function removeServerTemporaryFile(client, fileActor) {
+ let request = {
+ to: fileActor,
+ type: "remove"
+ };
+ client.request(request);
+}
+
+/**
+ * progressCallback argument:
+ * Function called as packaged app installation proceeds.
+ * The progress object passed to this function contains:
+ * * bytesSent: The number of bytes sent so far
+ * * totalBytes: The total number of bytes to send
+ */
+function installPackaged(client, webappsActor, packagePath, appId, progressCallback) {
+ let deferred = defer();
+ let file = FileUtils.File(packagePath);
+ let packagePromise;
+ if (file.isDirectory()) {
+ let tmpZipFile = FileUtils.getDir("TmpD", [], true);
+ tmpZipFile.append("application.zip");
+ tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+ packagePromise = zipDirectory(tmpZipFile, file);
+ } else {
+ packagePromise = promise.resolve(file);
+ }
+ packagePromise.then((zipFile) => {
+ uploadPackage(client, webappsActor, zipFile, progressCallback)
+ .then((fileActor) => {
+ let request = {
+ to: webappsActor,
+ type: "install",
+ appId: appId,
+ upload: fileActor
+ };
+ client.request(request, (res) => {
+ // If the install method immediatly fails,
+ // reject immediatly the installPackaged promise.
+ // Otherwise, wait for webappsEvent for completion
+ if (res.error) {
+ deferred.reject(res);
+ }
+ if ("error" in res)
+ deferred.reject({error: res.error, message: res.message});
+ else
+ deferred.resolve({appId: res.appId});
+ });
+ // Ensure deleting the temporary package file, but only if that a temporary
+ // package created when we pass a directory as `packagePath`
+ if (zipFile != file)
+ zipFile.remove(false);
+ // In case of success or error, ensure deleting the temporary package file
+ // also created on the device, but only once install request is done
+ deferred.promise.then(
+ () => removeServerTemporaryFile(client, fileActor),
+ () => removeServerTemporaryFile(client, fileActor));
+ });
+ });
+ return deferred.promise;
+}
+exports.installPackaged = installPackaged;
+
+function installHosted(client, webappsActor, appId, metadata, manifest) {
+ let deferred = defer();
+ let request = {
+ to: webappsActor,
+ type: "install",
+ appId: appId,
+ metadata: metadata,
+ manifest: manifest
+ };
+ client.request(request, (res) => {
+ if (res.error) {
+ deferred.reject(res);
+ }
+ if ("error" in res)
+ deferred.reject({error: res.error, message: res.message});
+ else
+ deferred.resolve({appId: res.appId});
+ });
+ return deferred.promise;
+}
+exports.installHosted = installHosted;
+
+function getTargetForApp(client, webappsActor, manifestURL) {
+ // Ensure always returning the exact same JS object for a target
+ // of the same app in order to show only one toolbox per app and
+ // avoid re-creating lot of objects twice.
+ let existingTarget = appTargets.get(manifestURL);
+ if (existingTarget)
+ return promise.resolve(existingTarget);
+
+ let deferred = defer();
+ let request = {
+ to: webappsActor,
+ type: "getAppActor",
+ manifestURL: manifestURL,
+ };
+ client.request(request, (res) => {
+ if (res.error) {
+ deferred.reject(res.error);
+ } else {
+ let options = {
+ form: res.actor,
+ client: client,
+ chrome: false
+ };
+
+ TargetFactory.forRemoteTab(options).then((target) => {
+ target.isApp = true;
+ appTargets.set(manifestURL, target);
+ target.on("close", () => {
+ appTargets.delete(manifestURL);
+ });
+ deferred.resolve(target);
+ }, (error) => {
+ deferred.reject(error);
+ });
+ }
+ });
+ return deferred.promise;
+}
+exports.getTargetForApp = getTargetForApp;
+
+function reloadApp(client, webappsActor, manifestURL) {
+ return getTargetForApp(client,
+ webappsActor,
+ manifestURL).
+ then((target) => {
+ // Request the ContentActor to reload the app
+ let request = {
+ to: target.form.actor,
+ type: "reload",
+ options: {
+ force: true
+ },
+ manifestURL: manifestURL
+ };
+ return client.request(request);
+ }, () => {
+ throw new Error("Not running");
+ });
+}
+exports.reloadApp = reloadApp;
+
+function launchApp(client, webappsActor, manifestURL) {
+ return client.request({
+ to: webappsActor,
+ type: "launch",
+ manifestURL: manifestURL
+ });
+}
+exports.launchApp = launchApp;
+
+function closeApp(client, webappsActor, manifestURL) {
+ return client.request({
+ to: webappsActor,
+ type: "close",
+ manifestURL: manifestURL
+ });
+}
+exports.closeApp = closeApp;
+
+function getTarget(client, form) {
+ let deferred = defer();
+ let options = {
+ form: form,
+ client: client,
+ chrome: false
+ };
+
+ TargetFactory.forRemoteTab(options).then((target) => {
+ target.isApp = true;
+ deferred.resolve(target);
+ }, (error) => {
+ deferred.reject(error);
+ });
+ return deferred.promise;
+}
+
+/**
+ * `App` instances are client helpers to manage a given app
+ * and its the tab actors
+ */
+function App(client, webappsActor, manifest) {
+ this.client = client;
+ this.webappsActor = webappsActor;
+ this.manifest = manifest;
+
+ // This attribute is managed by the AppActorFront
+ this.running = false;
+
+ this.iconURL = null;
+}
+
+App.prototype = {
+ getForm: function () {
+ if (this._form) {
+ return promise.resolve(this._form);
+ }
+ let request = {
+ to: this.webappsActor,
+ type: "getAppActor",
+ manifestURL: this.manifest.manifestURL
+ };
+ return this.client.request(request)
+ .then(res => {
+ return this._form = res.actor;
+ });
+ },
+
+ getTarget: function () {
+ if (this._target) {
+ return promise.resolve(this._target);
+ }
+ return this.getForm().
+ then((form) => getTarget(this.client, form)).
+ then((target) => {
+ target.on("close", () => {
+ delete this._form;
+ delete this._target;
+ });
+ return this._target = target;
+ });
+ },
+
+ launch: function () {
+ return launchApp(this.client, this.webappsActor,
+ this.manifest.manifestURL);
+ },
+
+ reload: function () {
+ return reloadApp(this.client, this.webappsActor,
+ this.manifest.manifestURL);
+ },
+
+ close: function () {
+ return closeApp(this.client, this.webappsActor,
+ this.manifest.manifestURL);
+ },
+
+ getIcon: function () {
+ if (this.iconURL) {
+ return promise.resolve(this.iconURL);
+ }
+
+ let deferred = defer();
+
+ let request = {
+ to: this.webappsActor,
+ type: "getIconAsDataURL",
+ manifestURL: this.manifest.manifestURL
+ };
+
+ this.client.request(request, res => {
+ if (res.error) {
+ deferred.reject(res.message || res.error);
+ } else if (res.url) {
+ this.iconURL = res.url;
+ deferred.resolve(res.url);
+ } else {
+ deferred.reject("Unable to fetch app icon");
+ }
+ });
+
+ return deferred.promise;
+ }
+};
+
+
+/**
+ * `AppActorFront` is a client for the webapps actor.
+ */
+function AppActorFront(client, form) {
+ this.client = client;
+ this.actor = form.webappsActor;
+
+ this._clientListener = this._clientListener.bind(this);
+ this._onInstallProgress = this._onInstallProgress.bind(this);
+
+ this._listeners = [];
+ EventEmitter.decorate(this);
+}
+
+AppActorFront.prototype = {
+ /**
+ * List `App` instances for all currently running apps.
+ */
+ get runningApps() {
+ if (!this._apps) {
+ throw new Error("Can't get running apps before calling watchApps.");
+ }
+ let r = new Map();
+ for (let [manifestURL, app] of this._apps) {
+ if (app.running) {
+ r.set(manifestURL, app);
+ }
+ }
+ return r;
+ },
+
+ /**
+ * List `App` instances for all installed apps.
+ */
+ get apps() {
+ if (!this._apps) {
+ throw new Error("Can't get apps before calling watchApps.");
+ }
+ return this._apps;
+ },
+
+ /**
+ * Returns a `App` object instance for the given manifest URL
+ * (and cache it per AppActorFront object)
+ */
+ _getApp: function (manifestURL) {
+ let app = this._apps ? this._apps.get(manifestURL) : null;
+ if (app) {
+ return promise.resolve(app);
+ } else {
+ let request = {
+ to: this.actor,
+ type: "getApp",
+ manifestURL: manifestURL
+ };
+ return this.client.request(request)
+ .then(res => {
+ let app = new App(this.client, this.actor, res.app);
+ if (this._apps) {
+ this._apps.set(manifestURL, app);
+ }
+ return app;
+ }, e => {
+ console.error("Unable to retrieve app", manifestURL, e);
+ });
+ }
+ },
+
+ /**
+ * Starts watching for app opening/closing installing/uninstalling.
+ * Needs to be called before using `apps` or `runningApps` attributes.
+ */
+ watchApps: function (listener) {
+ // Fixes race between two references to the same front
+ // calling watchApps at the same time
+ if (this._loadingPromise) {
+ return this._loadingPromise;
+ }
+
+ // Only call watchApps for the first listener being register,
+ // for all next ones, just send fake appOpen events for already
+ // opened apps
+ if (this._apps) {
+ this.runningApps.forEach((app, manifestURL) => {
+ listener("appOpen", app);
+ });
+ return promise.resolve();
+ }
+
+ // First retrieve all installed apps and create
+ // related `App` object for each
+ let request = {
+ to: this.actor,
+ type: "getAll"
+ };
+ return this._loadingPromise = this.client.request(request)
+ .then(res => {
+ delete this._loadingPromise;
+ this._apps = new Map();
+ for (let a of res.apps) {
+ let app = new App(this.client, this.actor, a);
+ this._apps.set(a.manifestURL, app);
+ }
+ })
+ .then(() => {
+ // Then retrieve all running apps in order to flag them as running
+ let request = {
+ to: this.actor,
+ type: "listRunningApps"
+ };
+ return this.client.request(request)
+ .then(res => res.apps);
+ })
+ .then(apps => {
+ let promises = apps.map(manifestURL => {
+ // _getApp creates `App` instance and register it to AppActorFront
+ return this._getApp(manifestURL)
+ .then(app => {
+ app.running = true;
+ // Fake appOpen event for all already opened
+ this._notifyListeners("appOpen", app);
+ });
+ });
+ return promise.all(promises);
+ })
+ .then(() => {
+ // Finally ask to receive all app events
+ return this._listenAppEvents(listener);
+ });
+ },
+
+ fetchIcons: function () {
+ // On demand, retrieve apps icons in order to be able
+ // to synchronously retrieve it on `App` objects
+ let promises = [];
+ for (let [manifestURL, app] of this._apps) {
+ promises.push(app.getIcon());
+ }
+
+ return DevToolsUtils.settleAll(promises)
+ .then(null, () => {});
+ },
+
+ _listenAppEvents: function (listener) {
+ this._listeners.push(listener);
+
+ if (this._listeners.length > 1) {
+ return promise.resolve();
+ }
+
+ let client = this.client;
+ let f = this._clientListener;
+ client.addListener("appOpen", f);
+ client.addListener("appClose", f);
+ client.addListener("appInstall", f);
+ client.addListener("appUninstall", f);
+
+ let request = {
+ to: this.actor,
+ type: "watchApps"
+ };
+ return this.client.request(request);
+ },
+
+ _unlistenAppEvents: function (listener) {
+ let idx = this._listeners.indexOf(listener);
+ if (idx != -1) {
+ this._listeners.splice(idx, 1);
+ }
+
+ // Until we released all listener, we don't ask to stop sending events
+ if (this._listeners.length != 0) {
+ return promise.resolve();
+ }
+
+ let client = this.client;
+ let f = this._clientListener;
+ client.removeListener("appOpen", f);
+ client.removeListener("appClose", f);
+ client.removeListener("appInstall", f);
+ client.removeListener("appUninstall", f);
+
+ // Remove `_apps` in order to allow calling watchApps again
+ // and repopulate the apps Map.
+ delete this._apps;
+
+ let request = {
+ to: this.actor,
+ type: "unwatchApps"
+ };
+ return this.client.request(request);
+ },
+
+ _clientListener: function (type, message) {
+ let { manifestURL } = message;
+
+ // Reset the app object to get a fresh copy when we (re)install the app.
+ if (type == "appInstall" && this._apps && this._apps.has(manifestURL)) {
+ this._apps.delete(manifestURL);
+ }
+
+ this._getApp(manifestURL).then((app) => {
+ switch (type) {
+ case "appOpen":
+ app.running = true;
+ this._notifyListeners("appOpen", app);
+ break;
+ case "appClose":
+ app.running = false;
+ this._notifyListeners("appClose", app);
+ break;
+ case "appInstall":
+ // The call to _getApp is going to create App object
+
+ // This app may have been running while being installed, so check the list
+ // of running apps again to get the right answer.
+ let request = {
+ to: this.actor,
+ type: "listRunningApps"
+ };
+ this.client.request(request)
+ .then(res => {
+ if (res.apps.indexOf(manifestURL) !== -1) {
+ app.running = true;
+ this._notifyListeners("appInstall", app);
+ this._notifyListeners("appOpen", app);
+ } else {
+ this._notifyListeners("appInstall", app);
+ }
+ });
+ break;
+ case "appUninstall":
+ // Fake a appClose event if we didn't got one before uninstall
+ if (app.running) {
+ app.running = false;
+ this._notifyListeners("appClose", app);
+ }
+ this._apps.delete(manifestURL);
+ this._notifyListeners("appUninstall", app);
+ break;
+ default:
+ return;
+ }
+ });
+ },
+
+ _notifyListeners: function (type, app) {
+ this._listeners.forEach(f => {
+ f(type, app);
+ });
+ },
+
+ unwatchApps: function (listener) {
+ return this._unlistenAppEvents(listener);
+ },
+
+ /*
+ * Install a packaged app.
+ *
+ * Events are going to be emitted on the front
+ * as install progresses. Events will have the following fields:
+ * * bytesSent: The number of bytes sent so far
+ * * totalBytes: The total number of bytes to send
+ */
+ installPackaged: function (packagePath, appId) {
+ let request = () => {
+ return installPackaged(this.client, this.actor, packagePath, appId,
+ this._onInstallProgress)
+ .then(response => ({
+ appId: response.appId,
+ manifestURL: "app://" + response.appId + "/manifest.webapp"
+ }));
+ };
+ return this._install(request);
+ },
+
+ _onInstallProgress: function (progress) {
+ this.emit("install-progress", progress);
+ },
+
+ _install: function (request) {
+ let deferred = defer();
+ let finalAppId = null, manifestURL = null;
+ let installs = {};
+
+ // We need to resolve only once the request is done *AND*
+ // once we receive the related appInstall message for
+ // the same manifestURL
+ let resolve = app => {
+ this._unlistenAppEvents(listener);
+ installs = null;
+ deferred.resolve({ app: app, appId: finalAppId });
+ };
+
+ // Listen for appInstall event, in order to resolve with
+ // the matching app object.
+ let listener = (type, app) => {
+ if (type == "appInstall") {
+ // Resolves immediately if the request has already resolved
+ // or just flag the installed app to eventually resolve
+ // when the request gets its response.
+ if (app.manifest.manifestURL === manifestURL) {
+ resolve(app);
+ } else {
+ installs[app.manifest.manifestURL] = app;
+ }
+ }
+ };
+ this._listenAppEvents(listener)
+ // Execute the request
+ .then(request)
+ .then(response => {
+ finalAppId = response.appId;
+ manifestURL = response.manifestURL;
+
+ // Resolves immediately if the appInstall event
+ // was dispatched during the request.
+ if (manifestURL in installs) {
+ resolve(installs[manifestURL]);
+ }
+ }, deferred.reject);
+
+ return deferred.promise;
+
+ },
+
+ /*
+ * Install a hosted app.
+ *
+ * Events are going to be emitted on the front
+ * as install progresses. Events will have the following fields:
+ * * bytesSent: The number of bytes sent so far
+ * * totalBytes: The total number of bytes to send
+ */
+ installHosted: function (appId, metadata, manifest) {
+ let manifestURL = metadata.manifestURL ||
+ metadata.origin + "/manifest.webapp";
+ let request = () => {
+ return installHosted(this.client, this.actor, appId, metadata,
+ manifest)
+ .then(response => ({
+ appId: response.appId,
+ manifestURL: manifestURL
+ }));
+ };
+ return this._install(request);
+ }
+};
+
+exports.AppActorFront = AppActorFront;
diff --git a/devtools/shared/apps/moz.build b/devtools/shared/apps/moz.build
new file mode 100644
index 000000000..8ab1a9eb9
--- /dev/null
+++ b/devtools/shared/apps/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'app-actor-front.js',
+ 'Devices.jsm',
+ 'Simulator.jsm'
+)
diff --git a/devtools/shared/async-storage.js b/devtools/shared/async-storage.js
new file mode 100644
index 000000000..0d260fc5a
--- /dev/null
+++ b/devtools/shared/async-storage.js
@@ -0,0 +1,188 @@
+/**
+ *
+ * Adapted from https://github.com/mozilla-b2g/gaia/blob/f09993563fb5fec4393eb71816ce76cb00463190/shared/js/async_storage.js
+ * (converted to use Promises instead of callbacks).
+ *
+ * This file defines an asynchronous version of the localStorage API, backed by
+ * an IndexedDB database. It creates a global asyncStorage object that has
+ * methods like the localStorage object.
+ *
+ * To store a value use setItem:
+ *
+ * asyncStorage.setItem("key", "value");
+ *
+ * This returns a promise in case you want confirmation that the value has been stored.
+ *
+ * asyncStorage.setItem("key", "newvalue").then(function() {
+ * console.log("new value stored");
+ * });
+ *
+ * To read a value, call getItem(), but note that you must wait for a promise
+ * resolution for the value to be retrieved.
+ *
+ * asyncStorage.getItem("key").then(function(value) {
+ * console.log("The value of key is:", value);
+ * });
+ *
+ * Note that unlike localStorage, asyncStorage does not allow you to store and
+ * retrieve values by setting and querying properties directly. You cannot just
+ * write asyncStorage.key; you have to explicitly call setItem() or getItem().
+ *
+ * removeItem(), clear(), length(), and key() are like the same-named methods of
+ * localStorage, and all return a promise.
+ *
+ * The asynchronous nature of getItem() makes it tricky to retrieve multiple
+ * values. But unlike localStorage, asyncStorage does not require the values you
+ * store to be strings. So if you need to save multiple values and want to
+ * retrieve them together, in a single asynchronous operation, just group the
+ * values into a single object. The properties of this object may not include
+ * DOM elements, but they may include things like Blobs and typed arrays.
+ *
+ */
+
+"use strict";
+
+const Promise = require("promise");
+
+const DBNAME = "devtools-async-storage";
+const DBVERSION = 1;
+const STORENAME = "keyvaluepairs";
+var db = null;
+
+function withStore(type, onsuccess, onerror) {
+ if (db) {
+ let transaction = db.transaction(STORENAME, type);
+ let store = transaction.objectStore(STORENAME);
+ onsuccess(store);
+ } else {
+ let openreq = indexedDB.open(DBNAME, DBVERSION);
+ openreq.onerror = function withStoreOnError() {
+ onerror();
+ };
+ openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() {
+ // First time setup: create an empty object store
+ openreq.result.createObjectStore(STORENAME);
+ };
+ openreq.onsuccess = function withStoreOnSuccess() {
+ db = openreq.result;
+ let transaction = db.transaction(STORENAME, type);
+ let store = transaction.objectStore(STORENAME);
+ onsuccess(store);
+ };
+ }
+}
+
+function getItem(itemKey) {
+ return new Promise((resolve, reject) => {
+ let req;
+ withStore("readonly", (store) => {
+ store.transaction.oncomplete = function onComplete() {
+ let value = req.result;
+ if (value === undefined) {
+ value = null;
+ }
+ resolve(value);
+ };
+ req = store.get(itemKey);
+ req.onerror = function getItemOnError() {
+ reject("Error in asyncStorage.getItem(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+function setItem(itemKey, value) {
+ return new Promise((resolve, reject) => {
+ withStore("readwrite", (store) => {
+ store.transaction.oncomplete = resolve;
+ let req = store.put(value, itemKey);
+ req.onerror = function setItemOnError() {
+ reject("Error in asyncStorage.setItem(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+function removeItem(itemKey) {
+ return new Promise((resolve, reject) => {
+ withStore("readwrite", (store) => {
+ store.transaction.oncomplete = resolve;
+ let req = store.delete(itemKey);
+ req.onerror = function removeItemOnError() {
+ reject("Error in asyncStorage.removeItem(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+function clear() {
+ return new Promise((resolve, reject) => {
+ withStore("readwrite", (store) => {
+ store.transaction.oncomplete = resolve;
+ let req = store.clear();
+ req.onerror = function clearOnError() {
+ reject("Error in asyncStorage.clear(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+function length() {
+ return new Promise((resolve, reject) => {
+ let req;
+ withStore("readonly", (store) => {
+ store.transaction.oncomplete = function onComplete() {
+ resolve(req.result);
+ };
+ req = store.count();
+ req.onerror = function lengthOnError() {
+ reject("Error in asyncStorage.length(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+function key(n) {
+ return new Promise((resolve, reject) => {
+ if (n < 0) {
+ resolve(null);
+ return;
+ }
+
+ let req;
+ withStore("readonly", (store) => {
+ store.transaction.oncomplete = function onComplete() {
+ let cursor = req.result;
+ resolve(cursor ? cursor.key : null);
+ };
+ let advanced = false;
+ req = store.openCursor();
+ req.onsuccess = function keyOnSuccess() {
+ let cursor = req.result;
+ if (!cursor) {
+ // this means there weren"t enough keys
+ return;
+ }
+ if (n === 0 || advanced) {
+ // Either 1) we have the first key, return it if that's what they
+ // wanted, or 2) we"ve got the nth key.
+ return;
+ }
+
+ // Otherwise, ask the cursor to skip ahead n records
+ advanced = true;
+ cursor.advance(n);
+ };
+ req.onerror = function keyOnError() {
+ reject("Error in asyncStorage.key(): ", req.error.name);
+ };
+ }, reject);
+ });
+}
+
+exports.getItem = getItem;
+exports.setItem = setItem;
+exports.removeItem = removeItem;
+exports.clear = clear;
+exports.length = length;
+exports.key = key;
diff --git a/devtools/shared/async-utils.js b/devtools/shared/async-utils.js
new file mode 100644
index 000000000..932f74d02
--- /dev/null
+++ b/devtools/shared/async-utils.js
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Helpers for async functions. Async functions are generator functions that are
+ * run by Tasks. An async function returns a Promise for the resolution of the
+ * function. When the function returns, the promise is resolved with the
+ * returned value. If it throws the promise rejects with the thrown error.
+ *
+ * See Task documentation at https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Task.jsm.
+ */
+
+var {Task} = require("devtools/shared/task");
+var Promise = require("promise");
+
+/**
+ * Create an async function that only executes once per instance of an object.
+ * Once called on a given object, the same promise will be returned for any
+ * future calls for that object.
+ *
+ * @param Function func
+ * The generator function that to wrap as an async function.
+ * @return Function
+ * The async function.
+ */
+exports.asyncOnce = function asyncOnce(func) {
+ const promises = new WeakMap();
+ return function (...args) {
+ let promise = promises.get(this);
+ if (!promise) {
+ promise = Task.spawn(func.apply(this, args));
+ promises.set(this, promise);
+ }
+ return promise;
+ };
+};
+
+/**
+ * Adds an event listener to the given element, and then removes its event
+ * listener once the event is called, returning the event object as a promise.
+ * @param nsIDOMElement element
+ * The DOM element to listen on
+ * @param String event
+ * The name of the event type to listen for
+ * @param Boolean useCapture
+ * Should we initiate the capture phase?
+ * @return Promise
+ * The promise resolved with the event object when the event first
+ * happens
+ */
+exports.listenOnce = function listenOnce(element, event, useCapture) {
+ return new Promise(function (resolve, reject) {
+ let onEvent = function (ev) {
+ element.removeEventListener(event, onEvent, useCapture);
+ resolve(ev);
+ };
+ element.addEventListener(event, onEvent, useCapture);
+ });
+};
+
+/**
+ * Call a function that expects a callback as the last argument and returns a
+ * promise for the result. This simplifies using callback APIs from tasks and
+ * async functions.
+ *
+ * @param Any obj
+ * The |this| value to call the function on.
+ * @param Function func
+ * The callback-expecting function to call.
+ * @param Array args
+ * Additional arguments to pass to the method.
+ * @return Promise
+ * The promise for the result. If the callback is called with only one
+ * argument, it is used as the resolution value. If there's multiple
+ * arguments, an array containing the arguments is the resolution value.
+ * If the method throws, the promise is rejected with the thrown value.
+ */
+function promisify(obj, func, args) {
+ return new Promise(resolve => {
+ args.push((...results) => {
+ resolve(results.length > 1 ? results : results[0]);
+ });
+ func.apply(obj, args);
+ });
+}
+
+/**
+ * Call a method that expects a callback as the last argument and returns a
+ * promise for the result.
+ *
+ * @see promisify
+ */
+exports.promiseInvoke = function promiseInvoke(obj, func, ...args) {
+ return promisify(obj, func, args);
+};
+
+/**
+ * Call a function that expects a callback as the last argument.
+ *
+ * @see promisify
+ */
+exports.promiseCall = function promiseCall(func, ...args) {
+ return promisify(undefined, func, args);
+};
diff --git a/devtools/shared/builtin-modules.js b/devtools/shared/builtin-modules.js
new file mode 100644
index 000000000..64fae6da7
--- /dev/null
+++ b/devtools/shared/builtin-modules.js
@@ -0,0 +1,288 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This module defines custom globals injected in all our modules and also
+ * pseudo modules that aren't separate files but just dynamically set values.
+ *
+ * As it does so, the module itself doesn't have access to these globals,
+ * nor the pseudo modules. Be careful to avoid loading any other js module as
+ * they would also miss them.
+ */
+
+const { Cu, CC, Cc, Ci } = require("chrome");
+const { Loader } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
+const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
+const { Services } = jsmScope;
+// Steal various globals only available in JSM scope (and not Sandbox one)
+const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot,
+ atob, btoa, Iterator } = jsmScope;
+const { URL } = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
+ {wantGlobalProperties: ["URL"]});
+
+/**
+ * Defines a getter on a specified object that will be created upon first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject.
+ * @param aLambda
+ * A function that returns what the getter should return. This will
+ * only ever be called once.
+ */
+function defineLazyGetter(aObject, aName, aLambda)
+{
+ Object.defineProperty(aObject, aName, {
+ get: function () {
+ // Redefine this accessor property as a data property.
+ // Delete it first, to rule out "too much recursion" in case aObject is
+ // a proxy whose defineProperty handler might unwittingly trigger this
+ // getter again.
+ delete aObject[aName];
+ let value = aLambda.apply(aObject);
+ Object.defineProperty(aObject, aName, {
+ value,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ });
+ return value;
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+
+/**
+ * Defines a getter on a specified object for a service. The service will not
+ * be obtained until first use.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject for the service.
+ * @param aContract
+ * The contract used to obtain the service.
+ * @param aInterfaceName
+ * The name of the interface to query the service to.
+ */
+function defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName)
+{
+ defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
+ return Cc[aContract].getService(Ci[aInterfaceName]);
+ });
+}
+
+/**
+ * Defines a getter on a specified object for a module. The module will not
+ * be imported until first use. The getter allows to execute setup and
+ * teardown code (e.g. to register/unregister to services) and accepts
+ * a proxy object which acts on behalf of the module until it is imported.
+ *
+ * @param aObject
+ * The object to define the lazy getter on.
+ * @param aName
+ * The name of the getter to define on aObject for the module.
+ * @param aResource
+ * The URL used to obtain the module.
+ * @param aSymbol
+ * The name of the symbol exported by the module.
+ * This parameter is optional and defaults to aName.
+ * @param aPreLambda
+ * A function that is executed when the proxy is set up.
+ * This will only ever be called once.
+ * @param aPostLambda
+ * A function that is executed when the module has been imported to
+ * run optional teardown procedures on the proxy object.
+ * This will only ever be called once.
+ * @param aProxy
+ * An object which acts on behalf of the module to be imported until
+ * the module has been imported.
+ */
+function defineLazyModuleGetter(aObject, aName, aResource, aSymbol,
+ aPreLambda, aPostLambda, aProxy)
+{
+ let proxy = aProxy || {};
+
+ if (typeof (aPreLambda) === "function") {
+ aPreLambda.apply(proxy);
+ }
+
+ defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
+ var temp = {};
+ try {
+ Cu.import(aResource, temp);
+
+ if (typeof (aPostLambda) === "function") {
+ aPostLambda.apply(proxy);
+ }
+ } catch (ex) {
+ Cu.reportError("Failed to load module " + aResource + ".");
+ throw ex;
+ }
+ return temp[aSymbol || aName];
+ });
+}
+
+/**
+ * Define a getter property on the given object that requires the given
+ * module. This enables delaying importing modules until the module is
+ * actually used.
+ *
+ * @param Object obj
+ * The object to define the property on.
+ * @param String property
+ * The property name.
+ * @param String module
+ * The module path.
+ * @param Boolean destructure
+ * Pass true if the property name is a member of the module's exports.
+ */
+function lazyRequireGetter(obj, property, module, destructure) {
+ Object.defineProperty(obj, property, {
+ get: () => {
+ // Redefine this accessor property as a data property.
+ // Delete it first, to rule out "too much recursion" in case obj is
+ // a proxy whose defineProperty handler might unwittingly trigger this
+ // getter again.
+ delete obj[property];
+ let value = destructure
+ ? require(module)[property]
+ : require(module || property);
+ Object.defineProperty(obj, property, {
+ value,
+ writable: true,
+ configurable: true,
+ enumerable: true
+ });
+ return value;
+ },
+ configurable: true,
+ enumerable: true
+ });
+}
+
+// List of pseudo modules exposed to all devtools modules.
+exports.modules = {
+ "Services": Object.create(Services),
+ "toolkit/loader": Loader,
+ promise,
+ PromiseDebugging,
+ ChromeUtils,
+ ThreadSafeChromeUtils,
+ HeapSnapshot,
+};
+
+defineLazyGetter(exports.modules, "Debugger", () => {
+ // addDebuggerToGlobal only allows adding the Debugger object to a global. The
+ // this object is not guaranteed to be a global (in particular on B2G, due to
+ // compartment sharing), so add the Debugger object to a sandbox instead.
+ let sandbox = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
+ Cu.evalInSandbox(
+ "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
+ "addDebuggerToGlobal(this);",
+ sandbox
+ );
+ return sandbox.Debugger;
+});
+
+defineLazyGetter(exports.modules, "Timer", () => {
+ let {setTimeout, clearTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
+ // Do not return Cu.import result, as SDK loader would freeze Timer.jsm globals...
+ return {
+ setTimeout,
+ clearTimeout
+ };
+});
+
+defineLazyGetter(exports.modules, "xpcInspector", () => {
+ return Cc["@mozilla.org/jsinspector;1"].getService(Ci.nsIJSInspector);
+});
+
+defineLazyGetter(exports.modules, "FileReader", () => {
+ let sandbox
+ = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
+ {wantGlobalProperties: ["FileReader"]});
+ return sandbox.FileReader;
+});
+
+// List of all custom globals exposed to devtools modules.
+// Changes here should be mirrored to devtools/.eslintrc.
+const globals = exports.globals = {
+ isWorker: false,
+ reportError: Cu.reportError,
+ atob: atob,
+ btoa: btoa,
+ URL,
+ loader: {
+ lazyGetter: defineLazyGetter,
+ lazyImporter: defineLazyModuleGetter,
+ lazyServiceGetter: defineLazyServiceGetter,
+ lazyRequireGetter: lazyRequireGetter,
+ id: null // Defined by Loader.jsm
+ },
+
+ // Let new XMLHttpRequest do the right thing.
+ XMLHttpRequest: function () {
+ return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ },
+
+ Node: Ci.nsIDOMNode,
+ Element: Ci.nsIDOMElement,
+ DocumentFragment: Ci.nsIDOMDocumentFragment,
+
+ // Make sure `define` function exists. This allows defining some modules
+ // in AMD format while retaining CommonJS compatibility through this hook.
+ // JSON Viewer needs modules in AMD format, as it currently uses RequireJS
+ // from a content document and can't access our usual loaders. So, any
+ // modules shared with the JSON Viewer should include a define wrapper:
+ //
+ // // Make this available to both AMD and CJS environments
+ // define(function(require, exports, module) {
+ // ... code ...
+ // });
+ //
+ // Bug 1248830 will work out a better plan here for our content module
+ // loading needs, especially as we head towards devtools.html.
+ define(factory) {
+ factory(this.require, this.exports, this.module);
+ },
+};
+
+// Lazily define a few things so that the corresponding jsms are only loaded
+// when used.
+defineLazyGetter(globals, "console", () => {
+ return Cu.import("resource://gre/modules/Console.jsm", {}).console;
+});
+defineLazyGetter(globals, "clearTimeout", () => {
+ return Cu.import("resource://gre/modules/Timer.jsm", {}).clearTimeout;
+});
+defineLazyGetter(globals, "setTimeout", () => {
+ return Cu.import("resource://gre/modules/Timer.jsm", {}).setTimeout;
+});
+defineLazyGetter(globals, "clearInterval", () => {
+ return Cu.import("resource://gre/modules/Timer.jsm", {}).clearInterval;
+});
+defineLazyGetter(globals, "setInterval", () => {
+ return Cu.import("resource://gre/modules/Timer.jsm", {}).setInterval;
+});
+defineLazyGetter(globals, "CSSRule", () => Ci.nsIDOMCSSRule);
+defineLazyGetter(globals, "DOMParser", () => {
+ return CC("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
+});
+defineLazyGetter(globals, "CSS", () => {
+ let sandbox
+ = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
+ {wantGlobalProperties: ["CSS"]});
+ return sandbox.CSS;
+});
+defineLazyGetter(globals, "WebSocket", () => {
+ return Services.appShell.hiddenDOMWindow.WebSocket;
+});
+lazyRequireGetter(globals, "indexedDB", "sdk/indexed-db", true);
diff --git a/devtools/shared/client/connection-manager.js b/devtools/shared/client/connection-manager.js
new file mode 100644
index 000000000..ef242db85
--- /dev/null
+++ b/devtools/shared/client/connection-manager.js
@@ -0,0 +1,382 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+const EventEmitter = require("devtools/shared/event-emitter");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/main");
+const Services = require("Services");
+const { Task } = require("devtools/shared/task");
+
+const REMOTE_TIMEOUT = "devtools.debugger.remote-timeout";
+
+/**
+ * Connection Manager.
+ *
+ * To use this module:
+ * const {ConnectionManager} = require("devtools/shared/client/connection-manager");
+ *
+ * # ConnectionManager
+ *
+ * Methods:
+ * . Connection createConnection(host, port)
+ * . void destroyConnection(connection)
+ * . Number getFreeTCPPort()
+ *
+ * Properties:
+ * . Array connections
+ *
+ * # Connection
+ *
+ * A connection is a wrapper around a debugger client. It has a simple
+ * API to instantiate a connection to a debugger server. Once disconnected,
+ * no need to re-create a Connection object. Calling `connect()` again
+ * will re-create a debugger client.
+ *
+ * Methods:
+ * . connect() Connect to host:port. Expect a "connecting" event.
+ * If no host is not specified, a local pipe is used
+ * . connect(transport) Connect via transport. Expect a "connecting" event.
+ * . disconnect() Disconnect if connected. Expect a "disconnecting" event
+ *
+ * Properties:
+ * . host IP address or hostname
+ * . port Port
+ * . logs Current logs. "newlog" event notifies new available logs
+ * . store Reference to a local data store (see below)
+ * . keepConnecting Should the connection keep trying to connect?
+ * . timeoutDelay When should we give up (in ms)?
+ * 0 means wait forever.
+ * . encryption Should the connection be encrypted?
+ * . authentication What authentication scheme should be used?
+ * . authenticator The |Authenticator| instance used. Overriding
+ * properties of this instance may be useful to
+ * customize authentication UX for a specific use case.
+ * . advertisement The server's advertisement if found by discovery
+ * . status Connection status:
+ * Connection.Status.CONNECTED
+ * Connection.Status.DISCONNECTED
+ * Connection.Status.CONNECTING
+ * Connection.Status.DISCONNECTING
+ * Connection.Status.DESTROYED
+ *
+ * Events (as in event-emitter.js):
+ * . Connection.Events.CONNECTING Trying to connect to host:port
+ * . Connection.Events.CONNECTED Connection is successful
+ * . Connection.Events.DISCONNECTING Trying to disconnect from server
+ * . Connection.Events.DISCONNECTED Disconnected (at client request, or because of a timeout or connection error)
+ * . Connection.Events.STATUS_CHANGED The connection status (connection.status) has changed
+ * . Connection.Events.TIMEOUT Connection timeout
+ * . Connection.Events.HOST_CHANGED Host has changed
+ * . Connection.Events.PORT_CHANGED Port has changed
+ * . Connection.Events.NEW_LOG A new log line is available
+ *
+ */
+
+var ConnectionManager = {
+ _connections: new Set(),
+ createConnection: function (host, port) {
+ let c = new Connection(host, port);
+ c.once("destroy", (event) => this.destroyConnection(c));
+ this._connections.add(c);
+ this.emit("new", c);
+ return c;
+ },
+ destroyConnection: function (connection) {
+ if (this._connections.has(connection)) {
+ this._connections.delete(connection);
+ if (connection.status != Connection.Status.DESTROYED) {
+ connection.destroy();
+ }
+ }
+ },
+ get connections() {
+ return [...this._connections];
+ },
+ getFreeTCPPort: function () {
+ let serv = Cc["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
+ serv.init(-1, true, -1);
+ let port = serv.port;
+ serv.close();
+ return port;
+ },
+};
+
+EventEmitter.decorate(ConnectionManager);
+
+var lastID = -1;
+
+function Connection(host, port) {
+ EventEmitter.decorate(this);
+ this.uid = ++lastID;
+ this.host = host;
+ this.port = port;
+ this._setStatus(Connection.Status.DISCONNECTED);
+ this._onDisconnected = this._onDisconnected.bind(this);
+ this._onConnected = this._onConnected.bind(this);
+ this._onTimeout = this._onTimeout.bind(this);
+ this.resetOptions();
+}
+
+Connection.Status = {
+ CONNECTED: "connected",
+ DISCONNECTED: "disconnected",
+ CONNECTING: "connecting",
+ DISCONNECTING: "disconnecting",
+ DESTROYED: "destroyed",
+};
+
+Connection.Events = {
+ CONNECTED: Connection.Status.CONNECTED,
+ DISCONNECTED: Connection.Status.DISCONNECTED,
+ CONNECTING: Connection.Status.CONNECTING,
+ DISCONNECTING: Connection.Status.DISCONNECTING,
+ DESTROYED: Connection.Status.DESTROYED,
+ TIMEOUT: "timeout",
+ STATUS_CHANGED: "status-changed",
+ HOST_CHANGED: "host-changed",
+ PORT_CHANGED: "port-changed",
+ NEW_LOG: "new_log"
+};
+
+Connection.prototype = {
+ logs: "",
+ log: function (str) {
+ let d = new Date();
+ let hours = ("0" + d.getHours()).slice(-2);
+ let minutes = ("0" + d.getMinutes()).slice(-2);
+ let seconds = ("0" + d.getSeconds()).slice(-2);
+ let timestamp = [hours, minutes, seconds].join(":") + ": ";
+ str = timestamp + str;
+ this.logs += "\n" + str;
+ this.emit(Connection.Events.NEW_LOG, str);
+ },
+
+ get client() {
+ return this._client;
+ },
+
+ get host() {
+ return this._host;
+ },
+
+ set host(value) {
+ if (this._host && this._host == value)
+ return;
+ this._host = value;
+ this.emit(Connection.Events.HOST_CHANGED);
+ },
+
+ get port() {
+ return this._port;
+ },
+
+ set port(value) {
+ if (this._port && this._port == value)
+ return;
+ this._port = value;
+ this.emit(Connection.Events.PORT_CHANGED);
+ },
+
+ get authentication() {
+ return this._authentication;
+ },
+
+ set authentication(value) {
+ this._authentication = value;
+ // Create an |Authenticator| of this type
+ if (!value) {
+ this.authenticator = null;
+ return;
+ }
+ let AuthenticatorType = DebuggerClient.Authenticators.get(value);
+ this.authenticator = new AuthenticatorType.Client();
+ },
+
+ get advertisement() {
+ return this._advertisement;
+ },
+
+ set advertisement(advertisement) {
+ // The full advertisement may contain more info than just the standard keys
+ // below, so keep a copy for use during connection later.
+ this._advertisement = advertisement;
+ if (advertisement) {
+ ["host", "port", "encryption", "authentication"].forEach(key => {
+ this[key] = advertisement[key];
+ });
+ }
+ },
+
+ /**
+ * Settings to be passed to |socketConnect| at connection time.
+ */
+ get socketSettings() {
+ let settings = {};
+ if (this.advertisement) {
+ // Use the advertisement as starting point if it exists, as it may contain
+ // extra data, like the server's cert.
+ Object.assign(settings, this.advertisement);
+ }
+ Object.assign(settings, {
+ host: this.host,
+ port: this.port,
+ encryption: this.encryption,
+ authenticator: this.authenticator
+ });
+ return settings;
+ },
+
+ timeoutDelay: Services.prefs.getIntPref(REMOTE_TIMEOUT),
+
+ resetOptions() {
+ this.keepConnecting = false;
+ this.timeoutDelay = Services.prefs.getIntPref(REMOTE_TIMEOUT);
+ this.encryption = false;
+ this.authentication = null;
+ this.advertisement = null;
+ },
+
+ disconnect: function (force) {
+ if (this.status == Connection.Status.DESTROYED) {
+ return;
+ }
+ clearTimeout(this._timeoutID);
+ if (this.status == Connection.Status.CONNECTED ||
+ this.status == Connection.Status.CONNECTING) {
+ this.log("disconnecting");
+ this._setStatus(Connection.Status.DISCONNECTING);
+ if (this._client) {
+ this._client.close();
+ }
+ }
+ },
+
+ connect: function (transport) {
+ if (this.status == Connection.Status.DESTROYED) {
+ return;
+ }
+ if (!this._client) {
+ this._customTransport = transport;
+ if (this._customTransport) {
+ this.log("connecting (custom transport)");
+ } else {
+ this.log("connecting to " + this.host + ":" + this.port);
+ }
+ this._setStatus(Connection.Status.CONNECTING);
+
+ if (this.timeoutDelay > 0) {
+ this._timeoutID = setTimeout(this._onTimeout, this.timeoutDelay);
+ }
+ this._clientConnect();
+ } else {
+ let msg = "Can't connect. Client is not fully disconnected";
+ this.log(msg);
+ throw new Error(msg);
+ }
+ },
+
+ destroy: function () {
+ this.log("killing connection");
+ clearTimeout(this._timeoutID);
+ this.keepConnecting = false;
+ if (this._client) {
+ this._client.close();
+ this._client = null;
+ }
+ this._setStatus(Connection.Status.DESTROYED);
+ },
+
+ _getTransport: Task.async(function* () {
+ if (this._customTransport) {
+ return this._customTransport;
+ }
+ if (!this.host) {
+ return DebuggerServer.connectPipe();
+ }
+ let settings = this.socketSettings;
+ let transport = yield DebuggerClient.socketConnect(settings);
+ return transport;
+ }),
+
+ _clientConnect: function () {
+ this._getTransport().then(transport => {
+ if (!transport) {
+ return;
+ }
+ this._client = new DebuggerClient(transport);
+ this._client.addOneTimeListener("closed", this._onDisconnected);
+ this._client.connect().then(this._onConnected);
+ }, e => {
+ // If we're continuously trying to connect, we expect the connection to be
+ // rejected a couple times, so don't log these.
+ if (!this.keepConnecting || e.result !== Cr.NS_ERROR_CONNECTION_REFUSED) {
+ console.error(e);
+ }
+ // In some cases, especially on Mac, the openOutputStream call in
+ // DebuggerClient.socketConnect may throw NS_ERROR_NOT_INITIALIZED.
+ // It occurs when we connect agressively to the simulator,
+ // and keep trying to open a socket to the server being started in
+ // the simulator.
+ this._onDisconnected();
+ });
+ },
+
+ get status() {
+ return this._status;
+ },
+
+ _setStatus: function (value) {
+ if (this._status && this._status == value)
+ return;
+ this._status = value;
+ this.emit(value);
+ this.emit(Connection.Events.STATUS_CHANGED, value);
+ },
+
+ _onDisconnected: function () {
+ this._client = null;
+ this._customTransport = null;
+
+ if (this._status == Connection.Status.CONNECTING && this.keepConnecting) {
+ setTimeout(() => this._clientConnect(), 100);
+ return;
+ }
+
+ clearTimeout(this._timeoutID);
+
+ switch (this.status) {
+ case Connection.Status.CONNECTED:
+ this.log("disconnected (unexpected)");
+ break;
+ case Connection.Status.CONNECTING:
+ this.log("connection error. Possible causes: USB port not connected, port not forwarded (adb forward), wrong host or port, remote debugging not enabled on the device.");
+ break;
+ default:
+ this.log("disconnected");
+ }
+ this._setStatus(Connection.Status.DISCONNECTED);
+ },
+
+ _onConnected: function () {
+ this.log("connected");
+ clearTimeout(this._timeoutID);
+ this._setStatus(Connection.Status.CONNECTED);
+ },
+
+ _onTimeout: function () {
+ this.log("connection timeout. Possible causes: didn't click on 'accept' (prompt).");
+ this.emit(Connection.Events.TIMEOUT);
+ this.disconnect();
+ },
+};
+
+exports.ConnectionManager = ConnectionManager;
+exports.Connection = Connection;
diff --git a/devtools/shared/client/main.js b/devtools/shared/client/main.js
new file mode 100644
index 000000000..0db8e16c2
--- /dev/null
+++ b/devtools/shared/client/main.js
@@ -0,0 +1,3123 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Ci, Cu } = require("chrome");
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { getStack, callFunctionWithAsyncStack } = require("devtools/shared/platform/stack");
+
+const promise = Cu.import("resource://devtools/shared/deprecated-sync-thenables.js", {}).Promise;
+
+loader.lazyRequireGetter(this, "events", "sdk/event/core");
+loader.lazyRequireGetter(this, "WebConsoleClient", "devtools/shared/webconsole/client", true);
+loader.lazyRequireGetter(this, "DebuggerSocket", "devtools/shared/security/socket", true);
+loader.lazyRequireGetter(this, "Authentication", "devtools/shared/security/auth");
+
+const noop = () => {};
+
+/**
+ * TODO: Get rid of this API in favor of EventTarget (bug 1042642)
+ *
+ * Add simple event notification to a prototype object. Any object that has
+ * some use for event notifications or the observer pattern in general can be
+ * augmented with the necessary facilities by passing its prototype to this
+ * function.
+ *
+ * @param aProto object
+ * The prototype object that will be modified.
+ */
+function eventSource(aProto) {
+ /**
+ * Add a listener to the event source for a given event.
+ *
+ * @param aName string
+ * The event to listen for.
+ * @param aListener function
+ * Called when the event is fired. If the same listener
+ * is added more than once, it will be called once per
+ * addListener call.
+ */
+ aProto.addListener = function (aName, aListener) {
+ if (typeof aListener != "function") {
+ throw TypeError("Listeners must be functions.");
+ }
+
+ if (!this._listeners) {
+ this._listeners = {};
+ }
+
+ this._getListeners(aName).push(aListener);
+ };
+
+ /**
+ * Add a listener to the event source for a given event. The
+ * listener will be removed after it is called for the first time.
+ *
+ * @param aName string
+ * The event to listen for.
+ * @param aListener function
+ * Called when the event is fired.
+ */
+ aProto.addOneTimeListener = function (aName, aListener) {
+ let l = (...args) => {
+ this.removeListener(aName, l);
+ aListener.apply(null, args);
+ };
+ this.addListener(aName, l);
+ };
+
+ /**
+ * Remove a listener from the event source previously added with
+ * addListener().
+ *
+ * @param aName string
+ * The event name used during addListener to add the listener.
+ * @param aListener function
+ * The callback to remove. If addListener was called multiple
+ * times, all instances will be removed.
+ */
+ aProto.removeListener = function (aName, aListener) {
+ if (!this._listeners || (aListener && !this._listeners[aName])) {
+ return;
+ }
+
+ if (!aListener) {
+ this._listeners[aName] = [];
+ }
+ else {
+ this._listeners[aName] =
+ this._listeners[aName].filter(function (l) { return l != aListener; });
+ }
+ };
+
+ /**
+ * Returns the listeners for the specified event name. If none are defined it
+ * initializes an empty list and returns that.
+ *
+ * @param aName string
+ * The event name.
+ */
+ aProto._getListeners = function (aName) {
+ if (aName in this._listeners) {
+ return this._listeners[aName];
+ }
+ this._listeners[aName] = [];
+ return this._listeners[aName];
+ };
+
+ /**
+ * Notify listeners of an event.
+ *
+ * @param aName string
+ * The event to fire.
+ * @param arguments
+ * All arguments will be passed along to the listeners,
+ * including the name argument.
+ */
+ aProto.emit = function () {
+ if (!this._listeners) {
+ return;
+ }
+
+ let name = arguments[0];
+ let listeners = this._getListeners(name).slice(0);
+
+ for (let listener of listeners) {
+ try {
+ listener.apply(null, arguments);
+ } catch (e) {
+ // Prevent a bad listener from interfering with the others.
+ DevToolsUtils.reportException("notify event '" + name + "'", e);
+ }
+ }
+ };
+}
+
+/**
+ * Set of protocol messages that affect thread state, and the
+ * state the actor is in after each message.
+ */
+const ThreadStateTypes = {
+ "paused": "paused",
+ "resumed": "attached",
+ "detached": "detached",
+ "running": "attached"
+};
+
+/**
+ * Set of protocol messages that are sent by the server without a prior request
+ * by the client.
+ */
+const UnsolicitedNotifications = {
+ "consoleAPICall": "consoleAPICall",
+ "eventNotification": "eventNotification",
+ "fileActivity": "fileActivity",
+ "lastPrivateContextExited": "lastPrivateContextExited",
+ "logMessage": "logMessage",
+ "networkEvent": "networkEvent",
+ "networkEventUpdate": "networkEventUpdate",
+ "newGlobal": "newGlobal",
+ "newScript": "newScript",
+ "tabDetached": "tabDetached",
+ "tabListChanged": "tabListChanged",
+ "reflowActivity": "reflowActivity",
+ "addonListChanged": "addonListChanged",
+ "workerListChanged": "workerListChanged",
+ "serviceWorkerRegistrationListChanged": "serviceWorkerRegistrationList",
+ "tabNavigated": "tabNavigated",
+ "frameUpdate": "frameUpdate",
+ "pageError": "pageError",
+ "documentLoad": "documentLoad",
+ "enteredFrame": "enteredFrame",
+ "exitedFrame": "exitedFrame",
+ "appOpen": "appOpen",
+ "appClose": "appClose",
+ "appInstall": "appInstall",
+ "appUninstall": "appUninstall",
+ "evaluationResult": "evaluationResult",
+ "newSource": "newSource",
+ "updatedSource": "updatedSource",
+};
+
+/**
+ * Set of pause types that are sent by the server and not as an immediate
+ * response to a client request.
+ */
+const UnsolicitedPauses = {
+ "resumeLimit": "resumeLimit",
+ "debuggerStatement": "debuggerStatement",
+ "breakpoint": "breakpoint",
+ "DOMEvent": "DOMEvent",
+ "watchpoint": "watchpoint",
+ "exception": "exception"
+};
+
+/**
+ * Creates a client for the remote debugging protocol server. This client
+ * provides the means to communicate with the server and exchange the messages
+ * required by the protocol in a traditional JavaScript API.
+ */
+const DebuggerClient = exports.DebuggerClient = function (aTransport)
+{
+ this._transport = aTransport;
+ this._transport.hooks = this;
+
+ // Map actor ID to client instance for each actor type.
+ this._clients = new Map();
+
+ this._pendingRequests = new Map();
+ this._activeRequests = new Map();
+ this._eventsEnabled = true;
+
+ this.traits = {};
+
+ this.request = this.request.bind(this);
+ this.localTransport = this._transport.onOutputStreamReady === undefined;
+
+ /*
+ * As the first thing on the connection, expect a greeting packet from
+ * the connection's root actor.
+ */
+ this.mainRoot = null;
+ this.expectReply("root", (aPacket) => {
+ this.mainRoot = new RootClient(this, aPacket);
+ this.emit("connected", aPacket.applicationType, aPacket.traits);
+ });
+};
+
+/**
+ * A declarative helper for defining methods that send requests to the server.
+ *
+ * @param aPacketSkeleton
+ * The form of the packet to send. Can specify fields to be filled from
+ * the parameters by using the |args| function.
+ * @param before
+ * The function to call before sending the packet. Is passed the packet,
+ * and the return value is used as the new packet. The |this| context is
+ * the instance of the client object we are defining a method for.
+ * @param after
+ * The function to call after the response is received. It is passed the
+ * response, and the return value is considered the new response that
+ * will be passed to the callback. The |this| context is the instance of
+ * the client object we are defining a method for.
+ * @return Request
+ * The `Request` object that is a Promise object and resolves once
+ * we receive the response. (See request method for more details)
+ */
+DebuggerClient.requester = function (aPacketSkeleton, config = {}) {
+ let { before, after } = config;
+ return DevToolsUtils.makeInfallible(function (...args) {
+ let outgoingPacket = {
+ to: aPacketSkeleton.to || this.actor
+ };
+
+ let maxPosition = -1;
+ for (let k of Object.keys(aPacketSkeleton)) {
+ if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
+ let { position } = aPacketSkeleton[k];
+ outgoingPacket[k] = aPacketSkeleton[k].getArgument(args);
+ maxPosition = Math.max(position, maxPosition);
+ } else {
+ outgoingPacket[k] = aPacketSkeleton[k];
+ }
+ }
+
+ if (before) {
+ outgoingPacket = before.call(this, outgoingPacket);
+ }
+
+ return this.request(outgoingPacket, DevToolsUtils.makeInfallible((aResponse) => {
+ if (after) {
+ let { from } = aResponse;
+ aResponse = after.call(this, aResponse);
+ if (!aResponse.from) {
+ aResponse.from = from;
+ }
+ }
+
+ // The callback is always the last parameter.
+ let thisCallback = args[maxPosition + 1];
+ if (thisCallback) {
+ thisCallback(aResponse);
+ }
+ }, "DebuggerClient.requester request callback"));
+ }, "DebuggerClient.requester");
+};
+
+function args(aPos) {
+ return new DebuggerClient.Argument(aPos);
+}
+
+DebuggerClient.Argument = function (aPosition) {
+ this.position = aPosition;
+};
+
+DebuggerClient.Argument.prototype.getArgument = function (aParams) {
+ if (!(this.position in aParams)) {
+ throw new Error("Bad index into params: " + this.position);
+ }
+ return aParams[this.position];
+};
+
+// Expose these to save callers the trouble of importing DebuggerSocket
+DebuggerClient.socketConnect = function (options) {
+ // Defined here instead of just copying the function to allow lazy-load
+ return DebuggerSocket.connect(options);
+};
+DevToolsUtils.defineLazyGetter(DebuggerClient, "Authenticators", () => {
+ return Authentication.Authenticators;
+});
+DevToolsUtils.defineLazyGetter(DebuggerClient, "AuthenticationResult", () => {
+ return Authentication.AuthenticationResult;
+});
+
+DebuggerClient.prototype = {
+ /**
+ * Connect to the server and start exchanging protocol messages.
+ *
+ * @param aOnConnected function
+ * If specified, will be called when the greeting packet is
+ * received from the debugging server.
+ *
+ * @return Promise
+ * Resolves once connected with an array whose first element
+ * is the application type, by default "browser", and the second
+ * element is the traits object (help figure out the features
+ * and behaviors of the server we connect to. See RootActor).
+ */
+ connect: function (aOnConnected) {
+ let deferred = promise.defer();
+ this.emit("connect");
+
+ // Also emit the event on the |DebuggerClient| object (not on the instance),
+ // so it's possible to track all instances.
+ events.emit(DebuggerClient, "connect", this);
+
+ this.addOneTimeListener("connected", (aName, aApplicationType, aTraits) => {
+ this.traits = aTraits;
+ if (aOnConnected) {
+ aOnConnected(aApplicationType, aTraits);
+ }
+ deferred.resolve([aApplicationType, aTraits]);
+ });
+
+ this._transport.ready();
+ return deferred.promise;
+ },
+
+ /**
+ * Shut down communication with the debugging server.
+ *
+ * @param aOnClosed function
+ * If specified, will be called when the debugging connection
+ * has been closed. This parameter is deprecated - please use
+ * the returned Promise.
+ * @return Promise
+ * Resolves after the underlying transport is closed.
+ */
+ close: function (aOnClosed) {
+ let deferred = promise.defer();
+ if (aOnClosed) {
+ deferred.promise.then(aOnClosed);
+ }
+
+ // Disable detach event notifications, because event handlers will be in a
+ // cleared scope by the time they run.
+ this._eventsEnabled = false;
+
+ let cleanup = () => {
+ this._transport.close();
+ this._transport = null;
+ };
+
+ // If the connection is already closed,
+ // there is no need to detach client
+ // as we won't be able to send any message.
+ if (this._closed) {
+ cleanup();
+ deferred.resolve();
+ return deferred.promise;
+ }
+
+ this.addOneTimeListener("closed", deferred.resolve);
+
+ // Call each client's `detach` method by calling
+ // lastly registered ones first to give a chance
+ // to detach child clients first.
+ let clients = [...this._clients.values()];
+ this._clients.clear();
+ const detachClients = () => {
+ let client = clients.pop();
+ if (!client) {
+ // All clients detached.
+ cleanup();
+ return;
+ }
+ if (client.detach) {
+ client.detach(detachClients);
+ return;
+ }
+ detachClients();
+ };
+ detachClients();
+
+ return deferred.promise;
+ },
+
+ /*
+ * This function exists only to preserve DebuggerClient's interface;
+ * new code should say 'client.mainRoot.listTabs()'.
+ */
+ listTabs: function (aOnResponse) { return this.mainRoot.listTabs(aOnResponse); },
+
+ /*
+ * This function exists only to preserve DebuggerClient's interface;
+ * new code should say 'client.mainRoot.listAddons()'.
+ */
+ listAddons: function (aOnResponse) { return this.mainRoot.listAddons(aOnResponse); },
+
+ getTab: function (aFilter) { return this.mainRoot.getTab(aFilter); },
+
+ /**
+ * Attach to a tab actor.
+ *
+ * @param string aTabActor
+ * The actor ID for the tab to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a TabClient
+ * (which will be undefined on error).
+ */
+ attachTab: function (aTabActor, aOnResponse = noop) {
+ if (this._clients.has(aTabActor)) {
+ let cachedTab = this._clients.get(aTabActor);
+ let cachedResponse = {
+ cacheDisabled: cachedTab.cacheDisabled,
+ javascriptEnabled: cachedTab.javascriptEnabled,
+ traits: cachedTab.traits,
+ };
+ DevToolsUtils.executeSoon(() => aOnResponse(cachedResponse, cachedTab));
+ return promise.resolve([cachedResponse, cachedTab]);
+ }
+
+ let packet = {
+ to: aTabActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ let tabClient;
+ if (!aResponse.error) {
+ tabClient = new TabClient(this, aResponse);
+ this.registerClient(tabClient);
+ }
+ aOnResponse(aResponse, tabClient);
+ return [aResponse, tabClient];
+ });
+ },
+
+ attachWorker: function DC_attachWorker(aWorkerActor, aOnResponse = noop) {
+ let workerClient = this._clients.get(aWorkerActor);
+ if (workerClient !== undefined) {
+ let response = {
+ from: workerClient.actor,
+ type: "attached",
+ url: workerClient.url
+ };
+ DevToolsUtils.executeSoon(() => aOnResponse(response, workerClient));
+ return promise.resolve([response, workerClient]);
+ }
+
+ return this.request({ to: aWorkerActor, type: "attach" }).then(aResponse => {
+ if (aResponse.error) {
+ aOnResponse(aResponse, null);
+ return [aResponse, null];
+ }
+
+ let workerClient = new WorkerClient(this, aResponse);
+ this.registerClient(workerClient);
+ aOnResponse(aResponse, workerClient);
+ return [aResponse, workerClient];
+ });
+ },
+
+ /**
+ * Attach to an addon actor.
+ *
+ * @param string aAddonActor
+ * The actor ID for the addon to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a AddonClient
+ * (which will be undefined on error).
+ */
+ attachAddon: function DC_attachAddon(aAddonActor, aOnResponse = noop) {
+ let packet = {
+ to: aAddonActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ let addonClient;
+ if (!aResponse.error) {
+ addonClient = new AddonClient(this, aAddonActor);
+ this.registerClient(addonClient);
+ this.activeAddon = addonClient;
+ }
+ aOnResponse(aResponse, addonClient);
+ return [aResponse, addonClient];
+ });
+ },
+
+ /**
+ * Attach to a Web Console actor.
+ *
+ * @param string aConsoleActor
+ * The ID for the console actor to attach to.
+ * @param array aListeners
+ * The console listeners you want to start.
+ * @param function aOnResponse
+ * Called with the response packet and a WebConsoleClient
+ * instance (which will be undefined on error).
+ */
+ attachConsole:
+ function (aConsoleActor, aListeners, aOnResponse = noop) {
+ let packet = {
+ to: aConsoleActor,
+ type: "startListeners",
+ listeners: aListeners,
+ };
+
+ return this.request(packet).then(aResponse => {
+ let consoleClient;
+ if (!aResponse.error) {
+ if (this._clients.has(aConsoleActor)) {
+ consoleClient = this._clients.get(aConsoleActor);
+ } else {
+ consoleClient = new WebConsoleClient(this, aResponse);
+ this.registerClient(consoleClient);
+ }
+ }
+ aOnResponse(aResponse, consoleClient);
+ return [aResponse, consoleClient];
+ });
+ },
+
+ /**
+ * Attach to a global-scoped thread actor for chrome debugging.
+ *
+ * @param string aThreadActor
+ * The actor ID for the thread to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a ThreadClient
+ * (which will be undefined on error).
+ * @param object aOptions
+ * Configuration options.
+ * - useSourceMaps: whether to use source maps or not.
+ */
+ attachThread: function (aThreadActor, aOnResponse = noop, aOptions = {}) {
+ if (this._clients.has(aThreadActor)) {
+ let client = this._clients.get(aThreadActor);
+ DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+ return promise.resolve([{}, client]);
+ }
+
+ let packet = {
+ to: aThreadActor,
+ type: "attach",
+ options: aOptions
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ var threadClient = new ThreadClient(this, aThreadActor);
+ this.registerClient(threadClient);
+ }
+ aOnResponse(aResponse, threadClient);
+ return [aResponse, threadClient];
+ });
+ },
+
+ /**
+ * Attach to a trace actor.
+ *
+ * @param string aTraceActor
+ * The actor ID for the tracer to attach.
+ * @param function aOnResponse
+ * Called with the response packet and a TraceClient
+ * (which will be undefined on error).
+ */
+ attachTracer: function (aTraceActor, aOnResponse = noop) {
+ if (this._clients.has(aTraceActor)) {
+ let client = this._clients.get(aTraceActor);
+ DevToolsUtils.executeSoon(() => aOnResponse({}, client));
+ return promise.resolve([{}, client]);
+ }
+
+ let packet = {
+ to: aTraceActor,
+ type: "attach"
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ var traceClient = new TraceClient(this, aTraceActor);
+ this.registerClient(traceClient);
+ }
+ aOnResponse(aResponse, traceClient);
+ return [aResponse, traceClient];
+ });
+ },
+
+ /**
+ * Fetch the ChromeActor for the main process or ChildProcessActor for a
+ * a given child process ID.
+ *
+ * @param number aId
+ * The ID for the process to attach (returned by `listProcesses`).
+ * Connected to the main process if omitted, or is 0.
+ */
+ getProcess: function (aId) {
+ let packet = {
+ to: "root",
+ type: "getProcess"
+ };
+ if (typeof (aId) == "number") {
+ packet.id = aId;
+ }
+ return this.request(packet);
+ },
+
+ /**
+ * Release an object actor.
+ *
+ * @param string aActor
+ * The actor ID to send the request to.
+ * @param aOnResponse function
+ * If specified, will be called with the response packet when
+ * debugging server responds.
+ */
+ release: DebuggerClient.requester({
+ to: args(0),
+ type: "release"
+ }),
+
+ /**
+ * Send a request to the debugging server.
+ *
+ * @param aRequest object
+ * A JSON packet to send to the debugging server.
+ * @param aOnResponse function
+ * If specified, will be called with the JSON response packet when
+ * debugging server responds.
+ * @return Request
+ * This object emits a number of events to allow you to respond to
+ * different parts of the request lifecycle.
+ * It is also a Promise object, with a `then` method, that is resolved
+ * whenever a JSON or a Bulk response is received; and is rejected
+ * if the response is an error.
+ * Note: This return value can be ignored if you are using JSON alone,
+ * because the callback provided in |aOnResponse| will be bound to the
+ * "json-reply" event automatically.
+ *
+ * Events emitted:
+ * * json-reply: The server replied with a JSON packet, which is
+ * passed as event data.
+ * * bulk-reply: The server replied with bulk data, which you can read
+ * using the event data object containing:
+ * * actor: Name of actor that received the packet
+ * * type: Name of actor's method that was called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you
+ * can ensure that you will read exactly |length| bytes
+ * and will not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as a
+ * rejection value, it will be logged via |dumpn|. If you
+ * do use |copyTo|, resolving is taken care of for you
+ * when copying completes.
+ * * copyTo: A helper function for getting your data out of the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ request: function (aRequest, aOnResponse) {
+ if (!this.mainRoot) {
+ throw Error("Have not yet received a hello packet from the server.");
+ }
+ let type = aRequest.type || "";
+ if (!aRequest.to) {
+ throw Error("'" + type + "' request packet has no destination.");
+ }
+ if (this._closed) {
+ let msg = "'" + type + "' request packet to " +
+ "'" + aRequest.to + "' " +
+ "can't be sent as the connection is closed.";
+ let resp = { error: "connectionClosed", message: msg };
+ if (aOnResponse) {
+ aOnResponse(resp);
+ }
+ return promise.reject(resp);
+ }
+
+ let request = new Request(aRequest);
+ request.format = "json";
+ request.stack = getStack();
+ if (aOnResponse) {
+ request.on("json-reply", aOnResponse);
+ }
+
+ this._sendOrQueueRequest(request);
+
+ // Implement a Promise like API on the returned object
+ // that resolves/rejects on request response
+ let deferred = promise.defer();
+ function listenerJson(resp) {
+ request.off("json-reply", listenerJson);
+ request.off("bulk-reply", listenerBulk);
+ if (resp.error) {
+ deferred.reject(resp);
+ } else {
+ deferred.resolve(resp);
+ }
+ }
+ function listenerBulk(resp) {
+ request.off("json-reply", listenerJson);
+ request.off("bulk-reply", listenerBulk);
+ deferred.resolve(resp);
+ }
+ request.on("json-reply", listenerJson);
+ request.on("bulk-reply", listenerBulk);
+ request.then = deferred.promise.then.bind(deferred.promise);
+
+ return request;
+ },
+
+ /**
+ * Transmit streaming data via a bulk request.
+ *
+ * This method initiates the bulk send process by queuing up the header data.
+ * The caller receives eventual access to a stream for writing.
+ *
+ * Since this opens up more options for how the server might respond (it could
+ * send back either JSON or bulk data), and the returned Request object emits
+ * events for different stages of the request process that you may want to
+ * react to.
+ *
+ * @param request Object
+ * This is modeled after the format of JSON packets above, but does not
+ * actually contain the data, but is instead just a routing header:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be sent
+ * @return Request
+ * This object emits a number of events to allow you to respond to
+ * different parts of the request lifecycle.
+ *
+ * Events emitted:
+ * * bulk-send-ready: Ready to send bulk data to the server, using the
+ * event data object containing:
+ * * stream: This output stream should only be used directly if
+ * you can ensure that you will write exactly |length|
+ * bytes and will not close the stream when writing is
+ * complete
+ * * done: If you use the stream directly (instead of |copyFrom|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as
+ * a rejection value, it will be logged via |dumpn|. If
+ * you do use |copyFrom|, resolving is taken care of for
+ * you when copying completes.
+ * * copyFrom: A helper function for getting your data onto the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ * * json-reply: The server replied with a JSON packet, which is
+ * passed as event data.
+ * * bulk-reply: The server replied with bulk data, which you can read
+ * using the event data object containing:
+ * * actor: Name of actor that received the packet
+ * * type: Name of actor's method that was called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you
+ * can ensure that you will read exactly |length| bytes
+ * and will not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as a
+ * rejection value, it will be logged via |dumpn|. If you
+ * do use |copyTo|, resolving is taken care of for you
+ * when copying completes.
+ * * copyTo: A helper function for getting your data out of the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ startBulkRequest: function (request) {
+ if (!this.traits.bulk) {
+ throw Error("Server doesn't support bulk transfers");
+ }
+ if (!this.mainRoot) {
+ throw Error("Have not yet received a hello packet from the server.");
+ }
+ if (!request.type) {
+ throw Error("Bulk packet is missing the required 'type' field.");
+ }
+ if (!request.actor) {
+ throw Error("'" + request.type + "' bulk packet has no destination.");
+ }
+ if (!request.length) {
+ throw Error("'" + request.type + "' bulk packet has no length.");
+ }
+
+ request = new Request(request);
+ request.format = "bulk";
+
+ this._sendOrQueueRequest(request);
+
+ return request;
+ },
+
+ /**
+ * If a new request can be sent immediately, do so. Otherwise, queue it.
+ */
+ _sendOrQueueRequest(request) {
+ let actor = request.actor;
+ if (!this._activeRequests.has(actor)) {
+ this._sendRequest(request);
+ } else {
+ this._queueRequest(request);
+ }
+ },
+
+ /**
+ * Send a request.
+ * @throws Error if there is already an active request in flight for the same
+ * actor.
+ */
+ _sendRequest(request) {
+ let actor = request.actor;
+ this.expectReply(actor, request);
+
+ if (request.format === "json") {
+ this._transport.send(request.request);
+ return false;
+ }
+
+ this._transport.startBulkSend(request.request).then((...args) => {
+ request.emit("bulk-send-ready", ...args);
+ });
+ },
+
+ /**
+ * Queue a request to be sent later. Queues are only drained when an in
+ * flight request to a given actor completes.
+ */
+ _queueRequest(request) {
+ let actor = request.actor;
+ let queue = this._pendingRequests.get(actor) || [];
+ queue.push(request);
+ this._pendingRequests.set(actor, queue);
+ },
+
+ /**
+ * Attempt the next request to a given actor (if any).
+ */
+ _attemptNextRequest(actor) {
+ if (this._activeRequests.has(actor)) {
+ return;
+ }
+ let queue = this._pendingRequests.get(actor);
+ if (!queue) {
+ return;
+ }
+ let request = queue.shift();
+ if (queue.length === 0) {
+ this._pendingRequests.delete(actor);
+ }
+ this._sendRequest(request);
+ },
+
+ /**
+ * Arrange to hand the next reply from |aActor| to the handler bound to
+ * |aRequest|.
+ *
+ * DebuggerClient.prototype.request / startBulkRequest usually takes care of
+ * establishing the handler for a given request, but in rare cases (well,
+ * greetings from new root actors, is the only case at the moment) we must be
+ * prepared for a "reply" that doesn't correspond to any request we sent.
+ */
+ expectReply: function (aActor, aRequest) {
+ if (this._activeRequests.has(aActor)) {
+ throw Error("clashing handlers for next reply from " + uneval(aActor));
+ }
+
+ // If a handler is passed directly (as it is with the handler for the root
+ // actor greeting), create a dummy request to bind this to.
+ if (typeof aRequest === "function") {
+ let handler = aRequest;
+ aRequest = new Request();
+ aRequest.on("json-reply", handler);
+ }
+
+ this._activeRequests.set(aActor, aRequest);
+ },
+
+ // Transport hooks.
+
+ /**
+ * Called by DebuggerTransport to dispatch incoming packets as appropriate.
+ *
+ * @param aPacket object
+ * The incoming packet.
+ */
+ onPacket: function (aPacket) {
+ if (!aPacket.from) {
+ DevToolsUtils.reportException(
+ "onPacket",
+ new Error("Server did not specify an actor, dropping packet: " +
+ JSON.stringify(aPacket)));
+ return;
+ }
+
+ // If we have a registered Front for this actor, let it handle the packet
+ // and skip all the rest of this unpleasantness.
+ let front = this.getActor(aPacket.from);
+ if (front) {
+ front.onPacket(aPacket);
+ return;
+ }
+
+ // Check for "forwardingCancelled" here instead of using a client to handle it.
+ // This is necessary because we might receive this event while the client is closing,
+ // and the clients have already been removed by that point.
+ if (this.mainRoot &&
+ aPacket.from == this.mainRoot.actor &&
+ aPacket.type == "forwardingCancelled") {
+ this.purgeRequests(aPacket.prefix);
+ return;
+ }
+
+ if (this._clients.has(aPacket.from) && aPacket.type) {
+ let client = this._clients.get(aPacket.from);
+ let type = aPacket.type;
+ if (client.events.indexOf(type) != -1) {
+ client.emit(type, aPacket);
+ // we ignore the rest, as the client is expected to handle this packet.
+ return;
+ }
+ }
+
+ let activeRequest;
+ // See if we have a handler function waiting for a reply from this
+ // actor. (Don't count unsolicited notifications or pauses as
+ // replies.)
+ if (this._activeRequests.has(aPacket.from) &&
+ !(aPacket.type in UnsolicitedNotifications) &&
+ !(aPacket.type == ThreadStateTypes.paused &&
+ aPacket.why.type in UnsolicitedPauses)) {
+ activeRequest = this._activeRequests.get(aPacket.from);
+ this._activeRequests.delete(aPacket.from);
+ }
+
+ // If there is a subsequent request for the same actor, hand it off to the
+ // transport. Delivery of packets on the other end is always async, even
+ // in the local transport case.
+ this._attemptNextRequest(aPacket.from);
+
+ // Packets that indicate thread state changes get special treatment.
+ if (aPacket.type in ThreadStateTypes &&
+ this._clients.has(aPacket.from) &&
+ typeof this._clients.get(aPacket.from)._onThreadState == "function") {
+ this._clients.get(aPacket.from)._onThreadState(aPacket);
+ }
+
+ // TODO: Bug 1151156 - Remove once Gecko 40 is on b2g-stable.
+ if (!this.traits.noNeedToFakeResumptionOnNavigation) {
+ // On navigation the server resumes, so the client must resume as well.
+ // We achieve that by generating a fake resumption packet that triggers
+ // the client's thread state change listeners.
+ if (aPacket.type == UnsolicitedNotifications.tabNavigated &&
+ this._clients.has(aPacket.from) &&
+ this._clients.get(aPacket.from).thread) {
+ let thread = this._clients.get(aPacket.from).thread;
+ let resumption = { from: thread._actor, type: "resumed" };
+ thread._onThreadState(resumption);
+ }
+ }
+
+ // Only try to notify listeners on events, not responses to requests
+ // that lack a packet type.
+ if (aPacket.type) {
+ this.emit(aPacket.type, aPacket);
+ }
+
+ if (activeRequest) {
+ let emitReply = () => activeRequest.emit("json-reply", aPacket);
+ if (activeRequest.stack) {
+ callFunctionWithAsyncStack(emitReply, activeRequest.stack,
+ "DevTools RDP");
+ } else {
+ emitReply();
+ }
+ }
+ },
+
+ /**
+ * Called by the DebuggerTransport to dispatch incoming bulk packets as
+ * appropriate.
+ *
+ * @param packet object
+ * The incoming packet, which contains:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you can
+ * ensure that you will read exactly |length| bytes and will
+ * not close the stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the transport
+ * will be closed. If an Error is supplied as a rejection
+ * value, it will be logged via |dumpn|. If you do use
+ * |copyTo|, resolving is taken care of for you when copying
+ * completes.
+ * * copyTo: A helper function for getting your data out of the stream
+ * that meets the stream handling requirements above, and has
+ * the following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected
+ * if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ onBulkPacket: function (packet) {
+ let { actor, type, length } = packet;
+
+ if (!actor) {
+ DevToolsUtils.reportException(
+ "onBulkPacket",
+ new Error("Server did not specify an actor, dropping bulk packet: " +
+ JSON.stringify(packet)));
+ return;
+ }
+
+ // See if we have a handler function waiting for a reply from this
+ // actor.
+ if (!this._activeRequests.has(actor)) {
+ return;
+ }
+
+ let activeRequest = this._activeRequests.get(actor);
+ this._activeRequests.delete(actor);
+
+ // If there is a subsequent request for the same actor, hand it off to the
+ // transport. Delivery of packets on the other end is always async, even
+ // in the local transport case.
+ this._attemptNextRequest(actor);
+
+ activeRequest.emit("bulk-reply", packet);
+ },
+
+ /**
+ * Called by DebuggerTransport when the underlying stream is closed.
+ *
+ * @param aStatus nsresult
+ * The status code that corresponds to the reason for closing
+ * the stream.
+ */
+ onClosed: function () {
+ this._closed = true;
+ this.emit("closed");
+
+ this.purgeRequests();
+
+ // The |_pools| array on the client-side currently is used only by
+ // protocol.js to store active fronts, mirroring the actor pools found in
+ // the server. So, read all usages of "pool" as "protocol.js front".
+ //
+ // In the normal case where we shutdown cleanly, the toolbox tells each tool
+ // to close, and they each call |destroy| on any fronts they were using.
+ // When |destroy| or |cleanup| is called on a protocol.js front, it also
+ // removes itself from the |_pools| array. Once the toolbox has shutdown,
+ // the connection is closed, and we reach here. All fronts (should have
+ // been) |destroy|ed, so |_pools| should empty.
+ //
+ // If the connection instead aborts unexpectedly, we may end up here with
+ // all fronts used during the life of the connection. So, we call |cleanup|
+ // on them clear their state, reject pending requests, and remove themselves
+ // from |_pools|. This saves the toolbox from hanging indefinitely, in case
+ // it waits for some server response before shutdown that will now never
+ // arrive.
+ for (let pool of this._pools) {
+ pool.cleanup();
+ }
+ },
+
+ /**
+ * Purge pending and active requests in this client.
+ *
+ * @param prefix string (optional)
+ * If a prefix is given, only requests for actor IDs that start with the prefix
+ * will be cleaned up. This is useful when forwarding of a portion of requests
+ * is cancelled on the server.
+ */
+ purgeRequests(prefix = "") {
+ let reject = function (type, request) {
+ // Server can send packets on its own and client only pass a callback
+ // to expectReply, so that there is no request object.
+ let msg;
+ if (request.request) {
+ msg = "'" + request.request.type + "' " + type + " request packet" +
+ " to '" + request.actor + "' " +
+ "can't be sent as the connection just closed.";
+ } else {
+ msg = "server side packet can't be received as the connection just closed.";
+ }
+ let packet = { error: "connectionClosed", message: msg };
+ request.emit("json-reply", packet);
+ };
+
+ let pendingRequestsToReject = [];
+ this._pendingRequests.forEach((requests, actor) => {
+ if (!actor.startsWith(prefix)) {
+ return;
+ }
+ this._pendingRequests.delete(actor);
+ pendingRequestsToReject = pendingRequestsToReject.concat(requests);
+ });
+ pendingRequestsToReject.forEach(request => reject("pending", request));
+
+ let activeRequestsToReject = [];
+ this._activeRequests.forEach((request, actor) => {
+ if (!actor.startsWith(prefix)) {
+ return;
+ }
+ this._activeRequests.delete(actor);
+ activeRequestsToReject = activeRequestsToReject.concat(request);
+ });
+ activeRequestsToReject.forEach(request => reject("active", request));
+ },
+
+ registerClient: function (client) {
+ let actorID = client.actor;
+ if (!actorID) {
+ throw new Error("DebuggerServer.registerClient expects " +
+ "a client instance with an `actor` attribute.");
+ }
+ if (!Array.isArray(client.events)) {
+ throw new Error("DebuggerServer.registerClient expects " +
+ "a client instance with an `events` attribute " +
+ "that is an array.");
+ }
+ if (client.events.length > 0 && typeof (client.emit) != "function") {
+ throw new Error("DebuggerServer.registerClient expects " +
+ "a client instance with non-empty `events` array to" +
+ "have an `emit` function.");
+ }
+ if (this._clients.has(actorID)) {
+ throw new Error("DebuggerServer.registerClient already registered " +
+ "a client for this actor.");
+ }
+ this._clients.set(actorID, client);
+ },
+
+ unregisterClient: function (client) {
+ let actorID = client.actor;
+ if (!actorID) {
+ throw new Error("DebuggerServer.unregisterClient expects " +
+ "a Client instance with a `actor` attribute.");
+ }
+ this._clients.delete(actorID);
+ },
+
+ /**
+ * Actor lifetime management, echos the server's actor pools.
+ */
+ __pools: null,
+ get _pools() {
+ if (this.__pools) {
+ return this.__pools;
+ }
+ this.__pools = new Set();
+ return this.__pools;
+ },
+
+ addActorPool: function (pool) {
+ this._pools.add(pool);
+ },
+ removeActorPool: function (pool) {
+ this._pools.delete(pool);
+ },
+ getActor: function (actorID) {
+ let pool = this.poolFor(actorID);
+ return pool ? pool.get(actorID) : null;
+ },
+
+ poolFor: function (actorID) {
+ for (let pool of this._pools) {
+ if (pool.has(actorID)) return pool;
+ }
+ return null;
+ },
+
+ /**
+ * Currently attached addon.
+ */
+ activeAddon: null
+};
+
+eventSource(DebuggerClient.prototype);
+
+function Request(request) {
+ this.request = request;
+}
+
+Request.prototype = {
+
+ on: function (type, listener) {
+ events.on(this, type, listener);
+ },
+
+ off: function (type, listener) {
+ events.off(this, type, listener);
+ },
+
+ once: function (type, listener) {
+ events.once(this, type, listener);
+ },
+
+ emit: function (type, ...args) {
+ events.emit(this, type, ...args);
+ },
+
+ get actor() { return this.request.to || this.request.actor; }
+
+};
+
+/**
+ * Creates a tab client for the remote debugging protocol server. This client
+ * is a front to the tab actor created in the server side, hiding the protocol
+ * details in a traditional JavaScript API.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aForm object
+ * The protocol form for this tab.
+ */
+function TabClient(aClient, aForm) {
+ this.client = aClient;
+ this._actor = aForm.from;
+ this._threadActor = aForm.threadActor;
+ this.javascriptEnabled = aForm.javascriptEnabled;
+ this.cacheDisabled = aForm.cacheDisabled;
+ this.thread = null;
+ this.request = this.client.request;
+ this.traits = aForm.traits || {};
+ this.events = ["workerListChanged"];
+}
+
+TabClient.prototype = {
+ get actor() { return this._actor; },
+ get _transport() { return this.client._transport; },
+
+ /**
+ * Attach to a thread actor.
+ *
+ * @param object aOptions
+ * Configuration options.
+ * - useSourceMaps: whether to use source maps or not.
+ * @param function aOnResponse
+ * Called with the response packet and a ThreadClient
+ * (which will be undefined on error).
+ */
+ attachThread: function (aOptions = {}, aOnResponse = noop) {
+ if (this.thread) {
+ DevToolsUtils.executeSoon(() => aOnResponse({}, this.thread));
+ return promise.resolve([{}, this.thread]);
+ }
+
+ let packet = {
+ to: this._threadActor,
+ type: "attach",
+ options: aOptions
+ };
+ return this.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this.thread = new ThreadClient(this, this._threadActor);
+ this.client.registerClient(this.thread);
+ }
+ aOnResponse(aResponse, this.thread);
+ return [aResponse, this.thread];
+ });
+ },
+
+ /**
+ * Detach the client from the tab actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ before: function (aPacket) {
+ if (this.thread) {
+ this.thread.detach();
+ }
+ return aPacket;
+ },
+ after: function (aResponse) {
+ this.client.unregisterClient(this);
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Bring the window to the front.
+ */
+ focus: DebuggerClient.requester({
+ type: "focus"
+ }, {}),
+
+ /**
+ * Reload the page in this tab.
+ *
+ * @param [optional] object options
+ * An object with a `force` property indicating whether or not
+ * this reload should skip the cache
+ */
+ reload: function (options = { force: false }) {
+ return this._reload(options);
+ },
+ _reload: DebuggerClient.requester({
+ type: "reload",
+ options: args(0)
+ }),
+
+ /**
+ * Navigate to another URL.
+ *
+ * @param string url
+ * The URL to navigate to.
+ */
+ navigateTo: DebuggerClient.requester({
+ type: "navigateTo",
+ url: args(0)
+ }),
+
+ /**
+ * Reconfigure the tab actor.
+ *
+ * @param object aOptions
+ * A dictionary object of the new options to use in the tab actor.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ reconfigure: DebuggerClient.requester({
+ type: "reconfigure",
+ options: args(0)
+ }),
+
+ listWorkers: DebuggerClient.requester({
+ type: "listWorkers"
+ }),
+
+ attachWorker: function (aWorkerActor, aOnResponse) {
+ this.client.attachWorker(aWorkerActor, aOnResponse);
+ },
+
+ /**
+ * Resolve a location ({ url, line, column }) to its current
+ * source mapping location.
+ *
+ * @param {String} arg[0].url
+ * @param {Number} arg[0].line
+ * @param {Number?} arg[0].column
+ */
+ resolveLocation: DebuggerClient.requester({
+ type: "resolveLocation",
+ location: args(0)
+ }),
+};
+
+eventSource(TabClient.prototype);
+
+function WorkerClient(aClient, aForm) {
+ this.client = aClient;
+ this._actor = aForm.from;
+ this._isClosed = false;
+ this._url = aForm.url;
+
+ this._onClose = this._onClose.bind(this);
+
+ this.addListener("close", this._onClose);
+
+ this.traits = {};
+}
+
+WorkerClient.prototype = {
+ get _transport() {
+ return this.client._transport;
+ },
+
+ get request() {
+ return this.client.request;
+ },
+
+ get actor() {
+ return this._actor;
+ },
+
+ get url() {
+ return this._url;
+ },
+
+ get isClosed() {
+ return this._isClosed;
+ },
+
+ detach: DebuggerClient.requester({ type: "detach" }, {
+ after: function (aResponse) {
+ if (this.thread) {
+ this.client.unregisterClient(this.thread);
+ }
+ this.client.unregisterClient(this);
+ return aResponse;
+ },
+ }),
+
+ attachThread: function (aOptions = {}, aOnResponse = noop) {
+ if (this.thread) {
+ let response = [{
+ type: "connected",
+ threadActor: this.thread._actor,
+ consoleActor: this.consoleActor,
+ }, this.thread];
+ DevToolsUtils.executeSoon(() => aOnResponse(response));
+ return response;
+ }
+
+ // The connect call on server doesn't attach the thread as of version 44.
+ return this.request({
+ to: this._actor,
+ type: "connect",
+ options: aOptions,
+ }).then(connectReponse => {
+ if (connectReponse.error) {
+ aOnResponse(connectReponse, null);
+ return [connectResponse, null];
+ }
+
+ return this.request({
+ to: connectReponse.threadActor,
+ type: "attach",
+ options: aOptions
+ }).then(attachResponse => {
+ if (attachResponse.error) {
+ aOnResponse(attachResponse, null);
+ }
+
+ this.thread = new ThreadClient(this, connectReponse.threadActor);
+ this.consoleActor = connectReponse.consoleActor;
+ this.client.registerClient(this.thread);
+
+ aOnResponse(connectReponse, this.thread);
+ return [connectResponse, this.thread];
+ });
+ }, error => {
+ aOnResponse(error, null);
+ });
+ },
+
+ _onClose: function () {
+ this.removeListener("close", this._onClose);
+
+ if (this.thread) {
+ this.client.unregisterClient(this.thread);
+ }
+ this.client.unregisterClient(this);
+ this._isClosed = true;
+ },
+
+ reconfigure: function () {
+ return Promise.resolve();
+ },
+
+ events: ["close"]
+};
+
+eventSource(WorkerClient.prototype);
+
+function AddonClient(aClient, aActor) {
+ this._client = aClient;
+ this._actor = aActor;
+ this.request = this._client.request;
+ this.events = [];
+}
+
+AddonClient.prototype = {
+ get actor() { return this._actor; },
+ get _transport() { return this._client._transport; },
+
+ /**
+ * Detach the client from the addon actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ if (this._client.activeAddon === this) {
+ this._client.activeAddon = null;
+ }
+ this._client.unregisterClient(this);
+ return aResponse;
+ },
+ })
+};
+
+/**
+ * A RootClient object represents a root actor on the server. Each
+ * DebuggerClient keeps a RootClient instance representing the root actor
+ * for the initial connection; DebuggerClient's 'listTabs' and
+ * 'listChildProcesses' methods forward to that root actor.
+ *
+ * @param aClient object
+ * The client connection to which this actor belongs.
+ * @param aGreeting string
+ * The greeting packet from the root actor we're to represent.
+ *
+ * Properties of a RootClient instance:
+ *
+ * @property actor string
+ * The name of this child's root actor.
+ * @property applicationType string
+ * The application type, as given in the root actor's greeting packet.
+ * @property traits object
+ * The traits object, as given in the root actor's greeting packet.
+ */
+function RootClient(aClient, aGreeting) {
+ this._client = aClient;
+ this.actor = aGreeting.from;
+ this.applicationType = aGreeting.applicationType;
+ this.traits = aGreeting.traits;
+}
+exports.RootClient = RootClient;
+
+RootClient.prototype = {
+ constructor: RootClient,
+
+ /**
+ * List the open tabs.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listTabs: DebuggerClient.requester({ type: "listTabs" }),
+
+ /**
+ * List the installed addons.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listAddons: DebuggerClient.requester({ type: "listAddons" }),
+
+ /**
+ * List the registered workers.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
+
+ /**
+ * List the registered service workers.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listServiceWorkerRegistrations: DebuggerClient.requester({
+ type: "listServiceWorkerRegistrations"
+ }),
+
+ /**
+ * List the running processes.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
+
+ /**
+ * Fetch the TabActor for the currently selected tab, or for a specific
+ * tab given as first parameter.
+ *
+ * @param [optional] object aFilter
+ * A dictionary object with following optional attributes:
+ * - outerWindowID: used to match tabs in parent process
+ * - tabId: used to match tabs in child processes
+ * - tab: a reference to xul:tab element
+ * If nothing is specified, returns the actor for the currently
+ * selected tab.
+ */
+ getTab: function (aFilter) {
+ let packet = {
+ to: this.actor,
+ type: "getTab"
+ };
+
+ if (aFilter) {
+ if (typeof (aFilter.outerWindowID) == "number") {
+ packet.outerWindowID = aFilter.outerWindowID;
+ } else if (typeof (aFilter.tabId) == "number") {
+ packet.tabId = aFilter.tabId;
+ } else if ("tab" in aFilter) {
+ let browser = aFilter.tab.linkedBrowser;
+ if (browser.frameLoader.tabParent) {
+ // Tabs in child process
+ packet.tabId = browser.frameLoader.tabParent.tabId;
+ } else if (browser.outerWindowID) {
+ // <xul:browser> tabs in parent process
+ packet.outerWindowID = browser.outerWindowID;
+ } else {
+ // <iframe mozbrowser> tabs in parent process
+ let windowUtils = browser.contentWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ packet.outerWindowID = windowUtils.outerWindowID;
+ }
+ } else {
+ // Throw if a filter object have been passed but without
+ // any clearly idenfified filter.
+ throw new Error("Unsupported argument given to getTab request");
+ }
+ }
+
+ return this.request(packet);
+ },
+
+ /**
+ * Description of protocol's actors and methods.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
+
+ /*
+ * Methods constructed by DebuggerClient.requester require these forwards
+ * on their 'this'.
+ */
+ get _transport() { return this._client._transport; },
+ get request() { return this._client.request; }
+};
+
+/**
+ * Creates a thread client for the remote debugging protocol server. This client
+ * is a front to the thread actor created in the server side, hiding the
+ * protocol details in a traditional JavaScript API.
+ *
+ * @param aClient DebuggerClient|TabClient
+ * The parent of the thread (tab for tab-scoped debuggers, DebuggerClient
+ * for chrome debuggers).
+ * @param aActor string
+ * The actor ID for this thread.
+ */
+function ThreadClient(aClient, aActor) {
+ this._parent = aClient;
+ this.client = aClient instanceof DebuggerClient ? aClient : aClient.client;
+ this._actor = aActor;
+ this._frameCache = [];
+ this._scriptCache = {};
+ this._pauseGrips = {};
+ this._threadGrips = {};
+ this.request = this.client.request;
+}
+
+ThreadClient.prototype = {
+ _state: "paused",
+ get state() { return this._state; },
+ get paused() { return this._state === "paused"; },
+
+ _pauseOnExceptions: false,
+ _ignoreCaughtExceptions: false,
+ _pauseOnDOMEvents: null,
+
+ _actor: null,
+ get actor() { return this._actor; },
+
+ get _transport() { return this.client._transport; },
+
+ _assertPaused: function (aCommand) {
+ if (!this.paused) {
+ throw Error(aCommand + " command sent while not paused. Currently " + this._state);
+ }
+ },
+
+ /**
+ * Resume a paused thread. If the optional aLimit parameter is present, then
+ * the thread will also pause when that limit is reached.
+ *
+ * @param [optional] object aLimit
+ * An object with a type property set to the appropriate limit (next,
+ * step, or finish) per the remote debugging protocol specification.
+ * Use null to specify no limit.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ _doResume: DebuggerClient.requester({
+ type: "resume",
+ resumeLimit: args(0)
+ }, {
+ before: function (aPacket) {
+ this._assertPaused("resume");
+
+ // Put the client in a tentative "resuming" state so we can prevent
+ // further requests that should only be sent in the paused state.
+ this._previousState = this._state;
+ this._state = "resuming";
+
+ if (this._pauseOnExceptions) {
+ aPacket.pauseOnExceptions = this._pauseOnExceptions;
+ }
+ if (this._ignoreCaughtExceptions) {
+ aPacket.ignoreCaughtExceptions = this._ignoreCaughtExceptions;
+ }
+ if (this._pauseOnDOMEvents) {
+ aPacket.pauseOnDOMEvents = this._pauseOnDOMEvents;
+ }
+ return aPacket;
+ },
+ after: function (aResponse) {
+ if (aResponse.error && this._state == "resuming") {
+ // There was an error resuming, update the state to the new one
+ // reported by the server, if given (only on wrongState), otherwise
+ // reset back to the previous state.
+ if (aResponse.state) {
+ this._state = ThreadStateTypes[aResponse.state];
+ } else {
+ this._state = this._previousState;
+ }
+ }
+ delete this._previousState;
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Reconfigure the thread actor.
+ *
+ * @param object aOptions
+ * A dictionary object of the new options to use in the thread actor.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ reconfigure: DebuggerClient.requester({
+ type: "reconfigure",
+ options: args(0)
+ }),
+
+ /**
+ * Resume a paused thread.
+ */
+ resume: function (aOnResponse) {
+ return this._doResume(null, aOnResponse);
+ },
+
+ /**
+ * Resume then pause without stepping.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ resumeThenPause: function (aOnResponse) {
+ return this._doResume({ type: "break" }, aOnResponse);
+ },
+
+ /**
+ * Step over a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepOver: function (aOnResponse) {
+ return this._doResume({ type: "next" }, aOnResponse);
+ },
+
+ /**
+ * Step into a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepIn: function (aOnResponse) {
+ return this._doResume({ type: "step" }, aOnResponse);
+ },
+
+ /**
+ * Step out of a function call.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ stepOut: function (aOnResponse) {
+ return this._doResume({ type: "finish" }, aOnResponse);
+ },
+
+ /**
+ * Immediately interrupt a running thread.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ interrupt: function (aOnResponse) {
+ return this._doInterrupt(null, aOnResponse);
+ },
+
+ /**
+ * Pause execution right before the next JavaScript bytecode is executed.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ breakOnNext: function (aOnResponse) {
+ return this._doInterrupt("onNext", aOnResponse);
+ },
+
+ /**
+ * Interrupt a running thread.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ _doInterrupt: DebuggerClient.requester({
+ type: "interrupt",
+ when: args(0)
+ }),
+
+ /**
+ * Enable or disable pausing when an exception is thrown.
+ *
+ * @param boolean aFlag
+ * Enables pausing if true, disables otherwise.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ pauseOnExceptions: function (aPauseOnExceptions,
+ aIgnoreCaughtExceptions,
+ aOnResponse = noop) {
+ this._pauseOnExceptions = aPauseOnExceptions;
+ this._ignoreCaughtExceptions = aIgnoreCaughtExceptions;
+
+ // Otherwise send the flag using a standard resume request.
+ if (!this.paused) {
+ return this.interrupt(aResponse => {
+ if (aResponse.error) {
+ // Can't continue if pausing failed.
+ aOnResponse(aResponse);
+ return aResponse;
+ }
+ return this.resume(aOnResponse);
+ });
+ }
+
+ aOnResponse();
+ return promise.resolve();
+ },
+
+ /**
+ * Enable pausing when the specified DOM events are triggered. Disabling
+ * pausing on an event can be realized by calling this method with the updated
+ * array of events that doesn't contain it.
+ *
+ * @param array|string events
+ * An array of strings, representing the DOM event types to pause on,
+ * or "*" to pause on all DOM events. Pass an empty array to
+ * completely disable pausing on DOM events.
+ * @param function onResponse
+ * Called with the response packet in a future turn of the event loop.
+ */
+ pauseOnDOMEvents: function (events, onResponse = noop) {
+ this._pauseOnDOMEvents = events;
+ // If the debuggee is paused, the value of the array will be communicated in
+ // the next resumption. Otherwise we have to force a pause in order to send
+ // the array.
+ if (this.paused) {
+ DevToolsUtils.executeSoon(() => onResponse({}));
+ return {};
+ }
+ return this.interrupt(response => {
+ // Can't continue if pausing failed.
+ if (response.error) {
+ onResponse(response);
+ return response;
+ }
+ return this.resume(onResponse);
+ });
+ },
+
+ /**
+ * Send a clientEvaluate packet to the debuggee. Response
+ * will be a resume packet.
+ *
+ * @param string aFrame
+ * The actor ID of the frame where the evaluation should take place.
+ * @param string aExpression
+ * The expression that will be evaluated in the scope of the frame
+ * above.
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ eval: DebuggerClient.requester({
+ type: "clientEvaluate",
+ frame: args(0),
+ expression: args(1)
+ }, {
+ before: function (aPacket) {
+ this._assertPaused("eval");
+ // Put the client in a tentative "resuming" state so we can prevent
+ // further requests that should only be sent in the paused state.
+ this._state = "resuming";
+ return aPacket;
+ },
+ after: function (aResponse) {
+ if (aResponse.error) {
+ // There was an error resuming, back to paused state.
+ this._state = "paused";
+ }
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Detach from the thread actor.
+ *
+ * @param function aOnResponse
+ * Called with the response packet.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ this.client.unregisterClient(this);
+ this._parent.thread = null;
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Release multiple thread-lifetime object actors. If any pause-lifetime
+ * actors are included in the request, a |notReleasable| error will return,
+ * but all the thread-lifetime ones will have been released.
+ *
+ * @param array actors
+ * An array with actor IDs to release.
+ */
+ releaseMany: DebuggerClient.requester({
+ type: "releaseMany",
+ actors: args(0),
+ }),
+
+ /**
+ * Promote multiple pause-lifetime object actors to thread-lifetime ones.
+ *
+ * @param array actors
+ * An array with actor IDs to promote.
+ */
+ threadGrips: DebuggerClient.requester({
+ type: "threadGrips",
+ actors: args(0)
+ }),
+
+ /**
+ * Return the event listeners defined on the page.
+ *
+ * @param aOnResponse Function
+ * Called with the thread's response.
+ */
+ eventListeners: DebuggerClient.requester({
+ type: "eventListeners"
+ }),
+
+ /**
+ * Request the loaded sources for the current thread.
+ *
+ * @param aOnResponse Function
+ * Called with the thread's response.
+ */
+ getSources: DebuggerClient.requester({
+ type: "sources"
+ }),
+
+ /**
+ * Clear the thread's source script cache. A scriptscleared event
+ * will be sent.
+ */
+ _clearScripts: function () {
+ if (Object.keys(this._scriptCache).length > 0) {
+ this._scriptCache = {};
+ this.emit("scriptscleared");
+ }
+ },
+
+ /**
+ * Request frames from the callstack for the current thread.
+ *
+ * @param aStart integer
+ * The number of the youngest stack frame to return (the youngest
+ * frame is 0).
+ * @param aCount integer
+ * The maximum number of frames to return, or null to return all
+ * frames.
+ * @param aOnResponse function
+ * Called with the thread's response.
+ */
+ getFrames: DebuggerClient.requester({
+ type: "frames",
+ start: args(0),
+ count: args(1)
+ }),
+
+ /**
+ * An array of cached frames. Clients can observe the framesadded and
+ * framescleared event to keep up to date on changes to this cache,
+ * and can fill it using the fillFrames method.
+ */
+ get cachedFrames() { return this._frameCache; },
+
+ /**
+ * true if there are more stack frames available on the server.
+ */
+ get moreFrames() {
+ return this.paused && (!this._frameCache || this._frameCache.length == 0
+ || !this._frameCache[this._frameCache.length - 1].oldest);
+ },
+
+ /**
+ * Ensure that at least aTotal stack frames have been loaded in the
+ * ThreadClient's stack frame cache. A framesadded event will be
+ * sent when the stack frame cache is updated.
+ *
+ * @param aTotal number
+ * The minimum number of stack frames to be included.
+ * @param aCallback function
+ * Optional callback function called when frames have been loaded
+ * @returns true if a framesadded notification should be expected.
+ */
+ fillFrames: function (aTotal, aCallback = noop) {
+ this._assertPaused("fillFrames");
+ if (this._frameCache.length >= aTotal) {
+ return false;
+ }
+
+ let numFrames = this._frameCache.length;
+
+ this.getFrames(numFrames, aTotal - numFrames, (aResponse) => {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return;
+ }
+
+ let threadGrips = DevToolsUtils.values(this._threadGrips);
+
+ for (let i in aResponse.frames) {
+ let frame = aResponse.frames[i];
+ if (!frame.where.source) {
+ // Older servers use urls instead, so we need to resolve
+ // them to source actors
+ for (let grip of threadGrips) {
+ if (grip instanceof SourceClient && grip.url === frame.url) {
+ frame.where.source = grip._form;
+ }
+ }
+ }
+
+ this._frameCache[frame.depth] = frame;
+ }
+
+ // If we got as many frames as we asked for, there might be more
+ // frames available.
+ this.emit("framesadded");
+
+ aCallback(aResponse);
+ });
+
+ return true;
+ },
+
+ /**
+ * Clear the thread's stack frame cache. A framescleared event
+ * will be sent.
+ */
+ _clearFrames: function () {
+ if (this._frameCache.length > 0) {
+ this._frameCache = [];
+ this.emit("framescleared");
+ }
+ },
+
+ /**
+ * Return a ObjectClient object for the given object grip.
+ *
+ * @param aGrip object
+ * A pause-lifetime object grip returned by the protocol.
+ */
+ pauseGrip: function (aGrip) {
+ if (aGrip.actor in this._pauseGrips) {
+ return this._pauseGrips[aGrip.actor];
+ }
+
+ let client = new ObjectClient(this.client, aGrip);
+ this._pauseGrips[aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Get or create a long string client, checking the grip client cache if it
+ * already exists.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ * @param aGripCacheName String
+ * The property name of the grip client cache to check for existing
+ * clients in.
+ */
+ _longString: function (aGrip, aGripCacheName) {
+ if (aGrip.actor in this[aGripCacheName]) {
+ return this[aGripCacheName][aGrip.actor];
+ }
+
+ let client = new LongStringClient(this.client, aGrip);
+ this[aGripCacheName][aGrip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the current pause.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ */
+ pauseLongString: function (aGrip) {
+ return this._longString(aGrip, "_pauseGrips");
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip that
+ * is scoped to the thread lifetime.
+ *
+ * @param aGrip Object
+ * The long string grip returned by the protocol.
+ */
+ threadLongString: function (aGrip) {
+ return this._longString(aGrip, "_threadGrips");
+ },
+
+ /**
+ * Clear and invalidate all the grip clients from the given cache.
+ *
+ * @param aGripCacheName
+ * The property name of the grip cache we want to clear.
+ */
+ _clearObjectClients: function (aGripCacheName) {
+ for (let id in this[aGripCacheName]) {
+ this[aGripCacheName][id].valid = false;
+ }
+ this[aGripCacheName] = {};
+ },
+
+ /**
+ * Invalidate pause-lifetime grip clients and clear the list of current grip
+ * clients.
+ */
+ _clearPauseGrips: function () {
+ this._clearObjectClients("_pauseGrips");
+ },
+
+ /**
+ * Invalidate thread-lifetime grip clients and clear the list of current grip
+ * clients.
+ */
+ _clearThreadGrips: function () {
+ this._clearObjectClients("_threadGrips");
+ },
+
+ /**
+ * Handle thread state change by doing necessary cleanup and notifying all
+ * registered listeners.
+ */
+ _onThreadState: function (aPacket) {
+ this._state = ThreadStateTypes[aPacket.type];
+ // The debugger UI may not be initialized yet so we want to keep
+ // the packet around so it knows what to pause state to display
+ // when it's initialized
+ this._lastPausePacket = aPacket.type === "resumed" ? null : aPacket;
+ this._clearFrames();
+ this._clearPauseGrips();
+ aPacket.type === ThreadStateTypes.detached && this._clearThreadGrips();
+ this.client._eventsEnabled && this.emit(aPacket.type, aPacket);
+ },
+
+ getLastPausePacket: function () {
+ return this._lastPausePacket;
+ },
+
+ /**
+ * Return an EnvironmentClient instance for the given environment actor form.
+ */
+ environment: function (aForm) {
+ return new EnvironmentClient(this.client, aForm);
+ },
+
+ /**
+ * Return an instance of SourceClient for the given source actor form.
+ */
+ source: function (aForm) {
+ if (aForm.actor in this._threadGrips) {
+ return this._threadGrips[aForm.actor];
+ }
+
+ return this._threadGrips[aForm.actor] = new SourceClient(this, aForm);
+ },
+
+ /**
+ * Request the prototype and own properties of mutlipleObjects.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ * @param actors [string]
+ * List of actor ID of the queried objects.
+ */
+ getPrototypesAndProperties: DebuggerClient.requester({
+ type: "prototypesAndProperties",
+ actors: args(0)
+ }),
+
+ events: ["newSource"]
+};
+
+eventSource(ThreadClient.prototype);
+
+/**
+ * Creates a tracing profiler client for the remote debugging protocol
+ * server. This client is a front to the trace actor created on the
+ * server side, hiding the protocol details in a traditional
+ * JavaScript API.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aActor string
+ * The actor ID for this thread.
+ */
+function TraceClient(aClient, aActor) {
+ this._client = aClient;
+ this._actor = aActor;
+ this._activeTraces = new Set();
+ this._waitingPackets = new Map();
+ this._expectedPacket = 0;
+ this.request = this._client.request;
+ this.events = [];
+}
+
+TraceClient.prototype = {
+ get actor() { return this._actor; },
+ get tracing() { return this._activeTraces.size > 0; },
+
+ get _transport() { return this._client._transport; },
+
+ /**
+ * Detach from the trace actor.
+ */
+ detach: DebuggerClient.requester({
+ type: "detach"
+ }, {
+ after: function (aResponse) {
+ this._client.unregisterClient(this);
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Start a new trace.
+ *
+ * @param aTrace [string]
+ * An array of trace types to be recorded by the new trace.
+ *
+ * @param aName string
+ * The name of the new trace.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ */
+ startTrace: DebuggerClient.requester({
+ type: "startTrace",
+ name: args(1),
+ trace: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.error) {
+ return aResponse;
+ }
+
+ if (!this.tracing) {
+ this._waitingPackets.clear();
+ this._expectedPacket = 0;
+ }
+ this._activeTraces.add(aResponse.name);
+
+ return aResponse;
+ },
+ }),
+
+ /**
+ * End a trace. If a name is provided, stop the named
+ * trace. Otherwise, stop the most recently started trace.
+ *
+ * @param aName string
+ * The name of the trace to stop.
+ *
+ * @param aOnResponse function
+ * Called with the request's response.
+ */
+ stopTrace: DebuggerClient.requester({
+ type: "stopTrace",
+ name: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.error) {
+ return aResponse;
+ }
+
+ this._activeTraces.delete(aResponse.name);
+
+ return aResponse;
+ },
+ })
+};
+
+/**
+ * Grip clients are used to retrieve information about the relevant object.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip object
+ * A pause-lifetime object grip returned by the protocol.
+ */
+function ObjectClient(aClient, aGrip)
+{
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+}
+exports.ObjectClient = ObjectClient;
+
+ObjectClient.prototype = {
+ get actor() { return this._grip.actor; },
+ get _transport() { return this._client._transport; },
+
+ valid: true,
+
+ get isFrozen() {
+ return this._grip.frozen;
+ },
+ get isSealed() {
+ return this._grip.sealed;
+ },
+ get isExtensible() {
+ return this._grip.extensible;
+ },
+
+ getDefinitionSite: DebuggerClient.requester({
+ type: "definitionSite"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class != "Function") {
+ throw new Error("getDefinitionSite is only valid for function grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the names of a function's formal parameters.
+ *
+ * @param aOnResponse function
+ * Called with an object of the form:
+ * { parameterNames:[<parameterName>, ...] }
+ * where each <parameterName> is the name of a parameter.
+ */
+ getParameterNames: DebuggerClient.requester({
+ type: "parameterNames"
+ }, {
+ before: function (aPacket) {
+ if (this._grip["class"] !== "Function") {
+ throw new Error("getParameterNames is only valid for function grips.");
+ }
+ return aPacket;
+ },
+ }),
+
+ /**
+ * Request the names of the properties defined on the object and not its
+ * prototype.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getOwnPropertyNames: DebuggerClient.requester({
+ type: "ownPropertyNames"
+ }),
+
+ /**
+ * Request the prototype and own properties of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getPrototypeAndProperties: DebuggerClient.requester({
+ type: "prototypeAndProperties"
+ }),
+
+ /**
+ * Request a PropertyIteratorClient instance to ease listing
+ * properties for this object.
+ *
+ * @param options Object
+ * A dictionary object with various boolean attributes:
+ * - ignoreIndexedProperties Boolean
+ * If true, filters out Array items.
+ * e.g. properties names between `0` and `object.length`.
+ * - ignoreNonIndexedProperties Boolean
+ * If true, filters out items that aren't array items
+ * e.g. properties names that are not a number between `0`
+ * and `object.length`.
+ * - sort Boolean
+ * If true, the iterator will sort the properties by name
+ * before dispatching them.
+ * @param aOnResponse function Called with the client instance.
+ */
+ enumProperties: DebuggerClient.requester({
+ type: "enumProperties",
+ options: args(0)
+ }, {
+ after: function (aResponse) {
+ if (aResponse.iterator) {
+ return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) };
+ }
+ return aResponse;
+ },
+ }),
+
+ /**
+ * Request a PropertyIteratorClient instance to enumerate entries in a
+ * Map/Set-like object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ enumEntries: DebuggerClient.requester({
+ type: "enumEntries"
+ }, {
+ before: function (packet) {
+ if (!["Map", "WeakMap", "Set", "WeakSet"].includes(this._grip.class)) {
+ throw new Error("enumEntries is only valid for Map/Set-like grips.");
+ }
+ return packet;
+ },
+ after: function (response) {
+ if (response.iterator) {
+ return {
+ iterator: new PropertyIteratorClient(this._client, response.iterator)
+ };
+ }
+ return response;
+ }
+ }),
+
+ /**
+ * Request the property descriptor of the object's specified property.
+ *
+ * @param aName string The name of the requested property.
+ * @param aOnResponse function Called with the request's response.
+ */
+ getProperty: DebuggerClient.requester({
+ type: "property",
+ name: args(0)
+ }),
+
+ /**
+ * Request the prototype of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getPrototype: DebuggerClient.requester({
+ type: "prototype"
+ }),
+
+ /**
+ * Request the display string of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getDisplayString: DebuggerClient.requester({
+ type: "displayString"
+ }),
+
+ /**
+ * Request the scope of the object.
+ *
+ * @param aOnResponse function Called with the request's response.
+ */
+ getScope: DebuggerClient.requester({
+ type: "scope"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Function") {
+ throw new Error("scope is only valid for function grips.");
+ }
+ return aPacket;
+ },
+ }),
+
+ /**
+ * Request the promises directly depending on the current promise.
+ */
+ getDependentPromises: DebuggerClient.requester({
+ type: "dependentPromises"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getDependentPromises is only valid for promise " +
+ "grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's allocation point.
+ */
+ getPromiseAllocationStack: DebuggerClient.requester({
+ type: "allocationStack"
+ }, {
+ before: function (aPacket) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getAllocationStack is only valid for promise grips.");
+ }
+ return aPacket;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's fulfillment point.
+ */
+ getPromiseFulfillmentStack: DebuggerClient.requester({
+ type: "fulfillmentStack"
+ }, {
+ before: function (packet) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getPromiseFulfillmentStack is only valid for " +
+ "promise grips.");
+ }
+ return packet;
+ }
+ }),
+
+ /**
+ * Request the stack to the promise's rejection point.
+ */
+ getPromiseRejectionStack: DebuggerClient.requester({
+ type: "rejectionStack"
+ }, {
+ before: function (packet) {
+ if (this._grip.class !== "Promise") {
+ throw new Error("getPromiseRejectionStack is only valid for " +
+ "promise grips.");
+ }
+ return packet;
+ }
+ })
+};
+
+/**
+ * A PropertyIteratorClient provides a way to access to property names and
+ * values of an object efficiently, slice by slice.
+ * Note that the properties can be sorted in the backend,
+ * this is controled while creating the PropertyIteratorClient
+ * from ObjectClient.enumProperties.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip Object
+ * A PropertyIteratorActor grip returned by the protocol via
+ * TabActor.enumProperties request.
+ */
+function PropertyIteratorClient(aClient, aGrip) {
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+}
+
+PropertyIteratorClient.prototype = {
+ get actor() { return this._grip.actor; },
+
+ /**
+ * Get the total number of properties available in the iterator.
+ */
+ get count() { return this._grip.count; },
+
+ /**
+ * Get one or more property names that correspond to the positions in the
+ * indexes parameter.
+ *
+ * @param indexes Array
+ * An array of property indexes.
+ * @param aCallback Function
+ * The function called when we receive the property names.
+ */
+ names: DebuggerClient.requester({
+ type: "names",
+ indexes: args(0)
+ }, {}),
+
+ /**
+ * Get a set of following property value(s).
+ *
+ * @param start Number
+ * The index of the first property to fetch.
+ * @param count Number
+ * The number of properties to fetch.
+ * @param aCallback Function
+ * The function called when we receive the property values.
+ */
+ slice: DebuggerClient.requester({
+ type: "slice",
+ start: args(0),
+ count: args(1)
+ }, {}),
+
+ /**
+ * Get all the property values.
+ *
+ * @param aCallback Function
+ * The function called when we receive the property values.
+ */
+ all: DebuggerClient.requester({
+ type: "all"
+ }, {}),
+};
+
+/**
+ * A LongStringClient provides a way to access "very long" strings from the
+ * debugger server.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aGrip Object
+ * A pause-lifetime long string grip returned by the protocol.
+ */
+function LongStringClient(aClient, aGrip) {
+ this._grip = aGrip;
+ this._client = aClient;
+ this.request = this._client.request;
+}
+exports.LongStringClient = LongStringClient;
+
+LongStringClient.prototype = {
+ get actor() { return this._grip.actor; },
+ get length() { return this._grip.length; },
+ get initial() { return this._grip.initial; },
+ get _transport() { return this._client._transport; },
+
+ valid: true,
+
+ /**
+ * Get the substring of this LongString from aStart to aEnd.
+ *
+ * @param aStart Number
+ * The starting index.
+ * @param aEnd Number
+ * The ending index.
+ * @param aCallback Function
+ * The function called when we receive the substring.
+ */
+ substring: DebuggerClient.requester({
+ type: "substring",
+ start: args(0),
+ end: args(1)
+ }),
+};
+
+/**
+ * A SourceClient provides a way to access the source text of a script.
+ *
+ * @param aClient ThreadClient
+ * The thread client parent.
+ * @param aForm Object
+ * The form sent across the remote debugging protocol.
+ */
+function SourceClient(aClient, aForm) {
+ this._form = aForm;
+ this._isBlackBoxed = aForm.isBlackBoxed;
+ this._isPrettyPrinted = aForm.isPrettyPrinted;
+ this._activeThread = aClient;
+ this._client = aClient.client;
+}
+
+SourceClient.prototype = {
+ get _transport() {
+ return this._client._transport;
+ },
+ get isBlackBoxed() {
+ return this._isBlackBoxed;
+ },
+ get isPrettyPrinted() {
+ return this._isPrettyPrinted;
+ },
+ get actor() {
+ return this._form.actor;
+ },
+ get request() {
+ return this._client.request;
+ },
+ get url() {
+ return this._form.url;
+ },
+
+ /**
+ * Black box this SourceClient's source.
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ blackBox: DebuggerClient.requester({
+ type: "blackbox"
+ }, {
+ after: function (aResponse) {
+ if (!aResponse.error) {
+ this._isBlackBoxed = true;
+ if (this._activeThread) {
+ this._activeThread.emit("blackboxchange", this);
+ }
+ }
+ return aResponse;
+ }
+ }),
+
+ /**
+ * Un-black box this SourceClient's source.
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ unblackBox: DebuggerClient.requester({
+ type: "unblackbox"
+ }, {
+ after: function (aResponse) {
+ if (!aResponse.error) {
+ this._isBlackBoxed = false;
+ if (this._activeThread) {
+ this._activeThread.emit("blackboxchange", this);
+ }
+ }
+ return aResponse;
+ }
+ }),
+
+ /**
+ * Get Executable Lines from a source
+ *
+ * @param aCallback Function
+ * The callback function called when we receive the response from the server.
+ */
+ getExecutableLines: function (cb = noop) {
+ let packet = {
+ to: this._form.actor,
+ type: "getExecutableLines"
+ };
+
+ return this._client.request(packet).then(res => {
+ cb(res.lines);
+ return res.lines;
+ });
+ },
+
+ /**
+ * Get a long string grip for this SourceClient's source.
+ */
+ source: function (aCallback = noop) {
+ let packet = {
+ to: this._form.actor,
+ type: "source"
+ };
+ return this._client.request(packet).then(aResponse => {
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ /**
+ * Pretty print this source's text.
+ */
+ prettyPrint: function (aIndent, aCallback = noop) {
+ const packet = {
+ to: this._form.actor,
+ type: "prettyPrint",
+ indent: aIndent
+ };
+ return this._client.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this._isPrettyPrinted = true;
+ this._activeThread._clearFrames();
+ this._activeThread.emit("prettyprintchange", this);
+ }
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ /**
+ * Stop pretty printing this source's text.
+ */
+ disablePrettyPrint: function (aCallback = noop) {
+ const packet = {
+ to: this._form.actor,
+ type: "disablePrettyPrint"
+ };
+ return this._client.request(packet).then(aResponse => {
+ if (!aResponse.error) {
+ this._isPrettyPrinted = false;
+ this._activeThread._clearFrames();
+ this._activeThread.emit("prettyprintchange", this);
+ }
+ return this._onSourceResponse(aResponse, aCallback);
+ });
+ },
+
+ _onSourceResponse: function (aResponse, aCallback) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return aResponse;
+ }
+
+ if (typeof aResponse.source === "string") {
+ aCallback(aResponse);
+ return aResponse;
+ }
+
+ let { contentType, source } = aResponse;
+ let longString = this._activeThread.threadLongString(source);
+ return longString.substring(0, longString.length).then(function (aResponse) {
+ if (aResponse.error) {
+ aCallback(aResponse);
+ return aReponse;
+ }
+
+ let response = {
+ source: aResponse.substring,
+ contentType: contentType
+ };
+ aCallback(response);
+ return response;
+ });
+ },
+
+ /**
+ * Request to set a breakpoint in the specified location.
+ *
+ * @param object aLocation
+ * The location and condition of the breakpoint in
+ * the form of { line[, column, condition] }.
+ * @param function aOnResponse
+ * Called with the thread's response.
+ */
+ setBreakpoint: function ({ line, column, condition, noSliding }, aOnResponse = noop) {
+ // A helper function that sets the breakpoint.
+ let doSetBreakpoint = aCallback => {
+ let root = this._client.mainRoot;
+ let location = {
+ line: line,
+ column: column
+ };
+
+ let packet = {
+ to: this.actor,
+ type: "setBreakpoint",
+ location: location,
+ condition: condition,
+ noSliding: noSliding
+ };
+
+ // Backwards compatibility: send the breakpoint request to the
+ // thread if the server doesn't support Debugger.Source actors.
+ if (!root.traits.debuggerSourceActors) {
+ packet.to = this._activeThread.actor;
+ packet.location.url = this.url;
+ }
+
+ return this._client.request(packet).then(aResponse => {
+ // Ignoring errors, since the user may be setting a breakpoint in a
+ // dead script that will reappear on a page reload.
+ let bpClient;
+ if (aResponse.actor) {
+ bpClient = new BreakpointClient(
+ this._client,
+ this,
+ aResponse.actor,
+ location,
+ root.traits.conditionalBreakpoints ? condition : undefined
+ );
+ }
+ aOnResponse(aResponse, bpClient);
+ if (aCallback) {
+ aCallback();
+ }
+ return [aResponse, bpClient];
+ });
+ };
+
+ // If the debuggee is paused, just set the breakpoint.
+ if (this._activeThread.paused) {
+ return doSetBreakpoint();
+ }
+ // Otherwise, force a pause in order to set the breakpoint.
+ return this._activeThread.interrupt().then(aResponse => {
+ if (aResponse.error) {
+ // Can't set the breakpoint if pausing failed.
+ aOnResponse(aResponse);
+ return aResponse;
+ }
+
+ const { type, why } = aResponse;
+ const cleanUp = type == "paused" && why.type == "interrupted"
+ ? () => this._activeThread.resume()
+ : noop;
+
+ return doSetBreakpoint(cleanUp);
+ });
+ }
+};
+
+/**
+ * Breakpoint clients are used to remove breakpoints that are no longer used.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aSourceClient SourceClient
+ * The source where this breakpoint exists
+ * @param aActor string
+ * The actor ID for this breakpoint.
+ * @param aLocation object
+ * The location of the breakpoint. This is an object with two properties:
+ * url and line.
+ * @param aCondition string
+ * The conditional expression of the breakpoint
+ */
+function BreakpointClient(aClient, aSourceClient, aActor, aLocation, aCondition) {
+ this._client = aClient;
+ this._actor = aActor;
+ this.location = aLocation;
+ this.location.actor = aSourceClient.actor;
+ this.location.url = aSourceClient.url;
+ this.source = aSourceClient;
+ this.request = this._client.request;
+
+ // The condition property should only exist if it's a truthy value
+ if (aCondition) {
+ this.condition = aCondition;
+ }
+}
+
+BreakpointClient.prototype = {
+
+ _actor: null,
+ get actor() { return this._actor; },
+ get _transport() { return this._client._transport; },
+
+ /**
+ * Remove the breakpoint from the server.
+ */
+ remove: DebuggerClient.requester({
+ type: "delete"
+ }),
+
+ /**
+ * Determines if this breakpoint has a condition
+ */
+ hasCondition: function () {
+ let root = this._client.mainRoot;
+ // XXX bug 990137: We will remove support for client-side handling of
+ // conditional breakpoints
+ if (root.traits.conditionalBreakpoints) {
+ return "condition" in this;
+ } else {
+ return "conditionalExpression" in this;
+ }
+ },
+
+ /**
+ * Get the condition of this breakpoint. Currently we have to
+ * support locally emulated conditional breakpoints until the
+ * debugger servers are updated (see bug 990137). We used a
+ * different property when moving it server-side to ensure that we
+ * are testing the right code.
+ */
+ getCondition: function () {
+ let root = this._client.mainRoot;
+ if (root.traits.conditionalBreakpoints) {
+ return this.condition;
+ } else {
+ return this.conditionalExpression;
+ }
+ },
+
+ /**
+ * Set the condition of this breakpoint
+ */
+ setCondition: function (gThreadClient, aCondition) {
+ let root = this._client.mainRoot;
+ let deferred = promise.defer();
+
+ if (root.traits.conditionalBreakpoints) {
+ let info = {
+ line: this.location.line,
+ column: this.location.column,
+ condition: aCondition
+ };
+
+ // Remove the current breakpoint and add a new one with the
+ // condition.
+ this.remove(aResponse => {
+ if (aResponse && aResponse.error) {
+ deferred.reject(aResponse);
+ return;
+ }
+
+ this.source.setBreakpoint(info, (aResponse, aNewBreakpoint) => {
+ if (aResponse && aResponse.error) {
+ deferred.reject(aResponse);
+ } else {
+ deferred.resolve(aNewBreakpoint);
+ }
+ });
+ });
+ } else {
+ // The property shouldn't even exist if the condition is blank
+ if (aCondition === "") {
+ delete this.conditionalExpression;
+ }
+ else {
+ this.conditionalExpression = aCondition;
+ }
+ deferred.resolve(this);
+ }
+
+ return deferred.promise;
+ }
+};
+
+eventSource(BreakpointClient.prototype);
+
+/**
+ * Environment clients are used to manipulate the lexical environment actors.
+ *
+ * @param aClient DebuggerClient
+ * The debugger client parent.
+ * @param aForm Object
+ * The form sent across the remote debugging protocol.
+ */
+function EnvironmentClient(aClient, aForm) {
+ this._client = aClient;
+ this._form = aForm;
+ this.request = this._client.request;
+}
+exports.EnvironmentClient = EnvironmentClient;
+
+EnvironmentClient.prototype = {
+
+ get actor() {
+ return this._form.actor;
+ },
+ get _transport() { return this._client._transport; },
+
+ /**
+ * Fetches the bindings introduced by this lexical environment.
+ */
+ getBindings: DebuggerClient.requester({
+ type: "bindings"
+ }),
+
+ /**
+ * Changes the value of the identifier whose name is name (a string) to that
+ * represented by value (a grip).
+ */
+ assign: DebuggerClient.requester({
+ type: "assign",
+ name: args(0),
+ value: args(1)
+ })
+};
+
+eventSource(EnvironmentClient.prototype);
diff --git a/devtools/shared/client/moz.build b/devtools/shared/client/moz.build
new file mode 100644
index 000000000..aba5a2bfe
--- /dev/null
+++ b/devtools/shared/client/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'connection-manager.js',
+ 'main.js',
+)
diff --git a/devtools/shared/content-observer.js b/devtools/shared/content-observer.js
new file mode 100644
index 000000000..6bef2ebd5
--- /dev/null
+++ b/devtools/shared/content-observer.js
@@ -0,0 +1,71 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Cc, Ci, Cu, Cr} = require("chrome");
+const Services = require("Services");
+
+const events = require("sdk/event/core");
+
+/**
+ * Handles adding an observer for the creation of content document globals,
+ * event sent immediately after a web content document window has been set up,
+ * but before any script code has been executed.
+ */
+function ContentObserver(tabActor) {
+ this._contentWindow = tabActor.window;
+ this._onContentGlobalCreated = this._onContentGlobalCreated.bind(this);
+ this._onInnerWindowDestroyed = this._onInnerWindowDestroyed.bind(this);
+ this.startListening();
+}
+
+module.exports.ContentObserver = ContentObserver;
+
+ContentObserver.prototype = {
+ /**
+ * Starts listening for the required observer messages.
+ */
+ startListening: function () {
+ Services.obs.addObserver(
+ this._onContentGlobalCreated, "content-document-global-created", false);
+ Services.obs.addObserver(
+ this._onInnerWindowDestroyed, "inner-window-destroyed", false);
+ },
+
+ /**
+ * Stops listening for the required observer messages.
+ */
+ stopListening: function () {
+ Services.obs.removeObserver(
+ this._onContentGlobalCreated, "content-document-global-created", false);
+ Services.obs.removeObserver(
+ this._onInnerWindowDestroyed, "inner-window-destroyed", false);
+ },
+
+ /**
+ * Fired immediately after a web content document window has been set up.
+ */
+ _onContentGlobalCreated: function (subject, topic, data) {
+ if (subject == this._contentWindow) {
+ events.emit(this, "global-created", subject);
+ }
+ },
+
+ /**
+ * Fired when an inner window is removed from the backward/forward cache.
+ */
+ _onInnerWindowDestroyed: function (subject, topic, data) {
+ let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
+ events.emit(this, "global-destroyed", id);
+ }
+};
+
+// Utility functions.
+
+ContentObserver.GetInnerWindowID = function (window) {
+ return window
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID;
+};
diff --git a/devtools/shared/css/color-db.js b/devtools/shared/css/color-db.js
new file mode 100644
index 000000000..5cbf508f1
--- /dev/null
+++ b/devtools/shared/css/color-db.js
@@ -0,0 +1,162 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// /!\ Auto-generated from nsColorNameList.h.
+// This should be kept in sync with that list.
+// test_cssColorDatabase.js tries to enforce this.
+
+const cssColors = {
+ aliceblue: [240, 248, 255, 1],
+ antiquewhite: [250, 235, 215, 1],
+ aqua: [0, 255, 255, 1],
+ aquamarine: [127, 255, 212, 1],
+ azure: [240, 255, 255, 1],
+ beige: [245, 245, 220, 1],
+ bisque: [255, 228, 196, 1],
+ black: [0, 0, 0, 1],
+ blanchedalmond: [255, 235, 205, 1],
+ blue: [0, 0, 255, 1],
+ blueviolet: [138, 43, 226, 1],
+ brown: [165, 42, 42, 1],
+ burlywood: [222, 184, 135, 1],
+ cadetblue: [95, 158, 160, 1],
+ chartreuse: [127, 255, 0, 1],
+ chocolate: [210, 105, 30, 1],
+ coral: [255, 127, 80, 1],
+ cornflowerblue: [100, 149, 237, 1],
+ cornsilk: [255, 248, 220, 1],
+ crimson: [220, 20, 60, 1],
+ cyan: [0, 255, 255, 1],
+ darkblue: [0, 0, 139, 1],
+ darkcyan: [0, 139, 139, 1],
+ darkgoldenrod: [184, 134, 11, 1],
+ darkgray: [169, 169, 169, 1],
+ darkgreen: [0, 100, 0, 1],
+ darkgrey: [169, 169, 169, 1],
+ darkkhaki: [189, 183, 107, 1],
+ darkmagenta: [139, 0, 139, 1],
+ darkolivegreen: [85, 107, 47, 1],
+ darkorange: [255, 140, 0, 1],
+ darkorchid: [153, 50, 204, 1],
+ darkred: [139, 0, 0, 1],
+ darksalmon: [233, 150, 122, 1],
+ darkseagreen: [143, 188, 143, 1],
+ darkslateblue: [72, 61, 139, 1],
+ darkslategray: [47, 79, 79, 1],
+ darkslategrey: [47, 79, 79, 1],
+ darkturquoise: [0, 206, 209, 1],
+ darkviolet: [148, 0, 211, 1],
+ deeppink: [255, 20, 147, 1],
+ deepskyblue: [0, 191, 255, 1],
+ dimgray: [105, 105, 105, 1],
+ dimgrey: [105, 105, 105, 1],
+ dodgerblue: [30, 144, 255, 1],
+ firebrick: [178, 34, 34, 1],
+ floralwhite: [255, 250, 240, 1],
+ forestgreen: [34, 139, 34, 1],
+ fuchsia: [255, 0, 255, 1],
+ gainsboro: [220, 220, 220, 1],
+ ghostwhite: [248, 248, 255, 1],
+ gold: [255, 215, 0, 1],
+ goldenrod: [218, 165, 32, 1],
+ gray: [128, 128, 128, 1],
+ grey: [128, 128, 128, 1],
+ green: [0, 128, 0, 1],
+ greenyellow: [173, 255, 47, 1],
+ honeydew: [240, 255, 240, 1],
+ hotpink: [255, 105, 180, 1],
+ indianred: [205, 92, 92, 1],
+ indigo: [75, 0, 130, 1],
+ ivory: [255, 255, 240, 1],
+ khaki: [240, 230, 140, 1],
+ lavender: [230, 230, 250, 1],
+ lavenderblush: [255, 240, 245, 1],
+ lawngreen: [124, 252, 0, 1],
+ lemonchiffon: [255, 250, 205, 1],
+ lightblue: [173, 216, 230, 1],
+ lightcoral: [240, 128, 128, 1],
+ lightcyan: [224, 255, 255, 1],
+ lightgoldenrodyellow: [250, 250, 210, 1],
+ lightgray: [211, 211, 211, 1],
+ lightgreen: [144, 238, 144, 1],
+ lightgrey: [211, 211, 211, 1],
+ lightpink: [255, 182, 193, 1],
+ lightsalmon: [255, 160, 122, 1],
+ lightseagreen: [32, 178, 170, 1],
+ lightskyblue: [135, 206, 250, 1],
+ lightslategray: [119, 136, 153, 1],
+ lightslategrey: [119, 136, 153, 1],
+ lightsteelblue: [176, 196, 222, 1],
+ lightyellow: [255, 255, 224, 1],
+ lime: [0, 255, 0, 1],
+ limegreen: [50, 205, 50, 1],
+ linen: [250, 240, 230, 1],
+ magenta: [255, 0, 255, 1],
+ maroon: [128, 0, 0, 1],
+ mediumaquamarine: [102, 205, 170, 1],
+ mediumblue: [0, 0, 205, 1],
+ mediumorchid: [186, 85, 211, 1],
+ mediumpurple: [147, 112, 219, 1],
+ mediumseagreen: [60, 179, 113, 1],
+ mediumslateblue: [123, 104, 238, 1],
+ mediumspringgreen: [0, 250, 154, 1],
+ mediumturquoise: [72, 209, 204, 1],
+ mediumvioletred: [199, 21, 133, 1],
+ midnightblue: [25, 25, 112, 1],
+ mintcream: [245, 255, 250, 1],
+ mistyrose: [255, 228, 225, 1],
+ moccasin: [255, 228, 181, 1],
+ navajowhite: [255, 222, 173, 1],
+ navy: [0, 0, 128, 1],
+ oldlace: [253, 245, 230, 1],
+ olive: [128, 128, 0, 1],
+ olivedrab: [107, 142, 35, 1],
+ orange: [255, 165, 0, 1],
+ orangered: [255, 69, 0, 1],
+ orchid: [218, 112, 214, 1],
+ palegoldenrod: [238, 232, 170, 1],
+ palegreen: [152, 251, 152, 1],
+ paleturquoise: [175, 238, 238, 1],
+ palevioletred: [219, 112, 147, 1],
+ papayawhip: [255, 239, 213, 1],
+ peachpuff: [255, 218, 185, 1],
+ peru: [205, 133, 63, 1],
+ pink: [255, 192, 203, 1],
+ plum: [221, 160, 221, 1],
+ powderblue: [176, 224, 230, 1],
+ purple: [128, 0, 128, 1],
+ rebeccapurple: [102, 51, 153, 1],
+ red: [255, 0, 0, 1],
+ rosybrown: [188, 143, 143, 1],
+ royalblue: [65, 105, 225, 1],
+ saddlebrown: [139, 69, 19, 1],
+ salmon: [250, 128, 114, 1],
+ sandybrown: [244, 164, 96, 1],
+ seagreen: [46, 139, 87, 1],
+ seashell: [255, 245, 238, 1],
+ sienna: [160, 82, 45, 1],
+ silver: [192, 192, 192, 1],
+ skyblue: [135, 206, 235, 1],
+ slateblue: [106, 90, 205, 1],
+ slategray: [112, 128, 144, 1],
+ slategrey: [112, 128, 144, 1],
+ snow: [255, 250, 250, 1],
+ springgreen: [0, 255, 127, 1],
+ steelblue: [70, 130, 180, 1],
+ tan: [210, 180, 140, 1],
+ teal: [0, 128, 128, 1],
+ thistle: [216, 191, 216, 1],
+ tomato: [255, 99, 71, 1],
+ turquoise: [64, 224, 208, 1],
+ violet: [238, 130, 238, 1],
+ wheat: [245, 222, 179, 1],
+ white: [255, 255, 255, 1],
+ whitesmoke: [245, 245, 245, 1],
+ yellow: [255, 255, 0, 1],
+ yellowgreen: [154, 205, 50, 1],
+};
+
+exports.cssColors = cssColors;
diff --git a/devtools/shared/css/color.js b/devtools/shared/css/color.js
new file mode 100644
index 000000000..b354043d7
--- /dev/null
+++ b/devtools/shared/css/color.js
@@ -0,0 +1,1117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Services = require("Services");
+
+const {CSS_ANGLEUNIT} = require("devtools/shared/css/properties-db");
+const {getAngleValueInDegrees} = require("devtools/shared/css/parsing-utils");
+
+const {getCSSLexer} = require("devtools/shared/css/lexer");
+const {cssColors} = require("devtools/shared/css/color-db");
+
+const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
+
+const SPECIALVALUES = new Set([
+ "currentcolor",
+ "initial",
+ "inherit",
+ "transparent",
+ "unset"
+]);
+
+/**
+ * This module is used to convert between various color types.
+ *
+ * Usage:
+ * let {colorUtils} = require("devtools/shared/css/color");
+ * let color = new colorUtils.CssColor("red");
+ *
+ * color.authored === "red"
+ * color.hasAlpha === false
+ * color.valid === true
+ * color.transparent === false // transparent has a special status.
+ * color.name === "red" // returns hex when no name available.
+ * color.hex === "#f00" // returns shortHex when available else returns
+ * longHex. If alpha channel is present then we
+ * return this.alphaHex if available,
+ * or this.longAlphaHex if not.
+ * color.alphaHex === "#f00f" // returns short alpha hex when available
+ * else returns longAlphaHex.
+ * color.longHex === "#ff0000" // If alpha channel is present then we return
+ * this.longAlphaHex.
+ * color.longAlphaHex === "#ff0000ff"
+ * color.rgb === "rgb(255, 0, 0)" // If alpha channel is present
+ * // then we return this.rgba.
+ * color.rgba === "rgba(255, 0, 0, 1)"
+ * color.hsl === "hsl(0, 100%, 50%)"
+ * color.hsla === "hsla(0, 100%, 50%, 1)" // If alpha channel is present
+ * then we return this.rgba.
+ *
+ * color.toString() === "#f00"; // Outputs the color type determined in the
+ * COLOR_UNIT_PREF constant (above).
+ * // Color objects can be reused
+ * color.newColor("green") === "#0f0"; // true
+ *
+ * Valid values for COLOR_UNIT_PREF are contained in CssColor.COLORUNIT.
+ */
+
+function CssColor(colorValue) {
+ this.newColor(colorValue);
+}
+
+module.exports.colorUtils = {
+ CssColor: CssColor,
+ rgbToHsl: rgbToHsl,
+ setAlpha: setAlpha,
+ classifyColor: classifyColor,
+ rgbToColorName: rgbToColorName,
+ colorToRGBA: colorToRGBA,
+ isValidCSSColor: isValidCSSColor,
+};
+
+/**
+ * Values used in COLOR_UNIT_PREF
+ */
+CssColor.COLORUNIT = {
+ "authored": "authored",
+ "hex": "hex",
+ "name": "name",
+ "rgb": "rgb",
+ "hsl": "hsl"
+};
+
+CssColor.prototype = {
+ _colorUnit: null,
+ _colorUnitUppercase: false,
+
+ // The value as-authored.
+ authored: null,
+ // A lower-cased copy of |authored|.
+ lowerCased: null,
+
+ _setColorUnitUppercase: function (color) {
+ // Specifically exclude the case where the color is
+ // case-insensitive. This makes it so that "#000" isn't
+ // considered "upper case" for the purposes of color cycling.
+ this._colorUnitUppercase = (color === color.toUpperCase()) &&
+ (color !== color.toLowerCase());
+ },
+
+ get colorUnit() {
+ if (this._colorUnit === null) {
+ let defaultUnit = Services.prefs.getCharPref(COLOR_UNIT_PREF);
+ this._colorUnit = CssColor.COLORUNIT[defaultUnit];
+ this._setColorUnitUppercase(this.authored);
+ }
+ return this._colorUnit;
+ },
+
+ set colorUnit(unit) {
+ this._colorUnit = unit;
+ },
+
+ /**
+ * If the current color unit pref is "authored", then set the
+ * default color unit from the given color. Otherwise, leave the
+ * color unit untouched.
+ *
+ * @param {String} color The color to use
+ */
+ setAuthoredUnitFromColor: function (color) {
+ if (Services.prefs.getCharPref(COLOR_UNIT_PREF) ===
+ CssColor.COLORUNIT.authored) {
+ this._colorUnit = classifyColor(color);
+ this._setColorUnitUppercase(color);
+ }
+ },
+
+ get hasAlpha() {
+ if (!this.valid) {
+ return false;
+ }
+ return this._getRGBATuple().a !== 1;
+ },
+
+ get valid() {
+ return isValidCSSColor(this.authored);
+ },
+
+ /**
+ * Return true for all transparent values e.g. rgba(0, 0, 0, 0).
+ */
+ get transparent() {
+ try {
+ let tuple = this._getRGBATuple();
+ return !(tuple.r || tuple.g || tuple.b || tuple.a);
+ } catch (e) {
+ return false;
+ }
+ },
+
+ get specialValue() {
+ return SPECIALVALUES.has(this.lowerCased) ? this.authored : null;
+ },
+
+ get name() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+
+ try {
+ let tuple = this._getRGBATuple();
+
+ if (tuple.a !== 1) {
+ return this.hex;
+ }
+ let {r, g, b} = tuple;
+ return rgbToColorName(r, g, b);
+ } catch (e) {
+ return this.hex;
+ }
+ },
+
+ get hex() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (this.hasAlpha) {
+ return this.alphaHex;
+ }
+
+ let hex = this.longHex;
+ if (hex.charAt(1) == hex.charAt(2) &&
+ hex.charAt(3) == hex.charAt(4) &&
+ hex.charAt(5) == hex.charAt(6)) {
+ hex = "#" + hex.charAt(1) + hex.charAt(3) + hex.charAt(5);
+ }
+ return hex;
+ },
+
+ get alphaHex() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+
+ let alphaHex = this.longAlphaHex;
+ if (alphaHex.charAt(1) == alphaHex.charAt(2) &&
+ alphaHex.charAt(3) == alphaHex.charAt(4) &&
+ alphaHex.charAt(5) == alphaHex.charAt(6) &&
+ alphaHex.charAt(7) == alphaHex.charAt(8)) {
+ alphaHex = "#" + alphaHex.charAt(1) + alphaHex.charAt(3) +
+ alphaHex.charAt(5) + alphaHex.charAt(7);
+ }
+ return alphaHex;
+ },
+
+ get longHex() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (this.hasAlpha) {
+ return this.longAlphaHex;
+ }
+
+ let tuple = this._getRGBATuple();
+ return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
+ (tuple.b << 0)).toString(16).substr(-6);
+ },
+
+ get longAlphaHex() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+
+ let tuple = this._getRGBATuple();
+ return "#" + ((1 << 24) + (tuple.r << 16) + (tuple.g << 8) +
+ (tuple.b << 0)).toString(16).substr(-6) +
+ Math.round(tuple.a * 255).toString(16).padEnd(2, "0");
+ },
+
+ get rgb() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (!this.hasAlpha) {
+ if (this.lowerCased.startsWith("rgb(")) {
+ // The color is valid and begins with rgb(.
+ return this.authored;
+ }
+ let tuple = this._getRGBATuple();
+ return "rgb(" + tuple.r + ", " + tuple.g + ", " + tuple.b + ")";
+ }
+ return this.rgba;
+ },
+
+ get rgba() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (this.lowerCased.startsWith("rgba(")) {
+ // The color is valid and begins with rgba(.
+ return this.authored;
+ }
+ let components = this._getRGBATuple();
+ return "rgba(" + components.r + ", " +
+ components.g + ", " +
+ components.b + ", " +
+ components.a + ")";
+ },
+
+ get hsl() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (this.lowerCased.startsWith("hsl(")) {
+ // The color is valid and begins with hsl(.
+ return this.authored;
+ }
+ if (this.hasAlpha) {
+ return this.hsla;
+ }
+ return this._hsl();
+ },
+
+ get hsla() {
+ let invalidOrSpecialValue = this._getInvalidOrSpecialValue();
+ if (invalidOrSpecialValue !== false) {
+ return invalidOrSpecialValue;
+ }
+ if (this.lowerCased.startsWith("hsla(")) {
+ // The color is valid and begins with hsla(.
+ return this.authored;
+ }
+ if (this.hasAlpha) {
+ let a = this._getRGBATuple().a;
+ return this._hsl(a);
+ }
+ return this._hsl(1);
+ },
+
+ /**
+ * Check whether the current color value is in the special list e.g.
+ * transparent or invalid.
+ *
+ * @return {String|Boolean}
+ * - If the current color is a special value e.g. "transparent" then
+ * return the color.
+ * - If the color is invalid return an empty string.
+ * - If the color is a regular color e.g. #F06 so we return false
+ * to indicate that the color is neither invalid or special.
+ */
+ _getInvalidOrSpecialValue: function () {
+ if (this.specialValue) {
+ return this.specialValue;
+ }
+ if (!this.valid) {
+ return "";
+ }
+ return false;
+ },
+
+ /**
+ * Change color
+ *
+ * @param {String} color
+ * Any valid color string
+ */
+ newColor: function (color) {
+ // Store a lower-cased version of the color to help with format
+ // testing. The original text is kept as well so it can be
+ // returned when needed.
+ this.lowerCased = color.toLowerCase();
+ this.authored = color;
+ this._setColorUnitUppercase(color);
+ return this;
+ },
+
+ nextColorUnit: function () {
+ // Reorder the formats array to have the current format at the
+ // front so we can cycle through.
+ let formats = ["hex", "hsl", "rgb", "name"];
+ let currentFormat = classifyColor(this.toString());
+ let putOnEnd = formats.splice(0, formats.indexOf(currentFormat));
+ formats = formats.concat(putOnEnd);
+ let currentDisplayedColor = this[formats[0]];
+
+ for (let format of formats) {
+ if (this[format].toLowerCase() !== currentDisplayedColor.toLowerCase()) {
+ this.colorUnit = CssColor.COLORUNIT[format];
+ break;
+ }
+ }
+
+ return this.toString();
+ },
+
+ /**
+ * Return a string representing a color of type defined in COLOR_UNIT_PREF.
+ */
+ toString: function () {
+ let color;
+
+ switch (this.colorUnit) {
+ case CssColor.COLORUNIT.authored:
+ color = this.authored;
+ break;
+ case CssColor.COLORUNIT.hex:
+ color = this.hex;
+ break;
+ case CssColor.COLORUNIT.hsl:
+ color = this.hsl;
+ break;
+ case CssColor.COLORUNIT.name:
+ color = this.name;
+ break;
+ case CssColor.COLORUNIT.rgb:
+ color = this.rgb;
+ break;
+ default:
+ color = this.rgb;
+ }
+
+ if (this._colorUnitUppercase &&
+ this.colorUnit != CssColor.COLORUNIT.authored) {
+ color = color.toUpperCase();
+ }
+
+ return color;
+ },
+
+ /**
+ * Returns a RGBA 4-Tuple representation of a color or transparent as
+ * appropriate.
+ */
+ _getRGBATuple: function () {
+ let tuple = colorToRGBA(this.authored);
+
+ tuple.a = parseFloat(tuple.a.toFixed(1));
+
+ return tuple;
+ },
+
+ _hsl: function (maybeAlpha) {
+ if (this.lowerCased.startsWith("hsl(") && maybeAlpha === undefined) {
+ // We can use it as-is.
+ return this.authored;
+ }
+
+ let {r, g, b} = this._getRGBATuple();
+ let [h, s, l] = rgbToHsl([r, g, b]);
+ if (maybeAlpha !== undefined) {
+ return "hsla(" + h + ", " + s + "%, " + l + "%, " + maybeAlpha + ")";
+ }
+ return "hsl(" + h + ", " + s + "%, " + l + "%)";
+ },
+
+ /**
+ * This method allows comparison of CssColor objects using ===.
+ */
+ valueOf: function () {
+ return this.rgba;
+ },
+};
+
+/**
+ * Convert rgb value to hsl
+ *
+ * @param {array} rgb
+ * Array of rgb values
+ * @return {array}
+ * Array of hsl values.
+ */
+function rgbToHsl([r, g, b]) {
+ r = r / 255;
+ g = g / 255;
+ b = b / 255;
+
+ let max = Math.max(r, g, b);
+ let min = Math.min(r, g, b);
+ let h;
+ let s;
+ let l = (max + min) / 2;
+
+ if (max == min) {
+ h = s = 0;
+ } else {
+ let d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+
+ switch (max) {
+ case r:
+ h = ((g - b) / d) % 6;
+ break;
+ case g:
+ h = (b - r) / d + 2;
+ break;
+ case b:
+ h = (r - g) / d + 4;
+ break;
+ }
+ h *= 60;
+ if (h < 0) {
+ h += 360;
+ }
+ }
+
+ return [roundTo(h, 1), roundTo(s * 100, 1), roundTo(l * 100, 1)];
+}
+
+function roundTo(number, digits) {
+ const multiplier = Math.pow(10, digits);
+ return Math.round(number * multiplier) / multiplier;
+}
+
+/**
+ * Takes a color value of any type (hex, hsl, hsla, rgb, rgba)
+ * and an alpha value to generate an rgba string with the correct
+ * alpha value.
+ *
+ * @param {String} colorValue
+ * Color in the form of hex, hsl, hsla, rgb, rgba.
+ * @param {Number} alpha
+ * Alpha value for the color, between 0 and 1.
+ * @return {String}
+ * Converted color with `alpha` value in rgba form.
+ */
+function setAlpha(colorValue, alpha) {
+ let color = new CssColor(colorValue);
+
+ // Throw if the color supplied is not valid.
+ if (!color.valid) {
+ throw new Error("Invalid color.");
+ }
+
+ // If an invalid alpha valid, just set to 1.
+ if (!(alpha >= 0 && alpha <= 1)) {
+ alpha = 1;
+ }
+
+ let { r, g, b } = color._getRGBATuple();
+ return "rgba(" + r + ", " + g + ", " + b + ", " + alpha + ")";
+}
+
+/**
+ * Given a color, classify its type as one of the possible color
+ * units, as known by |CssColor.colorUnit|.
+ *
+ * @param {String} value
+ * The color, in any form accepted by CSS.
+ * @return {String}
+ * The color classification, one of "rgb", "hsl", "hex", or "name".
+ */
+function classifyColor(value) {
+ value = value.toLowerCase();
+ if (value.startsWith("rgb(") || value.startsWith("rgba(")) {
+ return CssColor.COLORUNIT.rgb;
+ } else if (value.startsWith("hsl(") || value.startsWith("hsla(")) {
+ return CssColor.COLORUNIT.hsl;
+ } else if (/^#[0-9a-f]+$/.exec(value)) {
+ return CssColor.COLORUNIT.hex;
+ }
+ return CssColor.COLORUNIT.name;
+}
+
+// This holds a map from colors back to color names for use by
+// rgbToColorName.
+var cssRGBMap;
+
+/**
+ * Given a color, return its name, if it has one. Throws an exception
+ * if the color does not have a name.
+ *
+ * @param {Number} r, g, b The color components.
+ * @return {String} the name of the color
+ */
+function rgbToColorName(r, g, b) {
+ if (!cssRGBMap) {
+ cssRGBMap = {};
+ for (let name in cssColors) {
+ let key = JSON.stringify(cssColors[name]);
+ if (!(key in cssRGBMap)) {
+ cssRGBMap[key] = name;
+ }
+ }
+ }
+ let value = cssRGBMap[JSON.stringify([r, g, b, 1])];
+ if (!value) {
+ throw new Error("no such color");
+ }
+ return value;
+}
+
+// Translated from nsColor.cpp.
+function _hslValue(m1, m2, h) {
+ if (h < 0.0) {
+ h += 1.0;
+ }
+ if (h > 1.0) {
+ h -= 1.0;
+ }
+ if (h < 1.0 / 6.0) {
+ return m1 + (m2 - m1) * h * 6.0;
+ }
+ if (h < 1.0 / 2.0) {
+ return m2;
+ }
+ if (h < 2.0 / 3.0) {
+ return m1 + (m2 - m1) * (2.0 / 3.0 - h) * 6.0;
+ }
+ return m1;
+}
+
+// Translated from nsColor.cpp. All three values are expected to be
+// in the range 0-1.
+function hslToRGB([h, s, l]) {
+ let r, g, b;
+ let m1, m2;
+ if (l <= 0.5) {
+ m2 = l * (s + 1);
+ } else {
+ m2 = l + s - l * s;
+ }
+ m1 = l * 2 - m2;
+ r = Math.round(255 * _hslValue(m1, m2, h + 1.0 / 3.0));
+ g = Math.round(255 * _hslValue(m1, m2, h));
+ b = Math.round(255 * _hslValue(m1, m2, h - 1.0 / 3.0));
+ return [r, g, b];
+}
+
+/**
+ * A helper function to convert a hex string like "F0C" or "F0C8" to a color.
+ *
+ * @param {String} name the color string
+ * @return {Object} an object of the form {r, g, b, a}; or null if the
+ * name was not a valid color
+ */
+function hexToRGBA(name) {
+ let r, g, b, a = 1;
+
+ if (name.length === 3) {
+ // short hex string (e.g. F0C)
+ r = parseInt(name.charAt(0) + name.charAt(0), 16);
+ g = parseInt(name.charAt(1) + name.charAt(1), 16);
+ b = parseInt(name.charAt(2) + name.charAt(2), 16);
+ } else if (name.length === 4) {
+ // short alpha hex string (e.g. F0CA)
+ r = parseInt(name.charAt(0) + name.charAt(0), 16);
+ g = parseInt(name.charAt(1) + name.charAt(1), 16);
+ b = parseInt(name.charAt(2) + name.charAt(2), 16);
+ a = parseInt(name.charAt(3) + name.charAt(3), 16) / 255;
+ } else if (name.length === 6) {
+ // hex string (e.g. FD01CD)
+ r = parseInt(name.charAt(0) + name.charAt(1), 16);
+ g = parseInt(name.charAt(2) + name.charAt(3), 16);
+ b = parseInt(name.charAt(4) + name.charAt(5), 16);
+ } else if (name.length === 8) {
+ // alpha hex string (e.g. FD01CDAB)
+ r = parseInt(name.charAt(0) + name.charAt(1), 16);
+ g = parseInt(name.charAt(2) + name.charAt(3), 16);
+ b = parseInt(name.charAt(4) + name.charAt(5), 16);
+ a = parseInt(name.charAt(6) + name.charAt(7), 16) / 255;
+ } else {
+ return null;
+ }
+ a = Math.round(a * 10) / 10;
+ return {r, g, b, a};
+}
+
+/**
+ * A helper function to clamp a value.
+ *
+ * @param {Number} value The value to clamp
+ * @param {Number} min The minimum value
+ * @param {Number} max The maximum value
+ * @return {Number} A value between min and max
+ */
+function clamp(value, min, max) {
+ if (value < min) {
+ value = min;
+ }
+ if (value > max) {
+ value = max;
+ }
+ return value;
+}
+
+/**
+ * A helper function to get a token from a lexer, skipping comments
+ * and whitespace.
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @return {CSSToken} The next non-whitespace, non-comment token; or
+ * null at EOF.
+ */
+function getToken(lexer) {
+ if (lexer._hasPushBackToken) {
+ lexer._hasPushBackToken = false;
+ return lexer._currentToken;
+ }
+
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token || (token.tokenType !== "comment" &&
+ token.tokenType !== "whitespace")) {
+ lexer._currentToken = token;
+ return token;
+ }
+ }
+}
+
+/**
+ * A helper function to put a token back to lexer for the next call of
+ * getToken().
+ *
+ * @param {CSSLexer} lexer The lexer
+ */
+function unGetToken(lexer) {
+ if (lexer._hasPushBackToken) {
+ throw new Error("Double pushback.");
+ }
+ lexer._hasPushBackToken = true;
+}
+
+/**
+ * A helper function that checks if the next token matches symbol.
+ * If so, reads the token and returns true. If not, pushes the
+ * token back and returns false.
+ *
+ * @param {CSSLexer} lexer The lexer.
+ * @param {String} symbol The symbol.
+ * @return {Boolean} The expect symbol is parsed or not.
+ */
+function expectSymbol(lexer, symbol) {
+ let token = getToken(lexer);
+ if (!token) {
+ return false;
+ }
+
+ if (token.tokenType !== "symbol" || token.text !== symbol) {
+ unGetToken(lexer);
+ return false;
+ }
+
+ return true;
+}
+
+const COLOR_COMPONENT_TYPE = {
+ "integer": "integer",
+ "number": "number",
+ "percentage": "percentage",
+};
+
+/**
+ * Parse a <integer> or a <number> or a <percentage> color component. If
+ * |separator| is provided (not an empty string ""), this function will also
+ * attempt to parse that character after parsing the color component. The range
+ * of output component value is [0, 1] if the component type is percentage.
+ * Otherwise, the range is [0, 255].
+ *
+ * @param {CSSLexer} lexer The lexer.
+ * @param {COLOR_COMPONENT_TYPE} type The color component type.
+ * @param {String} separator The separator.
+ * @param {Array} colorArray [out] The parsed color component will push into this array.
+ * @return {Boolean} Return false on error.
+ */
+function parseColorComponent(lexer, type, separator, colorArray) {
+ let token = getToken(lexer);
+
+ if (!token) {
+ return false;
+ }
+
+ switch (type) {
+ case COLOR_COMPONENT_TYPE.integer:
+ if (token.tokenType !== "number" || !token.isInteger) {
+ return false;
+ }
+ break;
+ case COLOR_COMPONENT_TYPE.number:
+ if (token.tokenType !== "number") {
+ return false;
+ }
+ break;
+ case COLOR_COMPONENT_TYPE.percentage:
+ if (token.tokenType !== "percentage") {
+ return false;
+ }
+ break;
+ default:
+ throw new Error("Invalid color component type.");
+ }
+
+ let colorComponent = 0;
+ if (type === COLOR_COMPONENT_TYPE.percentage) {
+ colorComponent = clamp(token.number, 0, 1);
+ } else {
+ colorComponent = clamp(token.number, 0, 255);
+ }
+
+ if (separator !== "" && !expectSymbol(lexer, separator)) {
+ return false;
+ }
+
+ colorArray.push(colorComponent);
+
+ return true;
+}
+
+/**
+ * Parse an optional [ separator <alpha-value> ] expression, followed by a
+ * close-parenthesis, at the end of a css color function (e.g. rgba() or hsla()).
+ * If this function simply encounters a close-parenthesis (without the
+ * [ separator <alpha-value> ]), it will still succeed. Then put a fully-opaque
+ * alpha value into the colorArray. The range of output alpha value is [0, 1].
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @param {String} separator The separator.
+ * @param {Array} colorArray [out] The parsed color component will push into this array.
+ * @return {Boolean} Return false on error.
+ */
+function parseColorOpacityAndCloseParen(lexer, separator, colorArray) {
+ // The optional [separator <alpha-value>] was omitted, so set the opacity
+ // to a fully-opaque value '1.0' and return success.
+ if (expectSymbol(lexer, ")")) {
+ colorArray.push(1);
+ return true;
+ }
+
+ if (!expectSymbol(lexer, separator)) {
+ return false;
+ }
+
+ let token = getToken(lexer);
+ if (!token) {
+ return false;
+ }
+
+ // <number> or <percentage>
+ if (token.tokenType !== "number" && token.tokenType !== "percentage") {
+ return false;
+ }
+
+ if (!expectSymbol(lexer, ")")) {
+ return false;
+ }
+
+ colorArray.push(clamp(token.number, 0, 1));
+
+ return true;
+}
+
+/**
+ * Parse a hue value.
+ * <hue> = <number> | <angle>
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @param {Array} colorArray [out] The parsed color component will push into this array.
+ * @return {Boolean} Return false on error.
+ */
+function parseHue(lexer, colorArray) {
+ let token = getToken(lexer);
+
+ if (!token) {
+ return false;
+ }
+
+ let val = 0;
+ if (token.tokenType === "number") {
+ val = token.number;
+ } else if (token.tokenType === "dimension" && token.text in CSS_ANGLEUNIT) {
+ val = getAngleValueInDegrees(token.number, token.text);
+ } else {
+ return false;
+ }
+
+ val = val / 360.0;
+ colorArray.push(val - Math.floor(val));
+
+ return true;
+}
+
+/**
+ * A helper function to parse the color components of hsl()/hsla() function.
+ * hsl() and hsla() are now aliases.
+ *
+ * @param {CSSLexer} lexer The lexer
+ * @return {Array} An array of the form [r,g,b,a]; or null on error.
+ */
+function parseHsl(lexer) {
+ // comma-less expression:
+ // hsl() = hsl( <hue> <saturation> <lightness> [ / <alpha-value> ]? )
+ // the expression with comma:
+ // hsl() = hsl( <hue>, <saturation>, <lightness>, <alpha-value>? )
+ //
+ // <hue> = <number> | <angle>
+ // <alpha-value> = <number> | <percentage>
+
+ const commaSeparator = ",";
+ let hsl = [];
+ let a = [];
+
+ // Parse hue.
+ if (!parseHue(lexer, hsl)) {
+ return null;
+ }
+
+ // Look for a comma separator after "hue" component to determine if the
+ // expression is comma-less or not.
+ let hasComma = expectSymbol(lexer, commaSeparator);
+
+ // Parse saturation, lightness and opacity.
+ // The saturation and lightness are <percentage>, so reuse the <percentage>
+ // version of parseColorComponent function for them. No need to check the
+ // separator after 'lightness'. It will be checked in opacity value parsing.
+ let separatorBeforeAlpha = hasComma ? commaSeparator : "/";
+ if (parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage,
+ hasComma ? commaSeparator : "", hsl) &&
+ parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage, "", hsl) &&
+ parseColorOpacityAndCloseParen(lexer, separatorBeforeAlpha, a)) {
+ return [...hslToRGB(hsl), ...a];
+ }
+
+ return null;
+}
+
+/**
+ * A helper function to parse the color arguments of old style hsl()/hsla()
+ * function.
+ *
+ * @param {CSSLexer} lexer The lexer.
+ * @param {Boolean} hasAlpha The color function has alpha component or not.
+ * @return {Array} An array of the form [r,g,b,a]; or null on error.
+ */
+function parseOldStyleHsl(lexer, hasAlpha) {
+ // hsla() = hsla( <hue>, <saturation>, <lightness>, <alpha-value> )
+ // hsl() = hsl( <hue>, <saturation>, <lightness> )
+ //
+ // <hue> = <number>
+ // <alpha-value> = <number>
+
+ const commaSeparator = ",";
+ const closeParen = ")";
+ let hsl = [];
+ let a = [];
+
+ // Parse hue.
+ let token = getToken(lexer);
+ if (!token || token.tokenType !== "number") {
+ return null;
+ }
+ if (!expectSymbol(lexer, commaSeparator)) {
+ return null;
+ }
+ let val = token.number / 360.0;
+ hsl.push(val - Math.floor(val));
+
+ // Parse saturation, lightness and opacity.
+ // The saturation and lightness are <percentage>, so reuse the <percentage>
+ // version of parseColorComponent function for them. The opacity is <number>
+ if (hasAlpha) {
+ if (parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage,
+ commaSeparator, hsl) &&
+ parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage,
+ commaSeparator, hsl) &&
+ parseColorComponent(lexer, COLOR_COMPONENT_TYPE.number,
+ closeParen, a)) {
+ return [...hslToRGB(hsl), ...a];
+ }
+ } else if (parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage,
+ commaSeparator, hsl) &&
+ parseColorComponent(lexer, COLOR_COMPONENT_TYPE.percentage,
+ closeParen, hsl)) {
+ return [...hslToRGB(hsl), 1];
+ }
+
+ return null;
+}
+
+/**
+ * A helper function to parse the color arguments of rgb()/rgba() function.
+ * rgb() and rgba() now are aliases.
+ *
+ * @param {CSSLexer} lexer The lexer.
+ * @return {Array} An array of the form [r,g,b,a]; or null on error.
+ */
+function parseRgb(lexer) {
+ // comma-less expression:
+ // rgb() = rgb( component{3} [ / <alpha-value> ]? )
+ // the expression with comma:
+ // rgb() = rgb( component#{3} , <alpha-value>? )
+ //
+ // component = <number> | <percentage>
+ // <alpa-value> = <number> | <percentage>
+
+ const commaSeparator = ",";
+ let rgba = [];
+
+ let token = getToken(lexer);
+ if (token.tokenType !== "percentage" && token.tokenType !== "number") {
+ return null;
+ }
+ unGetToken(lexer);
+ let type = (token.tokenType === "percentage") ?
+ COLOR_COMPONENT_TYPE.percentage :
+ COLOR_COMPONENT_TYPE.number;
+
+ // Parse R.
+ if (!parseColorComponent(lexer, type, "", rgba)) {
+ return null;
+ }
+ let hasComma = expectSymbol(lexer, commaSeparator);
+
+ // Parse G, B and A.
+ // No need to check the separator after 'B'. It will be checked in 'A' values
+ // parsing.
+ let separatorBeforeAlpha = hasComma ? commaSeparator : "/";
+ if (parseColorComponent(lexer, type, hasComma ? commaSeparator : "", rgba) &&
+ parseColorComponent(lexer, type, "", rgba) &&
+ parseColorOpacityAndCloseParen(lexer, separatorBeforeAlpha, rgba)) {
+ if (type === COLOR_COMPONENT_TYPE.percentage) {
+ rgba[0] = Math.round(255 * rgba[0]);
+ rgba[1] = Math.round(255 * rgba[1]);
+ rgba[2] = Math.round(255 * rgba[2]);
+ }
+ return rgba;
+ }
+
+ return null;
+}
+
+/**
+ * A helper function to parse the color arguments of old style rgb()/rgba()
+ * function.
+ *
+ * @param {CSSLexer} lexer The lexer.
+ * @param {Boolean} hasAlpha The color function has alpha component or not.
+ * @return {Array} An array of the form [r,g,b,a]; or null on error.
+ */
+function parseOldStyleRgb(lexer, hasAlpha) {
+ // rgba() = rgba( component#{3} , <alpha-value> )
+ // rgb() = rgb( component#{3} )
+ //
+ // component = <integer> | <percentage>
+ // <alpha-value> = <number>
+
+ const commaSeparator = ",";
+ const closeParen = ")";
+ let rgba = [];
+
+ let token = getToken(lexer);
+ if (token.tokenType !== "percentage" &&
+ (token.tokenType !== "number" || !token.isInteger)) {
+ return null;
+ }
+ unGetToken(lexer);
+ let type = (token.tokenType === "percentage") ?
+ COLOR_COMPONENT_TYPE.percentage :
+ COLOR_COMPONENT_TYPE.integer;
+
+ // Parse R. G, B and A.
+ if (hasAlpha) {
+ if (!parseColorComponent(lexer, type, commaSeparator, rgba) ||
+ !parseColorComponent(lexer, type, commaSeparator, rgba) ||
+ !parseColorComponent(lexer, type, commaSeparator, rgba) ||
+ !parseColorComponent(lexer, COLOR_COMPONENT_TYPE.number,
+ closeParen, rgba)) {
+ return null;
+ }
+ } else if (!parseColorComponent(lexer, type, commaSeparator, rgba) ||
+ !parseColorComponent(lexer, type, commaSeparator, rgba) ||
+ !parseColorComponent(lexer, type, closeParen, rgba)) {
+ return null;
+ }
+
+ if (type === COLOR_COMPONENT_TYPE.percentage) {
+ rgba[0] = Math.round(255 * rgba[0]);
+ rgba[1] = Math.round(255 * rgba[1]);
+ rgba[2] = Math.round(255 * rgba[2]);
+ }
+ if (!hasAlpha) {
+ rgba.push(1);
+ }
+
+ return rgba;
+}
+
+/**
+ * Convert a string representing a color to an object holding the
+ * color's components. Any valid CSS color form can be passed in.
+ *
+ * @param {String} name the color
+ * @param {Boolean} oldColorFunctionSyntax use old color function syntax or the
+ * css-color-4 syntax
+ * @return {Object} an object of the form {r, g, b, a}; or null if the
+ * name was not a valid color
+ */
+function colorToRGBA(name, oldColorFunctionSyntax = true) {
+ name = name.trim().toLowerCase();
+
+ if (name in cssColors) {
+ let result = cssColors[name];
+ return {r: result[0], g: result[1], b: result[2], a: result[3]};
+ } else if (name === "transparent") {
+ return {r: 0, g: 0, b: 0, a: 0};
+ } else if (name === "currentcolor") {
+ return {r: 0, g: 0, b: 0, a: 1};
+ }
+
+ let lexer = getCSSLexer(name);
+
+ let func = getToken(lexer);
+ if (!func) {
+ return null;
+ }
+
+ if (func.tokenType === "id" || func.tokenType === "hash") {
+ if (getToken(lexer) !== null) {
+ return null;
+ }
+ return hexToRGBA(func.text);
+ }
+
+ const expectedFunctions = ["rgba", "rgb", "hsla", "hsl"];
+ if (!func || func.tokenType !== "function" ||
+ !expectedFunctions.includes(func.text)) {
+ return null;
+ }
+
+ let hsl = func.text === "hsl" || func.text === "hsla";
+
+ let vals;
+ if (oldColorFunctionSyntax) {
+ let hasAlpha = (func.text === "rgba" || func.text === "hsla");
+ vals = hsl ? parseOldStyleHsl(lexer, hasAlpha) : parseOldStyleRgb(lexer, hasAlpha);
+ } else {
+ vals = hsl ? parseHsl(lexer) : parseRgb(lexer);
+ }
+
+ if (!vals) {
+ return null;
+ }
+ if (getToken(lexer) !== null) {
+ return null;
+ }
+
+ return {r: vals[0], g: vals[1], b: vals[2], a: vals[3]};
+}
+
+/**
+ * Check whether a string names a valid CSS color.
+ *
+ * @param {String} name The string to check
+ * @return {Boolean} True if the string is a CSS color name.
+ */
+function isValidCSSColor(name) {
+ return colorToRGBA(name) !== null;
+}
diff --git a/devtools/shared/css/generated/generate-properties-db.js b/devtools/shared/css/generated/generate-properties-db.js
new file mode 100644
index 000000000..ad8dfb0ab
--- /dev/null
+++ b/devtools/shared/css/generated/generate-properties-db.js
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/*
+ * This is an xpcshell script that runs to generate a static list of CSS properties
+ * as known by the platform. It is run from ./mach_commands.py by running
+ * `mach devtools-css-db`.
+ */
+var {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+var {generateCssProperties} = require("devtools/server/actors/css-properties");
+
+// xpcshell can output extra information, so place some delimiter text between
+// the output of the css properties database.
+dump("DEVTOOLS_CSS_DB_DELIMITER");
+
+// Output JSON
+dump(JSON.stringify({
+ cssProperties: cssProperties(),
+ pseudoElements: pseudoElements()
+}));
+
+dump("DEVTOOLS_CSS_DB_DELIMITER");
+
+/*
+ * A list of CSS Properties and their various characteristics. This is used on the
+ * client-side when the CssPropertiesActor is not found, or when the client and server
+ * are the same version. A single property takes the form:
+ *
+ * "animation": {
+ * "isInherited": false,
+ * "supports": [ 7, 9, 10 ]
+ * }
+ */
+function cssProperties() {
+ const properties = generateCssProperties();
+ for (let key in properties) {
+ // Ignore OS-specific properties
+ if (key.indexOf("-moz-osx-") !== -1) {
+ properties[key] = undefined;
+ }
+ }
+ return properties;
+}
+
+/**
+ * The list of all CSS Pseudo Elements.
+ */
+function pseudoElements() {
+ const {classes: Cc, interfaces: Ci} = Components;
+ const domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Ci.inIDOMUtils);
+ return domUtils.getCSSPseudoElementNames();
+}
diff --git a/devtools/shared/css/generated/mach_commands.py b/devtools/shared/css/generated/mach_commands.py
new file mode 100644
index 000000000..4d6016276
--- /dev/null
+++ b/devtools/shared/css/generated/mach_commands.py
@@ -0,0 +1,91 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+"""
+This script implements the `mach devtools-css-db` command. It runs an xpcshell script
+that uses inIDOMUtils to query the CSS properties used by the browser. This information
+is used to generate the properties-db.js file.
+"""
+
+import json
+import os
+import sys
+import string
+import subprocess
+from mozbuild.base import (
+ MozbuildObject,
+ MachCommandBase,
+)
+from mach.decorators import (
+ CommandProvider,
+ Command,
+)
+
+def resolve_path(start, relativePath):
+ """Helper to resolve a path from a start, and a relative path"""
+ return os.path.normpath(os.path.join(start, relativePath))
+
+def stringify(obj):
+ """Helper to stringify to JSON"""
+ return json.dumps(obj, sort_keys=True, indent=2, separators=(',', ': '))
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+ @Command(
+ 'devtools-css-db', category='post-build',
+ description='Rebuild the devtool\'s static css properties database.')
+ def generate_css_db(self):
+ """Generate the static css properties database for devtools and write it to file."""
+
+ print("Re-generating the css properties database...")
+ db = self.get_properties_db_from_xpcshell()
+
+ self.output_template({
+ 'cssProperties': stringify(db['cssProperties']),
+ 'pseudoElements': stringify(db['pseudoElements'])})
+
+ def get_properties_db_from_xpcshell(self):
+ """Generate the static css properties db for devtools from an xpcshell script."""
+ build = MozbuildObject.from_environment()
+
+ # Get the paths
+ script_path = resolve_path(self.topsrcdir,
+ 'devtools/shared/css/generated/generate-properties-db.js')
+ gre_path = resolve_path(self.topobjdir, 'dist/bin')
+ browser_path = resolve_path(self.topobjdir, 'dist/bin/browser')
+ xpcshell_path = build.get_binary_path(what='xpcshell')
+ print(browser_path)
+
+ sub_env = dict(os.environ)
+ if sys.platform.startswith('linux'):
+ sub_env["LD_LIBRARY_PATH"] = gre_path
+
+ # Run the xcpshell script, and set the appdir flag to the browser path so that
+ # we have the proper dependencies for requiring the loader.
+ contents = subprocess.check_output([xpcshell_path, '-g', gre_path,
+ '-a', browser_path, script_path],
+ env = sub_env)
+ # Extract just the output between the delimiters as the xpcshell output can
+ # have extra output that we don't want.
+ contents = contents.split('DEVTOOLS_CSS_DB_DELIMITER')[1]
+
+ return json.loads(contents)
+
+ def output_template(self, substitutions):
+ """Output a the properties-db.js from a template."""
+ js_template_path = resolve_path(self.topsrcdir,
+ 'devtools/shared/css/generated/properties-db.js.in')
+ destination_path = resolve_path(self.topsrcdir,
+ 'devtools/shared/css/generated/properties-db.js')
+
+ with open(js_template_path, 'rb') as handle:
+ js_template = handle.read()
+
+ preamble = '/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */\n\n'
+ contents = string.Template(js_template).substitute(substitutions)
+
+ with open(destination_path, 'wb') as destination:
+ destination.write(preamble + contents)
+
+ print('The database was successfully generated at ' + destination_path)
diff --git a/devtools/shared/css/generated/moz.build b/devtools/shared/css/generated/moz.build
new file mode 100644
index 000000000..7f01281db
--- /dev/null
+++ b/devtools/shared/css/generated/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'properties-db.js',
+)
diff --git a/devtools/shared/css/generated/properties-db.js b/devtools/shared/css/generated/properties-db.js
new file mode 100644
index 000000000..83b3efafc
--- /dev/null
+++ b/devtools/shared/css/generated/properties-db.js
@@ -0,0 +1,9367 @@
+/* THIS IS AN AUTOGENERATED FILE. DO NOT EDIT */
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * This file is automatically generated by `mach devtools-css-db`. It contains
+ * a static list of CSS properties that can be computed by Gecko. The actual script
+ * to generate these files can be found at devtools/shared/css/generate-properties-db.js.
+ */
+
+/**
+ * A list of CSS Properties and their various characteristics.
+ */
+exports.CSS_PROPERTIES = {
+ "-moz-animation": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ "animation-name"
+ ],
+ "supports": [
+ 7,
+ 9,
+ 10
+ ],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "backwards",
+ "both",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "forwards",
+ "infinite",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "normal",
+ "paused",
+ "reverse",
+ "running",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-moz-animation-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-animation-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-direction"
+ ],
+ "supports": [],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "inherit",
+ "initial",
+ "normal",
+ "reverse",
+ "unset"
+ ]
+ },
+ "-moz-animation-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-animation-fill-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-fill-mode"
+ ],
+ "supports": [],
+ "values": [
+ "backwards",
+ "both",
+ "forwards",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-animation-iteration-count": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-iteration-count"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "infinite",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-animation-name": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-name"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-animation-play-state": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-play-state"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "paused",
+ "running",
+ "unset"
+ ]
+ },
+ "-moz-animation-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-moz-appearance": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-appearance"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-gtk-info-bar",
+ "-moz-mac-disclosure-button-closed",
+ "-moz-mac-disclosure-button-open",
+ "-moz-mac-fullscreen-button",
+ "-moz-mac-help-button",
+ "-moz-mac-source-list",
+ "-moz-mac-vibrancy-dark",
+ "-moz-mac-vibrancy-light",
+ "-moz-win-borderless-glass",
+ "-moz-win-browsertabbar-toolbox",
+ "-moz-win-communications-toolbox",
+ "-moz-win-exclude-glass",
+ "-moz-win-glass",
+ "-moz-win-media-toolbox",
+ "-moz-window-button-box",
+ "-moz-window-button-box-maximized",
+ "-moz-window-button-close",
+ "-moz-window-button-maximize",
+ "-moz-window-button-minimize",
+ "-moz-window-button-restore",
+ "-moz-window-frame-bottom",
+ "-moz-window-frame-left",
+ "-moz-window-frame-right",
+ "-moz-window-titlebar",
+ "-moz-window-titlebar-maximized",
+ "button",
+ "button-arrow-down",
+ "button-arrow-next",
+ "button-arrow-previous",
+ "button-arrow-up",
+ "button-bevel",
+ "button-focus",
+ "caret",
+ "checkbox",
+ "checkbox-container",
+ "checkbox-label",
+ "checkmenuitem",
+ "dialog",
+ "dualbutton",
+ "groupbox",
+ "inherit",
+ "initial",
+ "listbox",
+ "listitem",
+ "menuarrow",
+ "menubar",
+ "menucheckbox",
+ "menuimage",
+ "menuitem",
+ "menuitemtext",
+ "menulist",
+ "menulist-button",
+ "menulist-text",
+ "menulist-textfield",
+ "menupopup",
+ "menuradio",
+ "menuseparator",
+ "meterbar",
+ "meterchunk",
+ "none",
+ "number-input",
+ "progressbar",
+ "progressbar-vertical",
+ "progresschunk",
+ "progresschunk-vertical",
+ "radio",
+ "radio-container",
+ "radio-label",
+ "radiomenuitem",
+ "range",
+ "range-thumb",
+ "resizer",
+ "resizerpanel",
+ "scale-horizontal",
+ "scale-vertical",
+ "scalethumb-horizontal",
+ "scalethumb-vertical",
+ "scalethumbend",
+ "scalethumbstart",
+ "scalethumbtick",
+ "scrollbar",
+ "scrollbar-horizontal",
+ "scrollbar-small",
+ "scrollbar-vertical",
+ "scrollbarbutton-down",
+ "scrollbarbutton-left",
+ "scrollbarbutton-right",
+ "scrollbarbutton-up",
+ "scrollbarthumb-horizontal",
+ "scrollbarthumb-vertical",
+ "scrollbartrack-horizontal",
+ "scrollbartrack-vertical",
+ "searchfield",
+ "separator",
+ "spinner",
+ "spinner-downbutton",
+ "spinner-textfield",
+ "spinner-upbutton",
+ "splitter",
+ "statusbar",
+ "statusbarpanel",
+ "tab",
+ "tab-scroll-arrow-back",
+ "tab-scroll-arrow-forward",
+ "tabpanel",
+ "tabpanels",
+ "textfield",
+ "textfield-multiline",
+ "toolbar",
+ "toolbarbutton",
+ "toolbarbutton-dropdown",
+ "toolbargripper",
+ "toolbox",
+ "tooltip",
+ "treeheader",
+ "treeheadercell",
+ "treeheadersortarrow",
+ "treeitem",
+ "treeline",
+ "treetwisty",
+ "treetwistyopen",
+ "treeview",
+ "unset",
+ "window"
+ ]
+ },
+ "-moz-backface-visibility": {
+ "isInherited": false,
+ "subproperties": [
+ "backface-visibility"
+ ],
+ "supports": [],
+ "values": [
+ "hidden",
+ "inherit",
+ "initial",
+ "unset",
+ "visible"
+ ]
+ },
+ "-moz-binding": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-binding"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "-moz-border-bottom-colors": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-border-bottom-colors"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-border-end": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-width",
+ "border-inline-end-style",
+ "border-inline-end-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-border-end-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-border-end-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "-moz-border-end-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "-moz-border-image": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "-moz-border-left-colors": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-border-left-colors"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-border-right-colors": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-border-right-colors"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-border-start": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-width",
+ "border-inline-start-style",
+ "border-inline-start-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-border-start-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-border-start-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "-moz-border-start-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "-moz-border-top-colors": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-border-top-colors"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-box-align": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-align"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-moz-box-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-direction"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "normal",
+ "reverse",
+ "unset"
+ ]
+ },
+ "-moz-box-flex": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-flex"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-box-ordinal-group": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-ordinal-group"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-box-orient": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-orient"
+ ],
+ "supports": [],
+ "values": [
+ "block-axis",
+ "horizontal",
+ "inherit",
+ "initial",
+ "inline-axis",
+ "unset",
+ "vertical"
+ ]
+ },
+ "-moz-box-pack": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-pack"
+ ],
+ "supports": [],
+ "values": [
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "justify",
+ "start",
+ "unset"
+ ]
+ },
+ "-moz-box-sizing": {
+ "isInherited": false,
+ "subproperties": [
+ "box-sizing"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-column-count": {
+ "isInherited": false,
+ "subproperties": [
+ "column-count"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-column-fill": {
+ "isInherited": false,
+ "subproperties": [
+ "column-fill"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "balance",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-column-gap": {
+ "isInherited": false,
+ "subproperties": [
+ "column-gap"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "-moz-column-rule": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-width",
+ "column-rule-style",
+ "column-rule-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-column-rule-color": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-moz-column-rule-style": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "-moz-column-rule-width": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "-moz-column-width": {
+ "isInherited": false,
+ "subproperties": [
+ "column-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-columns": {
+ "isInherited": false,
+ "subproperties": [
+ "column-count",
+ "column-width"
+ ],
+ "supports": [
+ 6,
+ 7
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-control-character-visibility": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-control-character-visibility"
+ ],
+ "supports": [],
+ "values": [
+ "hidden",
+ "inherit",
+ "initial",
+ "unset",
+ "visible"
+ ]
+ },
+ "-moz-float-edge": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-float-edge"
+ ],
+ "supports": [],
+ "values": [
+ "content-box",
+ "inherit",
+ "initial",
+ "margin-box",
+ "unset"
+ ]
+ },
+ "-moz-font-feature-settings": {
+ "isInherited": true,
+ "subproperties": [
+ "font-feature-settings"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-font-language-override": {
+ "isInherited": true,
+ "subproperties": [
+ "font-language-override"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "-moz-force-broken-image-icon": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-force-broken-image-icon"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-hyphens": {
+ "isInherited": true,
+ "subproperties": [
+ "hyphens"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "manual",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-image-region": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-image-region"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-margin-end": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-inline-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-margin-start": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-inline-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-orient": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-orient"
+ ],
+ "supports": [],
+ "values": [
+ "block",
+ "horizontal",
+ "inherit",
+ "initial",
+ "inline",
+ "unset",
+ "vertical"
+ ]
+ },
+ "-moz-outline-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-outline-radius-topleft",
+ "-moz-outline-radius-topright",
+ "-moz-outline-radius-bottomright",
+ "-moz-outline-radius-bottomleft"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-outline-radius-bottomleft": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-outline-radius-bottomleft"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-outline-radius-bottomright": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-outline-radius-bottomright"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-outline-radius-topleft": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-outline-radius-topleft"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-outline-radius-topright": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-outline-radius-topright"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-padding-end": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-inline-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-padding-start": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-inline-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-perspective": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-perspective-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-stack-sizing": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-stack-sizing"
+ ],
+ "supports": [],
+ "values": [
+ "ignore",
+ "inherit",
+ "initial",
+ "stretch-to-fit",
+ "unset"
+ ]
+ },
+ "-moz-tab-size": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-tab-size"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-text-align-last": {
+ "isInherited": true,
+ "subproperties": [
+ "text-align-last"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "justify",
+ "left",
+ "right",
+ "start",
+ "unset"
+ ]
+ },
+ "-moz-text-size-adjust": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-text-size-adjust"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-transform": {
+ "isInherited": false,
+ "subproperties": [
+ "transform"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-transform-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-transform-style": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-style"
+ ],
+ "supports": [],
+ "values": [
+ "flat",
+ "inherit",
+ "initial",
+ "preserve-3d",
+ "unset"
+ ]
+ },
+ "-moz-transition": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay"
+ ],
+ "supports": [
+ 9,
+ 10
+ ],
+ "values": [
+ "all",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-moz-transition-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-transition-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-moz-transition-property": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property"
+ ],
+ "supports": [],
+ "values": [
+ "all",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-transition-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-moz-user-focus": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-user-focus"
+ ],
+ "supports": [],
+ "values": [
+ "ignore",
+ "inherit",
+ "initial",
+ "none",
+ "normal",
+ "select-after",
+ "select-all",
+ "select-before",
+ "select-menu",
+ "select-same",
+ "unset"
+ ]
+ },
+ "-moz-user-input": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-user-input"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "disabled",
+ "enabled",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-moz-user-modify": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-user-modify"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "read-only",
+ "read-write",
+ "unset",
+ "write-only"
+ ]
+ },
+ "-moz-user-select": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-user-select"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-all",
+ "-moz-none",
+ "-moz-text",
+ "all",
+ "auto",
+ "element",
+ "elements",
+ "inherit",
+ "initial",
+ "none",
+ "text",
+ "toggle",
+ "tri-state",
+ "unset"
+ ]
+ },
+ "-moz-window-dragging": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-window-dragging"
+ ],
+ "supports": [],
+ "values": [
+ "default",
+ "drag",
+ "inherit",
+ "initial",
+ "no-drag",
+ "unset"
+ ]
+ },
+ "-webkit-align-content": {
+ "isInherited": false,
+ "subproperties": [
+ "align-content"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-webkit-align-items": {
+ "isInherited": false,
+ "subproperties": [
+ "align-items"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-webkit-align-self": {
+ "isInherited": false,
+ "subproperties": [
+ "align-self"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-webkit-animation": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ "animation-name"
+ ],
+ "supports": [
+ 7,
+ 9,
+ 10
+ ],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "backwards",
+ "both",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "forwards",
+ "infinite",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "normal",
+ "paused",
+ "reverse",
+ "running",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-webkit-animation-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-animation-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-direction"
+ ],
+ "supports": [],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "inherit",
+ "initial",
+ "normal",
+ "reverse",
+ "unset"
+ ]
+ },
+ "-webkit-animation-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-animation-fill-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-fill-mode"
+ ],
+ "supports": [],
+ "values": [
+ "backwards",
+ "both",
+ "forwards",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-webkit-animation-iteration-count": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-iteration-count"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "infinite",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-animation-name": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-name"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-webkit-animation-play-state": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-play-state"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "paused",
+ "running",
+ "unset"
+ ]
+ },
+ "-webkit-animation-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-webkit-backface-visibility": {
+ "isInherited": false,
+ "subproperties": [
+ "backface-visibility"
+ ],
+ "supports": [],
+ "values": [
+ "hidden",
+ "inherit",
+ "initial",
+ "unset",
+ "visible"
+ ]
+ },
+ "-webkit-background-clip": {
+ "isInherited": false,
+ "subproperties": [
+ "background-clip"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "text",
+ "unset"
+ ]
+ },
+ "-webkit-background-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "background-origin"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "-webkit-background-size": {
+ "isInherited": false,
+ "subproperties": [
+ "background-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-border-bottom-left-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-border-bottom-right-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-right-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-border-image": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "-webkit-border-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-left-radius",
+ "border-top-right-radius",
+ "border-bottom-right-radius",
+ "border-bottom-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-border-top-left-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-border-top-right-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-right-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-box-align": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-align"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-webkit-box-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-direction"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "normal",
+ "reverse",
+ "unset"
+ ]
+ },
+ "-webkit-box-flex": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-flex"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-box-ordinal-group": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-ordinal-group"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-box-orient": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-orient"
+ ],
+ "supports": [],
+ "values": [
+ "block-axis",
+ "horizontal",
+ "inherit",
+ "initial",
+ "inline-axis",
+ "unset",
+ "vertical"
+ ]
+ },
+ "-webkit-box-pack": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-box-pack"
+ ],
+ "supports": [],
+ "values": [
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "justify",
+ "start",
+ "unset"
+ ]
+ },
+ "-webkit-box-shadow": {
+ "isInherited": false,
+ "subproperties": [
+ "box-shadow"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-box-sizing": {
+ "isInherited": false,
+ "subproperties": [
+ "box-sizing"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-filter": {
+ "isInherited": false,
+ "subproperties": [
+ "filter"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-flex": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-grow",
+ "flex-shrink",
+ "flex-basis"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-flex-basis": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-basis"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-flex-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-direction"
+ ],
+ "supports": [],
+ "values": [
+ "column",
+ "column-reverse",
+ "inherit",
+ "initial",
+ "row",
+ "row-reverse",
+ "unset"
+ ]
+ },
+ "-webkit-flex-flow": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-direction",
+ "flex-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "column",
+ "column-reverse",
+ "inherit",
+ "initial",
+ "nowrap",
+ "row",
+ "row-reverse",
+ "unset",
+ "wrap",
+ "wrap-reverse"
+ ]
+ },
+ "-webkit-flex-grow": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-grow"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-flex-shrink": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-shrink"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-flex-wrap": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "nowrap",
+ "unset",
+ "wrap",
+ "wrap-reverse"
+ ]
+ },
+ "-webkit-justify-content": {
+ "isInherited": false,
+ "subproperties": [
+ "justify-content"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "-webkit-mask": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-image",
+ "mask-repeat",
+ "mask-position-x",
+ "mask-position-y",
+ "mask-clip",
+ "mask-origin",
+ "mask-size",
+ "mask-composite",
+ "mask-mode"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 6,
+ 8,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "add",
+ "alpha",
+ "border-box",
+ "content-box",
+ "exclude",
+ "inherit",
+ "initial",
+ "intersect",
+ "linear-gradient",
+ "luminance",
+ "match-source",
+ "no-repeat",
+ "none",
+ "padding-box",
+ "radial-gradient",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "round",
+ "space",
+ "subtract",
+ "unset",
+ "url"
+ ]
+ },
+ "-webkit-mask-clip": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-clip"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "-webkit-mask-composite": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-composite"
+ ],
+ "supports": [],
+ "values": [
+ "add",
+ "exclude",
+ "inherit",
+ "initial",
+ "intersect",
+ "subtract",
+ "unset"
+ ]
+ },
+ "-webkit-mask-image": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-image"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "-webkit-mask-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-origin"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "-webkit-mask-position": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-x",
+ "mask-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-mask-position-x": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-x"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-mask-position-y": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-mask-repeat": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-repeat"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "no-repeat",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "round",
+ "space",
+ "unset"
+ ]
+ },
+ "-webkit-mask-size": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-order": {
+ "isInherited": false,
+ "subproperties": [
+ "order"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-perspective": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-webkit-perspective-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-text-fill-color": {
+ "isInherited": true,
+ "subproperties": [
+ "-webkit-text-fill-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-webkit-text-size-adjust": {
+ "isInherited": true,
+ "subproperties": [
+ "-moz-text-size-adjust"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-webkit-text-stroke": {
+ "isInherited": true,
+ "subproperties": [
+ "-webkit-text-stroke-width",
+ "-webkit-text-stroke-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "medium",
+ "rgb",
+ "rgba",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-webkit-text-stroke-color": {
+ "isInherited": true,
+ "subproperties": [
+ "-webkit-text-stroke-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "-webkit-text-stroke-width": {
+ "isInherited": true,
+ "subproperties": [
+ "-webkit-text-stroke-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "-webkit-transform": {
+ "isInherited": false,
+ "subproperties": [
+ "transform"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-transform-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-transform-style": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-style"
+ ],
+ "supports": [],
+ "values": [
+ "flat",
+ "inherit",
+ "initial",
+ "preserve-3d",
+ "unset"
+ ]
+ },
+ "-webkit-transition": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay"
+ ],
+ "supports": [
+ 9,
+ 10
+ ],
+ "values": [
+ "all",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-webkit-transition-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-transition-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "-webkit-transition-property": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property"
+ ],
+ "supports": [],
+ "values": [
+ "all",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "-webkit-transition-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "-webkit-user-select": {
+ "isInherited": false,
+ "subproperties": [
+ "-moz-user-select"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-all",
+ "-moz-none",
+ "-moz-text",
+ "all",
+ "auto",
+ "element",
+ "elements",
+ "inherit",
+ "initial",
+ "none",
+ "text",
+ "toggle",
+ "tri-state",
+ "unset"
+ ]
+ },
+ "align-content": {
+ "isInherited": false,
+ "subproperties": [
+ "align-content"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "align-items": {
+ "isInherited": false,
+ "subproperties": [
+ "align-items"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "align-self": {
+ "isInherited": false,
+ "subproperties": [
+ "align-self"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "all": {
+ "isInherited": false,
+ "subproperties": [
+ "align-content",
+ "align-items",
+ "align-self",
+ "animation-delay",
+ "animation-direction",
+ "animation-duration",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-name",
+ "animation-play-state",
+ "animation-timing-function",
+ "-moz-appearance",
+ "backface-visibility",
+ "background-attachment",
+ "background-blend-mode",
+ "background-clip",
+ "background-color",
+ "background-image",
+ "background-origin",
+ "background-position-x",
+ "background-position-y",
+ "background-repeat",
+ "background-size",
+ "-moz-binding",
+ "block-size",
+ "border-block-end-color",
+ "border-block-end-style",
+ "border-block-end-width",
+ "border-block-start-color",
+ "border-block-start-style",
+ "border-block-start-width",
+ "border-bottom-color",
+ "-moz-border-bottom-colors",
+ "border-bottom-left-radius",
+ "border-bottom-right-radius",
+ "border-bottom-style",
+ "border-bottom-width",
+ "border-collapse",
+ "border-image-outset",
+ "border-image-repeat",
+ "border-image-slice",
+ "border-image-source",
+ "border-image-width",
+ "border-inline-end-color",
+ "border-inline-end-style",
+ "border-inline-end-width",
+ "border-inline-start-color",
+ "border-inline-start-style",
+ "border-inline-start-width",
+ "border-left-color",
+ "-moz-border-left-colors",
+ "border-left-style",
+ "border-left-width",
+ "border-right-color",
+ "-moz-border-right-colors",
+ "border-right-style",
+ "border-right-width",
+ "border-spacing",
+ "border-top-color",
+ "-moz-border-top-colors",
+ "border-top-left-radius",
+ "border-top-right-radius",
+ "border-top-style",
+ "border-top-width",
+ "bottom",
+ "-moz-box-align",
+ "box-decoration-break",
+ "-moz-box-direction",
+ "-moz-box-flex",
+ "-moz-box-ordinal-group",
+ "-moz-box-orient",
+ "-moz-box-pack",
+ "box-shadow",
+ "box-sizing",
+ "caption-side",
+ "clear",
+ "clip",
+ "clip-path",
+ "clip-rule",
+ "color",
+ "color-adjust",
+ "color-interpolation",
+ "color-interpolation-filters",
+ "column-count",
+ "column-fill",
+ "column-gap",
+ "column-rule-color",
+ "column-rule-style",
+ "column-rule-width",
+ "column-width",
+ "contain",
+ "content",
+ "-moz-control-character-visibility",
+ "counter-increment",
+ "counter-reset",
+ "cursor",
+ "display",
+ "dominant-baseline",
+ "empty-cells",
+ "fill",
+ "fill-opacity",
+ "fill-rule",
+ "filter",
+ "flex-basis",
+ "flex-direction",
+ "flex-grow",
+ "flex-shrink",
+ "flex-wrap",
+ "float",
+ "-moz-float-edge",
+ "flood-color",
+ "flood-opacity",
+ "font-family",
+ "font-feature-settings",
+ "font-kerning",
+ "font-language-override",
+ "font-size",
+ "font-size-adjust",
+ "font-stretch",
+ "font-style",
+ "font-synthesis",
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position",
+ "font-weight",
+ "-moz-force-broken-image-icon",
+ "grid-auto-columns",
+ "grid-auto-flow",
+ "grid-auto-rows",
+ "grid-column-end",
+ "grid-column-gap",
+ "grid-column-start",
+ "grid-row-end",
+ "grid-row-gap",
+ "grid-row-start",
+ "grid-template-areas",
+ "grid-template-columns",
+ "grid-template-rows",
+ "height",
+ "hyphens",
+ "initial-letter",
+ "image-orientation",
+ "-moz-image-region",
+ "image-rendering",
+ "ime-mode",
+ "inline-size",
+ "isolation",
+ "justify-content",
+ "justify-items",
+ "justify-self",
+ "left",
+ "letter-spacing",
+ "lighting-color",
+ "line-height",
+ "list-style-image",
+ "list-style-position",
+ "list-style-type",
+ "margin-block-end",
+ "margin-block-start",
+ "margin-bottom",
+ "margin-inline-end",
+ "margin-inline-start",
+ "margin-left",
+ "margin-right",
+ "margin-top",
+ "marker-end",
+ "marker-mid",
+ "marker-start",
+ "mask-clip",
+ "mask-composite",
+ "mask-image",
+ "mask-mode",
+ "mask-origin",
+ "mask-position-x",
+ "mask-position-y",
+ "mask-repeat",
+ "mask-size",
+ "mask-type",
+ "max-block-size",
+ "max-height",
+ "max-inline-size",
+ "max-width",
+ "min-block-size",
+ "-moz-min-font-size-ratio",
+ "min-height",
+ "min-inline-size",
+ "min-width",
+ "mix-blend-mode",
+ "object-fit",
+ "object-position",
+ "offset-block-end",
+ "offset-block-start",
+ "offset-inline-end",
+ "offset-inline-start",
+ "opacity",
+ "order",
+ "-moz-orient",
+ "-moz-osx-font-smoothing",
+ "outline-color",
+ "outline-offset",
+ "-moz-outline-radius-bottomleft",
+ "-moz-outline-radius-bottomright",
+ "-moz-outline-radius-topleft",
+ "-moz-outline-radius-topright",
+ "outline-style",
+ "outline-width",
+ "overflow-clip-box",
+ "overflow-x",
+ "overflow-y",
+ "padding-block-end",
+ "padding-block-start",
+ "padding-bottom",
+ "padding-inline-end",
+ "padding-inline-start",
+ "padding-left",
+ "padding-right",
+ "padding-top",
+ "page-break-after",
+ "page-break-before",
+ "page-break-inside",
+ "paint-order",
+ "perspective",
+ "perspective-origin",
+ "pointer-events",
+ "position",
+ "quotes",
+ "resize",
+ "right",
+ "ruby-align",
+ "ruby-position",
+ "scroll-behavior",
+ "scroll-snap-coordinate",
+ "scroll-snap-destination",
+ "scroll-snap-points-x",
+ "scroll-snap-points-y",
+ "scroll-snap-type-x",
+ "scroll-snap-type-y",
+ "shape-outside",
+ "shape-rendering",
+ "-moz-stack-sizing",
+ "stop-color",
+ "stop-opacity",
+ "stroke",
+ "stroke-dasharray",
+ "stroke-dashoffset",
+ "stroke-linecap",
+ "stroke-linejoin",
+ "stroke-miterlimit",
+ "stroke-opacity",
+ "stroke-width",
+ "-x-system-font",
+ "-moz-tab-size",
+ "table-layout",
+ "text-align",
+ "text-align-last",
+ "text-anchor",
+ "text-combine-upright",
+ "text-decoration-color",
+ "text-decoration-line",
+ "text-decoration-style",
+ "text-emphasis-color",
+ "text-emphasis-position",
+ "text-emphasis-style",
+ "-webkit-text-fill-color",
+ "text-indent",
+ "text-orientation",
+ "text-overflow",
+ "text-rendering",
+ "text-shadow",
+ "-moz-text-size-adjust",
+ "-webkit-text-stroke-color",
+ "-webkit-text-stroke-width",
+ "text-transform",
+ "top",
+ "-moz-top-layer",
+ "touch-action",
+ "transform",
+ "transform-box",
+ "transform-origin",
+ "transform-style",
+ "transition-delay",
+ "transition-duration",
+ "transition-property",
+ "transition-timing-function",
+ "-moz-user-focus",
+ "-moz-user-input",
+ "-moz-user-modify",
+ "-moz-user-select",
+ "vector-effect",
+ "vertical-align",
+ "visibility",
+ "white-space",
+ "width",
+ "will-change",
+ "-moz-window-dragging",
+ "-moz-window-shadow",
+ "word-break",
+ "word-spacing",
+ "overflow-wrap",
+ "writing-mode",
+ "z-index"
+ ],
+ "supports": [
+ 1,
+ 2,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11
+ ],
+ "values": [
+ "COLOR",
+ "-moz-all",
+ "-moz-available",
+ "-moz-block-height",
+ "-moz-box",
+ "-moz-calc",
+ "-moz-center",
+ "-moz-crisp-edges",
+ "-moz-deck",
+ "-moz-element",
+ "-moz-fit-content",
+ "-moz-grid",
+ "-moz-grid-group",
+ "-moz-grid-line",
+ "-moz-groupbox",
+ "-moz-gtk-info-bar",
+ "-moz-hidden-unscrollable",
+ "-moz-image-rect",
+ "-moz-inline-box",
+ "-moz-inline-grid",
+ "-moz-inline-stack",
+ "-moz-left",
+ "-moz-linear-gradient",
+ "-moz-mac-disclosure-button-closed",
+ "-moz-mac-disclosure-button-open",
+ "-moz-mac-fullscreen-button",
+ "-moz-mac-help-button",
+ "-moz-mac-source-list",
+ "-moz-mac-vibrancy-dark",
+ "-moz-mac-vibrancy-light",
+ "-moz-max-content",
+ "-moz-middle-with-baseline",
+ "-moz-min-content",
+ "-moz-none",
+ "-moz-popup",
+ "-moz-pre-space",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "-moz-right",
+ "-moz-stack",
+ "-moz-text",
+ "-moz-win-borderless-glass",
+ "-moz-win-browsertabbar-toolbox",
+ "-moz-win-communications-toolbox",
+ "-moz-win-exclude-glass",
+ "-moz-win-glass",
+ "-moz-win-media-toolbox",
+ "-moz-window-button-box",
+ "-moz-window-button-box-maximized",
+ "-moz-window-button-close",
+ "-moz-window-button-maximize",
+ "-moz-window-button-minimize",
+ "-moz-window-button-restore",
+ "-moz-window-frame-bottom",
+ "-moz-window-frame-left",
+ "-moz-window-frame-right",
+ "-moz-window-titlebar",
+ "-moz-window-titlebar-maximized",
+ "-webkit-box",
+ "-webkit-flex",
+ "-webkit-inline-box",
+ "-webkit-inline-flex",
+ "absolute",
+ "active",
+ "add",
+ "all",
+ "all-petite-caps",
+ "all-small-caps",
+ "alpha",
+ "alphabetic",
+ "alternate",
+ "alternate-reverse",
+ "always",
+ "auto",
+ "avoid",
+ "backwards",
+ "balance",
+ "baseline",
+ "bevel",
+ "block",
+ "block-axis",
+ "border-box",
+ "both",
+ "bottom",
+ "bottom-outside",
+ "break-all",
+ "break-word",
+ "butt",
+ "button",
+ "button-arrow-down",
+ "button-arrow-next",
+ "button-arrow-previous",
+ "button-arrow-up",
+ "button-bevel",
+ "button-focus",
+ "calc",
+ "capitalize",
+ "caret",
+ "center",
+ "central",
+ "checkbox",
+ "checkbox-container",
+ "checkbox-label",
+ "checkmenuitem",
+ "clone",
+ "collapse",
+ "color",
+ "color-burn",
+ "color-dodge",
+ "column",
+ "column-reverse",
+ "condensed",
+ "contain",
+ "content-box",
+ "contents",
+ "cover",
+ "crispedges",
+ "cubic-bezier",
+ "currentColor",
+ "darken",
+ "dashed",
+ "default",
+ "dialog",
+ "difference",
+ "disabled",
+ "dotted",
+ "double",
+ "drag",
+ "dualbutton",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "economy",
+ "element",
+ "elements",
+ "enabled",
+ "end",
+ "evenodd",
+ "exact",
+ "exclude",
+ "exclusion",
+ "expanded",
+ "extra-condensed",
+ "extra-expanded",
+ "fill",
+ "fill-box",
+ "fixed",
+ "flat",
+ "flex",
+ "flex-end",
+ "flex-start",
+ "forwards",
+ "full-width",
+ "geometricprecision",
+ "grayscale",
+ "grid",
+ "groove",
+ "groupbox",
+ "hanging",
+ "hard-light",
+ "hidden",
+ "hide",
+ "horizontal",
+ "horizontal-tb",
+ "hsl",
+ "hsla",
+ "hue",
+ "ideographic",
+ "ignore",
+ "inactive",
+ "infinite",
+ "inherit",
+ "initial",
+ "inline",
+ "inline-axis",
+ "inline-block",
+ "inline-end",
+ "inline-flex",
+ "inline-grid",
+ "inline-start",
+ "inline-table",
+ "inset",
+ "inside",
+ "intersect",
+ "isolate",
+ "italic",
+ "justify",
+ "keep-all",
+ "large",
+ "larger",
+ "last baseline",
+ "left",
+ "lighten",
+ "linear",
+ "linear-gradient",
+ "linearrgb",
+ "list-item",
+ "listbox",
+ "listitem",
+ "local",
+ "lowercase",
+ "lr",
+ "lr-tb",
+ "luminance",
+ "luminosity",
+ "mandatory",
+ "manipulation",
+ "manual",
+ "margin-box",
+ "match-source",
+ "mathematical",
+ "medium",
+ "menuarrow",
+ "menubar",
+ "menucheckbox",
+ "menuimage",
+ "menuitem",
+ "menuitemtext",
+ "menulist",
+ "menulist-button",
+ "menulist-text",
+ "menulist-textfield",
+ "menupopup",
+ "menuradio",
+ "menuseparator",
+ "meterbar",
+ "meterchunk",
+ "middle",
+ "miter",
+ "mixed",
+ "multiply",
+ "no-change",
+ "no-drag",
+ "no-repeat",
+ "non-scaling-stroke",
+ "none",
+ "nonzero",
+ "normal",
+ "nowrap",
+ "number-input",
+ "oblique",
+ "optimizelegibility",
+ "optimizequality",
+ "optimizespeed",
+ "outset",
+ "outside",
+ "over",
+ "overlay",
+ "padding-box",
+ "painted",
+ "pan-x",
+ "pan-y",
+ "paused",
+ "petite-caps",
+ "pre",
+ "pre-line",
+ "pre-wrap",
+ "preserve-3d",
+ "progressbar",
+ "progressbar-vertical",
+ "progresschunk",
+ "progresschunk-vertical",
+ "proximity",
+ "radial-gradient",
+ "radio",
+ "radio-container",
+ "radio-label",
+ "radiomenuitem",
+ "range",
+ "range-thumb",
+ "read-only",
+ "read-write",
+ "relative",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "reset-size",
+ "resizer",
+ "resizerpanel",
+ "reverse",
+ "rgb",
+ "rgba",
+ "ridge",
+ "right",
+ "rl",
+ "rl-tb",
+ "round",
+ "row",
+ "row-reverse",
+ "ruby",
+ "ruby-base",
+ "ruby-base-container",
+ "ruby-text",
+ "ruby-text-container",
+ "running",
+ "saturation",
+ "scale-down",
+ "scale-horizontal",
+ "scale-vertical",
+ "scalethumb-horizontal",
+ "scalethumb-vertical",
+ "scalethumbend",
+ "scalethumbstart",
+ "scalethumbtick",
+ "screen",
+ "scroll",
+ "scrollbar",
+ "scrollbar-horizontal",
+ "scrollbar-small",
+ "scrollbar-vertical",
+ "scrollbarbutton-down",
+ "scrollbarbutton-left",
+ "scrollbarbutton-right",
+ "scrollbarbutton-up",
+ "scrollbarthumb-horizontal",
+ "scrollbarthumb-vertical",
+ "scrollbartrack-horizontal",
+ "scrollbartrack-vertical",
+ "searchfield",
+ "select-after",
+ "select-all",
+ "select-before",
+ "select-menu",
+ "select-same",
+ "self-end",
+ "self-start",
+ "semi-condensed",
+ "semi-expanded",
+ "separate",
+ "separator",
+ "show",
+ "sideways",
+ "sideways-lr",
+ "sideways-right",
+ "sideways-rl",
+ "slice",
+ "small",
+ "small-caps",
+ "smaller",
+ "smooth",
+ "soft-light",
+ "solid",
+ "space",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "spinner",
+ "spinner-downbutton",
+ "spinner-textfield",
+ "spinner-upbutton",
+ "splitter",
+ "square",
+ "srgb",
+ "start",
+ "static",
+ "statusbar",
+ "statusbarpanel",
+ "step-end",
+ "step-start",
+ "steps",
+ "sticky",
+ "stretch",
+ "stretch-to-fit",
+ "stroke",
+ "sub",
+ "subtract",
+ "super",
+ "tab",
+ "tab-scroll-arrow-back",
+ "tab-scroll-arrow-forward",
+ "table",
+ "table-caption",
+ "table-cell",
+ "table-column",
+ "table-column-group",
+ "table-footer-group",
+ "table-header-group",
+ "table-row",
+ "table-row-group",
+ "tabpanel",
+ "tabpanels",
+ "tb",
+ "tb-rl",
+ "text",
+ "text-after-edge",
+ "text-before-edge",
+ "text-bottom",
+ "text-top",
+ "textfield",
+ "textfield-multiline",
+ "thick",
+ "thin",
+ "titling-caps",
+ "toggle",
+ "toolbar",
+ "toolbarbutton",
+ "toolbarbutton-dropdown",
+ "toolbargripper",
+ "toolbox",
+ "tooltip",
+ "top",
+ "top-outside",
+ "transparent",
+ "treeheader",
+ "treeheadercell",
+ "treeheadersortarrow",
+ "treeitem",
+ "treeline",
+ "treetwisty",
+ "treetwistyopen",
+ "treeview",
+ "tri-state",
+ "ultra-condensed",
+ "ultra-expanded",
+ "under",
+ "unicase",
+ "unset",
+ "uppercase",
+ "upright",
+ "url",
+ "use-script",
+ "vertical",
+ "vertical-lr",
+ "vertical-rl",
+ "view-box",
+ "visible",
+ "visiblefill",
+ "visiblepainted",
+ "visiblestroke",
+ "wavy",
+ "window",
+ "wrap",
+ "wrap-reverse",
+ "write-only",
+ "x-large",
+ "x-small",
+ "xx-large",
+ "xx-small"
+ ]
+ },
+ "animation": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration",
+ "animation-timing-function",
+ "animation-delay",
+ "animation-direction",
+ "animation-fill-mode",
+ "animation-iteration-count",
+ "animation-play-state",
+ "animation-name"
+ ],
+ "supports": [
+ 7,
+ 9,
+ 10
+ ],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "backwards",
+ "both",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "forwards",
+ "infinite",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "normal",
+ "paused",
+ "reverse",
+ "running",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "animation-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "animation-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-direction"
+ ],
+ "supports": [],
+ "values": [
+ "alternate",
+ "alternate-reverse",
+ "inherit",
+ "initial",
+ "normal",
+ "reverse",
+ "unset"
+ ]
+ },
+ "animation-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "animation-fill-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-fill-mode"
+ ],
+ "supports": [],
+ "values": [
+ "backwards",
+ "both",
+ "forwards",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "animation-iteration-count": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-iteration-count"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "infinite",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "animation-name": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-name"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "animation-play-state": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-play-state"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "paused",
+ "running",
+ "unset"
+ ]
+ },
+ "animation-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "animation-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "backface-visibility": {
+ "isInherited": false,
+ "subproperties": [
+ "backface-visibility"
+ ],
+ "supports": [],
+ "values": [
+ "hidden",
+ "inherit",
+ "initial",
+ "unset",
+ "visible"
+ ]
+ },
+ "background": {
+ "isInherited": false,
+ "subproperties": [
+ "background-color",
+ "background-image",
+ "background-repeat",
+ "background-attachment",
+ "background-clip",
+ "background-origin",
+ "background-position-x",
+ "background-position-y",
+ "background-size"
+ ],
+ "supports": [
+ 2,
+ 4,
+ 5,
+ 6,
+ 8,
+ 11
+ ],
+ "values": [
+ "COLOR",
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "border-box",
+ "content-box",
+ "currentColor",
+ "fixed",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "local",
+ "no-repeat",
+ "none",
+ "padding-box",
+ "radial-gradient",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "rgb",
+ "rgba",
+ "round",
+ "scroll",
+ "space",
+ "text",
+ "transparent",
+ "unset",
+ "url"
+ ]
+ },
+ "background-attachment": {
+ "isInherited": false,
+ "subproperties": [
+ "background-attachment"
+ ],
+ "supports": [],
+ "values": [
+ "fixed",
+ "inherit",
+ "initial",
+ "local",
+ "scroll",
+ "unset"
+ ]
+ },
+ "background-blend-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "background-blend-mode"
+ ],
+ "supports": [],
+ "values": [
+ "color",
+ "color-burn",
+ "color-dodge",
+ "darken",
+ "difference",
+ "exclusion",
+ "hard-light",
+ "hue",
+ "inherit",
+ "initial",
+ "lighten",
+ "luminosity",
+ "multiply",
+ "normal",
+ "overlay",
+ "saturation",
+ "screen",
+ "soft-light",
+ "unset"
+ ]
+ },
+ "background-clip": {
+ "isInherited": false,
+ "subproperties": [
+ "background-clip"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "text",
+ "unset"
+ ]
+ },
+ "background-color": {
+ "isInherited": false,
+ "subproperties": [
+ "background-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "background-image": {
+ "isInherited": false,
+ "subproperties": [
+ "background-image"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "background-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "background-origin"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "background-position": {
+ "isInherited": false,
+ "subproperties": [
+ "background-position-x",
+ "background-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "background-position-x": {
+ "isInherited": false,
+ "subproperties": [
+ "background-position-x"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "background-position-y": {
+ "isInherited": false,
+ "subproperties": [
+ "background-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "background-repeat": {
+ "isInherited": false,
+ "subproperties": [
+ "background-repeat"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "no-repeat",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "round",
+ "space",
+ "unset"
+ ]
+ },
+ "background-size": {
+ "isInherited": false,
+ "subproperties": [
+ "background-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "block-size": {
+ "isInherited": false,
+ "subproperties": [
+ "block-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width",
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style",
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color",
+ "-moz-border-top-colors",
+ "-moz-border-right-colors",
+ "-moz-border-bottom-colors",
+ "-moz-border-left-colors",
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "linear-gradient",
+ "medium",
+ "none",
+ "outset",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset",
+ "url"
+ ]
+ },
+ "border-block-end": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-end-width",
+ "border-block-end-style",
+ "border-block-end-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-block-end-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-end-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-block-end-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-end-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-block-end-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-end-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-block-start": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-start-width",
+ "border-block-start-style",
+ "border-block-start-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-block-start-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-start-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-block-start-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-start-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-block-start-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-block-start-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-bottom": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-width",
+ "border-bottom-style",
+ "border-bottom-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-bottom-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-bottom-left-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-bottom-right-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-right-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-bottom-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-bottom-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-bottom-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-collapse": {
+ "isInherited": true,
+ "subproperties": [
+ "border-collapse"
+ ],
+ "supports": [],
+ "values": [
+ "collapse",
+ "inherit",
+ "initial",
+ "separate",
+ "unset"
+ ]
+ },
+ "border-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-color",
+ "border-right-color",
+ "border-bottom-color",
+ "border-left-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-image": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-source",
+ "border-image-slice",
+ "border-image-width",
+ "border-image-outset",
+ "border-image-repeat"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "border-image-outset": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-outset"
+ ],
+ "supports": [
+ 6,
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-image-repeat": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-repeat"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-image-slice": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-slice"
+ ],
+ "supports": [
+ 7,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-image-source": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-source"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "border-image-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-image-width"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-inline-end": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-width",
+ "border-inline-end-style",
+ "border-inline-end-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-inline-end-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-inline-end-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-inline-end-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-end-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-inline-start": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-width",
+ "border-inline-start-style",
+ "border-inline-start-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-inline-start-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-inline-start-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-inline-start-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-inline-start-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-left": {
+ "isInherited": false,
+ "subproperties": [
+ "border-left-width",
+ "border-left-style",
+ "border-left-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-left-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-left-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-left-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-left-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-left-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-left-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-left-radius",
+ "border-top-right-radius",
+ "border-bottom-right-radius",
+ "border-bottom-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-right": {
+ "isInherited": false,
+ "subproperties": [
+ "border-right-width",
+ "border-right-style",
+ "border-right-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-right-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-right-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-right-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-right-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-right-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-right-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-spacing": {
+ "isInherited": true,
+ "subproperties": [
+ "border-spacing"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-style",
+ "border-right-style",
+ "border-bottom-style",
+ "border-left-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-top": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-width",
+ "border-top-style",
+ "border-top-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-top-color": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "border-top-left-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-left-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-top-right-radius": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-right-radius"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "border-top-style": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "border-top-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "border-width": {
+ "isInherited": false,
+ "subproperties": [
+ "border-top-width",
+ "border-right-width",
+ "border-bottom-width",
+ "border-left-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "bottom": {
+ "isInherited": false,
+ "subproperties": [
+ "bottom"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "box-decoration-break": {
+ "isInherited": false,
+ "subproperties": [
+ "box-decoration-break"
+ ],
+ "supports": [],
+ "values": [
+ "clone",
+ "inherit",
+ "initial",
+ "slice",
+ "unset"
+ ]
+ },
+ "box-shadow": {
+ "isInherited": false,
+ "subproperties": [
+ "box-shadow"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "box-sizing": {
+ "isInherited": false,
+ "subproperties": [
+ "box-sizing"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "caption-side": {
+ "isInherited": true,
+ "subproperties": [
+ "caption-side"
+ ],
+ "supports": [],
+ "values": [
+ "bottom",
+ "bottom-outside",
+ "inherit",
+ "initial",
+ "left",
+ "right",
+ "top",
+ "top-outside",
+ "unset"
+ ]
+ },
+ "clear": {
+ "isInherited": false,
+ "subproperties": [
+ "clear"
+ ],
+ "supports": [],
+ "values": [
+ "both",
+ "inherit",
+ "initial",
+ "inline-end",
+ "inline-start",
+ "left",
+ "none",
+ "right",
+ "unset"
+ ]
+ },
+ "clip": {
+ "isInherited": false,
+ "subproperties": [
+ "clip"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "clip-path": {
+ "isInherited": false,
+ "subproperties": [
+ "clip-path"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "clip-rule": {
+ "isInherited": true,
+ "subproperties": [
+ "clip-rule"
+ ],
+ "supports": [],
+ "values": [
+ "evenodd",
+ "inherit",
+ "initial",
+ "nonzero",
+ "unset"
+ ]
+ },
+ "color": {
+ "isInherited": true,
+ "subproperties": [
+ "color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "color-adjust": {
+ "isInherited": true,
+ "subproperties": [
+ "color-adjust"
+ ],
+ "supports": [],
+ "values": [
+ "economy",
+ "exact",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "color-interpolation": {
+ "isInherited": true,
+ "subproperties": [
+ "color-interpolation"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "linearrgb",
+ "srgb",
+ "unset"
+ ]
+ },
+ "color-interpolation-filters": {
+ "isInherited": true,
+ "subproperties": [
+ "color-interpolation-filters"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "linearrgb",
+ "srgb",
+ "unset"
+ ]
+ },
+ "column-count": {
+ "isInherited": false,
+ "subproperties": [
+ "column-count"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "column-fill": {
+ "isInherited": false,
+ "subproperties": [
+ "column-fill"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "balance",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "column-gap": {
+ "isInherited": false,
+ "subproperties": [
+ "column-gap"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "column-rule": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-width",
+ "column-rule-style",
+ "column-rule-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "column-rule-color": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "column-rule-style": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-style"
+ ],
+ "supports": [],
+ "values": [
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hidden",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "column-rule-width": {
+ "isInherited": false,
+ "subproperties": [
+ "column-rule-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "column-width": {
+ "isInherited": false,
+ "subproperties": [
+ "column-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "columns": {
+ "isInherited": false,
+ "subproperties": [
+ "column-count",
+ "column-width"
+ ],
+ "supports": [
+ 6,
+ 7
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "content": {
+ "isInherited": false,
+ "subproperties": [
+ "content"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "counter-increment": {
+ "isInherited": false,
+ "subproperties": [
+ "counter-increment"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "counter-reset": {
+ "isInherited": false,
+ "subproperties": [
+ "counter-reset"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "cursor": {
+ "isInherited": true,
+ "subproperties": [
+ "cursor"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "direction": {
+ "isInherited": true,
+ "subproperties": [
+ "direction"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "ltr",
+ "rtl",
+ "unset"
+ ]
+ },
+ "display": {
+ "isInherited": false,
+ "subproperties": [
+ "display"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-box",
+ "-moz-deck",
+ "-moz-grid",
+ "-moz-grid-group",
+ "-moz-grid-line",
+ "-moz-groupbox",
+ "-moz-inline-box",
+ "-moz-inline-grid",
+ "-moz-inline-stack",
+ "-moz-popup",
+ "-moz-stack",
+ "-webkit-box",
+ "-webkit-flex",
+ "-webkit-inline-box",
+ "-webkit-inline-flex",
+ "block",
+ "contents",
+ "flex",
+ "grid",
+ "inherit",
+ "initial",
+ "inline",
+ "inline-block",
+ "inline-flex",
+ "inline-grid",
+ "inline-table",
+ "list-item",
+ "none",
+ "ruby",
+ "ruby-base",
+ "ruby-base-container",
+ "ruby-text",
+ "ruby-text-container",
+ "table",
+ "table-caption",
+ "table-cell",
+ "table-column",
+ "table-column-group",
+ "table-footer-group",
+ "table-header-group",
+ "table-row",
+ "table-row-group",
+ "unset"
+ ]
+ },
+ "dominant-baseline": {
+ "isInherited": false,
+ "subproperties": [
+ "dominant-baseline"
+ ],
+ "supports": [],
+ "values": [
+ "alphabetic",
+ "auto",
+ "central",
+ "hanging",
+ "ideographic",
+ "inherit",
+ "initial",
+ "mathematical",
+ "middle",
+ "no-change",
+ "reset-size",
+ "text-after-edge",
+ "text-before-edge",
+ "unset",
+ "use-script"
+ ]
+ },
+ "empty-cells": {
+ "isInherited": true,
+ "subproperties": [
+ "empty-cells"
+ ],
+ "supports": [],
+ "values": [
+ "hide",
+ "inherit",
+ "initial",
+ "show",
+ "unset"
+ ]
+ },
+ "fill": {
+ "isInherited": true,
+ "subproperties": [
+ "fill"
+ ],
+ "supports": [
+ 2,
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "fill-opacity": {
+ "isInherited": true,
+ "subproperties": [
+ "fill-opacity"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "fill-rule": {
+ "isInherited": true,
+ "subproperties": [
+ "fill-rule"
+ ],
+ "supports": [],
+ "values": [
+ "evenodd",
+ "inherit",
+ "initial",
+ "nonzero",
+ "unset"
+ ]
+ },
+ "filter": {
+ "isInherited": false,
+ "subproperties": [
+ "filter"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "flex": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-grow",
+ "flex-shrink",
+ "flex-basis"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "flex-basis": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-basis"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "flex-direction": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-direction"
+ ],
+ "supports": [],
+ "values": [
+ "column",
+ "column-reverse",
+ "inherit",
+ "initial",
+ "row",
+ "row-reverse",
+ "unset"
+ ]
+ },
+ "flex-flow": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-direction",
+ "flex-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "column",
+ "column-reverse",
+ "inherit",
+ "initial",
+ "nowrap",
+ "row",
+ "row-reverse",
+ "unset",
+ "wrap",
+ "wrap-reverse"
+ ]
+ },
+ "flex-grow": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-grow"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "flex-shrink": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-shrink"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "flex-wrap": {
+ "isInherited": false,
+ "subproperties": [
+ "flex-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "nowrap",
+ "unset",
+ "wrap",
+ "wrap-reverse"
+ ]
+ },
+ "float": {
+ "isInherited": false,
+ "subproperties": [
+ "float"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "inline-end",
+ "inline-start",
+ "left",
+ "none",
+ "right",
+ "unset"
+ ]
+ },
+ "flood-color": {
+ "isInherited": false,
+ "subproperties": [
+ "flood-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "flood-opacity": {
+ "isInherited": false,
+ "subproperties": [
+ "flood-opacity"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font": {
+ "isInherited": true,
+ "subproperties": [
+ "font-family",
+ "font-style",
+ "font-weight",
+ "font-size",
+ "line-height",
+ "font-size-adjust",
+ "font-stretch",
+ "-x-system-font",
+ "font-feature-settings",
+ "font-language-override",
+ "font-kerning",
+ "font-synthesis",
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "-moz-block-height",
+ "-moz-calc",
+ "all-petite-caps",
+ "all-small-caps",
+ "auto",
+ "calc",
+ "condensed",
+ "expanded",
+ "extra-condensed",
+ "extra-expanded",
+ "inherit",
+ "initial",
+ "italic",
+ "large",
+ "larger",
+ "medium",
+ "none",
+ "normal",
+ "oblique",
+ "petite-caps",
+ "semi-condensed",
+ "semi-expanded",
+ "small",
+ "small-caps",
+ "smaller",
+ "sub",
+ "super",
+ "titling-caps",
+ "ultra-condensed",
+ "ultra-expanded",
+ "unicase",
+ "unset",
+ "x-large",
+ "x-small",
+ "xx-large",
+ "xx-small"
+ ]
+ },
+ "font-family": {
+ "isInherited": true,
+ "subproperties": [
+ "font-family"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-feature-settings": {
+ "isInherited": true,
+ "subproperties": [
+ "font-feature-settings"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-kerning": {
+ "isInherited": true,
+ "subproperties": [
+ "font-kerning"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "none",
+ "normal",
+ "unset"
+ ]
+ },
+ "font-language-override": {
+ "isInherited": true,
+ "subproperties": [
+ "font-language-override"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "font-size": {
+ "isInherited": true,
+ "subproperties": [
+ "font-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "large",
+ "larger",
+ "medium",
+ "small",
+ "smaller",
+ "unset",
+ "x-large",
+ "x-small",
+ "xx-large",
+ "xx-small"
+ ]
+ },
+ "font-size-adjust": {
+ "isInherited": true,
+ "subproperties": [
+ "font-size-adjust"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "font-stretch": {
+ "isInherited": true,
+ "subproperties": [
+ "font-stretch"
+ ],
+ "supports": [],
+ "values": [
+ "condensed",
+ "expanded",
+ "extra-condensed",
+ "extra-expanded",
+ "inherit",
+ "initial",
+ "normal",
+ "semi-condensed",
+ "semi-expanded",
+ "ultra-condensed",
+ "ultra-expanded",
+ "unset"
+ ]
+ },
+ "font-style": {
+ "isInherited": true,
+ "subproperties": [
+ "font-style"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "italic",
+ "normal",
+ "oblique",
+ "unset"
+ ]
+ },
+ "font-synthesis": {
+ "isInherited": true,
+ "subproperties": [
+ "font-synthesis"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-variant": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-alternates",
+ "font-variant-caps",
+ "font-variant-east-asian",
+ "font-variant-ligatures",
+ "font-variant-numeric",
+ "font-variant-position"
+ ],
+ "supports": [],
+ "values": [
+ "all-petite-caps",
+ "all-small-caps",
+ "inherit",
+ "initial",
+ "normal",
+ "petite-caps",
+ "small-caps",
+ "sub",
+ "super",
+ "titling-caps",
+ "unicase",
+ "unset"
+ ]
+ },
+ "font-variant-alternates": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-alternates"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-variant-caps": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-caps"
+ ],
+ "supports": [],
+ "values": [
+ "all-petite-caps",
+ "all-small-caps",
+ "inherit",
+ "initial",
+ "normal",
+ "petite-caps",
+ "small-caps",
+ "titling-caps",
+ "unicase",
+ "unset"
+ ]
+ },
+ "font-variant-east-asian": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-east-asian"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-variant-ligatures": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-ligatures"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-variant-numeric": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-numeric"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "font-variant-position": {
+ "isInherited": true,
+ "subproperties": [
+ "font-variant-position"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "normal",
+ "sub",
+ "super",
+ "unset"
+ ]
+ },
+ "font-weight": {
+ "isInherited": true,
+ "subproperties": [
+ "font-weight"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns",
+ "grid-auto-flow",
+ "grid-auto-rows",
+ "grid-auto-columns",
+ "grid-row-gap",
+ "grid-column-gap"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-area": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-start",
+ "grid-column-start",
+ "grid-row-end",
+ "grid-column-end"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-auto-columns": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-auto-columns"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-auto-flow": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-auto-flow"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-auto-rows": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-auto-rows"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-column": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-column-start",
+ "grid-column-end"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-column-end": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-column-end"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-column-gap": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-column-gap"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-column-start": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-column-start"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-gap": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-gap",
+ "grid-column-gap"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-row": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-start",
+ "grid-row-end"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-row-end": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-end"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-row-gap": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-gap"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-row-start": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-row-start"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-template": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-template-areas",
+ "grid-template-rows",
+ "grid-template-columns"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-template-areas": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-template-areas"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-template-columns": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-template-columns"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "grid-template-rows": {
+ "isInherited": false,
+ "subproperties": [
+ "grid-template-rows"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "height": {
+ "isInherited": false,
+ "subproperties": [
+ "height"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "hyphens": {
+ "isInherited": true,
+ "subproperties": [
+ "hyphens"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "manual",
+ "none",
+ "unset"
+ ]
+ },
+ "image-orientation": {
+ "isInherited": true,
+ "subproperties": [
+ "image-orientation"
+ ],
+ "supports": [
+ 1
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "image-rendering": {
+ "isInherited": true,
+ "subproperties": [
+ "image-rendering"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-crisp-edges",
+ "auto",
+ "inherit",
+ "initial",
+ "optimizequality",
+ "optimizespeed",
+ "unset"
+ ]
+ },
+ "ime-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "ime-mode"
+ ],
+ "supports": [],
+ "values": [
+ "active",
+ "auto",
+ "disabled",
+ "inactive",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "inline-size": {
+ "isInherited": false,
+ "subproperties": [
+ "inline-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "isolation": {
+ "isInherited": false,
+ "subproperties": [
+ "isolation"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "isolate",
+ "unset"
+ ]
+ },
+ "justify-content": {
+ "isInherited": false,
+ "subproperties": [
+ "justify-content"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "justify-items": {
+ "isInherited": false,
+ "subproperties": [
+ "justify-items"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "justify-self": {
+ "isInherited": false,
+ "subproperties": [
+ "justify-self"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "left": {
+ "isInherited": false,
+ "subproperties": [
+ "left"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "letter-spacing": {
+ "isInherited": true,
+ "subproperties": [
+ "letter-spacing"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "lighting-color": {
+ "isInherited": false,
+ "subproperties": [
+ "lighting-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "line-height": {
+ "isInherited": true,
+ "subproperties": [
+ "line-height"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "-moz-block-height",
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "list-style": {
+ "isInherited": true,
+ "subproperties": [
+ "list-style-type",
+ "list-style-image",
+ "list-style-position"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "inside",
+ "none",
+ "outside",
+ "unset",
+ "url"
+ ]
+ },
+ "list-style-image": {
+ "isInherited": true,
+ "subproperties": [
+ "list-style-image"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "list-style-position": {
+ "isInherited": true,
+ "subproperties": [
+ "list-style-position"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "inside",
+ "outside",
+ "unset"
+ ]
+ },
+ "list-style-type": {
+ "isInherited": true,
+ "subproperties": [
+ "list-style-type"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-top",
+ "margin-right",
+ "margin-bottom",
+ "margin-left"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-block-end": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-block-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-block-start": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-block-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-bottom": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-bottom"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-inline-end": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-inline-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-inline-start": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-inline-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-left": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-left"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-right": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-right"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "margin-top": {
+ "isInherited": false,
+ "subproperties": [
+ "margin-top"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "marker": {
+ "isInherited": true,
+ "subproperties": [
+ "marker-start",
+ "marker-mid",
+ "marker-end"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "marker-end": {
+ "isInherited": true,
+ "subproperties": [
+ "marker-end"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "marker-mid": {
+ "isInherited": true,
+ "subproperties": [
+ "marker-mid"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "marker-start": {
+ "isInherited": true,
+ "subproperties": [
+ "marker-start"
+ ],
+ "supports": [
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "url"
+ ]
+ },
+ "mask": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-image",
+ "mask-repeat",
+ "mask-position-x",
+ "mask-position-y",
+ "mask-clip",
+ "mask-origin",
+ "mask-size",
+ "mask-composite",
+ "mask-mode"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 6,
+ 8,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "add",
+ "alpha",
+ "border-box",
+ "content-box",
+ "exclude",
+ "inherit",
+ "initial",
+ "intersect",
+ "linear-gradient",
+ "luminance",
+ "match-source",
+ "no-repeat",
+ "none",
+ "padding-box",
+ "radial-gradient",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "round",
+ "space",
+ "subtract",
+ "unset",
+ "url"
+ ]
+ },
+ "mask-clip": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-clip"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "mask-composite": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-composite"
+ ],
+ "supports": [],
+ "values": [
+ "add",
+ "exclude",
+ "inherit",
+ "initial",
+ "intersect",
+ "subtract",
+ "unset"
+ ]
+ },
+ "mask-image": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-image"
+ ],
+ "supports": [
+ 4,
+ 5,
+ 11
+ ],
+ "values": [
+ "-moz-element",
+ "-moz-image-rect",
+ "-moz-linear-gradient",
+ "-moz-radial-gradient",
+ "-moz-repeating-linear-gradient",
+ "-moz-repeating-radial-gradient",
+ "inherit",
+ "initial",
+ "linear-gradient",
+ "none",
+ "radial-gradient",
+ "repeating-linear-gradient",
+ "repeating-radial-gradient",
+ "unset",
+ "url"
+ ]
+ },
+ "mask-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-mode"
+ ],
+ "supports": [],
+ "values": [
+ "alpha",
+ "inherit",
+ "initial",
+ "luminance",
+ "match-source",
+ "unset"
+ ]
+ },
+ "mask-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-origin"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "content-box",
+ "inherit",
+ "initial",
+ "padding-box",
+ "unset"
+ ]
+ },
+ "mask-position": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-x",
+ "mask-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "mask-position-x": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-x"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "mask-position-y": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-position-y"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "mask-repeat": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-repeat"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "no-repeat",
+ "repeat",
+ "repeat-x",
+ "repeat-y",
+ "round",
+ "space",
+ "unset"
+ ]
+ },
+ "mask-size": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "mask-type": {
+ "isInherited": false,
+ "subproperties": [
+ "mask-type"
+ ],
+ "supports": [],
+ "values": [
+ "alpha",
+ "inherit",
+ "initial",
+ "luminance",
+ "unset"
+ ]
+ },
+ "max-block-size": {
+ "isInherited": false,
+ "subproperties": [
+ "max-block-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "max-height": {
+ "isInherited": false,
+ "subproperties": [
+ "max-height"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "max-inline-size": {
+ "isInherited": false,
+ "subproperties": [
+ "max-inline-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "max-width": {
+ "isInherited": false,
+ "subproperties": [
+ "max-width"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "calc",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "min-block-size": {
+ "isInherited": false,
+ "subproperties": [
+ "min-block-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "min-height": {
+ "isInherited": false,
+ "subproperties": [
+ "min-height"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "min-inline-size": {
+ "isInherited": false,
+ "subproperties": [
+ "min-inline-size"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "min-width": {
+ "isInherited": false,
+ "subproperties": [
+ "min-width"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "mix-blend-mode": {
+ "isInherited": false,
+ "subproperties": [
+ "mix-blend-mode"
+ ],
+ "supports": [],
+ "values": [
+ "color",
+ "color-burn",
+ "color-dodge",
+ "darken",
+ "difference",
+ "exclusion",
+ "hard-light",
+ "hue",
+ "inherit",
+ "initial",
+ "lighten",
+ "luminosity",
+ "multiply",
+ "normal",
+ "overlay",
+ "saturation",
+ "screen",
+ "soft-light",
+ "unset"
+ ]
+ },
+ "object-fit": {
+ "isInherited": false,
+ "subproperties": [
+ "object-fit"
+ ],
+ "supports": [],
+ "values": [
+ "contain",
+ "cover",
+ "fill",
+ "inherit",
+ "initial",
+ "none",
+ "scale-down",
+ "unset"
+ ]
+ },
+ "object-position": {
+ "isInherited": false,
+ "subproperties": [
+ "object-position"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "offset-block-end": {
+ "isInherited": false,
+ "subproperties": [
+ "offset-block-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "offset-block-start": {
+ "isInherited": false,
+ "subproperties": [
+ "offset-block-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "offset-inline-end": {
+ "isInherited": false,
+ "subproperties": [
+ "offset-inline-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "offset-inline-start": {
+ "isInherited": false,
+ "subproperties": [
+ "offset-inline-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "opacity": {
+ "isInherited": false,
+ "subproperties": [
+ "opacity"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "order": {
+ "isInherited": false,
+ "subproperties": [
+ "order"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "outline": {
+ "isInherited": false,
+ "subproperties": [
+ "outline-width",
+ "outline-style",
+ "outline-color"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "COLOR",
+ "-moz-calc",
+ "auto",
+ "calc",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "inset",
+ "medium",
+ "none",
+ "outset",
+ "rgb",
+ "rgba",
+ "ridge",
+ "solid",
+ "thick",
+ "thin",
+ "transparent",
+ "unset"
+ ]
+ },
+ "outline-color": {
+ "isInherited": false,
+ "subproperties": [
+ "outline-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "outline-offset": {
+ "isInherited": false,
+ "subproperties": [
+ "outline-offset"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "outline-style": {
+ "isInherited": false,
+ "subproperties": [
+ "outline-style"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "dashed",
+ "dotted",
+ "double",
+ "groove",
+ "inherit",
+ "initial",
+ "inset",
+ "none",
+ "outset",
+ "ridge",
+ "solid",
+ "unset"
+ ]
+ },
+ "outline-width": {
+ "isInherited": false,
+ "subproperties": [
+ "outline-width"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "medium",
+ "thick",
+ "thin",
+ "unset"
+ ]
+ },
+ "overflow": {
+ "isInherited": false,
+ "subproperties": [
+ "overflow-x",
+ "overflow-y"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-hidden-unscrollable",
+ "auto",
+ "hidden",
+ "inherit",
+ "initial",
+ "scroll",
+ "unset",
+ "visible"
+ ]
+ },
+ "overflow-wrap": {
+ "isInherited": true,
+ "subproperties": [
+ "overflow-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "break-word",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "overflow-x": {
+ "isInherited": false,
+ "subproperties": [
+ "overflow-x"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-hidden-unscrollable",
+ "auto",
+ "hidden",
+ "inherit",
+ "initial",
+ "scroll",
+ "unset",
+ "visible"
+ ]
+ },
+ "overflow-y": {
+ "isInherited": false,
+ "subproperties": [
+ "overflow-y"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-hidden-unscrollable",
+ "auto",
+ "hidden",
+ "inherit",
+ "initial",
+ "scroll",
+ "unset",
+ "visible"
+ ]
+ },
+ "padding": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-top",
+ "padding-right",
+ "padding-bottom",
+ "padding-left"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-block-end": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-block-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-block-start": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-block-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-bottom": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-bottom"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-inline-end": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-inline-end"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-inline-start": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-inline-start"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-left": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-left"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-right": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-right"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "padding-top": {
+ "isInherited": false,
+ "subproperties": [
+ "padding-top"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "page-break-after": {
+ "isInherited": false,
+ "subproperties": [
+ "page-break-after"
+ ],
+ "supports": [],
+ "values": [
+ "always",
+ "auto",
+ "avoid",
+ "inherit",
+ "initial",
+ "left",
+ "right",
+ "unset"
+ ]
+ },
+ "page-break-before": {
+ "isInherited": false,
+ "subproperties": [
+ "page-break-before"
+ ],
+ "supports": [],
+ "values": [
+ "always",
+ "auto",
+ "avoid",
+ "inherit",
+ "initial",
+ "left",
+ "right",
+ "unset"
+ ]
+ },
+ "page-break-inside": {
+ "isInherited": false,
+ "subproperties": [
+ "page-break-inside"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "avoid",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "paint-order": {
+ "isInherited": true,
+ "subproperties": [
+ "paint-order"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "perspective": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective"
+ ],
+ "supports": [
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "perspective-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "perspective-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "place-content": {
+ "isInherited": false,
+ "subproperties": [
+ "align-content",
+ "justify-content"
+ ],
+ "supports": [],
+ "values": [
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "space-around",
+ "space-between",
+ "space-evenly",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "place-items": {
+ "isInherited": false,
+ "subproperties": [
+ "align-items",
+ "justify-items"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "place-self": {
+ "isInherited": false,
+ "subproperties": [
+ "align-self",
+ "justify-self"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "baseline",
+ "center",
+ "end",
+ "flex-end",
+ "flex-start",
+ "inherit",
+ "initial",
+ "last baseline",
+ "left",
+ "normal",
+ "right",
+ "self-end",
+ "self-start",
+ "start",
+ "stretch",
+ "unset"
+ ]
+ },
+ "pointer-events": {
+ "isInherited": true,
+ "subproperties": [
+ "pointer-events"
+ ],
+ "supports": [],
+ "values": [
+ "all",
+ "auto",
+ "fill",
+ "inherit",
+ "initial",
+ "none",
+ "painted",
+ "stroke",
+ "unset",
+ "visible",
+ "visiblefill",
+ "visiblepainted",
+ "visiblestroke"
+ ]
+ },
+ "position": {
+ "isInherited": false,
+ "subproperties": [
+ "position"
+ ],
+ "supports": [],
+ "values": [
+ "absolute",
+ "fixed",
+ "inherit",
+ "initial",
+ "relative",
+ "static",
+ "sticky",
+ "unset"
+ ]
+ },
+ "quotes": {
+ "isInherited": true,
+ "subproperties": [
+ "quotes"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "resize": {
+ "isInherited": false,
+ "subproperties": [
+ "resize"
+ ],
+ "supports": [],
+ "values": [
+ "both",
+ "horizontal",
+ "inherit",
+ "initial",
+ "none",
+ "unset",
+ "vertical"
+ ]
+ },
+ "right": {
+ "isInherited": false,
+ "subproperties": [
+ "right"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "ruby-align": {
+ "isInherited": true,
+ "subproperties": [
+ "ruby-align"
+ ],
+ "supports": [],
+ "values": [
+ "center",
+ "inherit",
+ "initial",
+ "space-around",
+ "space-between",
+ "start",
+ "unset"
+ ]
+ },
+ "ruby-position": {
+ "isInherited": true,
+ "subproperties": [
+ "ruby-position"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "over",
+ "under",
+ "unset"
+ ]
+ },
+ "scroll-behavior": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-behavior"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "smooth",
+ "unset"
+ ]
+ },
+ "scroll-snap-coordinate": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-coordinate"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "scroll-snap-destination": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-destination"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "scroll-snap-points-x": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-points-x"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "scroll-snap-points-y": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-points-y"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "scroll-snap-type": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-type-x",
+ "scroll-snap-type-y"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "mandatory",
+ "none",
+ "proximity",
+ "unset"
+ ]
+ },
+ "scroll-snap-type-x": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-type-x"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "mandatory",
+ "none",
+ "proximity",
+ "unset"
+ ]
+ },
+ "scroll-snap-type-y": {
+ "isInherited": false,
+ "subproperties": [
+ "scroll-snap-type-y"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "mandatory",
+ "none",
+ "proximity",
+ "unset"
+ ]
+ },
+ "shape-rendering": {
+ "isInherited": true,
+ "subproperties": [
+ "shape-rendering"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "crispedges",
+ "geometricprecision",
+ "inherit",
+ "initial",
+ "optimizespeed",
+ "unset"
+ ]
+ },
+ "stop-color": {
+ "isInherited": false,
+ "subproperties": [
+ "stop-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "stop-opacity": {
+ "isInherited": false,
+ "subproperties": [
+ "stop-opacity"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke"
+ ],
+ "supports": [
+ 2,
+ 11
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke-dasharray": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-dasharray"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke-dashoffset": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-dashoffset"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke-linecap": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-linecap"
+ ],
+ "supports": [],
+ "values": [
+ "butt",
+ "inherit",
+ "initial",
+ "round",
+ "square",
+ "unset"
+ ]
+ },
+ "stroke-linejoin": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-linejoin"
+ ],
+ "supports": [],
+ "values": [
+ "bevel",
+ "inherit",
+ "initial",
+ "miter",
+ "round",
+ "unset"
+ ]
+ },
+ "stroke-miterlimit": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-miterlimit"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke-opacity": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-opacity"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "stroke-width": {
+ "isInherited": true,
+ "subproperties": [
+ "stroke-width"
+ ],
+ "supports": [
+ 6,
+ 7,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "table-layout": {
+ "isInherited": false,
+ "subproperties": [
+ "table-layout"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "fixed",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-align": {
+ "isInherited": true,
+ "subproperties": [
+ "text-align"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-center",
+ "-moz-left",
+ "-moz-right",
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "justify",
+ "left",
+ "right",
+ "start",
+ "unset"
+ ]
+ },
+ "text-align-last": {
+ "isInherited": true,
+ "subproperties": [
+ "text-align-last"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "center",
+ "end",
+ "inherit",
+ "initial",
+ "justify",
+ "left",
+ "right",
+ "start",
+ "unset"
+ ]
+ },
+ "text-anchor": {
+ "isInherited": true,
+ "subproperties": [
+ "text-anchor"
+ ],
+ "supports": [],
+ "values": [
+ "end",
+ "inherit",
+ "initial",
+ "middle",
+ "start",
+ "unset"
+ ]
+ },
+ "text-combine-upright": {
+ "isInherited": true,
+ "subproperties": [
+ "text-combine-upright"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-decoration": {
+ "isInherited": false,
+ "subproperties": [
+ "text-decoration-color",
+ "text-decoration-line",
+ "text-decoration-style"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "-moz-none",
+ "currentColor",
+ "dashed",
+ "dotted",
+ "double",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "solid",
+ "transparent",
+ "unset",
+ "wavy"
+ ]
+ },
+ "text-decoration-color": {
+ "isInherited": false,
+ "subproperties": [
+ "text-decoration-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "text-decoration-line": {
+ "isInherited": false,
+ "subproperties": [
+ "text-decoration-line"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-decoration-style": {
+ "isInherited": false,
+ "subproperties": [
+ "text-decoration-style"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-none",
+ "dashed",
+ "dotted",
+ "double",
+ "inherit",
+ "initial",
+ "solid",
+ "unset",
+ "wavy"
+ ]
+ },
+ "text-emphasis": {
+ "isInherited": true,
+ "subproperties": [
+ "text-emphasis-style",
+ "text-emphasis-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "text-emphasis-color": {
+ "isInherited": true,
+ "subproperties": [
+ "text-emphasis-color"
+ ],
+ "supports": [
+ 2
+ ],
+ "values": [
+ "COLOR",
+ "currentColor",
+ "hsl",
+ "hsla",
+ "inherit",
+ "initial",
+ "rgb",
+ "rgba",
+ "transparent",
+ "unset"
+ ]
+ },
+ "text-emphasis-position": {
+ "isInherited": true,
+ "subproperties": [
+ "text-emphasis-position"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-emphasis-style": {
+ "isInherited": true,
+ "subproperties": [
+ "text-emphasis-style"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-indent": {
+ "isInherited": true,
+ "subproperties": [
+ "text-indent"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-orientation": {
+ "isInherited": true,
+ "subproperties": [
+ "text-orientation"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "mixed",
+ "sideways",
+ "sideways-right",
+ "unset",
+ "upright"
+ ]
+ },
+ "text-overflow": {
+ "isInherited": false,
+ "subproperties": [
+ "text-overflow"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-rendering": {
+ "isInherited": true,
+ "subproperties": [
+ "text-rendering"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "geometricprecision",
+ "inherit",
+ "initial",
+ "optimizelegibility",
+ "optimizespeed",
+ "unset"
+ ]
+ },
+ "text-shadow": {
+ "isInherited": true,
+ "subproperties": [
+ "text-shadow"
+ ],
+ "supports": [
+ 2,
+ 6
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "text-transform": {
+ "isInherited": true,
+ "subproperties": [
+ "text-transform"
+ ],
+ "supports": [],
+ "values": [
+ "capitalize",
+ "full-width",
+ "inherit",
+ "initial",
+ "lowercase",
+ "none",
+ "unset",
+ "uppercase"
+ ]
+ },
+ "top": {
+ "isInherited": false,
+ "subproperties": [
+ "top"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "touch-action": {
+ "isInherited": false,
+ "subproperties": [
+ "touch-action"
+ ],
+ "supports": [],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "manipulation",
+ "none",
+ "pan-x",
+ "pan-y",
+ "unset"
+ ]
+ },
+ "transform": {
+ "isInherited": false,
+ "subproperties": [
+ "transform"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "transform-box": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-box"
+ ],
+ "supports": [],
+ "values": [
+ "border-box",
+ "fill-box",
+ "inherit",
+ "initial",
+ "unset",
+ "view-box"
+ ]
+ },
+ "transform-origin": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-origin"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "transform-style": {
+ "isInherited": false,
+ "subproperties": [
+ "transform-style"
+ ],
+ "supports": [],
+ "values": [
+ "flat",
+ "inherit",
+ "initial",
+ "preserve-3d",
+ "unset"
+ ]
+ },
+ "transition": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property",
+ "transition-duration",
+ "transition-timing-function",
+ "transition-delay"
+ ],
+ "supports": [
+ 9,
+ 10
+ ],
+ "values": [
+ "all",
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "none",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "transition-delay": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-delay"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "transition-duration": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-duration"
+ ],
+ "supports": [
+ 9
+ ],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "transition-property": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-property"
+ ],
+ "supports": [],
+ "values": [
+ "all",
+ "inherit",
+ "initial",
+ "none",
+ "unset"
+ ]
+ },
+ "transition-timing-function": {
+ "isInherited": false,
+ "subproperties": [
+ "transition-timing-function"
+ ],
+ "supports": [
+ 10
+ ],
+ "values": [
+ "cubic-bezier",
+ "ease",
+ "ease-in",
+ "ease-in-out",
+ "ease-out",
+ "inherit",
+ "initial",
+ "linear",
+ "step-end",
+ "step-start",
+ "steps",
+ "unset"
+ ]
+ },
+ "unicode-bidi": {
+ "isInherited": false,
+ "subproperties": [
+ "unicode-bidi"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-isolate",
+ "-moz-isolate-override",
+ "-moz-plaintext",
+ "bidi-override",
+ "embed",
+ "inherit",
+ "initial",
+ "isolate",
+ "isolate-override",
+ "normal",
+ "plaintext",
+ "unset"
+ ]
+ },
+ "vector-effect": {
+ "isInherited": false,
+ "subproperties": [
+ "vector-effect"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "non-scaling-stroke",
+ "none",
+ "unset"
+ ]
+ },
+ "vertical-align": {
+ "isInherited": false,
+ "subproperties": [
+ "vertical-align"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "-moz-middle-with-baseline",
+ "baseline",
+ "bottom",
+ "calc",
+ "inherit",
+ "initial",
+ "middle",
+ "sub",
+ "super",
+ "text-bottom",
+ "text-top",
+ "top",
+ "unset"
+ ]
+ },
+ "visibility": {
+ "isInherited": true,
+ "subproperties": [
+ "visibility"
+ ],
+ "supports": [],
+ "values": [
+ "collapse",
+ "hidden",
+ "inherit",
+ "initial",
+ "unset",
+ "visible"
+ ]
+ },
+ "white-space": {
+ "isInherited": true,
+ "subproperties": [
+ "white-space"
+ ],
+ "supports": [],
+ "values": [
+ "-moz-pre-space",
+ "inherit",
+ "initial",
+ "normal",
+ "nowrap",
+ "pre",
+ "pre-line",
+ "pre-wrap",
+ "unset"
+ ]
+ },
+ "width": {
+ "isInherited": false,
+ "subproperties": [
+ "width"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-available",
+ "-moz-calc",
+ "-moz-fit-content",
+ "-moz-max-content",
+ "-moz-min-content",
+ "auto",
+ "calc",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "will-change": {
+ "isInherited": false,
+ "subproperties": [
+ "will-change"
+ ],
+ "supports": [],
+ "values": [
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ },
+ "word-break": {
+ "isInherited": true,
+ "subproperties": [
+ "word-break"
+ ],
+ "supports": [],
+ "values": [
+ "break-all",
+ "inherit",
+ "initial",
+ "keep-all",
+ "normal",
+ "unset"
+ ]
+ },
+ "word-spacing": {
+ "isInherited": true,
+ "subproperties": [
+ "word-spacing"
+ ],
+ "supports": [
+ 6,
+ 8
+ ],
+ "values": [
+ "-moz-calc",
+ "calc",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "word-wrap": {
+ "isInherited": true,
+ "subproperties": [
+ "overflow-wrap"
+ ],
+ "supports": [],
+ "values": [
+ "break-word",
+ "inherit",
+ "initial",
+ "normal",
+ "unset"
+ ]
+ },
+ "writing-mode": {
+ "isInherited": true,
+ "subproperties": [
+ "writing-mode"
+ ],
+ "supports": [],
+ "values": [
+ "horizontal-tb",
+ "inherit",
+ "initial",
+ "lr",
+ "lr-tb",
+ "rl",
+ "rl-tb",
+ "sideways-lr",
+ "sideways-rl",
+ "tb",
+ "tb-rl",
+ "unset",
+ "vertical-lr",
+ "vertical-rl"
+ ]
+ },
+ "z-index": {
+ "isInherited": false,
+ "subproperties": [
+ "z-index"
+ ],
+ "supports": [
+ 7
+ ],
+ "values": [
+ "auto",
+ "inherit",
+ "initial",
+ "unset"
+ ]
+ }
+};
+
+/**
+ * A list of the pseudo elements.
+ */
+exports.PSEUDO_ELEMENTS = [
+ ":after",
+ ":before",
+ ":backdrop",
+ ":first-letter",
+ ":first-line",
+ ":-moz-selection",
+ ":-moz-focus-inner",
+ ":-moz-focus-outer",
+ ":-moz-list-bullet",
+ ":-moz-list-number",
+ ":-moz-math-anonymous",
+ ":-moz-progress-bar",
+ ":-moz-range-track",
+ ":-moz-range-progress",
+ ":-moz-range-thumb",
+ ":-moz-meter-bar",
+ ":-moz-placeholder",
+ ":placeholder",
+ ":-moz-color-swatch"
+];
diff --git a/devtools/shared/css/generated/properties-db.js.in b/devtools/shared/css/generated/properties-db.js.in
new file mode 100644
index 000000000..370c9199a
--- /dev/null
+++ b/devtools/shared/css/generated/properties-db.js.in
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * This file is automatically generated by `mach devtools-css-db`. It contains
+ * a static list of CSS properties that can be computed by Gecko. The actual script
+ * to generate these files can be found at devtools/shared/css/generate-properties-db.js.
+ */
+
+/**
+ * A list of CSS Properties and their various characteristics.
+ */
+exports.CSS_PROPERTIES = ${cssProperties};
+
+/**
+ * A list of the pseudo elements.
+ */
+exports.PSEUDO_ELEMENTS = ${pseudoElements};
diff --git a/devtools/shared/css/lexer.js b/devtools/shared/css/lexer.js
new file mode 100644
index 000000000..9013b63ea
--- /dev/null
+++ b/devtools/shared/css/lexer.js
@@ -0,0 +1,1240 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A CSS Lexer. This file is a bit unusual -- it is a more or less
+// direct translation of layout/style/nsCSSScanner.cpp and
+// layout/style/CSSLexer.cpp into JS. This implements the
+// CSSLexer.webidl interface, and the intent is to try to keep it in
+// sync with changes to the platform CSS lexer. Due to this goal,
+// this file violates some naming conventions and consequently locally
+// disables some eslint rules.
+
+/* eslint-disable camelcase, no-inline-comments, mozilla/no-aArgs */
+/* eslint-disable no-else-return */
+
+"use strict";
+
+// White space of any kind. No value fields are used. Note that
+// comments do *not* count as white space; comments separate tokens
+// but are not themselves tokens.
+const eCSSToken_Whitespace = "whitespace"; //
+// A comment.
+const eCSSToken_Comment = "comment"; // /*...*/
+
+// Identifier-like tokens. mIdent is the text of the identifier.
+// The difference between ID and Hash is: if the text after the #
+// would have been a valid Ident if the # hadn't been there, the
+// scanner produces an ID token. Otherwise it produces a Hash token.
+// (This distinction is required by css3-selectors.)
+const eCSSToken_Ident = "ident"; // word
+const eCSSToken_Function = "function"; // word(
+const eCSSToken_AtKeyword = "at"; // @word
+const eCSSToken_ID = "id"; // #word
+const eCSSToken_Hash = "hash"; // #0word
+
+// Numeric tokens. mNumber is the floating-point value of the
+// number, and mHasSign indicates whether there was an explicit sign
+// (+ or -) in front of the number. If mIntegerValid is true, the
+// number had the lexical form of an integer, and mInteger is its
+// integer value. Lexically integer values outside the range of a
+// 32-bit signed number are clamped to the maximum values; mNumber
+// will indicate a 'truer' value in that case. Percentage tokens
+// are always considered not to be integers, even if their numeric
+// value is integral (100% => mNumber = 1.0). For Dimension
+// tokens, mIdent holds the text of the unit.
+const eCSSToken_Number = "number"; // 1 -5 +2e3 3.14159 7.297352e-3
+const eCSSToken_Dimension = "dimension"; // 24px 8.5in
+const eCSSToken_Percentage = "percentage"; // 85% 1280.4%
+
+// String-like tokens. In all cases, mIdent holds the text
+// belonging to the string, and mSymbol holds the delimiter
+// character, which may be ', ", or zero (only for unquoted URLs).
+// Bad_String and Bad_URL tokens are emitted when the closing
+// delimiter or parenthesis was missing.
+const eCSSToken_String = "string"; // 'foo bar' "foo bar"
+const eCSSToken_Bad_String = "bad_string"; // 'foo bar
+const eCSSToken_URL = "url"; // url(foobar) url("foo bar")
+const eCSSToken_Bad_URL = "bad_url"; // url(foo
+
+// Any one-character symbol. mSymbol holds the character.
+const eCSSToken_Symbol = "symbol"; // . ; { } ! *
+
+// Match operators. These are single tokens rather than pairs of
+// Symbol tokens because css3-selectors forbids the presence of
+// comments between the two characters. No value fields are used;
+// the token type indicates which operator.
+const eCSSToken_Includes = "includes"; // ~=
+const eCSSToken_Dashmatch = "dashmatch"; // |=
+const eCSSToken_Beginsmatch = "beginsmatch"; // ^=
+const eCSSToken_Endsmatch = "endsmatch"; // $=
+const eCSSToken_Containsmatch = "containsmatch"; // *=
+
+// Unicode-range token: currently used only in @font-face.
+// The lexical rule for this token includes several forms that are
+// semantically invalid. Therefore, mIdent always holds the
+// complete original text of the token (so we can print it
+// accurately in diagnostics), and mIntegerValid is true iff the
+// token is semantically valid. In that case, mInteger holds the
+// lowest value included in the range, and mInteger2 holds the
+// highest value included in the range.
+const eCSSToken_URange = "urange"; // U+007e U+01?? U+2000-206F
+
+// HTML comment delimiters, ignored as a unit when they appear at
+// the top level of a style sheet, for compatibility with websites
+// written for compatibility with pre-CSS browsers. This token type
+// subsumes the css2.1 CDO and CDC tokens, which are always treated
+// the same by the parser. mIdent holds the text of the token, for
+// diagnostics.
+const eCSSToken_HTMLComment = "htmlcomment"; // <!-- -->
+
+const eEOFCharacters_None = 0x0000;
+
+// to handle \<EOF> inside strings
+const eEOFCharacters_DropBackslash = 0x0001;
+
+// to handle \<EOF> outside strings
+const eEOFCharacters_ReplacementChar = 0x0002;
+
+// to close comments
+const eEOFCharacters_Asterisk = 0x0004;
+const eEOFCharacters_Slash = 0x0008;
+
+// to close double-quoted strings
+const eEOFCharacters_DoubleQuote = 0x0010;
+
+// to close single-quoted strings
+const eEOFCharacters_SingleQuote = 0x0020;
+
+// to close URLs
+const eEOFCharacters_CloseParen = 0x0040;
+
+// Bridge the char/string divide.
+const APOSTROPHE = "'".charCodeAt(0);
+const ASTERISK = "*".charCodeAt(0);
+const CARRIAGE_RETURN = "\r".charCodeAt(0);
+const CIRCUMFLEX_ACCENT = "^".charCodeAt(0);
+const COMMERCIAL_AT = "@".charCodeAt(0);
+const DIGIT_NINE = "9".charCodeAt(0);
+const DIGIT_ZERO = "0".charCodeAt(0);
+const DOLLAR_SIGN = "$".charCodeAt(0);
+const EQUALS_SIGN = "=".charCodeAt(0);
+const EXCLAMATION_MARK = "!".charCodeAt(0);
+const FULL_STOP = ".".charCodeAt(0);
+const GREATER_THAN_SIGN = ">".charCodeAt(0);
+const HYPHEN_MINUS = "-".charCodeAt(0);
+const LATIN_CAPITAL_LETTER_E = "E".charCodeAt(0);
+const LATIN_CAPITAL_LETTER_U = "U".charCodeAt(0);
+const LATIN_SMALL_LETTER_E = "e".charCodeAt(0);
+const LATIN_SMALL_LETTER_U = "u".charCodeAt(0);
+const LEFT_PARENTHESIS = "(".charCodeAt(0);
+const LESS_THAN_SIGN = "<".charCodeAt(0);
+const LINE_FEED = "\n".charCodeAt(0);
+const NUMBER_SIGN = "#".charCodeAt(0);
+const PERCENT_SIGN = "%".charCodeAt(0);
+const PLUS_SIGN = "+".charCodeAt(0);
+const QUESTION_MARK = "?".charCodeAt(0);
+const QUOTATION_MARK = "\"".charCodeAt(0);
+const REVERSE_SOLIDUS = "\\".charCodeAt(0);
+const RIGHT_PARENTHESIS = ")".charCodeAt(0);
+const SOLIDUS = "/".charCodeAt(0);
+const TILDE = "~".charCodeAt(0);
+const VERTICAL_LINE = "|".charCodeAt(0);
+
+const UCS2_REPLACEMENT_CHAR = 0xFFFD;
+
+const kImpliedEOFCharacters = [
+ UCS2_REPLACEMENT_CHAR,
+ ASTERISK,
+ SOLIDUS,
+ QUOTATION_MARK,
+ APOSTROPHE,
+ RIGHT_PARENTHESIS,
+ 0
+];
+
+/**
+ * Ensure that the character is valid. If it is valid, return it;
+ * otherwise, return the replacement character.
+ *
+ * @param {Number} c the character to check
+ * @return {Number} the character or its replacement
+ */
+function ensureValidChar(c) {
+ if (c >= 0x00110000 || (c & 0xFFF800) == 0xD800) {
+ // Out of range or a surrogate.
+ return UCS2_REPLACEMENT_CHAR;
+ }
+ return c;
+}
+
+/**
+ * Turn a string into an array of character codes.
+ *
+ * @param {String} str the input string
+ * @return {Array} an array of character codes, one per character in
+ * the input string.
+ */
+function stringToCodes(str) {
+ return Array.prototype.map.call(str, (c) => c.charCodeAt(0));
+}
+
+const IS_HEX_DIGIT = 0x01;
+const IS_IDSTART = 0x02;
+const IS_IDCHAR = 0x04;
+const IS_URL_CHAR = 0x08;
+const IS_HSPACE = 0x10;
+const IS_VSPACE = 0x20;
+const IS_SPACE = IS_HSPACE | IS_VSPACE;
+const IS_STRING = 0x40;
+
+const H = IS_HSPACE;
+const V = IS_VSPACE;
+const I = IS_IDCHAR;
+const J = IS_IDSTART;
+const U = IS_URL_CHAR;
+const S = IS_STRING;
+const X = IS_HEX_DIGIT;
+
+const SH = S | H;
+const SU = S | U;
+const SUI = S | U | I;
+const SUIJ = S | U | I | J;
+const SUIX = S | U | I | X;
+const SUIJX = S | U | I | J | X;
+
+/* eslint-disable indent, no-multi-spaces, comma-spacing, spaced-comment */
+const gLexTable = [
+// 00 01 02 03 04 05 06 07
+ 0, S, S, S, S, S, S, S,
+// 08 TAB LF 0B FF CR 0E 0F
+ S, SH, V, S, V, V, S, S,
+// 10 11 12 13 14 15 16 17
+ S, S, S, S, S, S, S, S,
+// 18 19 1A 1B 1C 1D 1E 1F
+ S, S, S, S, S, S, S, S,
+//SPC ! " # $ % & '
+ SH, SU, 0, SU, SU, SU, SU, 0,
+// ( ) * + , - . /
+ S, S, SU, SU, SU, SUI, SU, SU,
+// 0 1 2 3 4 5 6 7
+ SUIX, SUIX, SUIX, SUIX, SUIX, SUIX, SUIX, SUIX,
+// 8 9 : ; < = > ?
+ SUIX, SUIX, SU, SU, SU, SU, SU, SU,
+// @ A B C D E F G
+ SU,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX, SUIJ,
+// H I J K L M N O
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// P Q R S T U V W
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// X Y Z [ \ ] ^ _
+ SUIJ, SUIJ, SUIJ, SU, J, SU, SU, SUIJ,
+// ` a b c d e f g
+ SU,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX,SUIJX, SUIJ,
+// h i j k l m n o
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// p q r s t u v w
+ SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ, SUIJ,
+// x y z { | } ~ 7F
+ SUIJ, SUIJ, SUIJ, SU, SU, SU, SU, S,
+];
+/* eslint-enable indent, no-multi-spaces, comma-spacing, spaced-comment */
+
+/**
+ * True if 'ch' is in character class 'cls', which should be one of
+ * the constants above or some combination of them. All characters
+ * above U+007F are considered to be in 'cls'. EOF is never in 'cls'.
+ */
+function IsOpenCharClass(ch, cls) {
+ return ch >= 0 && (ch >= 128 || (gLexTable[ch] & cls) != 0);
+}
+
+/**
+ * True if 'ch' is in character class 'cls', which should be one of
+ * the constants above or some combination of them. No characters
+ * above U+007F are considered to be in 'cls'. EOF is never in 'cls'.
+ */
+function IsClosedCharClass(ch, cls) {
+ return ch >= 0 && ch < 128 && (gLexTable[ch] & cls) != 0;
+}
+
+/**
+ * True if 'ch' is CSS whitespace, i.e. any of the ASCII characters
+ * TAB, LF, FF, CR, or SPC.
+ */
+function IsWhitespace(ch) {
+ return IsClosedCharClass(ch, IS_SPACE);
+}
+
+/**
+ * True if 'ch' is horizontal whitespace, i.e. TAB or SPC.
+ */
+function IsHorzSpace(ch) {
+ return IsClosedCharClass(ch, IS_HSPACE);
+}
+
+/**
+ * True if 'ch' is vertical whitespace, i.e. LF, FF, or CR. Vertical
+ * whitespace requires special handling when consumed, see AdvanceLine.
+ */
+function IsVertSpace(ch) {
+ return IsClosedCharClass(ch, IS_VSPACE);
+}
+
+/**
+ * True if 'ch' is a character that can appear in the middle of an identifier.
+ * This includes U+0000 since it is handled as U+FFFD, but for purposes of
+ * GatherText it should not be included in IsOpenCharClass.
+ */
+function IsIdentChar(ch) {
+ return IsOpenCharClass(ch, IS_IDCHAR) || ch == 0;
+}
+
+/**
+ * True if 'ch' is a character that by itself begins an identifier.
+ * This includes U+0000 since it is handled as U+FFFD, but for purposes of
+ * GatherText it should not be included in IsOpenCharClass.
+ * (This is a subset of IsIdentChar.)
+ */
+function IsIdentStart(ch) {
+ return IsOpenCharClass(ch, IS_IDSTART) || ch == 0;
+}
+
+/**
+ * True if the two-character sequence aFirstChar+aSecondChar begins an
+ * identifier.
+ */
+function StartsIdent(aFirstChar, aSecondChar) {
+ return IsIdentStart(aFirstChar) ||
+ (aFirstChar == HYPHEN_MINUS && (aSecondChar == HYPHEN_MINUS ||
+ IsIdentStart(aSecondChar)));
+}
+
+/**
+ * True if 'ch' is a decimal digit.
+ */
+function IsDigit(ch) {
+ return (ch >= DIGIT_ZERO) && (ch <= DIGIT_NINE);
+}
+
+/**
+ * True if 'ch' is a hexadecimal digit.
+ */
+function IsHexDigit(ch) {
+ return IsClosedCharClass(ch, IS_HEX_DIGIT);
+}
+
+/**
+ * Assuming that 'ch' is a decimal digit, return its numeric value.
+ */
+function DecimalDigitValue(ch) {
+ return ch - DIGIT_ZERO;
+}
+
+/**
+ * Assuming that 'ch' is a hexadecimal digit, return its numeric value.
+ */
+function HexDigitValue(ch) {
+ if (IsDigit(ch)) {
+ return DecimalDigitValue(ch);
+ } else {
+ // Note: c&7 just keeps the low three bits which causes
+ // upper and lower case alphabetics to both yield their
+ // "relative to 10" value for computing the hex value.
+ return (ch & 0x7) + 9;
+ }
+}
+
+/**
+ * If 'ch' can be the first character of a two-character match operator
+ * token, return the token type code for that token, otherwise return
+ * eCSSToken_Symbol to indicate that it can't.
+ */
+function MatchOperatorType(ch) {
+ switch (ch) {
+ case TILDE: return eCSSToken_Includes;
+ case VERTICAL_LINE: return eCSSToken_Dashmatch;
+ case CIRCUMFLEX_ACCENT: return eCSSToken_Beginsmatch;
+ case DOLLAR_SIGN: return eCSSToken_Endsmatch;
+ case ASTERISK: return eCSSToken_Containsmatch;
+ default: return eCSSToken_Symbol;
+ }
+}
+
+function Scanner(buffer) {
+ this.mBuffer = buffer || "";
+ this.mOffset = 0;
+ this.mCount = this.mBuffer.length;
+ this.mLineNumber = 1;
+ this.mLineOffset = 0;
+ this.mTokenLineOffset = 0;
+ this.mTokenOffset = 0;
+ this.mTokenLineNumber = 1;
+ this.mEOFCharacters = eEOFCharacters_None;
+}
+
+Scanner.prototype = {
+ /**
+ * @see CSSLexer.lineNumber
+ */
+ get lineNumber() {
+ return this.mTokenLineNumber - 1;
+ },
+
+ /**
+ * @see CSSLexer.columnNumber
+ */
+ get columnNumber() {
+ return this.mTokenOffset - this.mTokenLineOffset;
+ },
+
+ /**
+ * @see CSSLexer.performEOFFixup
+ */
+ performEOFFixup: function (aInputString, aPreserveBackslash) {
+ let result = aInputString;
+
+ let eofChars = this.mEOFCharacters;
+
+ if (aPreserveBackslash &&
+ (eofChars & (eEOFCharacters_DropBackslash |
+ eEOFCharacters_ReplacementChar)) != 0) {
+ eofChars &= ~(eEOFCharacters_DropBackslash |
+ eEOFCharacters_ReplacementChar);
+ result += "\\";
+ }
+
+ if ((eofChars & eEOFCharacters_DropBackslash) != 0 &&
+ result.length > 0 && result.endsWith("\\")) {
+ result = result.slice(0, -1);
+ }
+
+ let extra = [];
+ this.AppendImpliedEOFCharacters(eofChars, extra);
+ let asString = String.fromCharCode.apply(null, extra);
+
+ return result + asString;
+ },
+
+ /**
+ * @see CSSLexer.nextToken
+ */
+ nextToken: function () {
+ let token = {};
+ if (!this.Next(token)) {
+ return null;
+ }
+
+ let resultToken = {};
+ resultToken.tokenType = token.mType;
+ resultToken.startOffset = this.mTokenOffset;
+ resultToken.endOffset = this.mOffset;
+
+ let constructText = () => {
+ return String.fromCharCode.apply(null, token.mIdent);
+ };
+
+ switch (token.mType) {
+ case eCSSToken_Whitespace:
+ break;
+
+ case eCSSToken_Ident:
+ case eCSSToken_Function:
+ case eCSSToken_AtKeyword:
+ case eCSSToken_ID:
+ case eCSSToken_Hash:
+ resultToken.text = constructText();
+ break;
+
+ case eCSSToken_Dimension:
+ resultToken.text = constructText();
+ /* Fall through. */
+ case eCSSToken_Number:
+ case eCSSToken_Percentage:
+ resultToken.number = token.mNumber;
+ resultToken.hasSign = token.mHasSign;
+ resultToken.isInteger = token.mIntegerValid;
+ break;
+
+ case eCSSToken_String:
+ case eCSSToken_Bad_String:
+ case eCSSToken_URL:
+ case eCSSToken_Bad_URL:
+ resultToken.text = constructText();
+ /* Don't bother emitting the delimiter, as it is readily extracted
+ from the source string when needed. */
+ break;
+
+ case eCSSToken_Symbol:
+ resultToken.text = String.fromCharCode(token.mSymbol);
+ break;
+
+ case eCSSToken_Includes:
+ case eCSSToken_Dashmatch:
+ case eCSSToken_Beginsmatch:
+ case eCSSToken_Endsmatch:
+ case eCSSToken_Containsmatch:
+ case eCSSToken_URange:
+ break;
+
+ case eCSSToken_Comment:
+ case eCSSToken_HTMLComment:
+ /* The comment text is easily extracted from the source string,
+ and is rarely useful. */
+ break;
+ }
+
+ return resultToken;
+ },
+
+ /**
+ * Return the raw UTF-16 code unit at position |this.mOffset + n| within
+ * the read buffer. If that is beyond the end of the buffer, returns
+ * -1 to indicate end of input.
+ */
+ Peek: function (n = 0) {
+ if (this.mOffset + n >= this.mCount) {
+ return -1;
+ }
+ return this.mBuffer.charCodeAt(this.mOffset + n);
+ },
+
+ /**
+ * Advance |this.mOffset| over |n| code units. Advance(0) is a no-op.
+ * If |n| is greater than the distance to end of input, will silently
+ * stop at the end. May not be used to advance over a line boundary;
+ * AdvanceLine() must be used instead.
+ */
+ Advance: function (n = 1) {
+ if (this.mOffset + n >= this.mCount || this.mOffset + n < this.mOffset) {
+ this.mOffset = this.mCount;
+ } else {
+ this.mOffset += n;
+ }
+ },
+
+ /**
+ * Advance |this.mOffset| over a line boundary.
+ */
+ AdvanceLine: function () {
+ // Advance over \r\n as a unit.
+ if (this.mBuffer.charCodeAt(this.mOffset) == CARRIAGE_RETURN &&
+ this.mOffset + 1 < this.mCount &&
+ this.mBuffer.charCodeAt(this.mOffset + 1) == LINE_FEED) {
+ this.mOffset += 2;
+ } else {
+ this.mOffset += 1;
+ }
+ // 0 is a magical line number meaning that we don't know (i.e., script)
+ if (this.mLineNumber != 0) {
+ this.mLineNumber++;
+ }
+ this.mLineOffset = this.mOffset;
+ },
+
+ /**
+ * Skip over a sequence of whitespace characters (vertical or
+ * horizontal) starting at the current read position.
+ */
+ SkipWhitespace: function () {
+ for (;;) {
+ let ch = this.Peek();
+ if (!IsWhitespace(ch)) { // EOF counts as non-whitespace
+ break;
+ }
+ if (IsVertSpace(ch)) {
+ this.AdvanceLine();
+ } else {
+ this.Advance();
+ }
+ }
+ },
+
+ /**
+ * Skip over one CSS comment starting at the current read position.
+ */
+ SkipComment: function () {
+ this.Advance(2);
+ for (;;) {
+ let ch = this.Peek();
+ if (ch < 0) {
+ this.SetEOFCharacters(eEOFCharacters_Asterisk | eEOFCharacters_Slash);
+ return;
+ }
+ if (ch == ASTERISK) {
+ this.Advance();
+ ch = this.Peek();
+ if (ch < 0) {
+ this.SetEOFCharacters(eEOFCharacters_Slash);
+ return;
+ }
+ if (ch == SOLIDUS) {
+ this.Advance();
+ return;
+ }
+ } else if (IsVertSpace(ch)) {
+ this.AdvanceLine();
+ } else {
+ this.Advance();
+ }
+ }
+ },
+
+ /**
+ * If there is a valid escape sequence starting at the current read
+ * position, consume it, decode it, append the result to |aOutput|,
+ * and return true. Otherwise, consume nothing, leave |aOutput|
+ * unmodified, and return false. If |aInString| is true, accept the
+ * additional form of escape sequence allowed within string-like tokens.
+ */
+ GatherEscape: function (aOutput, aInString) {
+ let ch = this.Peek(1);
+ if (ch < 0) {
+ // If we are in a string (or a url() containing a string), we want to drop
+ // the backslash on the floor. Otherwise, we want to treat it as a U+FFFD
+ // character.
+ this.Advance();
+ if (aInString) {
+ this.SetEOFCharacters(eEOFCharacters_DropBackslash);
+ } else {
+ aOutput.push(UCS2_REPLACEMENT_CHAR);
+ this.SetEOFCharacters(eEOFCharacters_ReplacementChar);
+ }
+ return true;
+ }
+ if (IsVertSpace(ch)) {
+ if (aInString) {
+ // In strings (and in url() containing a string), escaped
+ // newlines are completely removed, to allow splitting over
+ // multiple lines.
+ this.Advance();
+ this.AdvanceLine();
+ return true;
+ }
+ // Outside of strings, backslash followed by a newline is not an escape.
+ return false;
+ }
+
+ if (!IsHexDigit(ch)) {
+ // "Any character (except a hexadecimal digit, linefeed, carriage
+ // return, or form feed) can be escaped with a backslash to remove
+ // its special meaning." -- CSS2.1 section 4.1.3
+ this.Advance(2);
+ if (ch == 0) {
+ aOutput.push(UCS2_REPLACEMENT_CHAR);
+ } else {
+ aOutput.push(ch);
+ }
+ return true;
+ }
+
+ // "[at most six hexadecimal digits following a backslash] stand
+ // for the ISO 10646 character with that number, which must not be
+ // zero. (It is undefined in CSS 2.1 what happens if a style sheet
+ // does contain a character with Unicode codepoint zero.)"
+ // -- CSS2.1 section 4.1.3
+
+ // At this point we know we have \ followed by at least one
+ // hexadecimal digit, therefore the escape sequence is valid and we
+ // can go ahead and consume the backslash.
+ this.Advance();
+ let val = 0;
+ let i = 0;
+ do {
+ val = val * 16 + HexDigitValue(ch);
+ i++;
+ this.Advance();
+ ch = this.Peek();
+ } while (i < 6 && IsHexDigit(ch));
+
+ // "Interpret the hex digits as a hexadecimal number. If this
+ // number is zero, or is greater than the maximum allowed
+ // codepoint, return U+FFFD REPLACEMENT CHARACTER" -- CSS Syntax
+ // Level 3
+ if (val == 0) {
+ aOutput.push(UCS2_REPLACEMENT_CHAR);
+ } else {
+ aOutput.push(ensureValidChar(val));
+ }
+
+ // Consume exactly one whitespace character after a
+ // hexadecimal escape sequence.
+ if (IsVertSpace(ch)) {
+ this.AdvanceLine();
+ } else if (IsHorzSpace(ch)) {
+ this.Advance();
+ }
+ return true;
+ },
+
+ /**
+ * Consume a run of "text" beginning with the current read position,
+ * consisting of characters in the class |aClass| (which must be a
+ * suitable argument to IsOpenCharClass) plus escape sequences.
+ * Append the text to |aText|, after decoding escape sequences.
+ *
+ * Returns true if at least one character was appended to |aText|,
+ * false otherwise.
+ */
+ GatherText: function (aClass, aText) {
+ let start = this.mOffset;
+ let inString = aClass == IS_STRING;
+
+ for (;;) {
+ // Consume runs of unescaped characters in one go.
+ let n = this.mOffset;
+ while (n < this.mCount && IsOpenCharClass(this.mBuffer.charCodeAt(n),
+ aClass)) {
+ n++;
+ }
+ if (n > this.mOffset) {
+ let substr = this.mBuffer.slice(this.mOffset, n);
+ Array.prototype.push.apply(aText, stringToCodes(substr));
+ this.mOffset = n;
+ }
+ if (n == this.mCount) {
+ break;
+ }
+
+ let ch = this.Peek();
+ if (ch == 0) {
+ this.Advance();
+ aText.push(UCS2_REPLACEMENT_CHAR);
+ continue;
+ }
+
+ if (ch != REVERSE_SOLIDUS) {
+ break;
+ }
+ if (!this.GatherEscape(aText, inString)) {
+ break;
+ }
+ }
+
+ return this.mOffset > start;
+ },
+
+ /**
+ * Scan an Ident token. This also handles Function and URL tokens,
+ * both of which begin indistinguishably from an identifier. It can
+ * produce a Symbol token when an apparent identifier actually led
+ * into an invalid escape sequence.
+ */
+ ScanIdent: function (aToken) {
+ if (!this.GatherText(IS_IDCHAR, aToken.mIdent)) {
+ aToken.mSymbol = this.Peek();
+ this.Advance();
+ return true;
+ }
+
+ if (this.Peek() != LEFT_PARENTHESIS) {
+ aToken.mType = eCSSToken_Ident;
+ return true;
+ }
+
+ this.Advance();
+ aToken.mType = eCSSToken_Function;
+
+ let asString = String.fromCharCode.apply(null, aToken.mIdent);
+ if (asString.toLowerCase() === "url") {
+ this.NextURL(aToken);
+ }
+ return true;
+ },
+
+ /**
+ * Scan an AtKeyword token. Also handles production of Symbol when
+ * an '@' is not followed by an identifier.
+ */
+ ScanAtKeyword: function (aToken) {
+ // Fall back for when '@' isn't followed by an identifier.
+ aToken.mSymbol = COMMERCIAL_AT;
+ this.Advance();
+
+ let ch = this.Peek();
+ if (StartsIdent(ch, this.Peek(1))) {
+ if (this.GatherText(IS_IDCHAR, aToken.mIdent)) {
+ aToken.mType = eCSSToken_AtKeyword;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Scan a Hash token. Handles the distinction between eCSSToken_ID
+ * and eCSSToken_Hash, and handles production of Symbol when a '#'
+ * is not followed by identifier characters.
+ */
+ ScanHash: function (aToken) {
+ // Fall back for when '#' isn't followed by identifier characters.
+ aToken.mSymbol = NUMBER_SIGN;
+ this.Advance();
+
+ let ch = this.Peek();
+ if (IsIdentChar(ch) || ch == REVERSE_SOLIDUS) {
+ let type =
+ StartsIdent(ch, this.Peek(1)) ? eCSSToken_ID : eCSSToken_Hash;
+ aToken.mIdent.length = 0;
+ if (this.GatherText(IS_IDCHAR, aToken.mIdent)) {
+ aToken.mType = type;
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Scan a Number, Percentage, or Dimension token (all of which begin
+ * like a Number). Can produce a Symbol when a '.' is not followed by
+ * digits, or when '+' or '-' are not followed by either a digit or a
+ * '.' and then a digit. Can also produce a HTMLComment when it
+ * encounters '-->'.
+ */
+ ScanNumber: function (aToken) {
+ let c = this.Peek();
+
+ // Sign of the mantissa (-1 or 1).
+ let sign = c == HYPHEN_MINUS ? -1 : 1;
+ // Absolute value of the integer part of the mantissa. This is a double so
+ // we don't run into overflow issues for consumers that only care about our
+ // floating-point value while still being able to express the full int32_t
+ // range for consumers who want integers.
+ let intPart = 0;
+ // Fractional part of the mantissa. This is a double so that when
+ // we convert to float at the end we'll end up rounding to nearest
+ // float instead of truncating down (as we would if fracPart were
+ // a float and we just effectively lost the last several digits).
+ let fracPart = 0;
+ // Absolute value of the power of 10 that we should multiply by
+ // (only relevant for numbers in scientific notation). Has to be
+ // a signed integer, because multiplication of signed by unsigned
+ // converts the unsigned to signed, so if we plan to actually
+ // multiply by expSign...
+ let exponent = 0;
+ // Sign of the exponent.
+ let expSign = 1;
+
+ aToken.mHasSign = (c == PLUS_SIGN || c == HYPHEN_MINUS);
+ if (aToken.mHasSign) {
+ this.Advance();
+ c = this.Peek();
+ }
+
+ let gotDot = (c == FULL_STOP);
+
+ if (!gotDot) {
+ // Scan the integer part of the mantissa.
+ do {
+ intPart = 10 * intPart + DecimalDigitValue(c);
+ this.Advance();
+ c = this.Peek();
+ } while (IsDigit(c));
+
+ gotDot = (c == FULL_STOP) && IsDigit(this.Peek(1));
+ }
+
+ if (gotDot) {
+ // Scan the fractional part of the mantissa.
+ this.Advance();
+ c = this.Peek();
+ // Power of ten by which we need to divide our next digit
+ let divisor = 10;
+ do {
+ fracPart += DecimalDigitValue(c) / divisor;
+ divisor *= 10;
+ this.Advance();
+ c = this.Peek();
+ } while (IsDigit(c));
+ }
+
+ let gotE = false;
+ if (c == LATIN_SMALL_LETTER_E || c == LATIN_CAPITAL_LETTER_E) {
+ let expSignChar = this.Peek(1);
+ let nextChar = this.Peek(2);
+ if (IsDigit(expSignChar) ||
+ ((expSignChar == HYPHEN_MINUS || expSignChar == PLUS_SIGN) &&
+ IsDigit(nextChar))) {
+ gotE = true;
+ if (expSignChar == HYPHEN_MINUS) {
+ expSign = -1;
+ }
+ this.Advance(); // consumes the E
+ if (expSignChar == HYPHEN_MINUS || expSignChar == PLUS_SIGN) {
+ this.Advance();
+ c = nextChar;
+ } else {
+ c = expSignChar;
+ }
+ do {
+ exponent = 10 * exponent + DecimalDigitValue(c);
+ this.Advance();
+ c = this.Peek();
+ } while (IsDigit(c));
+ }
+ }
+
+ let type = eCSSToken_Number;
+
+ // Set mIntegerValid for all cases (except %, below) because we need
+ // it for the "2n" in :nth-child(2n).
+ aToken.mIntegerValid = false;
+
+ // Time to reassemble our number.
+ // Do all the math in double precision so it's truncated only once.
+ let value = sign * (intPart + fracPart);
+ if (gotE) {
+ // Explicitly cast expSign*exponent to double to avoid issues with
+ // overloaded pow() on Windows.
+ value *= Math.pow(10.0, expSign * exponent);
+ } else if (!gotDot) {
+ // Clamp values outside of integer range.
+ if (sign > 0) {
+ aToken.mInteger = Math.min(intPart, Number.MAX_SAFE_INTEGER);
+ } else {
+ aToken.mInteger = Math.max(-intPart, Number.MIN_SAFE_INTEGER);
+ }
+ aToken.mIntegerValid = true;
+ }
+
+ let ident = aToken.mIdent;
+
+ // Check for Dimension and Percentage tokens.
+ if (c >= 0) {
+ if (StartsIdent(c, this.Peek(1))) {
+ if (this.GatherText(IS_IDCHAR, ident)) {
+ type = eCSSToken_Dimension;
+ }
+ } else if (c == PERCENT_SIGN) {
+ this.Advance();
+ type = eCSSToken_Percentage;
+ value = value / 100.0;
+ aToken.mIntegerValid = false;
+ }
+ }
+ aToken.mNumber = value;
+ aToken.mType = type;
+ return true;
+ },
+
+ /**
+ * Scan a string constant ('foo' or "foo"). Will always produce
+ * either a String or a Bad_String token; the latter occurs when the
+ * close quote is missing. Always returns true (for convenience in Next()).
+ */
+ ScanString: function (aToken) {
+ let aStop = this.Peek();
+ aToken.mType = eCSSToken_String;
+ aToken.mSymbol = aStop; // Remember how it's quoted.
+ this.Advance();
+
+ for (;;) {
+ this.GatherText(IS_STRING, aToken.mIdent);
+
+ let ch = this.Peek();
+ if (ch == -1) {
+ this.AddEOFCharacters(aStop == QUOTATION_MARK ?
+ eEOFCharacters_DoubleQuote :
+ eEOFCharacters_SingleQuote);
+ break; // EOF ends a string token with no error.
+ }
+ if (ch == aStop) {
+ this.Advance();
+ break;
+ }
+ // Both " and ' are excluded from IS_STRING.
+ if (ch == QUOTATION_MARK || ch == APOSTROPHE) {
+ aToken.mIdent.push(ch);
+ this.Advance();
+ continue;
+ }
+
+ aToken.mType = eCSSToken_Bad_String;
+ break;
+ }
+ return true;
+ },
+
+ /**
+ * Scan a unicode-range token. These match the regular expression
+ *
+ * u\+[0-9a-f?]{1,6}(-[0-9a-f]{1,6})?
+ *
+ * However, some such tokens are "invalid". There are three valid forms:
+ *
+ * u+[0-9a-f]{x} 1 <= x <= 6
+ * u+[0-9a-f]{x}\?{y} 1 <= x+y <= 6
+ * u+[0-9a-f]{x}-[0-9a-f]{y} 1 <= x <= 6, 1 <= y <= 6
+ *
+ * All unicode-range tokens have their text recorded in mIdent; valid ones
+ * are also decoded into mInteger and mInteger2, and mIntegerValid is set.
+ * Note that this does not validate the numeric range, only the syntactic
+ * form.
+ */
+ ScanURange: function (aResult) {
+ let intro1 = this.Peek();
+ let intro2 = this.Peek(1);
+ let ch = this.Peek(2);
+
+ aResult.mIdent.push(intro1);
+ aResult.mIdent.push(intro2);
+ this.Advance(2);
+
+ let valid = true;
+ let haveQues = false;
+ let low = 0;
+ let high = 0;
+ let i = 0;
+
+ do {
+ aResult.mIdent.push(ch);
+ if (IsHexDigit(ch)) {
+ if (haveQues) {
+ valid = false; // All question marks should be at the end.
+ }
+ low = low * 16 + HexDigitValue(ch);
+ high = high * 16 + HexDigitValue(ch);
+ } else {
+ haveQues = true;
+ low = low * 16 + 0x0;
+ high = high * 16 + 0xF;
+ }
+
+ i++;
+ this.Advance();
+ ch = this.Peek();
+ } while (i < 6 && (IsHexDigit(ch) || ch == QUESTION_MARK));
+
+ if (ch == HYPHEN_MINUS && IsHexDigit(this.Peek(1))) {
+ if (haveQues) {
+ valid = false;
+ }
+
+ aResult.mIdent.push(ch);
+ this.Advance();
+ ch = this.Peek();
+ high = 0;
+ i = 0;
+ do {
+ aResult.mIdent.push(ch);
+ high = high * 16 + HexDigitValue(ch);
+
+ i++;
+ this.Advance();
+ ch = this.Peek();
+ } while (i < 6 && IsHexDigit(ch));
+ }
+
+ aResult.mInteger = low;
+ aResult.mInteger2 = high;
+ aResult.mIntegerValid = valid;
+ aResult.mType = eCSSToken_URange;
+ return true;
+ },
+
+ SetEOFCharacters: function (aEOFCharacters) {
+ this.mEOFCharacters = aEOFCharacters;
+ },
+
+ AddEOFCharacters: function (aEOFCharacters) {
+ this.mEOFCharacters = this.mEOFCharacters | aEOFCharacters;
+ },
+
+ AppendImpliedEOFCharacters: function (aEOFCharacters, aResult) {
+ // First, ignore eEOFCharacters_DropBackslash.
+ let c = aEOFCharacters >> 1;
+
+ // All of the remaining EOFCharacters bits represent appended characters,
+ // and the bits are in the order that they need appending.
+ for (let p of kImpliedEOFCharacters) {
+ if (c & 1) {
+ aResult.push(p);
+ }
+ c >>= 1;
+ }
+ },
+
+ /**
+ * Consume the part of an URL token after the initial 'url('. Caller
+ * is assumed to have consumed 'url(' already. Will always produce
+ * either an URL or a Bad_URL token.
+ *
+ * Exposed for use by nsCSSParser::ParseMozDocumentRule, which applies
+ * the special lexical rules for URL tokens in a nonstandard context.
+ */
+ NextURL: function (aToken) {
+ this.SkipWhitespace();
+
+ // aToken.mIdent may be "url" at this point; clear that out
+ aToken.mIdent.length = 0;
+
+ let ch = this.Peek();
+ // Do we have a string?
+ if (ch == QUOTATION_MARK || ch == APOSTROPHE) {
+ this.ScanString(aToken);
+ if (aToken.mType == eCSSToken_Bad_String) {
+ aToken.mType = eCSSToken_Bad_URL;
+ return;
+ }
+ } else {
+ // Otherwise, this is the start of a non-quoted url (which may be empty).
+ aToken.mSymbol = 0;
+ this.GatherText(IS_URL_CHAR, aToken.mIdent);
+ }
+
+ // Consume trailing whitespace and then look for a close parenthesis.
+ this.SkipWhitespace();
+ ch = this.Peek();
+ // ch can be less than zero indicating EOF
+ if (ch < 0 || ch == RIGHT_PARENTHESIS) {
+ this.Advance();
+ aToken.mType = eCSSToken_URL;
+ if (ch < 0) {
+ this.AddEOFCharacters(eEOFCharacters_CloseParen);
+ }
+ } else {
+ aToken.mType = eCSSToken_Bad_URL;
+ }
+ },
+
+ /**
+ * Primary scanner entry point. Consume one token and fill in
+ * |aToken| accordingly. Will skip over any number of comments first,
+ * and will also skip over rather than return whitespace and comment
+ * tokens, depending on the value of |aSkip|.
+ *
+ * Returns true if it successfully consumed a token, false if EOF has
+ * been reached. Will always advance the current read position by at
+ * least one character unless called when already at EOF.
+ */
+ Next: function (aToken, aSkip) {
+ let ch;
+
+ // do this here so we don't have to do it in dozens of other places
+ aToken.mIdent = [];
+ aToken.mType = eCSSToken_Symbol;
+
+ this.mTokenOffset = this.mOffset;
+ this.mTokenLineOffset = this.mLineOffset;
+ this.mTokenLineNumber = this.mLineNumber;
+
+ ch = this.Peek();
+ if (IsWhitespace(ch)) {
+ this.SkipWhitespace();
+ aToken.mType = eCSSToken_Whitespace;
+ return true;
+ }
+ if (ch == SOLIDUS && // !IsSVGMode() &&
+ this.Peek(1) == ASTERISK) {
+ this.SkipComment();
+ aToken.mType = eCSSToken_Comment;
+ return true;
+ }
+
+ // EOF
+ if (ch < 0) {
+ return false;
+ }
+
+ // 'u' could be UNICODE-RANGE or an identifier-family token
+ if (ch == LATIN_SMALL_LETTER_U || ch == LATIN_CAPITAL_LETTER_U) {
+ let c2 = this.Peek(1);
+ let c3 = this.Peek(2);
+ if (c2 == PLUS_SIGN && (IsHexDigit(c3) || c3 == QUESTION_MARK)) {
+ return this.ScanURange(aToken);
+ }
+ return this.ScanIdent(aToken);
+ }
+
+ // identifier family
+ if (IsIdentStart(ch)) {
+ return this.ScanIdent(aToken);
+ }
+
+ // number family
+ if (IsDigit(ch)) {
+ return this.ScanNumber(aToken);
+ }
+
+ if (ch == FULL_STOP && IsDigit(this.Peek(1))) {
+ return this.ScanNumber(aToken);
+ }
+
+ if (ch == PLUS_SIGN) {
+ let c2 = this.Peek(1);
+ if (IsDigit(c2) || (c2 == FULL_STOP && IsDigit(this.Peek(2)))) {
+ return this.ScanNumber(aToken);
+ }
+ }
+
+ // HYPHEN_MINUS can start an identifier-family token, a number-family token,
+ // or an HTML-comment
+ if (ch == HYPHEN_MINUS) {
+ let c2 = this.Peek(1);
+ let c3 = this.Peek(2);
+ if (IsIdentStart(c2) || (c2 == HYPHEN_MINUS && c3 != GREATER_THAN_SIGN)) {
+ return this.ScanIdent(aToken);
+ }
+ if (IsDigit(c2) || (c2 == FULL_STOP && IsDigit(c3))) {
+ return this.ScanNumber(aToken);
+ }
+ if (c2 == HYPHEN_MINUS && c3 == GREATER_THAN_SIGN) {
+ this.Advance(3);
+ aToken.mType = eCSSToken_HTMLComment;
+ aToken.mIdent = stringToCodes("-->");
+ return true;
+ }
+ }
+
+ // the other HTML-comment token
+ if (ch == LESS_THAN_SIGN &&
+ this.Peek(1) == EXCLAMATION_MARK &&
+ this.Peek(2) == HYPHEN_MINUS &&
+ this.Peek(3) == HYPHEN_MINUS) {
+ this.Advance(4);
+ aToken.mType = eCSSToken_HTMLComment;
+ aToken.mIdent = stringToCodes("<!--");
+ return true;
+ }
+
+ // AT_KEYWORD
+ if (ch == COMMERCIAL_AT) {
+ return this.ScanAtKeyword(aToken);
+ }
+
+ // HASH
+ if (ch == NUMBER_SIGN) {
+ return this.ScanHash(aToken);
+ }
+
+ // STRING
+ if (ch == QUOTATION_MARK || ch == APOSTROPHE) {
+ return this.ScanString(aToken);
+ }
+
+ // Match operators: ~= |= ^= $= *=
+ let opType = MatchOperatorType(ch);
+ if (opType != eCSSToken_Symbol && this.Peek(1) == EQUALS_SIGN) {
+ aToken.mType = opType;
+ this.Advance(2);
+ return true;
+ }
+
+ // Otherwise, a symbol (DELIM).
+ aToken.mSymbol = ch;
+ this.Advance();
+ return true;
+ },
+};
+
+/**
+ * Create and return a new CSS lexer, conforming to the @see CSSLexer
+ * webidl interface.
+ *
+ * @param {String} input the CSS text to lex
+ * @return {CSSLexer} the new lexer
+ */
+function getCSSLexer(input) {
+ return new Scanner(input);
+}
+
+exports.getCSSLexer = getCSSLexer;
diff --git a/devtools/shared/css/moz.build b/devtools/shared/css/moz.build
new file mode 100644
index 000000000..c65b8d141
--- /dev/null
+++ b/devtools/shared/css/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'generated',
+]
+
+DevToolsModules(
+ 'color-db.js',
+ 'color.js',
+ 'lexer.js',
+ 'parsing-utils.js',
+ 'properties-db.js',
+)
diff --git a/devtools/shared/css/parsing-utils.js b/devtools/shared/css/parsing-utils.js
new file mode 100644
index 000000000..f477b0f12
--- /dev/null
+++ b/devtools/shared/css/parsing-utils.js
@@ -0,0 +1,1171 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// This file holds various CSS parsing and rewriting utilities.
+// Some entry points of note are:
+// parseDeclarations - parse a CSS rule into declarations
+// RuleRewriter - rewrite CSS rule text
+// parsePseudoClassesAndAttributes - parse selector and extract
+// pseudo-classes
+// parseSingleValue - parse a single CSS property value
+
+"use strict";
+
+const {CSS_ANGLEUNIT} = require("devtools/shared/css/properties-db");
+
+const promise = require("promise");
+const {getCSSLexer} = require("devtools/shared/css/lexer");
+const {Task} = require("devtools/shared/task");
+
+const SELECTOR_ATTRIBUTE = exports.SELECTOR_ATTRIBUTE = 1;
+const SELECTOR_ELEMENT = exports.SELECTOR_ELEMENT = 2;
+const SELECTOR_PSEUDO_CLASS = exports.SELECTOR_PSEUDO_CLASS = 3;
+
+// Used to test whether a newline appears anywhere in some text.
+const NEWLINE_RX = /[\r\n]/;
+// Used to test whether a bit of text starts an empty comment, either
+// an "ordinary" /* ... */ comment, or a "heuristic bypass" comment
+// like /*! ... */.
+const EMPTY_COMMENT_START_RX = /^\/\*!?[ \r\n\t\f]*$/;
+// Used to test whether a bit of text ends an empty comment.
+const EMPTY_COMMENT_END_RX = /^[ \r\n\t\f]*\*\//;
+// Used to test whether a string starts with a blank line.
+const BLANK_LINE_RX = /^[ \t]*(?:\r\n|\n|\r|\f|$)/;
+
+// When commenting out a declaration, we put this character into the
+// comment opener so that future parses of the commented text know to
+// bypass the property name validity heuristic.
+const COMMENT_PARSING_HEURISTIC_BYPASS_CHAR = "!";
+
+/**
+ * A generator function that lexes a CSS source string, yielding the
+ * CSS tokens. Comment tokens are dropped.
+ *
+ * @param {String} CSS source string
+ * @yield {CSSToken} The next CSSToken that is lexed
+ * @see CSSToken for details about the returned tokens
+ */
+function* cssTokenizer(string) {
+ let lexer = getCSSLexer(string);
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token) {
+ break;
+ }
+ // None of the existing consumers want comments.
+ if (token.tokenType !== "comment") {
+ yield token;
+ }
+ }
+}
+
+/**
+ * Pass |string| to the CSS lexer and return an array of all the
+ * returned tokens. Comment tokens are not included. In addition to
+ * the usual information, each token will have starting and ending
+ * line and column information attached. Specifically, each token
+ * has an additional "loc" attribute. This attribute is an object
+ * of the form {line: L, column: C}. Lines and columns are both zero
+ * based.
+ *
+ * It's best not to add new uses of this function. In general it is
+ * simpler and better to use the CSSToken offsets, rather than line
+ * and column. Also, this function lexes the entire input string at
+ * once, rather than lazily yielding a token stream. Use
+ * |cssTokenizer| or |getCSSLexer| instead.
+ *
+ * @param{String} string The input string.
+ * @return {Array} An array of tokens (@see CSSToken) that have
+ * line and column information.
+ */
+function cssTokenizerWithLineColumn(string) {
+ let lexer = getCSSLexer(string);
+ let result = [];
+ let prevToken = undefined;
+ while (true) {
+ let token = lexer.nextToken();
+ let lineNumber = lexer.lineNumber;
+ let columnNumber = lexer.columnNumber;
+
+ if (prevToken) {
+ prevToken.loc.end = {
+ line: lineNumber,
+ column: columnNumber
+ };
+ }
+
+ if (!token) {
+ break;
+ }
+
+ if (token.tokenType === "comment") {
+ // We've already dealt with the previous token's location.
+ prevToken = undefined;
+ } else {
+ let startLoc = {
+ line: lineNumber,
+ column: columnNumber
+ };
+ token.loc = {start: startLoc};
+
+ result.push(token);
+ prevToken = token;
+ }
+ }
+
+ return result;
+}
+
+/**
+ * Escape a comment body. Find the comment start and end strings in a
+ * string and inserts backslashes so that the resulting text can
+ * itself be put inside a comment.
+ *
+ * @param {String} inputString
+ * input string
+ * @return {String} the escaped result
+ */
+function escapeCSSComment(inputString) {
+ let result = inputString.replace(/\/(\\*)\*/g, "/\\$1*");
+ return result.replace(/\*(\\*)\//g, "*\\$1/");
+}
+
+/**
+ * Un-escape a comment body. This undoes any comment escaping that
+ * was done by escapeCSSComment. That is, given input like "/\*
+ * comment *\/", it will strip the backslashes.
+ *
+ * @param {String} inputString
+ * input string
+ * @return {String} the un-escaped result
+ */
+function unescapeCSSComment(inputString) {
+ let result = inputString.replace(/\/\\(\\*)\*/g, "/$1*");
+ return result.replace(/\*\\(\\*)\//g, "*$1/");
+}
+
+/**
+ * A helper function for @see parseDeclarations that handles parsing
+ * of comment text. This wraps a recursive call to parseDeclarations
+ * with the processing needed to ensure that offsets in the result
+ * refer back to the original, unescaped, input string.
+ *
+ * @param {Function} isCssPropertyKnown
+ * A function to check if the CSS property is known. This is either an
+ * internal server function or from the CssPropertiesFront.
+ * @param {String} commentText The text of the comment, without the
+ * delimiters.
+ * @param {Number} startOffset The offset of the comment opener
+ * in the original text.
+ * @param {Number} endOffset The offset of the comment closer
+ * in the original text.
+ * @return {array} Array of declarations of the same form as returned
+ * by parseDeclarations.
+ */
+function parseCommentDeclarations(isCssPropertyKnown, commentText, startOffset,
+ endOffset) {
+ let commentOverride = false;
+ if (commentText === "") {
+ return [];
+ } else if (commentText[0] === COMMENT_PARSING_HEURISTIC_BYPASS_CHAR) {
+ // This is the special sign that the comment was written by
+ // rewriteDeclarations and so we should bypass the usual
+ // heuristic.
+ commentOverride = true;
+ commentText = commentText.substring(1);
+ }
+
+ let rewrittenText = unescapeCSSComment(commentText);
+
+ // We might have rewritten an embedded comment. For example
+ // /\* ... *\/ would turn into /* ... */.
+ // This rewriting is necessary for proper lexing, but it means
+ // that the offsets we get back can be off. So now we compute
+ // a map so that we can rewrite offsets later. The map is the same
+ // length as |rewrittenText| and tells us how to map an index
+ // into |rewrittenText| to an index into |commentText|.
+ //
+ // First, we find the location of each comment starter or closer in
+ // |rewrittenText|. At these spots we put a 1 into |rewrites|.
+ // Then we walk the array again, using the elements to compute a
+ // delta, which we use to make the final mapping.
+ //
+ // Note we allocate one extra entry because we can see an ending
+ // offset that is equal to the length.
+ let rewrites = new Array(rewrittenText.length + 1).fill(0);
+
+ let commentRe = /\/\\*\*|\*\\*\//g;
+ while (true) {
+ let matchData = commentRe.exec(rewrittenText);
+ if (!matchData) {
+ break;
+ }
+ rewrites[matchData.index] = 1;
+ }
+
+ let delta = 0;
+ for (let i = 0; i <= rewrittenText.length; ++i) {
+ delta += rewrites[i];
+ // |startOffset| to add the offset from the comment starter, |+2|
+ // for the length of the "/*", then |i| and |delta| as described
+ // above.
+ rewrites[i] = startOffset + 2 + i + delta;
+ if (commentOverride) {
+ ++rewrites[i];
+ }
+ }
+
+ // Note that we pass "false" for parseComments here. It doesn't
+ // seem worthwhile to support declarations in comments-in-comments
+ // here, as there's no way to generate those using the tools, and
+ // users would be crazy to write such things.
+ let newDecls = parseDeclarationsInternal(isCssPropertyKnown, rewrittenText,
+ false, true, commentOverride);
+ for (let decl of newDecls) {
+ decl.offsets[0] = rewrites[decl.offsets[0]];
+ decl.offsets[1] = rewrites[decl.offsets[1]];
+ decl.colonOffsets[0] = rewrites[decl.colonOffsets[0]];
+ decl.colonOffsets[1] = rewrites[decl.colonOffsets[1]];
+ decl.commentOffsets = [startOffset, endOffset];
+ }
+ return newDecls;
+}
+
+/**
+ * A helper function for parseDeclarationsInternal that creates a new
+ * empty declaration.
+ *
+ * @return {object} an empty declaration of the form returned by
+ * parseDeclarations
+ */
+function getEmptyDeclaration() {
+ return {name: "", value: "", priority: "",
+ terminator: "",
+ offsets: [undefined, undefined],
+ colonOffsets: false};
+}
+
+/**
+ * A helper function that does all the parsing work for
+ * parseDeclarations. This is separate because it has some arguments
+ * that don't make sense in isolation.
+ *
+ * The return value and arguments are like parseDeclarations, with
+ * these additional arguments.
+ *
+ * @param {Function} isCssPropertyKnown
+ * Function to check if the CSS property is known.
+ * @param {Boolean} inComment
+ * If true, assume that this call is parsing some text
+ * which came from a comment in another declaration.
+ * In this case some heuristics are used to avoid parsing
+ * text which isn't obviously a series of declarations.
+ * @param {Boolean} commentOverride
+ * This only makes sense when inComment=true.
+ * When true, assume that the comment was generated by
+ * rewriteDeclarations, and skip the usual name-checking
+ * heuristic.
+ */
+function parseDeclarationsInternal(isCssPropertyKnown, inputString,
+ parseComments, inComment, commentOverride) {
+ if (inputString === null || inputString === undefined) {
+ throw new Error("empty input string");
+ }
+
+ let lexer = getCSSLexer(inputString);
+
+ let declarations = [getEmptyDeclaration()];
+ let lastProp = declarations[0];
+
+ let current = "", hasBang = false;
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token) {
+ break;
+ }
+
+ // Ignore HTML comment tokens (but parse anything they might
+ // happen to surround).
+ if (token.tokenType === "htmlcomment") {
+ continue;
+ }
+
+ // Update the start and end offsets of the declaration, but only
+ // when we see a significant token.
+ if (token.tokenType !== "whitespace" && token.tokenType !== "comment") {
+ if (lastProp.offsets[0] === undefined) {
+ lastProp.offsets[0] = token.startOffset;
+ }
+ lastProp.offsets[1] = token.endOffset;
+ } else if (lastProp.name && !current && !hasBang &&
+ !lastProp.priority && lastProp.colonOffsets[1]) {
+ // Whitespace appearing after the ":" is attributed to it.
+ lastProp.colonOffsets[1] = token.endOffset;
+ }
+
+ if (token.tokenType === "symbol" && token.text === ":") {
+ if (!lastProp.name) {
+ // Set the current declaration name if there's no name yet
+ lastProp.name = current.trim();
+ lastProp.colonOffsets = [token.startOffset, token.endOffset];
+ current = "";
+ hasBang = false;
+
+ // When parsing a comment body, if the left-hand-side is not a
+ // valid property name, then drop it and stop parsing.
+ if (inComment && !commentOverride &&
+ !isCssPropertyKnown(lastProp.name)) {
+ lastProp.name = null;
+ break;
+ }
+ } else {
+ // Otherwise, just append ':' to the current value (declaration value
+ // with colons)
+ current += ":";
+ }
+ } else if (token.tokenType === "symbol" && token.text === ";") {
+ lastProp.terminator = "";
+ // When parsing a comment, if the name hasn't been set, then we
+ // have probably just seen an ordinary semicolon used in text,
+ // so drop this and stop parsing.
+ if (inComment && !lastProp.name) {
+ current = "";
+ break;
+ }
+ lastProp.value = current.trim();
+ current = "";
+ hasBang = false;
+ declarations.push(getEmptyDeclaration());
+ lastProp = declarations[declarations.length - 1];
+ } else if (token.tokenType === "ident") {
+ if (token.text === "important" && hasBang) {
+ lastProp.priority = "important";
+ hasBang = false;
+ } else {
+ if (hasBang) {
+ current += "!";
+ }
+ // Re-escape the token to avoid dequoting problems.
+ // See bug 1287620.
+ current += CSS.escape(token.text);
+ }
+ } else if (token.tokenType === "symbol" && token.text === "!") {
+ hasBang = true;
+ } else if (token.tokenType === "whitespace") {
+ if (current !== "") {
+ current += " ";
+ }
+ } else if (token.tokenType === "comment") {
+ if (parseComments && !lastProp.name && !lastProp.value) {
+ let commentText = inputString.substring(token.startOffset + 2,
+ token.endOffset - 2);
+ let newDecls = parseCommentDeclarations(isCssPropertyKnown, commentText,
+ token.startOffset,
+ token.endOffset);
+
+ // Insert the new declarations just before the final element.
+ let lastDecl = declarations.pop();
+ declarations = [...declarations, ...newDecls, lastDecl];
+ } else {
+ current += " ";
+ }
+ } else {
+ current += inputString.substring(token.startOffset, token.endOffset);
+ }
+ }
+
+ // Handle whatever trailing properties or values might still be there
+ if (current) {
+ if (!lastProp.name) {
+ // Ignore this case in comments.
+ if (!inComment) {
+ // Trailing property found, e.g. p1:v1;p2:v2;p3
+ lastProp.name = current.trim();
+ }
+ } else {
+ // Trailing value found, i.e. value without an ending ;
+ lastProp.value = current.trim();
+ let terminator = lexer.performEOFFixup("", true);
+ lastProp.terminator = terminator + ";";
+ // If the input was unterminated, attribute the remainder to
+ // this property. This avoids some bad behavior when rewriting
+ // an unterminated comment.
+ if (terminator) {
+ lastProp.offsets[1] = inputString.length;
+ }
+ }
+ }
+
+ // Remove declarations that have neither a name nor a value
+ declarations = declarations.filter(prop => prop.name || prop.value);
+
+ return declarations;
+}
+
+/**
+ * Returns an array of CSS declarations given a string.
+ * For example, parseDeclarations(isCssPropertyKnown, "width: 1px; height: 1px")
+ * would return:
+ * [{name:"width", value: "1px"}, {name: "height", "value": "1px"}]
+ *
+ * The input string is assumed to only contain declarations so { and }
+ * characters will be treated as part of either the property or value,
+ * depending where it's found.
+ *
+ * @param {Function} isCssPropertyKnown
+ * A function to check if the CSS property is known. This is either an
+ * internal server function or from the CssPropertiesFront.
+ * that are supported by the server.
+ * @param {String} inputString
+ * An input string of CSS
+ * @param {Boolean} parseComments
+ * If true, try to parse the contents of comments as well.
+ * A comment will only be parsed if it occurs outside of
+ * the body of some other declaration.
+ * @return {Array} an array of objects with the following signature:
+ * [{"name": string, "value": string, "priority": string,
+ * "terminator": string,
+ * "offsets": [start, end], "colonOffsets": [start, end]},
+ * ...]
+ * Here, "offsets" holds the offsets of the start and end
+ * of the declaration text, in a form suitable for use with
+ * String.substring.
+ * "terminator" is a string to use to terminate the declaration,
+ * usually "" to mean no additional termination is needed.
+ * "colonOffsets" holds the start and end locations of the
+ * ":" that separates the property name from the value.
+ * If the declaration appears in a comment, then there will
+ * be an additional {"commentOffsets": [start, end] property
+ * on the object, which will hold the offsets of the start
+ * and end of the enclosing comment.
+ */
+function parseDeclarations(isCssPropertyKnown, inputString,
+ parseComments = false) {
+ return parseDeclarationsInternal(isCssPropertyKnown, inputString,
+ parseComments, false, false);
+}
+
+/**
+ * Return an object that can be used to rewrite declarations in some
+ * source text. The source text and parsing are handled in the same
+ * way as @see parseDeclarations, with |parseComments| being true.
+ * Rewriting is done by calling one of the modification functions like
+ * setPropertyEnabled. The returned object has the same interface
+ * as @see RuleModificationList.
+ *
+ * An example showing how to disable the 3rd property in a rule:
+ *
+ * let rewriter = new RuleRewriter(isCssPropertyKnown, ruleActor,
+ * ruleActor.authoredText);
+ * rewriter.setPropertyEnabled(3, "color", false);
+ * rewriter.apply().then(() => { ... the change is made ... });
+ *
+ * The exported rewriting methods are |renameProperty|, |setPropertyEnabled|,
+ * |createProperty|, |setProperty|, and |removeProperty|. The |apply|
+ * method can be used to send the edited text to the StyleRuleActor;
+ * |getDefaultIndentation| is useful for the methods requiring a
+ * default indentation value; and |getResult| is useful for testing.
+ *
+ * Additionally, editing will set the |changedDeclarations| property
+ * on this object. This property has the same form as the |changed|
+ * property of the object returned by |getResult|.
+ *
+ * @param {Function} isCssPropertyKnown
+ * A function to check if the CSS property is known. This is either an
+ * internal server function or from the CssPropertiesFront.
+ * that are supported by the server. Note that if Bug 1222047
+ * is completed then isCssPropertyKnown will not need to be passed in.
+ * The CssProperty front will be able to obtained directly from the
+ * RuleRewriter.
+ * @param {StyleRuleFront} rule The style rule to use. Note that this
+ * is only needed by the |apply| and |getDefaultIndentation| methods;
+ * and in particular for testing it can be |null|.
+ * @param {String} inputString The CSS source text to parse and modify.
+ * @return {Object} an object that can be used to rewrite the input text.
+ */
+function RuleRewriter(isCssPropertyKnown, rule, inputString) {
+ this.rule = rule;
+ this.isCssPropertyKnown = isCssPropertyKnown;
+
+ // Keep track of which any declarations we had to rewrite while
+ // performing the requested action.
+ this.changedDeclarations = {};
+
+ // If not null, a promise that must be wait upon before |apply| can
+ // do its work.
+ this.editPromise = null;
+
+ // If the |defaultIndentation| property is set, then it is used;
+ // otherwise the RuleRewriter will try to compute the default
+ // indentation based on the style sheet's text. This override
+ // facility is for testing.
+ this.defaultIndentation = null;
+
+ this.startInitialization(inputString);
+}
+
+RuleRewriter.prototype = {
+ /**
+ * An internal function to initialize the rewriter with a given
+ * input string.
+ *
+ * @param {String} inputString the input to use
+ */
+ startInitialization: function (inputString) {
+ this.inputString = inputString;
+ // Whether there are any newlines in the input text.
+ this.hasNewLine = /[\r\n]/.test(this.inputString);
+ // The declarations.
+ this.declarations = parseDeclarations(this.isCssPropertyKnown, this.inputString,
+ true);
+ this.decl = null;
+ this.result = null;
+ },
+
+ /**
+ * An internal function to complete initialization and set some
+ * properties for further processing.
+ *
+ * @param {Number} index The index of the property to modify
+ */
+ completeInitialization: function (index) {
+ if (index < 0) {
+ throw new Error("Invalid index " + index + ". Expected positive integer");
+ }
+ // |decl| is the declaration to be rewritten, or null if there is no
+ // declaration corresponding to |index|.
+ // |result| is used to accumulate the result text.
+ if (index < this.declarations.length) {
+ this.decl = this.declarations[index];
+ this.result = this.inputString.substring(0, this.decl.offsets[0]);
+ } else {
+ this.decl = null;
+ this.result = this.inputString;
+ }
+ },
+
+ /**
+ * A helper function to compute the indentation of some text. This
+ * examines the rule's existing text to guess the indentation to use;
+ * unlike |getDefaultIndentation|, which examines the entire style
+ * sheet.
+ *
+ * @param {String} string the input text
+ * @param {Number} offset the offset at which to compute the indentation
+ * @return {String} the indentation at the indicated position
+ */
+ getIndentation: function (string, offset) {
+ let originalOffset = offset;
+ for (--offset; offset >= 0; --offset) {
+ let c = string[offset];
+ if (c === "\r" || c === "\n" || c === "\f") {
+ return string.substring(offset + 1, originalOffset);
+ }
+ if (c !== " " && c !== "\t") {
+ // Found some non-whitespace character before we found a newline
+ // -- let's reset the starting point and keep going, as we saw
+ // something on the line before the declaration.
+ originalOffset = offset;
+ }
+ }
+ // Ran off the end.
+ return "";
+ },
+
+ /**
+ * Modify a property value to ensure it is "lexically safe" for
+ * insertion into a style sheet. This function doesn't attempt to
+ * ensure that the resulting text is a valid value for the given
+ * property; but rather just that inserting the text into the style
+ * sheet will not cause unwanted changes to other rules or
+ * declarations.
+ *
+ * @param {String} text The input text. This should include the trailing ";".
+ * @return {Array} An array of the form [anySanitized, text], where
+ * |anySanitized| is a boolean that indicates
+ * whether anything substantive has changed; and
+ * where |text| is the text that has been rewritten
+ * to be "lexically safe".
+ */
+ sanitizePropertyValue: function (text) {
+ let lexer = getCSSLexer(text);
+
+ let result = "";
+ let previousOffset = 0;
+ let braceDepth = 0;
+ let anySanitized = false;
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token) {
+ break;
+ }
+
+ if (token.tokenType === "symbol") {
+ switch (token.text) {
+ case ";":
+ // We simply drop the ";" here. This lets us cope with
+ // declarations that don't have a ";" and also other
+ // termination. The caller handles adding the ";" again.
+ result += text.substring(previousOffset, token.startOffset);
+ previousOffset = token.endOffset;
+ break;
+
+ case "{":
+ ++braceDepth;
+ break;
+
+ case "}":
+ --braceDepth;
+ if (braceDepth < 0) {
+ // Found an unmatched close bracket.
+ braceDepth = 0;
+ // Copy out text from |previousOffset|.
+ result += text.substring(previousOffset, token.startOffset);
+ // Quote the offending symbol.
+ result += "\\" + token.text;
+ previousOffset = token.endOffset;
+ anySanitized = true;
+ }
+ break;
+ }
+ }
+ }
+
+ // Copy out any remaining text, then any needed terminators.
+ result += text.substring(previousOffset, text.length);
+ let eofFixup = lexer.performEOFFixup("", true);
+ if (eofFixup) {
+ anySanitized = true;
+ result += eofFixup;
+ }
+ return [anySanitized, result];
+ },
+
+ /**
+ * Start at |index| and skip whitespace
+ * backward in |string|. Return the index of the first
+ * non-whitespace character, or -1 if the entire string was
+ * whitespace.
+ * @param {String} string the input string
+ * @param {Number} index the index at which to start
+ * @return {Number} index of the first non-whitespace character, or -1
+ */
+ skipWhitespaceBackward: function (string, index) {
+ for (--index;
+ index >= 0 && (string[index] === " " || string[index] === "\t");
+ --index) {
+ // Nothing.
+ }
+ return index;
+ },
+
+ /**
+ * Terminate a given declaration, if needed.
+ *
+ * @param {Number} index The index of the rule to possibly
+ * terminate. It might be invalid, so this
+ * function must check for that.
+ */
+ maybeTerminateDecl: function (index) {
+ if (index < 0 || index >= this.declarations.length
+ // No need to rewrite declarations in comments.
+ || ("commentOffsets" in this.declarations[index])) {
+ return;
+ }
+
+ let termDecl = this.declarations[index];
+ let endIndex = termDecl.offsets[1];
+ // Due to an oddity of the lexer, we might have gotten a bit of
+ // extra whitespace in a trailing bad_url token -- so be sure to
+ // skip that as well.
+ endIndex = this.skipWhitespaceBackward(this.result, endIndex) + 1;
+
+ let trailingText = this.result.substring(endIndex);
+ if (termDecl.terminator) {
+ // Insert the terminator just at the end of the declaration,
+ // before any trailing whitespace.
+ this.result = this.result.substring(0, endIndex) + termDecl.terminator +
+ trailingText;
+ // In a couple of cases, we may have had to add something to
+ // terminate the declaration, but the termination did not
+ // actually affect the property's value -- and at this spot, we
+ // only care about reporting value changes. In particular, we
+ // might have added a plain ";", or we might have terminated a
+ // comment with "*/;". Neither of these affect the value.
+ if (termDecl.terminator !== ";" && termDecl.terminator !== "*/;") {
+ this.changedDeclarations[index] =
+ termDecl.value + termDecl.terminator.slice(0, -1);
+ }
+ }
+ // If the rule generally has newlines, but this particular
+ // declaration doesn't have a trailing newline, insert one now.
+ // Maybe this style is too weird to bother with.
+ if (this.hasNewLine && !NEWLINE_RX.test(trailingText)) {
+ this.result += "\n";
+ }
+ },
+
+ /**
+ * Sanitize the given property value and return the sanitized form.
+ * If the property is rewritten during sanitization, make a note in
+ * |changedDeclarations|.
+ *
+ * @param {String} text The property text.
+ * @param {Number} index The index of the property.
+ * @return {String} The sanitized text.
+ */
+ sanitizeText: function (text, index) {
+ let [anySanitized, sanitizedText] = this.sanitizePropertyValue(text);
+ if (anySanitized) {
+ this.changedDeclarations[index] = sanitizedText;
+ }
+ return sanitizedText;
+ },
+
+ /**
+ * Rename a declaration.
+ *
+ * @param {Number} index index of the property in the rule.
+ * @param {String} name current name of the property
+ * @param {String} newName new name of the property
+ */
+ renameProperty: function (index, name, newName) {
+ this.completeInitialization(index);
+ this.result += CSS.escape(newName);
+ // We could conceivably compute the name offsets instead so we
+ // could preserve white space and comments on the LHS of the ":".
+ this.completeCopying(this.decl.colonOffsets[0]);
+ },
+
+ /**
+ * Enable or disable a declaration
+ *
+ * @param {Number} index index of the property in the rule.
+ * @param {String} name current name of the property
+ * @param {Boolean} isEnabled true if the property should be enabled;
+ * false if it should be disabled
+ */
+ setPropertyEnabled: function (index, name, isEnabled) {
+ this.completeInitialization(index);
+ const decl = this.decl;
+ let copyOffset = decl.offsets[1];
+ if (isEnabled) {
+ // Enable it. First see if the comment start can be deleted.
+ let commentStart = decl.commentOffsets[0];
+ if (EMPTY_COMMENT_START_RX.test(this.result.substring(commentStart))) {
+ this.result = this.result.substring(0, commentStart);
+ } else {
+ this.result += "*/ ";
+ }
+
+ // Insert the name and value separately, so we can report
+ // sanitization changes properly.
+ let commentNamePart =
+ this.inputString.substring(decl.offsets[0],
+ decl.colonOffsets[1]);
+ this.result += unescapeCSSComment(commentNamePart);
+
+ // When uncommenting, we must be sure to sanitize the text, to
+ // avoid things like /* decl: }; */, which will be accepted as
+ // a property but which would break the entire style sheet.
+ let newText = this.inputString.substring(decl.colonOffsets[1],
+ decl.offsets[1]);
+ newText = unescapeCSSComment(newText).trimRight();
+ this.result += this.sanitizeText(newText, index) + ";";
+
+ // See if the comment end can be deleted.
+ let trailingText = this.inputString.substring(decl.offsets[1]);
+ if (EMPTY_COMMENT_END_RX.test(trailingText)) {
+ copyOffset = decl.commentOffsets[1];
+ } else {
+ this.result += " /*";
+ }
+ } else {
+ // Disable it. Note that we use our special comment syntax
+ // here.
+ let declText = this.inputString.substring(decl.offsets[0],
+ decl.offsets[1]);
+ this.result += "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR +
+ " " + escapeCSSComment(declText) + " */";
+ }
+ this.completeCopying(copyOffset);
+ },
+
+ /**
+ * Return a promise that will be resolved to the default indentation
+ * of the rule. This is a helper for internalCreateProperty.
+ *
+ * @return {Promise} a promise that will be resolved to a string
+ * that holds the default indentation that should be used
+ * for edits to the rule.
+ */
+ getDefaultIndentation: function () {
+ return this.rule.parentStyleSheet.guessIndentation();
+ },
+
+ /**
+ * An internal function to create a new declaration. This does all
+ * the work of |createProperty|.
+ *
+ * @param {Number} index index of the property in the rule.
+ * @param {String} name name of the new property
+ * @param {String} value value of the new property
+ * @param {String} priority priority of the new property; either
+ * the empty string or "important"
+ * @param {Boolean} enabled True if the new property should be
+ * enabled, false if disabled
+ * @return {Promise} a promise that is resolved when the edit has
+ * completed
+ */
+ internalCreateProperty: Task.async(function* (index, name, value, priority, enabled) {
+ this.completeInitialization(index);
+ let newIndentation = "";
+ if (this.hasNewLine) {
+ if (this.declarations.length > 0) {
+ newIndentation = this.getIndentation(this.inputString,
+ this.declarations[0].offsets[0]);
+ } else if (this.defaultIndentation) {
+ newIndentation = this.defaultIndentation;
+ } else {
+ newIndentation = yield this.getDefaultIndentation();
+ }
+ }
+
+ this.maybeTerminateDecl(index - 1);
+
+ // If we generally have newlines, and if skipping whitespace
+ // backward stops at a newline, then insert our text before that
+ // whitespace. This ensures the indentation we computed is what
+ // is actually used.
+ let savedWhitespace = "";
+ if (this.hasNewLine) {
+ let wsOffset = this.skipWhitespaceBackward(this.result,
+ this.result.length);
+ if (this.result[wsOffset] === "\r" || this.result[wsOffset] === "\n") {
+ savedWhitespace = this.result.substring(wsOffset + 1);
+ this.result = this.result.substring(0, wsOffset + 1);
+ }
+ }
+
+ let newText = CSS.escape(name) + ": " + this.sanitizeText(value, index);
+ if (priority === "important") {
+ newText += " !important";
+ }
+ newText += ";";
+
+ if (!enabled) {
+ newText = "/*" + COMMENT_PARSING_HEURISTIC_BYPASS_CHAR + " " +
+ escapeCSSComment(newText) + " */";
+ }
+
+ this.result += newIndentation + newText;
+ if (this.hasNewLine) {
+ this.result += "\n";
+ }
+ this.result += savedWhitespace;
+
+ if (this.decl) {
+ // Still want to copy in the declaration previously at this
+ // index.
+ this.completeCopying(this.decl.offsets[0]);
+ }
+ }),
+
+ /**
+ * Create a new declaration.
+ *
+ * @param {Number} index index of the property in the rule.
+ * @param {String} name name of the new property
+ * @param {String} value value of the new property
+ * @param {String} priority priority of the new property; either
+ * the empty string or "important"
+ * @param {Boolean} enabled True if the new property should be
+ * enabled, false if disabled
+ */
+ createProperty: function (index, name, value, priority, enabled) {
+ this.editPromise = this.internalCreateProperty(index, name, value,
+ priority, enabled);
+ },
+
+ /**
+ * Set a declaration's value.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name the property's name
+ * @param {String} value the property's value
+ * @param {String} priority the property's priority, either the empty
+ * string or "important"
+ */
+ setProperty: function (index, name, value, priority) {
+ this.completeInitialization(index);
+ // We might see a "set" on a previously non-existent property; in
+ // that case, act like "create".
+ if (!this.decl) {
+ this.createProperty(index, name, value, priority, true);
+ return;
+ }
+
+ // Note that this assumes that "set" never operates on disabled
+ // properties.
+ this.result += this.inputString.substring(this.decl.offsets[0],
+ this.decl.colonOffsets[1]) +
+ this.sanitizeText(value, index);
+
+ if (priority === "important") {
+ this.result += " !important";
+ }
+ this.result += ";";
+ this.completeCopying(this.decl.offsets[1]);
+ },
+
+ /**
+ * Remove a declaration.
+ *
+ * @param {Number} index index of the property in the rule.
+ * @param {String} name the name of the property to remove
+ */
+ removeProperty: function (index, name) {
+ this.completeInitialization(index);
+
+ // If asked to remove a property that does not exist, bail out.
+ if (!this.decl) {
+ return;
+ }
+
+ // If the property is disabled, then first enable it, and then
+ // delete it. We take this approach because we want to remove the
+ // entire comment if possible; but the logic for dealing with
+ // comments is hairy and already implemented in
+ // setPropertyEnabled.
+ if (this.decl.commentOffsets) {
+ this.setPropertyEnabled(index, name, true);
+ this.startInitialization(this.result);
+ this.completeInitialization(index);
+ }
+
+ let copyOffset = this.decl.offsets[1];
+ // Maybe removing this rule left us with a completely blank
+ // line. In this case, we'll delete the whole thing. We only
+ // bother with this if we're looking at sources that already
+ // have a newline somewhere.
+ if (this.hasNewLine) {
+ let nlOffset = this.skipWhitespaceBackward(this.result,
+ this.decl.offsets[0]);
+ if (nlOffset < 0 || this.result[nlOffset] === "\r" ||
+ this.result[nlOffset] === "\n") {
+ let trailingText = this.inputString.substring(copyOffset);
+ let match = BLANK_LINE_RX.exec(trailingText);
+ if (match) {
+ this.result = this.result.substring(0, nlOffset + 1);
+ copyOffset += match[0].length;
+ }
+ }
+ }
+ this.completeCopying(copyOffset);
+ },
+
+ /**
+ * An internal function to copy any trailing text to the output
+ * string.
+ *
+ * @param {Number} copyOffset Offset into |inputString| of the
+ * final text to copy to the output string.
+ */
+ completeCopying: function (copyOffset) {
+ // Add the trailing text.
+ this.result += this.inputString.substring(copyOffset);
+ },
+
+ /**
+ * Apply the modifications in this object to the associated rule.
+ *
+ * @return {Promise} A promise which will be resolved when the modifications
+ * are complete.
+ */
+ apply: function () {
+ return promise.resolve(this.editPromise).then(() => {
+ return this.rule.setRuleText(this.result);
+ });
+ },
+
+ /**
+ * Get the result of the rewriting. This is used for testing.
+ *
+ * @return {object} an object of the form {changed: object, text: string}
+ * |changed| is an object where each key is
+ * the index of a property whose value had to be
+ * rewritten during the sanitization process, and
+ * whose value is the new text of the property.
+ * |text| is the rewritten text of the rule.
+ */
+ getResult: function () {
+ return {changed: this.changedDeclarations, text: this.result};
+ },
+};
+
+/**
+ * Returns an array of the parsed CSS selector value and type given a string.
+ *
+ * The components making up the CSS selector can be extracted into 3 different
+ * types: element, attribute and pseudoclass. The object that is appended to
+ * the returned array contains the value related to one of the 3 types described
+ * along with the actual type.
+ *
+ * The following are the 3 types that can be returned in the object signature:
+ * (1) SELECTOR_ATTRIBUTE
+ * (2) SELECTOR_ELEMENT
+ * (3) SELECTOR_PSEUDO_CLASS
+ *
+ * @param {String} value
+ * The CSS selector text.
+ * @return {Array} an array of objects with the following signature:
+ * [{ "value": string, "type": integer }, ...]
+ */
+function parsePseudoClassesAndAttributes(value) {
+ if (!value) {
+ throw new Error("empty input string");
+ }
+
+ let tokens = cssTokenizer(value);
+ let result = [];
+ let current = "";
+ let functionCount = 0;
+ let hasAttribute = false;
+ let hasColon = false;
+
+ for (let token of tokens) {
+ if (token.tokenType === "ident") {
+ current += value.substring(token.startOffset, token.endOffset);
+
+ if (hasColon && !functionCount) {
+ if (current) {
+ result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
+ }
+
+ current = "";
+ hasColon = false;
+ }
+ } else if (token.tokenType === "symbol" && token.text === ":") {
+ if (!hasColon) {
+ if (current) {
+ result.push({ value: current, type: SELECTOR_ELEMENT });
+ }
+
+ current = "";
+ hasColon = true;
+ }
+
+ current += token.text;
+ } else if (token.tokenType === "function") {
+ current += value.substring(token.startOffset, token.endOffset);
+ functionCount++;
+ } else if (token.tokenType === "symbol" && token.text === ")") {
+ current += token.text;
+
+ if (hasColon && functionCount == 1) {
+ if (current) {
+ result.push({ value: current, type: SELECTOR_PSEUDO_CLASS });
+ }
+
+ current = "";
+ functionCount--;
+ hasColon = false;
+ } else {
+ functionCount--;
+ }
+ } else if (token.tokenType === "symbol" && token.text === "[") {
+ if (!hasAttribute && !functionCount) {
+ if (current) {
+ result.push({ value: current, type: SELECTOR_ELEMENT });
+ }
+
+ current = "";
+ hasAttribute = true;
+ }
+
+ current += token.text;
+ } else if (token.tokenType === "symbol" && token.text === "]") {
+ current += token.text;
+
+ if (hasAttribute && !functionCount) {
+ if (current) {
+ result.push({ value: current, type: SELECTOR_ATTRIBUTE });
+ }
+
+ current = "";
+ hasAttribute = false;
+ }
+ } else {
+ current += value.substring(token.startOffset, token.endOffset);
+ }
+ }
+
+ if (current) {
+ result.push({ value: current, type: SELECTOR_ELEMENT });
+ }
+
+ return result;
+}
+
+/**
+ * Expects a single CSS value to be passed as the input and parses the value
+ * and priority.
+ *
+ * @param {Function} isCssPropertyKnown
+ * A function to check if the CSS property is known. This is either an
+ * internal server function or from the CssPropertiesFront.
+ * that are supported by the server.
+ * @param {String} value
+ * The value from the text editor.
+ * @return {Object} an object with 'value' and 'priority' properties.
+ */
+function parseSingleValue(isCssPropertyKnown, value) {
+ let declaration = parseDeclarations(isCssPropertyKnown,
+ "a: " + value + ";")[0];
+ return {
+ value: declaration ? declaration.value : "",
+ priority: declaration ? declaration.priority : ""
+ };
+}
+
+/**
+ * Convert an angle value to degree.
+ *
+ * @param {Number} angleValue The angle value.
+ * @param {CSS_ANGLEUNIT} angleUnit The angleValue's angle unit.
+ * @return {Number} An angle value in degree.
+ */
+function getAngleValueInDegrees(angleValue, angleUnit) {
+ switch (angleUnit) {
+ case CSS_ANGLEUNIT.deg:
+ return angleValue;
+ case CSS_ANGLEUNIT.grad:
+ return angleValue * 0.9;
+ case CSS_ANGLEUNIT.rad:
+ return angleValue * 180 / Math.PI;
+ case CSS_ANGLEUNIT.turn:
+ return angleValue * 360;
+ default:
+ throw new Error("No matched angle unit.");
+ }
+}
+
+exports.cssTokenizer = cssTokenizer;
+exports.cssTokenizerWithLineColumn = cssTokenizerWithLineColumn;
+exports.escapeCSSComment = escapeCSSComment;
+// unescapeCSSComment is exported for testing.
+exports._unescapeCSSComment = unescapeCSSComment;
+exports.parseDeclarations = parseDeclarations;
+// parseCommentDeclarations is exported for testing.
+exports._parseCommentDeclarations = parseCommentDeclarations;
+exports.RuleRewriter = RuleRewriter;
+exports.parsePseudoClassesAndAttributes = parsePseudoClassesAndAttributes;
+exports.parseSingleValue = parseSingleValue;
+exports.getAngleValueInDegrees = getAngleValueInDegrees;
diff --git a/devtools/shared/css/properties-db.js b/devtools/shared/css/properties-db.js
new file mode 100644
index 000000000..32454482a
--- /dev/null
+++ b/devtools/shared/css/properties-db.js
@@ -0,0 +1,100 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file contains static lists of CSS properties and values. Some of the small lists
+ * are edited manually, while the larger ones are generated by a script. The comments
+ * above each list indicates how it should be updated.
+ */
+
+let db;
+
+// Allow this require to fail in case it's been deleted in the process of running
+// `mach devtools-css-db` to regenerate the database.
+try {
+ db = require("devtools/shared/css/generated/properties-db");
+} catch (error) {
+ console.error(`If this error is being displayed and "mach devtools-css-db" is not ` +
+ `being run, then it needs to be fixed.`, error);
+ db = {
+ CSS_PROPERTIES: {},
+ PSEUDO_ELEMENTS: []
+ };
+}
+
+/**
+ * All CSS types that properties can support. This list can be manually edited.
+ */
+exports.CSS_TYPES = {
+ "ANGLE": 1,
+ "COLOR": 2,
+ "FREQUENCY": 3,
+ "GRADIENT": 4,
+ "IMAGE_RECT": 5,
+ "LENGTH": 6,
+ "NUMBER": 7,
+ "PERCENTAGE": 8,
+ "TIME": 9,
+ "TIMING_FUNCTION": 10,
+ "URL": 11,
+};
+
+/**
+ * All CSS <angle> types that properties can support. This list can be manually edited.
+ */
+exports.CSS_ANGLEUNIT = {
+ "deg": "deg",
+ "rad": "rad",
+ "grad": "grad",
+ "turn": "turn"
+};
+
+/**
+ * All cubic-bezier CSS timing-function names. This list can be manually edited.
+ */
+exports.BEZIER_KEYWORDS = ["linear", "ease-in-out", "ease-in", "ease-out", "ease"];
+
+/**
+ * Functions that accept a color argument. This list can be manually edited.
+ */
+exports.COLOR_TAKING_FUNCTIONS = ["linear-gradient", "-moz-linear-gradient",
+ "repeating-linear-gradient",
+ "-moz-repeating-linear-gradient", "radial-gradient",
+ "-moz-radial-gradient", "repeating-radial-gradient",
+ "-moz-repeating-radial-gradient", "drop-shadow"];
+
+/**
+ * Functions that accept an angle argument. This list can be manually edited.
+ */
+exports.ANGLE_TAKING_FUNCTIONS = ["linear-gradient", "-moz-linear-gradient",
+ "repeating-linear-gradient",
+ "-moz-repeating-linear-gradient", "rotate", "rotateX",
+ "rotateY", "rotateZ", "rotate3d", "skew", "skewX",
+ "skewY", "hue-rotate"];
+
+/**
+ * The list of all CSS Pseudo Elements.
+ *
+ * This list can be updated with `mach devtools-css-db`.
+ */
+exports.PSEUDO_ELEMENTS = db.PSEUDO_ELEMENTS;
+
+/**
+ * A list of CSS Properties and their various characteristics. This is used on the
+ * client-side when the CssPropertiesActor is not found, or when the client and server
+ * are the same version. A single property takes the form:
+ *
+ * "animation": {
+ * "isInherited": false,
+ * "supports": [ 7, 9, 10 ]
+ * }
+ */
+exports.CSS_PROPERTIES = db.CSS_PROPERTIES;
+
+exports.CSS_PROPERTIES_DB = {
+ properties: db.CSS_PROPERTIES,
+ pseudoElements: db.PSEUDO_ELEMENTS
+};
diff --git a/devtools/shared/defer.js b/devtools/shared/defer.js
new file mode 100644
index 000000000..a27690740
--- /dev/null
+++ b/devtools/shared/defer.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// See bug 1273941 to understand this choice of promise.
+const Promise = require("promise");
+
+/**
+ * Returns a deferred object, with a resolve and reject property.
+ * https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/Promise.jsm/Deferred
+ */
+module.exports = function defer() {
+ let resolve, reject;
+ let promise = new Promise(function () {
+ resolve = arguments[0];
+ reject = arguments[1];
+ });
+ return {
+ resolve: resolve,
+ reject: reject,
+ promise: promise
+ };
+};
diff --git a/devtools/shared/deprecated-sync-thenables.js b/devtools/shared/deprecated-sync-thenables.js
new file mode 100644
index 000000000..52bf671cd
--- /dev/null
+++ b/devtools/shared/deprecated-sync-thenables.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * THIS MODULE IS DEPRECATED. IMPORT "Promise.jsm" INSTEAD.
+ */
+
+"use strict";
+
+this.Promise = {};
+
+if (typeof (require) === "function") {
+ module.exports = Promise;
+} else {
+ this.EXPORTED_SYMBOLS = ["Promise"];
+}
+
+function fulfilled(value) {
+ return { then: function then(fulfill) { fulfill(value); } };
+}
+
+function rejected(reason) {
+ return { then: function then(fulfill, reject) { reject(reason); } };
+}
+
+function isPromise(value) {
+ return value && typeof (value.then) === "function";
+}
+
+function defer() {
+ var observers = [];
+ var result = null;
+ var promise = {
+ then: function then(onFulfill, onError) {
+ var deferred = defer();
+
+ function resolve(value) {
+ try {
+ deferred.resolve(onFulfill ? onFulfill(value) : value);
+ } catch (error) {
+ deferred.resolve(rejected(error));
+ }
+ }
+
+ function reject(reason) {
+ try {
+ if (onError) deferred.resolve(onError(reason));
+ else deferred.resolve(rejected(reason));
+ } catch (error) {
+ deferred.resolve(rejected(error));
+ }
+ }
+
+ if (observers) {
+ observers.push({ resolve: resolve, reject: reject });
+ } else {
+ result.then(resolve, reject);
+ }
+
+ return deferred.promise;
+ }
+ };
+
+ var deferred = {
+ promise: promise,
+ resolve: function resolve(value) {
+ if (!result) {
+ result = isPromise(value) ? value : fulfilled(value);
+ while (observers.length) {
+ var observer = observers.shift();
+ result.then(observer.resolve, observer.reject);
+ }
+ observers = null;
+ }
+ },
+ reject: function reject(reason) {
+ deferred.resolve(rejected(reason));
+ }
+ };
+
+ return deferred;
+}
+Promise.defer = defer;
+
+function resolve(value) {
+ var deferred = defer();
+ deferred.resolve(value);
+ return deferred.promise;
+}
+Promise.resolve = resolve;
+
+function reject(reason) {
+ var deferred = defer();
+ deferred.reject(reason);
+ return deferred.promise;
+}
+Promise.reject = reject;
+
+var promised = (function () {
+ var call = Function.call;
+ var concat = Array.prototype.concat;
+ function execute(args) { return call.apply(call, args); }
+ function promisedConcat(promises, unknown) {
+ return promises.then(function (values) {
+ return resolve(unknown).then(function (value) {
+ return values.concat([ value ]);
+ });
+ });
+ }
+ return function promised(f) {
+ return function promised() {
+ return concat.apply([ f, this ], arguments).
+ reduce(promisedConcat, resolve([])).
+ then(execute);
+ };
+ };
+})();
+Promise.all = promised(Array);
diff --git a/devtools/shared/discovery/discovery.js b/devtools/shared/discovery/discovery.js
new file mode 100644
index 000000000..d0b49f129
--- /dev/null
+++ b/devtools/shared/discovery/discovery.js
@@ -0,0 +1,496 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * This implements a UDP mulitcast device discovery protocol that:
+ * * Is optimized for mobile devices
+ * * Doesn't require any special schema for service info
+ *
+ * To ensure it works well on mobile devices, there is no heartbeat or other
+ * recurring transmission.
+ *
+ * Devices are typically in one of two groups: scanning for services or
+ * providing services (though they may be in both groups as well).
+ *
+ * Scanning devices listen on UPDATE_PORT for UDP multicast traffic. When the
+ * scanning device wants to force an update of the services available, it sends
+ * a status packet to SCAN_PORT.
+ *
+ * Service provider devices listen on SCAN_PORT for any packets from scanning
+ * devices. If one is recevied, the provider device sends a status packet
+ * (listing the services it offers) to UPDATE_PORT.
+ *
+ * Scanning devices purge any previously known devices after REPLY_TIMEOUT ms
+ * from that start of a scan if no reply is received during the most recent
+ * scan.
+ *
+ * When a service is registered, is supplies a regular object with any details
+ * about itself (a port number, for example) in a service-defined format, which
+ * is then available to scanning devices.
+ */
+
+const { Cu, CC, Cc, Ci } = require("chrome");
+const EventEmitter = require("devtools/shared/event-emitter");
+const Services = require("Services");
+
+const UDPSocket = CC("@mozilla.org/network/udp-socket;1",
+ "nsIUDPSocket",
+ "init");
+
+const SCAN_PORT = 50624;
+const UPDATE_PORT = 50625;
+const ADDRESS = "224.0.0.115";
+const REPLY_TIMEOUT = 5000;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+
+XPCOMUtils.defineLazyGetter(this, "converter", () => {
+ let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ conv.charset = "utf8";
+ return conv;
+});
+
+XPCOMUtils.defineLazyGetter(this, "sysInfo", () => {
+ return Cc["@mozilla.org/system-info;1"].getService(Ci.nsIPropertyBag2);
+});
+
+XPCOMUtils.defineLazyGetter(this, "libcutils", function () {
+ let { libcutils } = Cu.import("resource://gre/modules/systemlibs.js", {});
+ return libcutils;
+});
+
+var logging = Services.prefs.getBoolPref("devtools.discovery.log");
+function log(msg) {
+ if (logging) {
+ console.log("DISCOVERY: " + msg);
+ }
+}
+
+/**
+ * Each Transport instance owns a single UDPSocket.
+ * @param port integer
+ * The port to listen on for incoming UDP multicast packets.
+ */
+function Transport(port) {
+ EventEmitter.decorate(this);
+ try {
+ this.socket = new UDPSocket(port, false, Services.scriptSecurityManager.getSystemPrincipal());
+ this.socket.joinMulticast(ADDRESS);
+ this.socket.asyncListen(this);
+ } catch (e) {
+ log("Failed to start new socket: " + e);
+ }
+}
+
+Transport.prototype = {
+
+ /**
+ * Send a object to some UDP port.
+ * @param object object
+ * Object which is the message to send
+ * @param port integer
+ * UDP port to send the message to
+ */
+ send: function (object, port) {
+ if (logging) {
+ log("Send to " + port + ":\n" + JSON.stringify(object, null, 2));
+ }
+ let message = JSON.stringify(object);
+ let rawMessage = converter.convertToByteArray(message);
+ try {
+ this.socket.send(ADDRESS, port, rawMessage, rawMessage.length);
+ } catch (e) {
+ log("Failed to send message: " + e);
+ }
+ },
+
+ destroy: function () {
+ this.socket.close();
+ },
+
+ // nsIUDPSocketListener
+
+ onPacketReceived: function (socket, message) {
+ let messageData = message.data;
+ let object = JSON.parse(messageData);
+ object.from = message.fromAddr.address;
+ let port = message.fromAddr.port;
+ if (port == this.socket.port) {
+ log("Ignoring looped message");
+ return;
+ }
+ if (logging) {
+ log("Recv on " + this.socket.port + ":\n" +
+ JSON.stringify(object, null, 2));
+ }
+ this.emit("message", object);
+ },
+
+ onStopListening: function () {}
+
+};
+
+/**
+ * Manages the local device's name. The name can be generated in serveral
+ * platform-specific ways (see |_generate|). The aim is for each device on the
+ * same local network to have a unique name. If the Settings API is available,
+ * the name is saved there to persist across reboots.
+ */
+function LocalDevice() {
+ this._name = LocalDevice.UNKNOWN;
+ if ("@mozilla.org/settingsService;1" in Cc) {
+ this._settings =
+ Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
+ Services.obs.addObserver(this, "mozsettings-changed", false);
+ }
+ this._get(); // Trigger |_get| to load name eagerly
+}
+
+LocalDevice.SETTING = "devtools.discovery.device";
+LocalDevice.UNKNOWN = "unknown";
+
+LocalDevice.prototype = {
+
+ _get: function () {
+ if (!this._settings) {
+ // Without Settings API, just generate a name and stop, since the value
+ // can't be persisted.
+ this._generate();
+ return;
+ }
+ // Initial read of setting value
+ this._settings.createLock().get(LocalDevice.SETTING, {
+ handle: (_, name) => {
+ if (name && name !== LocalDevice.UNKNOWN) {
+ this._name = name;
+ log("Device: " + this._name);
+ return;
+ }
+ // No existing name saved, so generate one.
+ this._generate();
+ },
+ handleError: () => log("Failed to get device name setting")
+ });
+ },
+
+ /**
+ * Generate a new device name from various platform-specific properties.
+ * Triggers the |name| setter to persist if needed.
+ */
+ _generate: function () {
+ if (Services.appinfo.widgetToolkit == "gonk") {
+ // For Firefox OS devices, create one from the device name plus a little
+ // randomness. The goal is just to distinguish devices in an office
+ // environment where many people may have the same device model for
+ // testing purposes (which would otherwise all report the same name).
+ let name = libcutils.property_get("ro.product.device");
+ // Pick a random number from [0, 2^32)
+ let randomID = Math.floor(Math.random() * Math.pow(2, 32));
+ // To hex and zero pad
+ randomID = ("00000000" + randomID.toString(16)).slice(-8);
+ this.name = name + "-" + randomID;
+ } else if (Services.appinfo.widgetToolkit == "android") {
+ // For Firefox for Android, use the device's model name.
+ // TODO: Bug 1180997: Find the right way to expose an editable name
+ this.name = sysInfo.get("device");
+ } else {
+ this.name = sysInfo.get("host");
+ }
+ },
+
+ /**
+ * Observe any changes that might be made via the Settings app
+ */
+ observe: function (subject, topic, data) {
+ if (topic !== "mozsettings-changed") {
+ return;
+ }
+ if ("wrappedJSObject" in subject) {
+ subject = subject.wrappedJSObject;
+ }
+ if (subject.key !== LocalDevice.SETTING) {
+ return;
+ }
+ this._name = subject.value;
+ log("Device: " + this._name);
+ },
+
+ get name() {
+ return this._name;
+ },
+
+ set name(name) {
+ if (!this._settings) {
+ this._name = name;
+ log("Device: " + this._name);
+ return;
+ }
+ // Persist to Settings API
+ // The new value will be seen and stored by the observer above
+ this._settings.createLock().set(LocalDevice.SETTING, name, {
+ handle: () => {},
+ handleError: () => log("Failed to set device name setting")
+ });
+ }
+
+};
+
+function Discovery() {
+ EventEmitter.decorate(this);
+
+ this.localServices = {};
+ this.remoteServices = {};
+ this.device = new LocalDevice();
+ this.replyTimeout = REPLY_TIMEOUT;
+
+ // Defaulted to Transport, but can be altered by tests
+ this._factories = { Transport: Transport };
+
+ this._transports = {
+ scan: null,
+ update: null
+ };
+ this._expectingReplies = {
+ from: new Set()
+ };
+
+ this._onRemoteScan = this._onRemoteScan.bind(this);
+ this._onRemoteUpdate = this._onRemoteUpdate.bind(this);
+ this._purgeMissingDevices = this._purgeMissingDevices.bind(this);
+}
+
+Discovery.prototype = {
+
+ /**
+ * Add a new service offered by this device.
+ * @param service string
+ * Name of the service
+ * @param info object
+ * Arbitrary data about the service to announce to scanning devices
+ */
+ addService: function (service, info) {
+ log("ADDING LOCAL SERVICE");
+ if (Object.keys(this.localServices).length === 0) {
+ this._startListeningForScan();
+ }
+ this.localServices[service] = info;
+ },
+
+ /**
+ * Remove a service offered by this device.
+ * @param service string
+ * Name of the service
+ */
+ removeService: function (service) {
+ delete this.localServices[service];
+ if (Object.keys(this.localServices).length === 0) {
+ this._stopListeningForScan();
+ }
+ },
+
+ /**
+ * Scan for service updates from other devices.
+ */
+ scan: function () {
+ this._startListeningForUpdate();
+ this._waitForReplies();
+ // TODO Bug 1027457: Use timer to debounce
+ this._sendStatusTo(SCAN_PORT);
+ },
+
+ /**
+ * Get a list of all remote devices currently offering some service.:w
+ */
+ getRemoteDevices: function () {
+ let devices = new Set();
+ for (let service in this.remoteServices) {
+ for (let device in this.remoteServices[service]) {
+ devices.add(device);
+ }
+ }
+ return [...devices];
+ },
+
+ /**
+ * Get a list of all remote devices currently offering a particular service.
+ */
+ getRemoteDevicesWithService: function (service) {
+ let devicesWithService = this.remoteServices[service] || {};
+ return Object.keys(devicesWithService);
+ },
+
+ /**
+ * Get service info (any details registered by the remote device) for a given
+ * service on a device.
+ */
+ getRemoteService: function (service, device) {
+ let devicesWithService = this.remoteServices[service] || {};
+ return devicesWithService[device];
+ },
+
+ _waitForReplies: function () {
+ clearTimeout(this._expectingReplies.timer);
+ this._expectingReplies.from = new Set(this.getRemoteDevices());
+ this._expectingReplies.timer =
+ setTimeout(this._purgeMissingDevices, this.replyTimeout);
+ },
+
+ get Transport() {
+ return this._factories.Transport;
+ },
+
+ _startListeningForScan: function () {
+ if (this._transports.scan) {
+ return; // Already listening
+ }
+ log("LISTEN FOR SCAN");
+ this._transports.scan = new this.Transport(SCAN_PORT);
+ this._transports.scan.on("message", this._onRemoteScan);
+ },
+
+ _stopListeningForScan: function () {
+ if (!this._transports.scan) {
+ return; // Not listening
+ }
+ this._transports.scan.off("message", this._onRemoteScan);
+ this._transports.scan.destroy();
+ this._transports.scan = null;
+ },
+
+ _startListeningForUpdate: function () {
+ if (this._transports.update) {
+ return; // Already listening
+ }
+ log("LISTEN FOR UPDATE");
+ this._transports.update = new this.Transport(UPDATE_PORT);
+ this._transports.update.on("message", this._onRemoteUpdate);
+ },
+
+ _stopListeningForUpdate: function () {
+ if (!this._transports.update) {
+ return; // Not listening
+ }
+ this._transports.update.off("message", this._onRemoteUpdate);
+ this._transports.update.destroy();
+ this._transports.update = null;
+ },
+
+ _restartListening: function () {
+ if (this._transports.scan) {
+ this._stopListeningForScan();
+ this._startListeningForScan();
+ }
+ if (this._transports.update) {
+ this._stopListeningForUpdate();
+ this._startListeningForUpdate();
+ }
+ },
+
+ /**
+ * When sending message, we can use either transport, so just pick the first
+ * one currently alive.
+ */
+ get _outgoingTransport() {
+ if (this._transports.scan) {
+ return this._transports.scan;
+ }
+ if (this._transports.update) {
+ return this._transports.update;
+ }
+ return null;
+ },
+
+ _sendStatusTo: function (port) {
+ let status = {
+ device: this.device.name,
+ services: this.localServices
+ };
+ this._outgoingTransport.send(status, port);
+ },
+
+ _onRemoteScan: function () {
+ // Send my own status in response
+ log("GOT SCAN REQUEST");
+ this._sendStatusTo(UPDATE_PORT);
+ },
+
+ _onRemoteUpdate: function (e, update) {
+ log("GOT REMOTE UPDATE");
+
+ let remoteDevice = update.device;
+ let remoteHost = update.from;
+
+ // Record the reply as received so it won't be purged as missing
+ this._expectingReplies.from.delete(remoteDevice);
+
+ // First, loop over the known services
+ for (let service in this.remoteServices) {
+ let devicesWithService = this.remoteServices[service];
+ let hadServiceForDevice = !!devicesWithService[remoteDevice];
+ let haveServiceForDevice = service in update.services;
+ // If the remote device used to have service, but doesn't any longer, then
+ // it was deleted, so we remove it here.
+ if (hadServiceForDevice && !haveServiceForDevice) {
+ delete devicesWithService[remoteDevice];
+ log("REMOVED " + service + ", DEVICE " + remoteDevice);
+ this.emit(service + "-device-removed", remoteDevice);
+ }
+ }
+
+ // Second, loop over the services in the received update
+ for (let service in update.services) {
+ // Detect if this is a new device for this service
+ let newDevice = !this.remoteServices[service] ||
+ !this.remoteServices[service][remoteDevice];
+
+ // Look up the service info we may have received previously from the same
+ // remote device
+ let devicesWithService = this.remoteServices[service] || {};
+ let oldDeviceInfo = devicesWithService[remoteDevice];
+
+ // Store the service info from the remote device
+ let newDeviceInfo = Cu.cloneInto(update.services[service], {});
+ newDeviceInfo.host = remoteHost;
+ devicesWithService[remoteDevice] = newDeviceInfo;
+ this.remoteServices[service] = devicesWithService;
+
+ // If this is a new service for the remote device, announce the addition
+ if (newDevice) {
+ log("ADDED " + service + ", DEVICE " + remoteDevice);
+ this.emit(service + "-device-added", remoteDevice, newDeviceInfo);
+ }
+
+ // If we've seen this service from the remote device, but the details have
+ // changed, announce the update
+ if (!newDevice &&
+ JSON.stringify(oldDeviceInfo) != JSON.stringify(newDeviceInfo)) {
+ log("UPDATED " + service + ", DEVICE " + remoteDevice);
+ this.emit(service + "-device-updated", remoteDevice, newDeviceInfo);
+ }
+ }
+ },
+
+ _purgeMissingDevices: function () {
+ log("PURGING MISSING DEVICES");
+ for (let service in this.remoteServices) {
+ let devicesWithService = this.remoteServices[service];
+ for (let remoteDevice in devicesWithService) {
+ // If we're still expecting a reply from a remote device when it's time
+ // to purge, then the service is removed.
+ if (this._expectingReplies.from.has(remoteDevice)) {
+ delete devicesWithService[remoteDevice];
+ log("REMOVED " + service + ", DEVICE " + remoteDevice);
+ this.emit(service + "-device-removed", remoteDevice);
+ }
+ }
+ }
+ }
+
+};
+
+var discovery = new Discovery();
+
+module.exports = discovery;
diff --git a/devtools/shared/discovery/moz.build b/devtools/shared/discovery/moz.build
new file mode 100644
index 000000000..9aeaba45e
--- /dev/null
+++ b/devtools/shared/discovery/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'discovery.js',
+)
diff --git a/devtools/shared/discovery/tests/unit/test_discovery.js b/devtools/shared/discovery/tests/unit/test_discovery.js
new file mode 100644
index 000000000..c31340b08
--- /dev/null
+++ b/devtools/shared/discovery/tests/unit/test_discovery.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cu = Components.utils;
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const EventEmitter = require("devtools/shared/event-emitter");
+const discovery = require("devtools/shared/discovery/discovery");
+const { setTimeout, clearTimeout } = require("sdk/timers");
+
+Services.prefs.setBoolPref("devtools.discovery.log", true);
+
+do_register_cleanup(() => {
+ Services.prefs.clearUserPref("devtools.discovery.log");
+});
+
+function log(msg) {
+ do_print("DISCOVERY: " + msg);
+}
+
+// Global map of actively listening ports to TestTransport instances
+var gTestTransports = {};
+
+/**
+ * Implements the same API as Transport in discovery.js. Here, no UDP sockets
+ * are used. Instead, messages are delivered immediately.
+ */
+function TestTransport(port) {
+ EventEmitter.decorate(this);
+ this.port = port;
+ gTestTransports[this.port] = this;
+}
+
+TestTransport.prototype = {
+
+ send: function (object, port) {
+ log("Send to " + port + ":\n" + JSON.stringify(object, null, 2));
+ if (!gTestTransports[port]) {
+ log("No listener on port " + port);
+ return;
+ }
+ let message = JSON.stringify(object);
+ gTestTransports[port].onPacketReceived(null, message);
+ },
+
+ destroy: function () {
+ delete gTestTransports[this.port];
+ },
+
+ // nsIUDPSocketListener
+
+ onPacketReceived: function (socket, message) {
+ let object = JSON.parse(message);
+ object.from = "localhost";
+ log("Recv on " + this.port + ":\n" + JSON.stringify(object, null, 2));
+ this.emit("message", object);
+ },
+
+ onStopListening: function (socket, status) {}
+
+};
+
+// Use TestTransport instead of the usual Transport
+discovery._factories.Transport = TestTransport;
+
+// Ignore name generation on b2g and force a fixed value
+Object.defineProperty(discovery.device, "name", {
+ get: function () {
+ return "test-device";
+ }
+});
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ // At startup, no remote devices are known
+ deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
+ deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
+
+ discovery.scan();
+
+ // No services added yet, still empty
+ deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
+ deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
+
+ discovery.addService("devtools", { port: 1234 });
+
+ // Changes not visible until next scan
+ deepEqual(discovery.getRemoteDevicesWithService("devtools"), []);
+ deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
+
+ yield scanForChange("devtools", "added");
+
+ // Now we see the new service
+ deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]);
+ deepEqual(discovery.getRemoteDevicesWithService("penguins"), []);
+
+ discovery.addService("penguins", { tux: true });
+ yield scanForChange("penguins", "added");
+
+ deepEqual(discovery.getRemoteDevicesWithService("devtools"), ["test-device"]);
+ deepEqual(discovery.getRemoteDevicesWithService("penguins"), ["test-device"]);
+ deepEqual(discovery.getRemoteDevices(), ["test-device"]);
+
+ deepEqual(discovery.getRemoteService("devtools", "test-device"),
+ { port: 1234, host: "localhost" });
+ deepEqual(discovery.getRemoteService("penguins", "test-device"),
+ { tux: true, host: "localhost" });
+
+ discovery.removeService("devtools");
+ yield scanForChange("devtools", "removed");
+
+ discovery.addService("penguins", { tux: false });
+ yield scanForChange("penguins", "updated");
+
+ // Scan again, but nothing should be removed
+ yield scanForNoChange("penguins", "removed");
+
+ // Split the scanning side from the service side to simulate the machine with
+ // the service becoming unreachable
+ gTestTransports = {};
+
+ discovery.removeService("penguins");
+ yield scanForChange("penguins", "removed");
+});
+
+function scanForChange(service, changeType) {
+ let deferred = defer();
+ let timer = setTimeout(() => {
+ deferred.reject(new Error("Reply never arrived"));
+ }, discovery.replyTimeout + 500);
+ discovery.on(service + "-device-" + changeType, function onChange() {
+ discovery.off(service + "-device-" + changeType, onChange);
+ clearTimeout(timer);
+ deferred.resolve();
+ });
+ discovery.scan();
+ return deferred.promise;
+}
+
+function scanForNoChange(service, changeType) {
+ let deferred = defer();
+ let timer = setTimeout(() => {
+ deferred.resolve();
+ }, discovery.replyTimeout + 500);
+ discovery.on(service + "-device-" + changeType, function onChange() {
+ discovery.off(service + "-device-" + changeType, onChange);
+ clearTimeout(timer);
+ deferred.reject(new Error("Unexpected change occurred"));
+ });
+ discovery.scan();
+ return deferred.promise;
+}
diff --git a/devtools/shared/discovery/tests/unit/xpcshell.ini b/devtools/shared/discovery/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..b13d4a222
--- /dev/null
+++ b/devtools/shared/discovery/tests/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+
+[test_discovery.js]
diff --git a/devtools/shared/dom-node-constants.js b/devtools/shared/dom-node-constants.js
new file mode 100644
index 000000000..0b86ae49b
--- /dev/null
+++ b/devtools/shared/dom-node-constants.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function (require, exports, module) {
+ module.exports = {
+ ELEMENT_NODE: 1,
+ ATTRIBUTE_NODE: 2,
+ TEXT_NODE: 3,
+ CDATA_SECTION_NODE: 4,
+ ENTITY_REFERENCE_NODE: 5,
+ ENTITY_NODE: 6,
+ PROCESSING_INSTRUCTION_NODE: 7,
+ COMMENT_NODE: 8,
+ DOCUMENT_NODE: 9,
+ DOCUMENT_TYPE_NODE: 10,
+ DOCUMENT_FRAGMENT_NODE: 11,
+ NOTATION_NODE: 12,
+
+ // DocumentPosition
+ DOCUMENT_POSITION_DISCONNECTED: 0x01,
+ DOCUMENT_POSITION_PRECEDING: 0x02,
+ DOCUMENT_POSITION_FOLLOWING: 0x04,
+ DOCUMENT_POSITION_CONTAINS: 0x08,
+ DOCUMENT_POSITION_CONTAINED_BY: 0x10,
+ DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
+ };
+});
diff --git a/devtools/shared/dom-node-filter-constants.js b/devtools/shared/dom-node-filter-constants.js
new file mode 100644
index 000000000..527904f15
--- /dev/null
+++ b/devtools/shared/dom-node-filter-constants.js
@@ -0,0 +1,21 @@
+"use strict";
+
+module.exports = {
+ FILTER_ACCEPT: 1,
+ FILTER_REJECT: 2,
+ FILTER_SKIP: 3,
+
+ SHOW_ALL: 0xFFFFFFFF,
+ SHOW_ELEMENT: 0x00000001,
+ SHOW_ATTRIBUTE: 0x00000002,
+ SHOW_TEXT: 0x00000004,
+ SHOW_CDATA_SECTION: 0x00000008,
+ SHOW_ENTITY_REFERENCE: 0x00000010,
+ SHOW_ENTITY: 0x00000020,
+ SHOW_PROCESSING_INSTRUCTION: 0x00000040,
+ SHOW_COMMENT: 0x00000080,
+ SHOW_DOCUMENT: 0x00000100,
+ SHOW_DOCUMENT_TYPE: 0x00000200,
+ SHOW_DOCUMENT_FRAGMENT: 0x00000400,
+ SHOW_NOTATION: 0x00000800
+};
diff --git a/devtools/shared/event-emitter.js b/devtools/shared/event-emitter.js
new file mode 100644
index 000000000..5fcd5dcaa
--- /dev/null
+++ b/devtools/shared/event-emitter.js
@@ -0,0 +1,250 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+(function (factory) {
+ // This file can be loaded in several different ways. It can be
+ // require()d, either from the main thread or from a worker thread;
+ // or it can be imported via Cu.import. These different forms
+ // explain some of the hairiness of this code.
+ //
+ // It's important for the devtools-as-html project that a require()
+ // on the main thread not use any chrome privileged APIs. Instead,
+ // the body of the main function can only require() (not Cu.import)
+ // modules that are available in the devtools content mode. This,
+ // plus the lack of |console| in workers, results in some gyrations
+ // in the definition of |console|.
+ if (this.module && module.id.indexOf("event-emitter") >= 0) {
+ let console;
+ if (isWorker) {
+ console = {
+ error: () => {}
+ };
+ } else {
+ console = this.console;
+ }
+ // require
+ factory.call(this, require, exports, module, console);
+ } else {
+ // Cu.import. This snippet implements a sort of miniature loader,
+ // which is responsible for appropriately translating require()
+ // requests from the client function. This code can use
+ // Cu.import, because it is never run in the devtools-in-content
+ // mode.
+ this.isWorker = false;
+ const Cu = Components.utils;
+ let console = Cu.import("resource://gre/modules/Console.jsm", {}).console;
+ // Bug 1259045: This module is loaded early in firefox startup as a JSM,
+ // but it doesn't depends on any real module. We can save a few cycles
+ // and bytes by not loading Loader.jsm.
+ let require = function (module) {
+ switch (module) {
+ case "devtools/shared/defer":
+ return Cu.import("resource://gre/modules/Promise.jsm", {}).Promise.defer;
+ case "Services":
+ return Cu.import("resource://gre/modules/Services.jsm", {}).Services;
+ case "devtools/shared/platform/stack": {
+ let obj = {};
+ Cu.import("resource://devtools/shared/platform/chrome/stack.js", obj);
+ return obj;
+ }
+ }
+ return null;
+ };
+ factory.call(this, require, this, { exports: this }, console);
+ this.EXPORTED_SYMBOLS = ["EventEmitter"];
+ }
+}).call(this, function (require, exports, module, console) {
+ // ⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠⚠
+ // After this point the code may not use Cu.import, and should only
+ // require() modules that are "clean-for-content".
+ let EventEmitter = this.EventEmitter = function () {};
+ module.exports = EventEmitter;
+
+ // See comment in JSM module boilerplate when adding a new dependency.
+ const Services = require("Services");
+ const defer = require("devtools/shared/defer");
+ const { describeNthCaller } = require("devtools/shared/platform/stack");
+ let loggingEnabled = true;
+
+ if (!isWorker) {
+ loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
+ Services.prefs.addObserver("devtools.dump.emit", {
+ observe: () => {
+ loggingEnabled = Services.prefs.getBoolPref("devtools.dump.emit");
+ }
+ }, false);
+ }
+
+ /**
+ * Decorate an object with event emitter functionality.
+ *
+ * @param Object objectToDecorate
+ * Bind all public methods of EventEmitter to
+ * the objectToDecorate object.
+ */
+ EventEmitter.decorate = function (objectToDecorate) {
+ let emitter = new EventEmitter();
+ objectToDecorate.on = emitter.on.bind(emitter);
+ objectToDecorate.off = emitter.off.bind(emitter);
+ objectToDecorate.once = emitter.once.bind(emitter);
+ objectToDecorate.emit = emitter.emit.bind(emitter);
+ };
+
+ EventEmitter.prototype = {
+ /**
+ * Connect a listener.
+ *
+ * @param string event
+ * The event name to which we're connecting.
+ * @param function listener
+ * Called when the event is fired.
+ */
+ on(event, listener) {
+ if (!this._eventEmitterListeners) {
+ this._eventEmitterListeners = new Map();
+ }
+ if (!this._eventEmitterListeners.has(event)) {
+ this._eventEmitterListeners.set(event, []);
+ }
+ this._eventEmitterListeners.get(event).push(listener);
+ },
+
+ /**
+ * Listen for the next time an event is fired.
+ *
+ * @param string event
+ * The event name to which we're connecting.
+ * @param function listener
+ * (Optional) Called when the event is fired. Will be called at most
+ * one time.
+ * @return promise
+ * A promise which is resolved when the event next happens. The
+ * resolution value of the promise is the first event argument. If
+ * you need access to second or subsequent event arguments (it's rare
+ * that this is needed) then use listener
+ */
+ once(event, listener) {
+ let deferred = defer();
+
+ let handler = (_, first, ...rest) => {
+ this.off(event, handler);
+ if (listener) {
+ listener.apply(null, [event, first, ...rest]);
+ }
+ deferred.resolve(first);
+ };
+
+ handler._originalListener = listener;
+ this.on(event, handler);
+
+ return deferred.promise;
+ },
+
+ /**
+ * Remove a previously-registered event listener. Works for events
+ * registered with either on or once.
+ *
+ * @param string event
+ * The event name whose listener we're disconnecting.
+ * @param function listener
+ * The listener to remove.
+ */
+ off(event, listener) {
+ if (!this._eventEmitterListeners) {
+ return;
+ }
+ let listeners = this._eventEmitterListeners.get(event);
+ if (listeners) {
+ this._eventEmitterListeners.set(event, listeners.filter(l => {
+ return l !== listener && l._originalListener !== listener;
+ }));
+ }
+ },
+
+ /**
+ * Emit an event. All arguments to this method will
+ * be sent to listener functions.
+ */
+ emit(event) {
+ this.logEvent(event, arguments);
+
+ if (!this._eventEmitterListeners || !this._eventEmitterListeners.has(event)) {
+ return;
+ }
+
+ let originalListeners = this._eventEmitterListeners.get(event);
+ for (let listener of this._eventEmitterListeners.get(event)) {
+ // If the object was destroyed during event emission, stop
+ // emitting.
+ if (!this._eventEmitterListeners) {
+ break;
+ }
+
+ // If listeners were removed during emission, make sure the
+ // event handler we're going to fire wasn't removed.
+ if (originalListeners === this._eventEmitterListeners.get(event) ||
+ this._eventEmitterListeners.get(event).some(l => l === listener)) {
+ try {
+ listener.apply(null, arguments);
+ } catch (ex) {
+ // Prevent a bad listener from interfering with the others.
+ let msg = ex + ": " + ex.stack;
+ console.error(msg);
+ dump(msg + "\n");
+ }
+ }
+ }
+ },
+
+ logEvent(event, args) {
+ if (!loggingEnabled) {
+ return;
+ }
+
+ let description = describeNthCaller(2);
+
+ let argOut = "(";
+ if (args.length === 1) {
+ argOut += event;
+ }
+
+ let out = "EMITTING: ";
+
+ // We need this try / catch to prevent any dead object errors.
+ try {
+ for (let i = 1; i < args.length; i++) {
+ if (i === 1) {
+ argOut = "(" + event + ", ";
+ } else {
+ argOut += ", ";
+ }
+
+ let arg = args[i];
+ argOut += arg;
+
+ if (arg && arg.nodeName) {
+ argOut += " (" + arg.nodeName;
+ if (arg.id) {
+ argOut += "#" + arg.id;
+ }
+ if (arg.className) {
+ argOut += "." + arg.className;
+ }
+ argOut += ")";
+ }
+ }
+ } catch (e) {
+ // Object is dead so the toolbox is most likely shutting down,
+ // do nothing.
+ }
+
+ argOut += ")";
+ out += "emit" + argOut + " from " + description + "\n";
+
+ dump(out);
+ },
+ };
+});
diff --git a/devtools/shared/flags.js b/devtools/shared/flags.js
new file mode 100644
index 000000000..9903e6f50
--- /dev/null
+++ b/devtools/shared/flags.js
@@ -0,0 +1,21 @@
+
+/*
+ * Create a writable property by tracking it with a private variable.
+ * We cannot make a normal property writeable on `exports` because
+ * the module system freezes it.
+ */
+function makeWritableFlag(exports, name) {
+ let flag = false;
+ Object.defineProperty(exports, name, {
+ get: function () { return flag; },
+ set: function (state) { flag = state; }
+ });
+}
+
+makeWritableFlag(exports, "wantLogging");
+makeWritableFlag(exports, "wantVerbose");
+
+// When the testing flag is set, various behaviors may be altered from
+// production mode, typically to enable easier testing or enhanced
+// debugging.
+makeWritableFlag(exports, "testing");
diff --git a/devtools/shared/fronts/actor-registry.js b/devtools/shared/fronts/actor-registry.js
new file mode 100644
index 000000000..40f87b609
--- /dev/null
+++ b/devtools/shared/fronts/actor-registry.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { components } = require("chrome");
+const Services = require("Services");
+const { actorActorSpec, actorRegistrySpec } = require("devtools/shared/specs/actor-registry");
+const protocol = require("devtools/shared/protocol");
+const { custom } = protocol;
+
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+const ActorActorFront = protocol.FrontClassWithSpec(actorActorSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+exports.ActorActorFront = ActorActorFront;
+
+function request(uri) {
+ return new Promise((resolve, reject) => {
+ try {
+ uri = Services.io.newURI(uri, null, null);
+ } catch (e) {
+ reject(e);
+ }
+
+ NetUtil.asyncFetch({
+ uri,
+ loadUsingSystemPrincipal: true,
+ }, (stream, status, req) => {
+ if (!components.isSuccessCode(status)) {
+ reject(new Error("Request failed with status code = "
+ + status
+ + " after NetUtil.asyncFetch for url = "
+ + uri));
+ return;
+ }
+
+ let source = NetUtil.readInputStreamToString(stream, stream.available());
+ stream.close();
+ resolve(source);
+ });
+ });
+}
+
+const ActorRegistryFront = protocol.FrontClassWithSpec(actorRegistrySpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client,
+ { actor: form.actorRegistryActor });
+
+ this.manage(this);
+ },
+
+ registerActor: custom(function (uri, options) {
+ return request(uri, options)
+ .then(sourceText => {
+ return this._registerActor(sourceText, uri, options);
+ });
+ }, {
+ impl: "_registerActor"
+ })
+});
+
+exports.ActorRegistryFront = ActorRegistryFront;
diff --git a/devtools/shared/fronts/addons.js b/devtools/shared/fronts/addons.js
new file mode 100644
index 000000000..780cc9e03
--- /dev/null
+++ b/devtools/shared/fronts/addons.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {addonsSpec} = require("devtools/shared/specs/addons");
+const protocol = require("devtools/shared/protocol");
+
+const AddonsFront = protocol.FrontClassWithSpec(addonsSpec, {
+ initialize: function (client, {addonsActor}) {
+ protocol.Front.prototype.initialize.call(this, client);
+ this.actorID = addonsActor;
+ this.manage(this);
+ }
+});
+
+exports.AddonsFront = AddonsFront;
diff --git a/devtools/shared/fronts/animation.js b/devtools/shared/fronts/animation.js
new file mode 100644
index 000000000..01c9f0bfa
--- /dev/null
+++ b/devtools/shared/fronts/animation.js
@@ -0,0 +1,140 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Front,
+ FrontClassWithSpec,
+ custom,
+ preEvent
+} = require("devtools/shared/protocol");
+const {
+ animationPlayerSpec,
+ animationsSpec
+} = require("devtools/shared/specs/animation");
+const { Task } = require("devtools/shared/task");
+
+const AnimationPlayerFront = FrontClassWithSpec(animationPlayerSpec, {
+ initialize: function (conn, form, detail, ctx) {
+ Front.prototype.initialize.call(this, conn, form, detail, ctx);
+
+ this.state = {};
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this._form = form;
+ this.state = this.initialState;
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ /**
+ * If the AnimationsActor was given a reference to the WalkerActor previously
+ * then calling this getter will return the animation target NodeFront.
+ */
+ get animationTargetNodeFront() {
+ if (!this._form.animationTargetNodeActorID) {
+ return null;
+ }
+
+ return this.conn.getActor(this._form.animationTargetNodeActorID);
+ },
+
+ /**
+ * Getter for the initial state of the player. Up to date states can be
+ * retrieved by calling the getCurrentState method.
+ */
+ get initialState() {
+ return {
+ type: this._form.type,
+ startTime: this._form.startTime,
+ previousStartTime: this._form.previousStartTime,
+ currentTime: this._form.currentTime,
+ playState: this._form.playState,
+ playbackRate: this._form.playbackRate,
+ name: this._form.name,
+ duration: this._form.duration,
+ delay: this._form.delay,
+ endDelay: this._form.endDelay,
+ iterationCount: this._form.iterationCount,
+ iterationStart: this._form.iterationStart,
+ easing: this._form.easing,
+ fill: this._form.fill,
+ direction: this._form.direction,
+ isRunningOnCompositor: this._form.isRunningOnCompositor,
+ propertyState: this._form.propertyState,
+ documentCurrentTime: this._form.documentCurrentTime
+ };
+ },
+
+ /**
+ * Executed when the AnimationPlayerActor emits a "changed" event. Used to
+ * update the local knowledge of the state.
+ */
+ onChanged: preEvent("changed", function (partialState) {
+ let {state} = this.reconstructState(partialState);
+ this.state = state;
+ }),
+
+ /**
+ * Refresh the current state of this animation on the client from information
+ * found on the server. Doesn't return anything, just stores the new state.
+ */
+ refreshState: Task.async(function* () {
+ let data = yield this.getCurrentState();
+ if (this.currentStateHasChanged) {
+ this.state = data;
+ }
+ }),
+
+ /**
+ * getCurrentState interceptor re-constructs incomplete states since the actor
+ * only sends the values that have changed.
+ */
+ getCurrentState: custom(function () {
+ this.currentStateHasChanged = false;
+ return this._getCurrentState().then(partialData => {
+ let {state, hasChanged} = this.reconstructState(partialData);
+ this.currentStateHasChanged = hasChanged;
+ return state;
+ });
+ }, {
+ impl: "_getCurrentState"
+ }),
+
+ reconstructState: function (data) {
+ let hasChanged = false;
+
+ for (let key in this.state) {
+ if (typeof data[key] === "undefined") {
+ data[key] = this.state[key];
+ } else if (data[key] !== this.state[key]) {
+ hasChanged = true;
+ }
+ }
+
+ return {state: data, hasChanged};
+ }
+});
+
+exports.AnimationPlayerFront = AnimationPlayerFront;
+
+const AnimationsFront = FrontClassWithSpec(animationsSpec, {
+ initialize: function (client, {animationsActor}) {
+ Front.prototype.initialize.call(this, client, {actor: animationsActor});
+ this.manage(this);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ }
+});
+
+exports.AnimationsFront = AnimationsFront;
diff --git a/devtools/shared/fronts/call-watcher.js b/devtools/shared/fronts/call-watcher.js
new file mode 100644
index 000000000..5f41c2fbd
--- /dev/null
+++ b/devtools/shared/fronts/call-watcher.js
@@ -0,0 +1,226 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { functionCallSpec, callWatcherSpec } = require("devtools/shared/specs/call-watcher");
+const protocol = require("devtools/shared/protocol");
+
+/**
+ * The corresponding Front object for the FunctionCallActor.
+ */
+const FunctionCallFront = protocol.FrontClassWithSpec(functionCallSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ },
+
+ /**
+ * Adds some generic information directly to this instance,
+ * to avoid extra roundtrips.
+ */
+ form: function (form) {
+ this.actorID = form.actor;
+ this.type = form.type;
+ this.name = form.name;
+ this.file = form.file;
+ this.line = form.line;
+ this.timestamp = form.timestamp;
+ this.callerPreview = form.callerPreview;
+ this.argsPreview = form.argsPreview;
+ this.resultPreview = form.resultPreview;
+ }
+});
+
+exports.FunctionCallFront = FunctionCallFront;
+
+/**
+ * The corresponding Front object for the CallWatcherActor.
+ */
+var CallWatcherFront =
+exports.CallWatcherFront =
+protocol.FrontClassWithSpec(callWatcherSpec, {
+ initialize: function (client, { callWatcherActor }) {
+ protocol.Front.prototype.initialize.call(this, client, { actor: callWatcherActor });
+ this.manage(this);
+ }
+});
+
+/**
+ * Constants.
+ */
+CallWatcherFront.METHOD_FUNCTION = 0;
+CallWatcherFront.GETTER_FUNCTION = 1;
+CallWatcherFront.SETTER_FUNCTION = 2;
+
+CallWatcherFront.KNOWN_METHODS = {};
+
+CallWatcherFront.KNOWN_METHODS.CanvasRenderingContext2D = {
+ asyncDrawXULElement: {
+ enums: new Set([6]),
+ },
+ drawWindow: {
+ enums: new Set([6])
+ },
+};
+
+CallWatcherFront.KNOWN_METHODS.WebGLRenderingContext = {
+ activeTexture: {
+ enums: new Set([0]),
+ },
+ bindBuffer: {
+ enums: new Set([0]),
+ },
+ bindFramebuffer: {
+ enums: new Set([0]),
+ },
+ bindRenderbuffer: {
+ enums: new Set([0]),
+ },
+ bindTexture: {
+ enums: new Set([0]),
+ },
+ blendEquation: {
+ enums: new Set([0]),
+ },
+ blendEquationSeparate: {
+ enums: new Set([0, 1]),
+ },
+ blendFunc: {
+ enums: new Set([0, 1]),
+ },
+ blendFuncSeparate: {
+ enums: new Set([0, 1, 2, 3]),
+ },
+ bufferData: {
+ enums: new Set([0, 1, 2]),
+ },
+ bufferSubData: {
+ enums: new Set([0, 1]),
+ },
+ checkFramebufferStatus: {
+ enums: new Set([0]),
+ },
+ clear: {
+ enums: new Set([0]),
+ },
+ compressedTexImage2D: {
+ enums: new Set([0, 2]),
+ },
+ compressedTexSubImage2D: {
+ enums: new Set([0, 6]),
+ },
+ copyTexImage2D: {
+ enums: new Set([0, 2]),
+ },
+ copyTexSubImage2D: {
+ enums: new Set([0]),
+ },
+ createShader: {
+ enums: new Set([0]),
+ },
+ cullFace: {
+ enums: new Set([0]),
+ },
+ depthFunc: {
+ enums: new Set([0]),
+ },
+ disable: {
+ enums: new Set([0]),
+ },
+ drawArrays: {
+ enums: new Set([0]),
+ },
+ drawElements: {
+ enums: new Set([0, 2]),
+ },
+ enable: {
+ enums: new Set([0]),
+ },
+ framebufferRenderbuffer: {
+ enums: new Set([0, 1, 2]),
+ },
+ framebufferTexture2D: {
+ enums: new Set([0, 1, 2]),
+ },
+ frontFace: {
+ enums: new Set([0]),
+ },
+ generateMipmap: {
+ enums: new Set([0]),
+ },
+ getBufferParameter: {
+ enums: new Set([0, 1]),
+ },
+ getParameter: {
+ enums: new Set([0]),
+ },
+ getFramebufferAttachmentParameter: {
+ enums: new Set([0, 1, 2]),
+ },
+ getProgramParameter: {
+ enums: new Set([1]),
+ },
+ getRenderbufferParameter: {
+ enums: new Set([0, 1]),
+ },
+ getShaderParameter: {
+ enums: new Set([1]),
+ },
+ getShaderPrecisionFormat: {
+ enums: new Set([0, 1]),
+ },
+ getTexParameter: {
+ enums: new Set([0, 1]),
+ },
+ getVertexAttrib: {
+ enums: new Set([1]),
+ },
+ getVertexAttribOffset: {
+ enums: new Set([1]),
+ },
+ hint: {
+ enums: new Set([0, 1]),
+ },
+ isEnabled: {
+ enums: new Set([0]),
+ },
+ pixelStorei: {
+ enums: new Set([0]),
+ },
+ readPixels: {
+ enums: new Set([4, 5]),
+ },
+ renderbufferStorage: {
+ enums: new Set([0, 1]),
+ },
+ stencilFunc: {
+ enums: new Set([0]),
+ },
+ stencilFuncSeparate: {
+ enums: new Set([0, 1]),
+ },
+ stencilMaskSeparate: {
+ enums: new Set([0]),
+ },
+ stencilOp: {
+ enums: new Set([0, 1, 2]),
+ },
+ stencilOpSeparate: {
+ enums: new Set([0, 1, 2, 3]),
+ },
+ texImage2D: {
+ enums: args => args.length > 6 ? new Set([0, 2, 6, 7]) : new Set([0, 2, 3, 4]),
+ },
+ texParameterf: {
+ enums: new Set([0, 1]),
+ },
+ texParameteri: {
+ enums: new Set([0, 1, 2]),
+ },
+ texSubImage2D: {
+ enums: args => args.length === 9 ? new Set([0, 6, 7]) : new Set([0, 4, 5]),
+ },
+ vertexAttribPointer: {
+ enums: new Set([2])
+ },
+};
diff --git a/devtools/shared/fronts/canvas.js b/devtools/shared/fronts/canvas.js
new file mode 100644
index 000000000..f3a1a6075
--- /dev/null
+++ b/devtools/shared/fronts/canvas.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ frameSnapshotSpec,
+ canvasSpec,
+ CANVAS_CONTEXTS,
+ ANIMATION_GENERATORS,
+ LOOP_GENERATORS,
+ DRAW_CALLS,
+ INTERESTING_CALLS,
+} = require("devtools/shared/specs/canvas");
+const protocol = require("devtools/shared/protocol");
+const promise = require("promise");
+
+/**
+ * The corresponding Front object for the FrameSnapshotActor.
+ */
+const FrameSnapshotFront = protocol.FrontClassWithSpec(frameSnapshotSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ this._animationFrameEndScreenshot = null;
+ this._cachedScreenshots = new WeakMap();
+ },
+
+ /**
+ * This implementation caches the animation frame end screenshot to optimize
+ * frontend requests to `generateScreenshotFor`.
+ */
+ getOverview: protocol.custom(function () {
+ return this._getOverview().then(data => {
+ this._animationFrameEndScreenshot = data.screenshot;
+ return data;
+ });
+ }, {
+ impl: "_getOverview"
+ }),
+
+ /**
+ * This implementation saves a roundtrip to the backend if the screenshot
+ * was already generated and retrieved once.
+ */
+ generateScreenshotFor: protocol.custom(function (functionCall) {
+ if (CanvasFront.ANIMATION_GENERATORS.has(functionCall.name) ||
+ CanvasFront.LOOP_GENERATORS.has(functionCall.name)) {
+ return promise.resolve(this._animationFrameEndScreenshot);
+ }
+ let cachedScreenshot = this._cachedScreenshots.get(functionCall);
+ if (cachedScreenshot) {
+ return cachedScreenshot;
+ }
+ let screenshot = this._generateScreenshotFor(functionCall);
+ this._cachedScreenshots.set(functionCall, screenshot);
+ return screenshot;
+ }, {
+ impl: "_generateScreenshotFor"
+ })
+});
+
+exports.FrameSnapshotFront = FrameSnapshotFront;
+
+/**
+ * The corresponding Front object for the CanvasActor.
+ */
+const CanvasFront = protocol.FrontClassWithSpec(canvasSpec, {
+ initialize: function (client, { canvasActor }) {
+ protocol.Front.prototype.initialize.call(this, client, { actor: canvasActor });
+ this.manage(this);
+ }
+});
+
+/**
+ * Constants.
+ */
+CanvasFront.CANVAS_CONTEXTS = new Set(CANVAS_CONTEXTS);
+CanvasFront.ANIMATION_GENERATORS = new Set(ANIMATION_GENERATORS);
+CanvasFront.LOOP_GENERATORS = new Set(LOOP_GENERATORS);
+CanvasFront.DRAW_CALLS = new Set(DRAW_CALLS);
+CanvasFront.INTERESTING_CALLS = new Set(INTERESTING_CALLS);
+CanvasFront.THUMBNAIL_SIZE = 50;
+CanvasFront.WEBGL_SCREENSHOT_MAX_HEIGHT = 256;
+CanvasFront.INVALID_SNAPSHOT_IMAGE = {
+ index: -1,
+ width: 0,
+ height: 0,
+ pixels: []
+};
+
+exports.CanvasFront = CanvasFront;
diff --git a/devtools/shared/fronts/css-properties.js b/devtools/shared/fronts/css-properties.js
new file mode 100644
index 000000000..9b3172a22
--- /dev/null
+++ b/devtools/shared/fronts/css-properties.js
@@ -0,0 +1,323 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { FrontClassWithSpec, Front } = require("devtools/shared/protocol");
+const { cssPropertiesSpec } = require("devtools/shared/specs/css-properties");
+const { Task } = require("devtools/shared/task");
+const { CSS_PROPERTIES_DB } = require("devtools/shared/css/properties-db");
+const { cssColors } = require("devtools/shared/css/color-db");
+
+/**
+ * Build up a regular expression that matches a CSS variable token. This is an
+ * ident token that starts with two dashes "--".
+ *
+ * https://www.w3.org/TR/css-syntax-3/#ident-token-diagram
+ */
+var NON_ASCII = "[^\\x00-\\x7F]";
+var ESCAPE = "\\\\[^\n\r]";
+var FIRST_CHAR = ["[_a-z]", NON_ASCII, ESCAPE].join("|");
+var TRAILING_CHAR = ["[_a-z0-9-]", NON_ASCII, ESCAPE].join("|");
+var IS_VARIABLE_TOKEN = new RegExp(`^--(${FIRST_CHAR})(${TRAILING_CHAR})*$`,
+ "i");
+/**
+ * Check that this is a CSS variable.
+ *
+ * @param {String} input
+ * @return {Boolean}
+ */
+function isCssVariable(input) {
+ return !!input.match(IS_VARIABLE_TOKEN);
+}
+
+var cachedCssProperties = new WeakMap();
+
+/**
+ * The CssProperties front provides a mechanism to have a one-time asynchronous
+ * load of a CSS properties database. This is then fed into the CssProperties
+ * interface that provides synchronous methods for finding out what CSS
+ * properties the current server supports.
+ */
+const CssPropertiesFront = FrontClassWithSpec(cssPropertiesSpec, {
+ initialize: function (client, { cssPropertiesActor }) {
+ Front.prototype.initialize.call(this, client, {actor: cssPropertiesActor});
+ this.manage(this);
+ }
+});
+
+/**
+ * Ask questions to a CSS database. This class does not care how the database
+ * gets loaded in, only the questions that you can ask to it.
+ * Prototype functions are bound to 'this' so they can be passed around as helper
+ * functions.
+ *
+ * @param {Object} db
+ * A database of CSS properties
+ * @param {Object} inheritedList
+ * The key is the property name, the value is whether or not
+ * that property is inherited.
+ */
+function CssProperties(db) {
+ this.properties = db.properties;
+ this.pseudoElements = db.pseudoElements;
+
+ this.isKnown = this.isKnown.bind(this);
+ this.isInherited = this.isInherited.bind(this);
+ this.supportsType = this.supportsType.bind(this);
+ this.isValidOnClient = this.isValidOnClient.bind(this);
+
+ // A weakly held dummy HTMLDivElement to test CSS properties on the client.
+ this._dummyElements = new WeakMap();
+}
+
+CssProperties.prototype = {
+ /**
+ * Checks to see if the property is known by the browser. This function has
+ * `this` already bound so that it can be passed around by reference.
+ *
+ * @param {String} property The property name to be checked.
+ * @return {Boolean}
+ */
+ isKnown(property) {
+ return !!this.properties[property] || isCssVariable(property);
+ },
+
+ /**
+ * Quickly check if a CSS name/value combo is valid on the client.
+ *
+ * @param {String} Property name.
+ * @param {String} Property value.
+ * @param {Document} The client's document object.
+ * @return {Boolean}
+ */
+ isValidOnClient(name, value, doc) {
+ let dummyElement = this._dummyElements.get(doc);
+ if (!dummyElement) {
+ dummyElement = doc.createElement("div");
+ this._dummyElements.set(doc, dummyElement);
+ }
+
+ // `!important` is not a valid value when setting a style declaration in the
+ // CSS Object Model.
+ const sanitizedValue = ("" + value).replace(/!\s*important\s*$/, "");
+
+ // Test the style on the element.
+ dummyElement.style[name] = sanitizedValue;
+ const isValid = !!dummyElement.style[name];
+
+ // Reset the state of the dummy element;
+ dummyElement.style[name] = "";
+ return isValid;
+ },
+
+ /**
+ * Get a function that will check the validity of css name/values for a given document.
+ * Useful for injecting isValidOnClient into components when needed.
+ *
+ * @param {Document} The client's document object.
+ * @return {Function} this.isValidOnClient with the document pre-set.
+ */
+ getValidityChecker(doc) {
+ return (name, value) => this.isValidOnClient(name, value, doc);
+ },
+
+ /**
+ * Checks to see if the property is an inherited one.
+ *
+ * @param {String} property The property name to be checked.
+ * @return {Boolean}
+ */
+ isInherited(property) {
+ return this.properties[property] && this.properties[property].isInherited;
+ },
+
+ /**
+ * Checks if the property supports the given CSS type.
+ * CSS types should come from devtools/shared/css/properties-db.js' CSS_TYPES.
+ *
+ * @param {String} property The property to be checked.
+ * @param {Number} type One of the type values from CSS_TYPES.
+ * @return {Boolean}
+ */
+ supportsType(property, type) {
+ return this.properties[property] && this.properties[property].supports.includes(type);
+ },
+
+ /**
+ * Gets the CSS values for a given property name.
+ *
+ * @param {String} property The property to use.
+ * @return {Array} An array of strings.
+ */
+ getValues(property) {
+ return this.properties[property] ? this.properties[property].values : [];
+ },
+
+ /**
+ * Gets the CSS property names.
+ *
+ * @return {Array} An array of strings.
+ */
+ getNames(property) {
+ return Object.keys(this.properties);
+ },
+
+ /**
+ * Return a list of subproperties for the given property. If |name|
+ * does not name a valid property, an empty array is returned. If
+ * the property is not a shorthand property, then array containing
+ * just the property itself is returned.
+ *
+ * @param {String} name The property to query
+ * @return {Array} An array of subproperty names.
+ */
+ getSubproperties(name) {
+ if (this.isKnown(name)) {
+ if (this.properties[name] && this.properties[name].subproperties) {
+ return this.properties[name].subproperties;
+ }
+ return [name];
+ }
+ return [];
+ },
+};
+
+/**
+ * Create a CssProperties object with a fully loaded CSS database. The
+ * CssProperties interface can be queried synchronously, but the initialization
+ * is potentially async and should be handled up-front when the tool is created.
+ *
+ * The front is returned only with this function so that it can be destroyed
+ * once the toolbox is destroyed.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {Promise} Resolves to {cssProperties, cssPropertiesFront}.
+ */
+const initCssProperties = Task.async(function* (toolbox) {
+ const client = toolbox.target.client;
+ if (cachedCssProperties.has(client)) {
+ return cachedCssProperties.get(client);
+ }
+
+ let db, front;
+
+ // Get the list dynamically if the cssProperties actor exists.
+ if (toolbox.target.hasActor("cssProperties")) {
+ front = CssPropertiesFront(client, toolbox.target.form);
+ const serverDB = yield front.getCSSDatabase();
+
+ // Ensure the database was returned in a format that is understood.
+ // Older versions of the protocol could return a blank database.
+ if (!serverDB.properties && !serverDB.margin) {
+ db = CSS_PROPERTIES_DB;
+ } else {
+ db = serverDB;
+ }
+ } else {
+ // The target does not support this actor, so require a static list of supported
+ // properties.
+ db = CSS_PROPERTIES_DB;
+ }
+
+ const cssProperties = new CssProperties(normalizeCssData(db));
+ cachedCssProperties.set(client, {cssProperties, front});
+ return {cssProperties, front};
+});
+
+/**
+ * Synchronously get a cached and initialized CssProperties.
+ *
+ * @param {Toolbox} The current toolbox.
+ * @returns {CssProperties}
+ */
+function getCssProperties(toolbox) {
+ if (!cachedCssProperties.has(toolbox.target.client)) {
+ throw new Error("The CSS database has not been initialized, please make " +
+ "sure initCssDatabase was called once before for this " +
+ "toolbox.");
+ }
+ return cachedCssProperties.get(toolbox.target.client).cssProperties;
+}
+
+/**
+ * Get a client-side CssProperties. This is useful for dependencies in tests, or parts
+ * of the codebase that don't particularly need to match every known CSS property on
+ * the target.
+ * @return {CssProperties}
+ */
+function getClientCssProperties() {
+ return new CssProperties(normalizeCssData(CSS_PROPERTIES_DB));
+}
+
+/**
+ * Even if the target has the cssProperties actor, the returned data may not be in the
+ * same shape or have all of the data we need. This normalizes the data and fills in
+ * any missing information like color values.
+ *
+ * @return {Object} The normalized CSS database.
+ */
+function normalizeCssData(db) {
+ if (db !== CSS_PROPERTIES_DB) {
+ // Firefox 49's getCSSDatabase() just returned the properties object, but
+ // now it returns an object with multiple types of CSS information.
+ if (!db.properties) {
+ db = { properties: db };
+ }
+
+ // Fill in any missing DB information from the static database.
+ db = Object.assign({}, CSS_PROPERTIES_DB, db);
+
+ for (let name in db.properties) {
+ // Skip the current property if we can't find it in CSS_PROPERTIES_DB.
+ if (typeof CSS_PROPERTIES_DB.properties[name] !== "object") {
+ continue;
+ }
+
+ // Add "supports" information to the css properties if it's missing.
+ if (!db.properties.color.supports) {
+ db.properties[name].supports = CSS_PROPERTIES_DB.properties[name].supports;
+ }
+ // Add "values" information to the css properties if it's missing.
+ if (!db.properties.color.values) {
+ db.properties[name].values = CSS_PROPERTIES_DB.properties[name].values;
+ }
+ // Add "subproperties" information to the css properties if it's missing.
+ if (!db.properties.background.subproperties) {
+ db.properties[name].subproperties =
+ CSS_PROPERTIES_DB.properties[name].subproperties;
+ }
+ }
+ }
+
+ reattachCssColorValues(db);
+
+ return db;
+}
+
+/**
+ * Color values are omitted to save on space. Add them back here.
+ * @param {Object} The CSS database.
+ */
+function reattachCssColorValues(db) {
+ if (db.properties.color.values[0] === "COLOR") {
+ const colors = Object.keys(cssColors);
+
+ for (let name in db.properties) {
+ const property = db.properties[name];
+ // "values" can be undefined if {name} was not found in CSS_PROPERTIES_DB.
+ if (property.values && property.values[0] === "COLOR") {
+ property.values.shift();
+ property.values = property.values.concat(colors).sort();
+ }
+ }
+ }
+}
+
+module.exports = {
+ CssPropertiesFront,
+ CssProperties,
+ getCssProperties,
+ getClientCssProperties,
+ initCssProperties
+};
diff --git a/devtools/shared/fronts/csscoverage.js b/devtools/shared/fronts/csscoverage.js
new file mode 100644
index 000000000..28ab399c5
--- /dev/null
+++ b/devtools/shared/fronts/csscoverage.js
@@ -0,0 +1,125 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {cssUsageSpec} = require("devtools/shared/specs/csscoverage");
+const protocol = require("devtools/shared/protocol");
+const {custom} = protocol;
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/shared/locales/csscoverage.properties");
+
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+
+/**
+ * Allow: let foo = l10n.lookup("csscoverageFoo");
+ */
+const l10n = exports.l10n = {
+ lookup: (msg) => L10N.getStr(msg)
+};
+
+/**
+ * Running more than one usage report at a time is probably bad for performance
+ * and it isn't particularly useful, and it's confusing from a notification POV
+ * so we only allow one.
+ */
+var isRunning = false;
+var notification;
+var target;
+var chromeWindow;
+
+/**
+ * Front for CSSUsageActor
+ */
+const CSSUsageFront = protocol.FrontClassWithSpec(cssUsageSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ this.actorID = form.cssUsageActor;
+ this.manage(this);
+ },
+
+ _onStateChange: protocol.preEvent("state-change", function (ev) {
+ isRunning = ev.isRunning;
+ ev.target = target;
+
+ if (isRunning) {
+ let gnb = chromeWindow.document.getElementById("global-notificationbox");
+ notification = gnb.getNotificationWithValue("csscoverage-running");
+
+ if (notification == null) {
+ let notifyStop = reason => {
+ if (reason == "removed") {
+ this.stop();
+ }
+ };
+
+ let msg = l10n.lookup("csscoverageRunningReply");
+ notification = gnb.appendNotification(msg, "csscoverage-running",
+ "",
+ gnb.PRIORITY_INFO_HIGH,
+ null,
+ notifyStop);
+ }
+ } else {
+ if (notification) {
+ notification.remove();
+ notification = undefined;
+ }
+
+ gDevTools.showToolbox(target, "styleeditor");
+ target = undefined;
+ }
+ }),
+
+ /**
+ * Server-side start is above. Client-side start adds a notification box
+ */
+ start: custom(function (newChromeWindow, newTarget, noreload = false) {
+ target = newTarget;
+ chromeWindow = newChromeWindow;
+
+ return this._start(noreload);
+ }, {
+ impl: "_start"
+ }),
+
+ /**
+ * Server-side start is above. Client-side start adds a notification box
+ */
+ toggle: custom(function (newChromeWindow, newTarget) {
+ target = newTarget;
+ chromeWindow = newChromeWindow;
+
+ return this._toggle();
+ }, {
+ impl: "_toggle"
+ }),
+
+ /**
+ * We count STARTING and STOPPING as 'running'
+ */
+ isRunning: function () {
+ return isRunning;
+ }
+});
+
+exports.CSSUsageFront = CSSUsageFront;
+
+const knownFronts = new WeakMap();
+
+/**
+ * Create a CSSUsageFront only when needed (returns a promise)
+ * For notes on target.makeRemote(), see
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1016330#c7
+ */
+exports.getUsage = function (trgt) {
+ return trgt.makeRemote().then(() => {
+ let front = knownFronts.get(trgt.client);
+ if (front == null && trgt.form.cssUsageActor != null) {
+ front = new CSSUsageFront(trgt.client, trgt.form);
+ knownFronts.set(trgt.client, front);
+ }
+ return front;
+ });
+};
diff --git a/devtools/shared/fronts/device.js b/devtools/shared/fronts/device.js
new file mode 100644
index 000000000..28f7a096a
--- /dev/null
+++ b/devtools/shared/fronts/device.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Cc, Ci, Cu} = require("chrome");
+const {deviceSpec} = require("devtools/shared/specs/device");
+const protocol = require("devtools/shared/protocol");
+const defer = require("devtools/shared/defer");
+
+const DeviceFront = protocol.FrontClassWithSpec(deviceSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client);
+ this.actorID = form.deviceActor;
+ this.manage(this);
+ },
+
+ screenshotToBlob: function () {
+ return this.screenshotToDataURL().then(longstr => {
+ return longstr.string().then(dataURL => {
+ let deferred = defer();
+ longstr.release().then(null, Cu.reportError);
+ let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ req.open("GET", dataURL, true);
+ req.responseType = "blob";
+ req.onload = () => {
+ deferred.resolve(req.response);
+ };
+ req.onerror = () => {
+ deferred.reject(req.status);
+ };
+ req.send();
+ return deferred.promise;
+ });
+ });
+ },
+});
+
+const _knownDeviceFronts = new WeakMap();
+
+exports.getDeviceFront = function (client, form) {
+ if (!form.deviceActor) {
+ return null;
+ }
+
+ if (_knownDeviceFronts.has(client)) {
+ return _knownDeviceFronts.get(client);
+ }
+
+ let front = new DeviceFront(client, form);
+ _knownDeviceFronts.set(client, front);
+ return front;
+};
diff --git a/devtools/shared/fronts/director-manager.js b/devtools/shared/fronts/director-manager.js
new file mode 100644
index 000000000..afef42395
--- /dev/null
+++ b/devtools/shared/fronts/director-manager.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ messagePortSpec,
+ directorScriptSpec,
+ directorManagerSpec,
+} = require("devtools/shared/specs/director-manager");
+const protocol = require("devtools/shared/protocol");
+
+/**
+ * The corresponding Front object for the MessagePortActor.
+ */
+const MessagePortFront = protocol.FrontClassWithSpec(messagePortSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+exports.MessagePortFront = MessagePortFront;
+
+/**
+ * The corresponding Front object for the DirectorScriptActor.
+ */
+const DirectorScriptFront = protocol.FrontClassWithSpec(directorScriptSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+exports.DirectorScriptFront = DirectorScriptFront;
+
+/**
+ * The corresponding Front object for the DirectorManagerActor.
+ */
+const DirectorManagerFront = protocol.FrontClassWithSpec(directorManagerSpec, {
+ initialize: function (client, { directorManagerActor }) {
+ protocol.Front.prototype.initialize.call(this, client, {
+ actor: directorManagerActor
+ });
+ this.manage(this);
+ }
+});
+
+exports.DirectorManagerFront = DirectorManagerFront;
diff --git a/devtools/shared/fronts/director-registry.js b/devtools/shared/fronts/director-registry.js
new file mode 100644
index 000000000..559fe2052
--- /dev/null
+++ b/devtools/shared/fronts/director-registry.js
@@ -0,0 +1,21 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {directorRegistrySpec} = require("devtools/shared/specs/director-registry");
+const protocol = require("devtools/shared/protocol");
+
+/**
+ * The corresponding Front object for the DirectorRegistryActor.
+ */
+const DirectorRegistryFront = protocol.FrontClassWithSpec(directorRegistrySpec, {
+ initialize: function (client, { directorRegistryActor }) {
+ protocol.Front.prototype.initialize.call(this, client, {
+ actor: directorRegistryActor
+ });
+ this.manage(this);
+ }
+});
+
+exports.DirectorRegistryFront = DirectorRegistryFront;
diff --git a/devtools/shared/fronts/emulation.js b/devtools/shared/fronts/emulation.js
new file mode 100644
index 000000000..99dbf565b
--- /dev/null
+++ b/devtools/shared/fronts/emulation.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { emulationSpec } = require("devtools/shared/specs/emulation");
+
+/**
+ * The corresponding Front object for the EmulationActor.
+ */
+const EmulationFront = FrontClassWithSpec(emulationSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = form.emulationActor;
+ this.manage(this);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+});
+
+exports.EmulationFront = EmulationFront;
diff --git a/devtools/shared/fronts/eventlooplag.js b/devtools/shared/fronts/eventlooplag.js
new file mode 100644
index 000000000..7c130e621
--- /dev/null
+++ b/devtools/shared/fronts/eventlooplag.js
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { eventLoopLagSpec } = require("devtools/shared/specs/eventlooplag");
+
+exports.EventLoopLagFront = FrontClassWithSpec(eventLoopLagSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = form.eventLoopLagActor;
+ this.manage(this);
+ },
+});
diff --git a/devtools/shared/fronts/framerate.js b/devtools/shared/fronts/framerate.js
new file mode 100644
index 000000000..2aa678a0d
--- /dev/null
+++ b/devtools/shared/fronts/framerate.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { framerateSpec } = require("devtools/shared/specs/framerate");
+
+/**
+ * The corresponding Front object for the FramerateActor.
+ */
+var FramerateFront = exports.FramerateFront = FrontClassWithSpec(framerateSpec, {
+ initialize: function (client, { framerateActor }) {
+ Front.prototype.initialize.call(this, client, { actor: framerateActor });
+ this.manage(this);
+ }
+});
+
+exports.FramerateFront = FramerateFront;
diff --git a/devtools/shared/fronts/gcli.js b/devtools/shared/fronts/gcli.js
new file mode 100644
index 000000000..28ae1138b
--- /dev/null
+++ b/devtools/shared/fronts/gcli.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { gcliSpec } = require("devtools/shared/specs/gcli");
+
+/**
+ *
+ */
+const GcliFront = exports.GcliFront = FrontClassWithSpec(gcliSpec, {
+ initialize: function (client, tabForm) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = tabForm.gcliActor;
+
+ // XXX: This is the first actor type in its hierarchy to use the protocol
+ // library, so we're going to self-own on the client side for now.
+ this.manage(this);
+ },
+});
+
+// A cache of created fronts: WeakMap<Client, Front>
+const knownFronts = new WeakMap();
+
+/**
+ * Create a GcliFront only when needed (returns a promise)
+ * For notes on target.makeRemote(), see
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=1016330#c7
+ */
+exports.GcliFront.create = function (target) {
+ return target.makeRemote().then(() => {
+ let front = knownFronts.get(target.client);
+ if (front == null && target.form.gcliActor != null) {
+ front = new GcliFront(target.client, target.form);
+ knownFronts.set(target.client, front);
+ }
+ return front;
+ });
+};
diff --git a/devtools/shared/fronts/highlighters.js b/devtools/shared/fronts/highlighters.js
new file mode 100644
index 000000000..ca39ed526
--- /dev/null
+++ b/devtools/shared/fronts/highlighters.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { FrontClassWithSpec, custom } = require("devtools/shared/protocol");
+const {
+ customHighlighterSpec,
+ highlighterSpec
+} = require("devtools/shared/specs/highlighters");
+
+const HighlighterFront = FrontClassWithSpec(highlighterSpec, {
+ // Update the object given a form representation off the wire.
+ form: function (json) {
+ this.actorID = json.actor;
+ // FF42+ HighlighterActors starts exposing custom form, with traits object
+ this.traits = json.traits || {};
+ },
+
+ pick: custom(function (doFocus) {
+ if (doFocus && this.pickAndFocus) {
+ return this.pickAndFocus();
+ }
+ return this._pick();
+ }, {
+ impl: "_pick"
+ })
+});
+
+exports.HighlighterFront = HighlighterFront;
+
+const CustomHighlighterFront = FrontClassWithSpec(customHighlighterSpec, {});
+
+exports.CustomHighlighterFront = CustomHighlighterFront;
diff --git a/devtools/shared/fronts/inspector.js b/devtools/shared/fronts/inspector.js
new file mode 100644
index 000000000..c76b41fe7
--- /dev/null
+++ b/devtools/shared/fronts/inspector.js
@@ -0,0 +1,1007 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+require("devtools/shared/fronts/styles");
+require("devtools/shared/fronts/highlighters");
+require("devtools/shared/fronts/layout");
+const { SimpleStringFront } = require("devtools/shared/fronts/string");
+const {
+ Front,
+ FrontClassWithSpec,
+ custom,
+ preEvent,
+ types
+} = require("devtools/shared/protocol.js");
+const {
+ inspectorSpec,
+ nodeSpec,
+ nodeListSpec,
+ walkerSpec
+} = require("devtools/shared/specs/inspector");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+const { Class } = require("sdk/core/heritage");
+const events = require("sdk/event/core");
+const object = require("sdk/util/object");
+const nodeConstants = require("devtools/shared/dom-node-constants.js");
+loader.lazyRequireGetter(this, "CommandUtils",
+ "devtools/client/shared/developer-toolbar", true);
+
+const HIDDEN_CLASS = "__fx-devtools-hide-shortcut__";
+
+/**
+ * Convenience API for building a list of attribute modifications
+ * for the `modifyAttributes` request.
+ */
+const AttributeModificationList = Class({
+ initialize: function (node) {
+ this.node = node;
+ this.modifications = [];
+ },
+
+ apply: function () {
+ let ret = this.node.modifyAttributes(this.modifications);
+ return ret;
+ },
+
+ destroy: function () {
+ this.node = null;
+ this.modification = null;
+ },
+
+ setAttributeNS: function (ns, name, value) {
+ this.modifications.push({
+ attributeNamespace: ns,
+ attributeName: name,
+ newValue: value
+ });
+ },
+
+ setAttribute: function (name, value) {
+ this.setAttributeNS(undefined, name, value);
+ },
+
+ removeAttributeNS: function (ns, name) {
+ this.setAttributeNS(ns, name, undefined);
+ },
+
+ removeAttribute: function (name) {
+ this.setAttributeNS(undefined, name, undefined);
+ }
+});
+
+/**
+ * Client side of the node actor.
+ *
+ * Node fronts are strored in a tree that mirrors the DOM tree on the
+ * server, but with a few key differences:
+ * - Not all children will be necessary loaded for each node.
+ * - The order of children isn't guaranteed to be the same as the DOM.
+ * Children are stored in a doubly-linked list, to make addition/removal
+ * and traversal quick.
+ *
+ * Due to the order/incompleteness of the child list, it is safe to use
+ * the parent node from clients, but the `children` request should be used
+ * to traverse children.
+ */
+const NodeFront = FrontClassWithSpec(nodeSpec, {
+ initialize: function (conn, form, detail, ctx) {
+ // The parent node
+ this._parent = null;
+ // The first child of this node.
+ this._child = null;
+ // The next sibling of this node.
+ this._next = null;
+ // The previous sibling of this node.
+ this._prev = null;
+ Front.prototype.initialize.call(this, conn, form, detail, ctx);
+ },
+
+ /**
+ * Destroy a node front. The node must have been removed from the
+ * ownership tree before this is called, unless the whole walker front
+ * is being destroyed.
+ */
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ // Update the object given a form representation off the wire.
+ form: function (form, detail, ctx) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+
+ // backward-compatibility: shortValue indicates we are connected to old server
+ if (form.shortValue) {
+ // If the value is not complete, set nodeValue to null, it will be fetched
+ // when calling getNodeValue()
+ form.nodeValue = form.incompleteValue ? null : form.shortValue;
+ }
+
+ // Shallow copy of the form. We could just store a reference, but
+ // eventually we'll want to update some of the data.
+ this._form = object.merge(form);
+ this._form.attrs = this._form.attrs ? this._form.attrs.slice() : [];
+
+ if (form.parent) {
+ // Get the owner actor for this actor (the walker), and find the
+ // parent node of this actor from it, creating a standin node if
+ // necessary.
+ let parentNodeFront = ctx.marshallPool().ensureParentFront(form.parent);
+ this.reparent(parentNodeFront);
+ }
+
+ if (form.inlineTextChild) {
+ this.inlineTextChild =
+ types.getType("domnode").read(form.inlineTextChild, ctx);
+ } else {
+ this.inlineTextChild = undefined;
+ }
+ },
+
+ /**
+ * Returns the parent NodeFront for this NodeFront.
+ */
+ parentNode: function () {
+ return this._parent;
+ },
+
+ /**
+ * Process a mutation entry as returned from the walker's `getMutations`
+ * request. Only tries to handle changes of the node's contents
+ * themselves (character data and attribute changes), the walker itself
+ * will keep the ownership tree up to date.
+ */
+ updateMutation: function (change) {
+ if (change.type === "attributes") {
+ // We'll need to lazily reparse the attributes after this change.
+ this._attrMap = undefined;
+
+ // Update any already-existing attributes.
+ let found = false;
+ for (let i = 0; i < this.attributes.length; i++) {
+ let attr = this.attributes[i];
+ if (attr.name == change.attributeName &&
+ attr.namespace == change.attributeNamespace) {
+ if (change.newValue !== null) {
+ attr.value = change.newValue;
+ } else {
+ this.attributes.splice(i, 1);
+ }
+ found = true;
+ break;
+ }
+ }
+ // This is a new attribute. The null check is because of Bug 1192270,
+ // in the case of a newly added then removed attribute
+ if (!found && change.newValue !== null) {
+ this.attributes.push({
+ name: change.attributeName,
+ namespace: change.attributeNamespace,
+ value: change.newValue
+ });
+ }
+ } else if (change.type === "characterData") {
+ this._form.nodeValue = change.newValue;
+ } else if (change.type === "pseudoClassLock") {
+ this._form.pseudoClassLocks = change.pseudoClassLocks;
+ } else if (change.type === "events") {
+ this._form.hasEventListeners = change.hasEventListeners;
+ }
+ },
+
+ // Some accessors to make NodeFront feel more like an nsIDOMNode
+
+ get id() {
+ return this.getAttribute("id");
+ },
+
+ get nodeType() {
+ return this._form.nodeType;
+ },
+ get namespaceURI() {
+ return this._form.namespaceURI;
+ },
+ get nodeName() {
+ return this._form.nodeName;
+ },
+ get displayName() {
+ let {displayName, nodeName} = this._form;
+
+ // Keep `nodeName.toLowerCase()` for backward compatibility
+ return displayName || nodeName.toLowerCase();
+ },
+ get doctypeString() {
+ return "<!DOCTYPE " + this._form.name +
+ (this._form.publicId ? " PUBLIC \"" + this._form.publicId + "\"" : "") +
+ (this._form.systemId ? " \"" + this._form.systemId + "\"" : "") +
+ ">";
+ },
+
+ get baseURI() {
+ return this._form.baseURI;
+ },
+
+ get className() {
+ return this.getAttribute("class") || "";
+ },
+
+ get hasChildren() {
+ return this._form.numChildren > 0;
+ },
+ get numChildren() {
+ return this._form.numChildren;
+ },
+ get hasEventListeners() {
+ return this._form.hasEventListeners;
+ },
+
+ get isBeforePseudoElement() {
+ return this._form.isBeforePseudoElement;
+ },
+ get isAfterPseudoElement() {
+ return this._form.isAfterPseudoElement;
+ },
+ get isPseudoElement() {
+ return this.isBeforePseudoElement || this.isAfterPseudoElement;
+ },
+ get isAnonymous() {
+ return this._form.isAnonymous;
+ },
+ get isInHTMLDocument() {
+ return this._form.isInHTMLDocument;
+ },
+ get tagName() {
+ return this.nodeType === nodeConstants.ELEMENT_NODE ? this.nodeName : null;
+ },
+
+ get isDocumentElement() {
+ return !!this._form.isDocumentElement;
+ },
+
+ // doctype properties
+ get name() {
+ return this._form.name;
+ },
+ get publicId() {
+ return this._form.publicId;
+ },
+ get systemId() {
+ return this._form.systemId;
+ },
+
+ getAttribute: function (name) {
+ let attr = this._getAttribute(name);
+ return attr ? attr.value : null;
+ },
+ hasAttribute: function (name) {
+ this._cacheAttributes();
+ return (name in this._attrMap);
+ },
+
+ get hidden() {
+ let cls = this.getAttribute("class");
+ return cls && cls.indexOf(HIDDEN_CLASS) > -1;
+ },
+
+ get attributes() {
+ return this._form.attrs;
+ },
+
+ get pseudoClassLocks() {
+ return this._form.pseudoClassLocks || [];
+ },
+ hasPseudoClassLock: function (pseudo) {
+ return this.pseudoClassLocks.some(locked => locked === pseudo);
+ },
+
+ get isDisplayed() {
+ // The NodeActor's form contains the isDisplayed information as a boolean
+ // starting from FF32. Before that, the property is missing
+ return "isDisplayed" in this._form ? this._form.isDisplayed : true;
+ },
+
+ get isTreeDisplayed() {
+ let parent = this;
+ while (parent) {
+ if (!parent.isDisplayed) {
+ return false;
+ }
+ parent = parent.parentNode();
+ }
+ return true;
+ },
+
+ getNodeValue: custom(function () {
+ // backward-compatibility: if nodevalue is null and shortValue is defined, the actual
+ // value of the node needs to be fetched on the server.
+ if (this._form.nodeValue === null && this._form.shortValue) {
+ return this._getNodeValue();
+ }
+
+ let str = this._form.nodeValue || "";
+ return promise.resolve(new SimpleStringFront(str));
+ }, {
+ impl: "_getNodeValue"
+ }),
+
+ // Accessors for custom form properties.
+
+ getFormProperty: function (name) {
+ return this._form.props ? this._form.props[name] : null;
+ },
+
+ hasFormProperty: function (name) {
+ return this._form.props ? (name in this._form.props) : null;
+ },
+
+ get formProperties() {
+ return this._form.props;
+ },
+
+ /**
+ * Return a new AttributeModificationList for this node.
+ */
+ startModifyingAttributes: function () {
+ return AttributeModificationList(this);
+ },
+
+ _cacheAttributes: function () {
+ if (typeof this._attrMap != "undefined") {
+ return;
+ }
+ this._attrMap = {};
+ for (let attr of this.attributes) {
+ this._attrMap[attr.name] = attr;
+ }
+ },
+
+ _getAttribute: function (name) {
+ this._cacheAttributes();
+ return this._attrMap[name] || undefined;
+ },
+
+ /**
+ * Set this node's parent. Note that the children saved in
+ * this tree are unordered and incomplete, so shouldn't be used
+ * instead of a `children` request.
+ */
+ reparent: function (parent) {
+ if (this._parent === parent) {
+ return;
+ }
+
+ if (this._parent && this._parent._child === this) {
+ this._parent._child = this._next;
+ }
+ if (this._prev) {
+ this._prev._next = this._next;
+ }
+ if (this._next) {
+ this._next._prev = this._prev;
+ }
+ this._next = null;
+ this._prev = null;
+ this._parent = parent;
+ if (!parent) {
+ // Subtree is disconnected, we're done
+ return;
+ }
+ this._next = parent._child;
+ if (this._next) {
+ this._next._prev = this;
+ }
+ parent._child = this;
+ },
+
+ /**
+ * Return all the known children of this node.
+ */
+ treeChildren: function () {
+ let ret = [];
+ for (let child = this._child; child != null; child = child._next) {
+ ret.push(child);
+ }
+ return ret;
+ },
+
+ /**
+ * Do we use a local target?
+ * Useful to know if a rawNode is available or not.
+ *
+ * This will, one day, be removed. External code should
+ * not need to know if the target is remote or not.
+ */
+ isLocalToBeDeprecated: function () {
+ return !!this.conn._transport._serverConnection;
+ },
+
+ /**
+ * Get an nsIDOMNode for the given node front. This only works locally,
+ * and is only intended as a stopgap during the transition to the remote
+ * protocol. If you depend on this you're likely to break soon.
+ */
+ rawNode: function (rawNode) {
+ if (!this.isLocalToBeDeprecated()) {
+ console.warn("Tried to use rawNode on a remote connection.");
+ return null;
+ }
+ const { DebuggerServer } = require("devtools/server/main");
+ let actor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
+ if (!actor) {
+ // Can happen if we try to get the raw node for an already-expired
+ // actor.
+ return null;
+ }
+ return actor.rawNode;
+ }
+});
+
+exports.NodeFront = NodeFront;
+
+/**
+ * Client side of a node list as returned by querySelectorAll()
+ */
+const NodeListFront = FrontClassWithSpec(nodeListSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ marshallPool: function () {
+ return this.parent();
+ },
+
+ // Update the object given a form representation off the wire.
+ form: function (json) {
+ this.length = json.length;
+ },
+
+ item: custom(function (index) {
+ return this._item(index).then(response => {
+ return response.node;
+ });
+ }, {
+ impl: "_item"
+ }),
+
+ items: custom(function (start, end) {
+ return this._items(start, end).then(response => {
+ return response.nodes;
+ });
+ }, {
+ impl: "_items"
+ })
+});
+
+exports.NodeListFront = NodeListFront;
+
+/**
+ * Client side of the DOM walker.
+ */
+const WalkerFront = FrontClassWithSpec(walkerSpec, {
+ // Set to true if cleanup should be requested after every mutation list.
+ autoCleanup: true,
+
+ /**
+ * This is kept for backward-compatibility reasons with older remote target.
+ * Targets previous to bug 916443
+ */
+ pick: custom(function () {
+ return this._pick().then(response => {
+ return response.node;
+ });
+ }, {impl: "_pick"}),
+
+ initialize: function (client, form) {
+ this._createRootNodePromise();
+ Front.prototype.initialize.call(this, client, form);
+ this._orphaned = new Set();
+ this._retainedOrphans = new Set();
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ // Update the object given a form representation off the wire.
+ form: function (json) {
+ this.actorID = json.actor;
+ this.rootNode = types.getType("domnode").read(json.root, this);
+ this._rootNodeDeferred.resolve(this.rootNode);
+ // FF42+ the actor starts exposing traits
+ this.traits = json.traits || {};
+ },
+
+ /**
+ * Clients can use walker.rootNode to get the current root node of the
+ * walker, but during a reload the root node might be null. This
+ * method returns a promise that will resolve to the root node when it is
+ * set.
+ */
+ getRootNode: function () {
+ return this._rootNodeDeferred.promise;
+ },
+
+ /**
+ * Create the root node promise, triggering the "new-root" notification
+ * on resolution.
+ */
+ _createRootNodePromise: function () {
+ this._rootNodeDeferred = defer();
+ this._rootNodeDeferred.promise.then(() => {
+ events.emit(this, "new-root");
+ });
+ },
+
+ /**
+ * When reading an actor form off the wire, we want to hook it up to its
+ * parent front. The protocol guarantees that the parent will be seen
+ * by the client in either a previous or the current request.
+ * So if we've already seen this parent return it, otherwise create
+ * a bare-bones stand-in node. The stand-in node will be updated
+ * with a real form by the end of the deserialization.
+ */
+ ensureParentFront: function (id) {
+ let front = this.get(id);
+ if (front) {
+ return front;
+ }
+
+ return types.getType("domnode").read({ actor: id }, this, "standin");
+ },
+
+ /**
+ * See the documentation for WalkerActor.prototype.retainNode for
+ * information on retained nodes.
+ *
+ * From the client's perspective, `retainNode` can fail if the node in
+ * question is removed from the ownership tree before the `retainNode`
+ * request reaches the server. This can only happen if the client has
+ * asked the server to release nodes but hasn't gotten a response
+ * yet: Either a `releaseNode` request or a `getMutations` with `cleanup`
+ * set is outstanding.
+ *
+ * If either of those requests is outstanding AND releases the retained
+ * node, this request will fail with noSuchActor, but the ownership tree
+ * will stay in a consistent state.
+ *
+ * Because the protocol guarantees that requests will be processed and
+ * responses received in the order they were sent, we get the right
+ * semantics by setting our local retained flag on the node only AFTER
+ * a SUCCESSFUL retainNode call.
+ */
+ retainNode: custom(function (node) {
+ return this._retainNode(node).then(() => {
+ node.retained = true;
+ });
+ }, {
+ impl: "_retainNode",
+ }),
+
+ unretainNode: custom(function (node) {
+ return this._unretainNode(node).then(() => {
+ node.retained = false;
+ if (this._retainedOrphans.has(node)) {
+ this._retainedOrphans.delete(node);
+ this._releaseFront(node);
+ }
+ });
+ }, {
+ impl: "_unretainNode"
+ }),
+
+ releaseNode: custom(function (node, options = {}) {
+ // NodeFront.destroy will destroy children in the ownership tree too,
+ // mimicking what the server will do here.
+ let actorID = node.actorID;
+ this._releaseFront(node, !!options.force);
+ return this._releaseNode({ actorID: actorID });
+ }, {
+ impl: "_releaseNode"
+ }),
+
+ findInspectingNode: custom(function () {
+ return this._findInspectingNode().then(response => {
+ return response.node;
+ });
+ }, {
+ impl: "_findInspectingNode"
+ }),
+
+ querySelector: custom(function (queryNode, selector) {
+ return this._querySelector(queryNode, selector).then(response => {
+ return response.node;
+ });
+ }, {
+ impl: "_querySelector"
+ }),
+
+ getNodeActorFromObjectActor: custom(function (objectActorID) {
+ return this._getNodeActorFromObjectActor(objectActorID).then(response => {
+ return response ? response.node : null;
+ });
+ }, {
+ impl: "_getNodeActorFromObjectActor"
+ }),
+
+ getStyleSheetOwnerNode: custom(function (styleSheetActorID) {
+ return this._getStyleSheetOwnerNode(styleSheetActorID).then(response => {
+ return response ? response.node : null;
+ });
+ }, {
+ impl: "_getStyleSheetOwnerNode"
+ }),
+
+ getNodeFromActor: custom(function (actorID, path) {
+ return this._getNodeFromActor(actorID, path).then(response => {
+ return response ? response.node : null;
+ });
+ }, {
+ impl: "_getNodeFromActor"
+ }),
+
+ /*
+ * Incrementally search the document for a given string.
+ * For modern servers, results will be searched with using the WalkerActor
+ * `search` function (includes tag names, attributes, and text contents).
+ * Only 1 result is sent back, and calling the method again with the same
+ * query will send the next result. When there are no more results to be sent
+ * back, null is sent.
+ * @param {String} query
+ * @param {Object} options
+ * - "reverse": search backwards
+ * - "selectorOnly": treat input as a selector string (don't search text
+ * tags, attributes, etc)
+ */
+ search: custom(Task.async(function* (query, options = { }) {
+ let nodeList;
+ let searchType;
+ let searchData = this.searchData = this.searchData || { };
+ let selectorOnly = !!options.selectorOnly;
+
+ // Backwards compat. Use selector only search if the new
+ // search functionality isn't implemented, or if the caller (tests)
+ // want it.
+ if (selectorOnly || !this.traits.textSearch) {
+ searchType = "selector";
+ if (this.traits.multiFrameQuerySelectorAll) {
+ nodeList = yield this.multiFrameQuerySelectorAll(query);
+ } else {
+ nodeList = yield this.querySelectorAll(this.rootNode, query);
+ }
+ } else {
+ searchType = "search";
+ let result = yield this._search(query, options);
+ nodeList = result.list;
+ }
+
+ // If this is a new search, start at the beginning.
+ if (searchData.query !== query ||
+ searchData.selectorOnly !== selectorOnly) {
+ searchData.selectorOnly = selectorOnly;
+ searchData.query = query;
+ searchData.index = -1;
+ }
+
+ if (!nodeList.length) {
+ return null;
+ }
+
+ // Move search result cursor and cycle if necessary.
+ searchData.index = options.reverse ? searchData.index - 1 :
+ searchData.index + 1;
+ if (searchData.index >= nodeList.length) {
+ searchData.index = 0;
+ }
+ if (searchData.index < 0) {
+ searchData.index = nodeList.length - 1;
+ }
+
+ // Send back the single node, along with any relevant search data
+ let node = yield nodeList.item(searchData.index);
+ return {
+ type: searchType,
+ node: node,
+ resultsLength: nodeList.length,
+ resultsIndex: searchData.index,
+ };
+ }), {
+ impl: "_search"
+ }),
+
+ _releaseFront: function (node, force) {
+ if (node.retained && !force) {
+ node.reparent(null);
+ this._retainedOrphans.add(node);
+ return;
+ }
+
+ if (node.retained) {
+ // Forcing a removal.
+ this._retainedOrphans.delete(node);
+ }
+
+ // Release any children
+ for (let child of node.treeChildren()) {
+ this._releaseFront(child, force);
+ }
+
+ // All children will have been removed from the node by this point.
+ node.reparent(null);
+ node.destroy();
+ },
+
+ /**
+ * Get any unprocessed mutation records and process them.
+ */
+ getMutations: custom(function (options = {}) {
+ return this._getMutations(options).then(mutations => {
+ let emitMutations = [];
+ for (let change of mutations) {
+ // The target is only an actorID, get the associated front.
+ let targetID;
+ let targetFront;
+
+ if (change.type === "newRoot") {
+ // We may receive a new root without receiving any documentUnload
+ // beforehand. Like when opening tools in middle of a document load.
+ if (this.rootNode) {
+ this._createRootNodePromise();
+ }
+ this.rootNode = types.getType("domnode").read(change.target, this);
+ this._rootNodeDeferred.resolve(this.rootNode);
+ targetID = this.rootNode.actorID;
+ targetFront = this.rootNode;
+ } else {
+ targetID = change.target;
+ targetFront = this.get(targetID);
+ }
+
+ if (!targetFront) {
+ console.trace("Got a mutation for an unexpected actor: " + targetID +
+ ", please file a bug on bugzilla.mozilla.org!");
+ continue;
+ }
+
+ let emittedMutation = object.merge(change, { target: targetFront });
+
+ if (change.type === "childList" ||
+ change.type === "nativeAnonymousChildList") {
+ // Update the ownership tree according to the mutation record.
+ let addedFronts = [];
+ let removedFronts = [];
+ for (let removed of change.removed) {
+ let removedFront = this.get(removed);
+ if (!removedFront) {
+ console.error("Got a removal of an actor we didn't know about: " +
+ removed);
+ continue;
+ }
+ // Remove from the ownership tree
+ removedFront.reparent(null);
+
+ // This node is orphaned unless we get it in the 'added' list
+ // eventually.
+ this._orphaned.add(removedFront);
+ removedFronts.push(removedFront);
+ }
+ for (let added of change.added) {
+ let addedFront = this.get(added);
+ if (!addedFront) {
+ console.error("Got an addition of an actor we didn't know " +
+ "about: " + added);
+ continue;
+ }
+ addedFront.reparent(targetFront);
+
+ // The actor is reconnected to the ownership tree, unorphan
+ // it.
+ this._orphaned.delete(addedFront);
+ addedFronts.push(addedFront);
+ }
+
+ // Before passing to users, replace the added and removed actor
+ // ids with front in the mutation record.
+ emittedMutation.added = addedFronts;
+ emittedMutation.removed = removedFronts;
+
+ // If this is coming from a DOM mutation, the actor's numChildren
+ // was passed in. Otherwise, it is simulated from a frame load or
+ // unload, so don't change the front's form.
+ if ("numChildren" in change) {
+ targetFront._form.numChildren = change.numChildren;
+ }
+ } else if (change.type === "frameLoad") {
+ // Nothing we need to do here, except verify that we don't have any
+ // document children, because we should have gotten a documentUnload
+ // first.
+ for (let child of targetFront.treeChildren()) {
+ if (child.nodeType === nodeConstants.DOCUMENT_NODE) {
+ console.trace("Got an unexpected frameLoad in the inspector, " +
+ "please file a bug on bugzilla.mozilla.org!");
+ }
+ }
+ } else if (change.type === "documentUnload") {
+ if (targetFront === this.rootNode) {
+ this._createRootNodePromise();
+ }
+
+ // We try to give fronts instead of actorIDs, but these fronts need
+ // to be destroyed now.
+ emittedMutation.target = targetFront.actorID;
+ emittedMutation.targetParent = targetFront.parentNode();
+
+ // Release the document node and all of its children, even retained.
+ this._releaseFront(targetFront, true);
+ } else if (change.type === "unretained") {
+ // Retained orphans were force-released without the intervention of
+ // client (probably a navigated frame).
+ for (let released of change.nodes) {
+ let releasedFront = this.get(released);
+ this._retainedOrphans.delete(released);
+ this._releaseFront(releasedFront, true);
+ }
+ } else {
+ targetFront.updateMutation(change);
+ }
+
+ // Update the inlineTextChild property of the target for a selected list of
+ // mutation types.
+ if (change.type === "inlineTextChild" ||
+ change.type === "childList" ||
+ change.type === "nativeAnonymousChildList") {
+ if (change.inlineTextChild) {
+ targetFront.inlineTextChild =
+ types.getType("domnode").read(change.inlineTextChild, this);
+ } else {
+ targetFront.inlineTextChild = undefined;
+ }
+ }
+
+ emitMutations.push(emittedMutation);
+ }
+
+ if (options.cleanup) {
+ for (let node of this._orphaned) {
+ // This will move retained nodes to this._retainedOrphans.
+ this._releaseFront(node);
+ }
+ this._orphaned = new Set();
+ }
+
+ events.emit(this, "mutations", emitMutations);
+ });
+ }, {
+ impl: "_getMutations"
+ }),
+
+ /**
+ * Handle the `new-mutations` notification by fetching the
+ * available mutation records.
+ */
+ onMutations: preEvent("new-mutations", function () {
+ // Fetch and process the mutations.
+ this.getMutations({cleanup: this.autoCleanup}).catch(() => {});
+ }),
+
+ isLocal: function () {
+ return !!this.conn._transport._serverConnection;
+ },
+
+ // XXX hack during transition to remote inspector: get a proper NodeFront
+ // for a given local node. Only works locally.
+ frontForRawNode: function (rawNode) {
+ if (!this.isLocal()) {
+ console.warn("Tried to use frontForRawNode on a remote connection.");
+ return null;
+ }
+ const { DebuggerServer } = require("devtools/server/main");
+ let walkerActor = DebuggerServer._searchAllConnectionsForActor(this.actorID);
+ if (!walkerActor) {
+ throw Error("Could not find client side for actor " + this.actorID);
+ }
+ let nodeActor = walkerActor._ref(rawNode);
+
+ // Pass the node through a read/write pair to create the client side actor.
+ let nodeType = types.getType("domnode");
+ let returnNode = nodeType.read(
+ nodeType.write(nodeActor, walkerActor), this);
+ let top = returnNode;
+ let extras = walkerActor.parents(nodeActor, {sameTypeRootTreeItem: true});
+ for (let extraActor of extras) {
+ top = nodeType.read(nodeType.write(extraActor, walkerActor), this);
+ }
+
+ if (top !== this.rootNode) {
+ // Imported an already-orphaned node.
+ this._orphaned.add(top);
+ walkerActor._orphaned
+ .add(DebuggerServer._searchAllConnectionsForActor(top.actorID));
+ }
+ return returnNode;
+ },
+
+ removeNode: custom(Task.async(function* (node) {
+ let previousSibling = yield this.previousSibling(node);
+ let nextSibling = yield this._removeNode(node);
+ return {
+ previousSibling: previousSibling,
+ nextSibling: nextSibling,
+ };
+ }), {
+ impl: "_removeNode"
+ }),
+});
+
+exports.WalkerFront = WalkerFront;
+
+/**
+ * Client side of the inspector actor, which is used to create
+ * inspector-related actors, including the walker.
+ */
+var InspectorFront = FrontClassWithSpec(inspectorSpec, {
+ initialize: function (client, tabForm) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = tabForm.inspectorActor;
+
+ // XXX: This is the first actor type in its hierarchy to use the protocol
+ // library, so we're going to self-own on the client side for now.
+ this.manage(this);
+ },
+
+ destroy: function () {
+ delete this.walker;
+ Front.prototype.destroy.call(this);
+ },
+
+ getWalker: custom(function (options = {}) {
+ return this._getWalker(options).then(walker => {
+ this.walker = walker;
+ return walker;
+ });
+ }, {
+ impl: "_getWalker"
+ }),
+
+ getPageStyle: custom(function () {
+ return this._getPageStyle().then(pageStyle => {
+ // We need a walker to understand node references from the
+ // node style.
+ if (this.walker) {
+ return pageStyle;
+ }
+ return this.getWalker().then(() => {
+ return pageStyle;
+ });
+ });
+ }, {
+ impl: "_getPageStyle"
+ }),
+
+ pickColorFromPage: custom(Task.async(function* (toolbox, options) {
+ if (toolbox) {
+ // If the eyedropper was already started using the gcli command, hide it so we don't
+ // end up with 2 instances of the eyedropper on the page.
+ let {target} = toolbox;
+ let requisition = yield CommandUtils.createRequisition(target, {
+ environment: CommandUtils.createEnvironment({target})
+ });
+ yield requisition.updateExec("eyedropper --hide");
+ }
+
+ yield this._pickColorFromPage(options);
+ }), {
+ impl: "_pickColorFromPage"
+ })
+});
+
+exports.InspectorFront = InspectorFront;
diff --git a/devtools/shared/fronts/layout.js b/devtools/shared/fronts/layout.js
new file mode 100644
index 000000000..5a1a6185d
--- /dev/null
+++ b/devtools/shared/fronts/layout.js
@@ -0,0 +1,30 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { FrontClassWithSpec } = require("devtools/shared/protocol");
+const { gridSpec, layoutSpec } = require("devtools/shared/specs/layout");
+
+const GridFront = FrontClassWithSpec(gridSpec, {
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this._form = form;
+ },
+
+ /**
+ * Getter for the grid fragments data.
+ */
+ get gridFragments() {
+ return this._form.gridFragments;
+ }
+});
+
+const LayoutFront = FrontClassWithSpec(layoutSpec, {});
+
+exports.GridFront = GridFront;
+exports.LayoutFront = LayoutFront;
diff --git a/devtools/shared/fronts/memory.js b/devtools/shared/fronts/memory.js
new file mode 100644
index 000000000..d7a6a3108
--- /dev/null
+++ b/devtools/shared/fronts/memory.js
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { memorySpec } = require("devtools/shared/specs/memory");
+const { Task } = require("devtools/shared/task");
+const protocol = require("devtools/shared/protocol");
+
+loader.lazyRequireGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm", true);
+loader.lazyRequireGetter(this, "HeapSnapshotFileUtils",
+ "devtools/shared/heapsnapshot/HeapSnapshotFileUtils");
+
+const MemoryFront = protocol.FrontClassWithSpec(memorySpec, {
+ initialize: function (client, form, rootForm = null) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ this._client = client;
+ this.actorID = form.memoryActor;
+ this.heapSnapshotFileActorID = rootForm
+ ? rootForm.heapSnapshotFileActor
+ : null;
+ this.manage(this);
+ },
+
+ /**
+ * Save a heap snapshot, transfer it from the server to the client if the
+ * server and client do not share a file system, and return the local file
+ * path to the heap snapshot.
+ *
+ * Note that this is safe to call for actors inside sandoxed child processes,
+ * as we jump through the correct IPDL hoops.
+ *
+ * @params Boolean options.forceCopy
+ * Always force a bulk data copy of the saved heap snapshot, even when
+ * the server and client share a file system.
+ *
+ * @params {Object|undefined} options.boundaries
+ * The boundaries for the heap snapshot. See
+ * ThreadSafeChromeUtils.webidl for more details.
+ *
+ * @returns Promise<String>
+ */
+ saveHeapSnapshot: protocol.custom(Task.async(function* (options = {}) {
+ const snapshotId = yield this._saveHeapSnapshotImpl(options.boundaries);
+
+ if (!options.forceCopy &&
+ (yield HeapSnapshotFileUtils.haveHeapSnapshotTempFile(snapshotId))) {
+ return HeapSnapshotFileUtils.getHeapSnapshotTempFilePath(snapshotId);
+ }
+
+ return yield this.transferHeapSnapshot(snapshotId);
+ }), {
+ impl: "_saveHeapSnapshotImpl"
+ }),
+
+ /**
+ * Given that we have taken a heap snapshot with the given id, transfer the
+ * heap snapshot file to the client. The path to the client's local file is
+ * returned.
+ *
+ * @param {String} snapshotId
+ *
+ * @returns Promise<String>
+ */
+ transferHeapSnapshot: protocol.custom(function (snapshotId) {
+ if (!this.heapSnapshotFileActorID) {
+ throw new Error("MemoryFront initialized without a rootForm");
+ }
+
+ const request = this._client.request({
+ to: this.heapSnapshotFileActorID,
+ type: "transferHeapSnapshot",
+ snapshotId
+ });
+
+ return new Promise((resolve, reject) => {
+ const outFilePath =
+ HeapSnapshotFileUtils.getNewUniqueHeapSnapshotTempFilePath();
+ const outFile = new FileUtils.File(outFilePath);
+
+ const outFileStream = FileUtils.openSafeFileOutputStream(outFile);
+ request.on("bulk-reply", Task.async(function* ({ copyTo }) {
+ yield copyTo(outFileStream);
+ FileUtils.closeSafeFileOutputStream(outFileStream);
+ resolve(outFilePath);
+ }));
+ });
+ })
+});
+
+exports.MemoryFront = MemoryFront;
diff --git a/devtools/shared/fronts/moz.build b/devtools/shared/fronts/moz.build
new file mode 100644
index 000000000..8a38d6b5d
--- /dev/null
+++ b/devtools/shared/fronts/moz.build
@@ -0,0 +1,41 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'actor-registry.js',
+ 'addons.js',
+ 'animation.js',
+ 'call-watcher.js',
+ 'canvas.js',
+ 'css-properties.js',
+ 'csscoverage.js',
+ 'device.js',
+ 'director-manager.js',
+ 'director-registry.js',
+ 'emulation.js',
+ 'eventlooplag.js',
+ 'framerate.js',
+ 'gcli.js',
+ 'highlighters.js',
+ 'inspector.js',
+ 'layout.js',
+ 'memory.js',
+ 'performance-entries.js',
+ 'performance-recording.js',
+ 'performance.js',
+ 'preference.js',
+ 'profiler.js',
+ 'promises.js',
+ 'reflow.js',
+ 'settings.js',
+ 'storage.js',
+ 'string.js',
+ 'styles.js',
+ 'stylesheets.js',
+ 'timeline.js',
+ 'webaudio.js',
+ 'webgl.js'
+)
diff --git a/devtools/shared/fronts/performance-entries.js b/devtools/shared/fronts/performance-entries.js
new file mode 100644
index 000000000..5f3236530
--- /dev/null
+++ b/devtools/shared/fronts/performance-entries.js
@@ -0,0 +1,17 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const performanceSpec = require("devtools/shared/specs/performance-entries");
+
+var PerformanceEntriesFront = FrontClassWithSpec(performanceSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = form.performanceEntriesActor;
+ this.manage(this);
+ },
+});
+
+exports.PerformanceEntriesFront = PerformanceEntriesFront;
diff --git a/devtools/shared/fronts/performance-recording.js b/devtools/shared/fronts/performance-recording.js
new file mode 100644
index 000000000..09c61d4ee
--- /dev/null
+++ b/devtools/shared/fronts/performance-recording.js
@@ -0,0 +1,152 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const { performanceRecordingSpec } = require("devtools/shared/specs/performance-recording");
+
+loader.lazyRequireGetter(this, "PerformanceIO",
+ "devtools/client/performance/modules/io");
+loader.lazyRequireGetter(this, "PerformanceRecordingCommon",
+ "devtools/shared/performance/recording-common", true);
+loader.lazyRequireGetter(this, "RecordingUtils",
+ "devtools/shared/performance/recording-utils");
+loader.lazyRequireGetter(this, "merge", "sdk/util/object", true);
+
+/**
+ * This can be used on older Profiler implementations, but the methods cannot
+ * be changed -- you must introduce a new method, and detect the server.
+ */
+const PerformanceRecordingFront = FrontClassWithSpec(performanceRecordingSpec, merge({
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ this._configuration = form.configuration;
+ this._startingBufferStatus = form.startingBufferStatus;
+ this._console = form.console;
+ this._label = form.label;
+ this._startTime = form.startTime;
+ this._localStartTime = form.localStartTime;
+ this._recording = form.recording;
+ this._completed = form.completed;
+ this._duration = form.duration;
+
+ if (form.finalizedData) {
+ this._profile = form.profile;
+ this._systemHost = form.systemHost;
+ this._systemClient = form.systemClient;
+ }
+
+ // Sort again on the client side if we're using realtime markers and the recording
+ // just finished. This is because GC/Compositing markers can come into the array out
+ // of order with the other markers, leading to strange collapsing in waterfall view.
+ if (this._completed && !this._markersSorted) {
+ this._markers = this._markers.sort((a, b) => (a.start > b.start));
+ this._markersSorted = true;
+ }
+ },
+
+ initialize: function (client, form, config) {
+ Front.prototype.initialize.call(this, client, form);
+ this._markers = [];
+ this._frames = [];
+ this._memory = [];
+ this._ticks = [];
+ this._allocations = { sites: [], timestamps: [], frames: [], sizes: [] };
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ /**
+ * Saves the current recording to a file.
+ *
+ * @param nsILocalFile file
+ * The file to stream the data into.
+ */
+ exportRecording: function (file) {
+ let recordingData = this.getAllData();
+ return PerformanceIO.saveRecordingToFile(recordingData, file);
+ },
+
+ /**
+ * Fired whenever the PerformanceFront emits markers, memory or ticks.
+ */
+ _addTimelineData: function (eventName, data) {
+ let config = this.getConfiguration();
+
+ switch (eventName) {
+ // Accumulate timeline markers into an array. Furthermore, the timestamps
+ // do not have a zero epoch, so offset all of them by the start time.
+ case "markers": {
+ if (!config.withMarkers) {
+ break;
+ }
+ let { markers } = data;
+ RecordingUtils.offsetMarkerTimes(markers, this._startTime);
+ RecordingUtils.pushAll(this._markers, markers);
+ break;
+ }
+ // Accumulate stack frames into an array.
+ case "frames": {
+ if (!config.withMarkers) {
+ break;
+ }
+ let { frames } = data;
+ RecordingUtils.pushAll(this._frames, frames);
+ break;
+ }
+ // Accumulate memory measurements into an array. Furthermore, the timestamp
+ // does not have a zero epoch, so offset it by the actor's start time.
+ case "memory": {
+ if (!config.withMemory) {
+ break;
+ }
+ let { delta, measurement } = data;
+ this._memory.push({
+ delta: delta - this._startTime,
+ value: measurement.total / 1024 / 1024
+ });
+ break;
+ }
+ // Save the accumulated refresh driver ticks.
+ case "ticks": {
+ if (!config.withTicks) {
+ break;
+ }
+ let { timestamps } = data;
+ this._ticks = timestamps;
+ break;
+ }
+ // Accumulate allocation sites into an array.
+ case "allocations": {
+ if (!config.withAllocations) {
+ break;
+ }
+ let {
+ allocations: sites,
+ allocationsTimestamps: timestamps,
+ allocationSizes: sizes,
+ frames,
+ } = data;
+
+ RecordingUtils.offsetAndScaleTimestamps(timestamps, this._startTime);
+ RecordingUtils.pushAll(this._allocations.sites, sites);
+ RecordingUtils.pushAll(this._allocations.timestamps, timestamps);
+ RecordingUtils.pushAll(this._allocations.frames, frames);
+ RecordingUtils.pushAll(this._allocations.sizes, sizes);
+ break;
+ }
+ }
+ },
+
+ toString: () => "[object PerformanceRecordingFront]"
+}, PerformanceRecordingCommon));
+
+exports.PerformanceRecordingFront = PerformanceRecordingFront;
diff --git a/devtools/shared/fronts/performance.js b/devtools/shared/fronts/performance.js
new file mode 100644
index 000000000..da5a9ffb0
--- /dev/null
+++ b/devtools/shared/fronts/performance.js
@@ -0,0 +1,148 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cu } = require("chrome");
+const { Front, FrontClassWithSpec, custom, preEvent } = require("devtools/shared/protocol");
+const { PerformanceRecordingFront } = require("devtools/shared/fronts/performance-recording");
+const { performanceSpec } = require("devtools/shared/specs/performance");
+const { Task } = require("devtools/shared/task");
+
+loader.lazyRequireGetter(this, "PerformanceIO",
+ "devtools/client/performance/modules/io");
+loader.lazyRequireGetter(this, "LegacyPerformanceFront",
+ "devtools/client/performance/legacy/front", true);
+loader.lazyRequireGetter(this, "getSystemInfo",
+ "devtools/shared/system", true);
+
+const PerformanceFront = FrontClassWithSpec(performanceSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+ this.actorID = form.performanceActor;
+ this.manage(this);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ /**
+ * Conenct to the server, and handle once-off tasks like storing traits
+ * or system info.
+ */
+ connect: custom(Task.async(function* () {
+ let systemClient = yield getSystemInfo();
+ let { traits } = yield this._connect({ systemClient });
+ this._traits = traits;
+
+ return this._traits;
+ }), {
+ impl: "_connect"
+ }),
+
+ get traits() {
+ if (!this._traits) {
+ Cu.reportError("Cannot access traits of PerformanceFront before " +
+ "calling `connect()`.");
+ }
+ return this._traits;
+ },
+
+ /**
+ * Pass in a PerformanceRecording and get a normalized value from 0 to 1 of how much
+ * of this recording's lifetime remains without being overwritten.
+ *
+ * @param {PerformanceRecording} recording
+ * @return {number?}
+ */
+ getBufferUsageForRecording: function (recording) {
+ if (!recording.isRecording()) {
+ return void 0;
+ }
+ let {
+ position: currentPosition,
+ totalSize,
+ generation: currentGeneration
+ } = this._currentBufferStatus;
+ let {
+ position: origPosition,
+ generation: origGeneration
+ } = recording.getStartingBufferStatus();
+
+ let normalizedCurrent = (totalSize * (currentGeneration - origGeneration)) +
+ currentPosition;
+ let percent = (normalizedCurrent - origPosition) / totalSize;
+
+ // Clamp between 0 and 1; can get negative percentage values when a new
+ // recording starts and the currentBufferStatus has not yet been updated. Rather
+ // than fetching another status update, just clamp to 0, and this will be updated
+ // on the next profiler-status event.
+ if (percent < 0) {
+ return 0;
+ } else if (percent > 1) {
+ return 1;
+ }
+
+ return percent;
+ },
+
+ /**
+ * Loads a recording from a file.
+ *
+ * @param {nsILocalFile} file
+ * The file to import the data from.
+ * @return {Promise<PerformanceRecordingFront>}
+ */
+ importRecording: function (file) {
+ return PerformanceIO.loadRecordingFromFile(file).then(recordingData => {
+ let model = new PerformanceRecordingFront();
+ model._imported = true;
+ model._label = recordingData.label || "";
+ model._duration = recordingData.duration;
+ model._markers = recordingData.markers;
+ model._frames = recordingData.frames;
+ model._memory = recordingData.memory;
+ model._ticks = recordingData.ticks;
+ model._allocations = recordingData.allocations;
+ model._profile = recordingData.profile;
+ model._configuration = recordingData.configuration || {};
+ model._systemHost = recordingData.systemHost;
+ model._systemClient = recordingData.systemClient;
+ return model;
+ });
+ },
+
+ /**
+ * Store profiler status when the position has been update so we can
+ * calculate recording's buffer percentage usage after emitting the event.
+ */
+ _onProfilerStatus: preEvent("profiler-status", function (data) {
+ this._currentBufferStatus = data;
+ }),
+
+ /**
+ * For all PerformanceRecordings that are recording, and needing realtime markers,
+ * apply the timeline data to the front PerformanceRecording (so we only have one event
+ * for each timeline data chunk as they could be shared amongst several recordings).
+ */
+ _onTimelineEvent: preEvent("timeline-data", function (type, data, recordings) {
+ for (let recording of recordings) {
+ recording._addTimelineData(type, data);
+ }
+ }),
+});
+
+exports.PerformanceFront = PerformanceFront;
+
+exports.createPerformanceFront = function createPerformanceFront(target) {
+ // If we force legacy mode, or the server does not have a performance actor (< Fx42),
+ // use our LegacyPerformanceFront which will handle
+ // the communication over RDP to other underlying actors.
+ if (target.TEST_PERFORMANCE_LEGACY_FRONT || !target.form.performanceActor) {
+ return new LegacyPerformanceFront(target);
+ }
+ // If our server has a PerformanceActor implementation, set this
+ // up like a normal front.
+ return new PerformanceFront(target.client, target.form);
+};
diff --git a/devtools/shared/fronts/preference.js b/devtools/shared/fronts/preference.js
new file mode 100644
index 000000000..22b3f912f
--- /dev/null
+++ b/devtools/shared/fronts/preference.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {preferenceSpec} = require("devtools/shared/specs/preference");
+const protocol = require("devtools/shared/protocol");
+
+const PreferenceFront = protocol.FrontClassWithSpec(preferenceSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client);
+ this.actorID = form.preferenceActor;
+ this.manage(this);
+ },
+});
+
+const _knownPreferenceFronts = new WeakMap();
+
+exports.getPreferenceFront = function (client, form) {
+ if (!form.preferenceActor) {
+ return null;
+ }
+
+ if (_knownPreferenceFronts.has(client)) {
+ return _knownPreferenceFronts.get(client);
+ }
+
+ let front = new PreferenceFront(client, form);
+ _knownPreferenceFronts.set(client, front);
+ return front;
+};
diff --git a/devtools/shared/fronts/profiler.js b/devtools/shared/fronts/profiler.js
new file mode 100644
index 000000000..f564513e3
--- /dev/null
+++ b/devtools/shared/fronts/profiler.js
@@ -0,0 +1,80 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cu } = require("chrome");
+const {
+ Front,
+ FrontClassWithSpec,
+ custom
+} = require("devtools/shared/protocol");
+const { profilerSpec } = require("devtools/shared/specs/profiler");
+
+loader.lazyRequireGetter(this, "events", "sdk/event/core");
+loader.lazyRequireGetter(this, "extend", "sdk/util/object", true);
+
+/**
+ * This can be used on older Profiler implementations, but the methods cannot
+ * be changed -- you must introduce a new method, and detect the server.
+ */
+exports.ProfilerFront = FrontClassWithSpec(profilerSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+ this.actorID = form.profilerActor;
+ this.manage(this);
+
+ this._onProfilerEvent = this._onProfilerEvent.bind(this);
+ events.on(this, "*", this._onProfilerEvent);
+ },
+
+ destroy: function () {
+ events.off(this, "*", this._onProfilerEvent);
+ Front.prototype.destroy.call(this);
+ },
+
+ /**
+ * If using the protocol.js Fronts, then make stringify default,
+ * since the read/write mechanisms will expose it as an object anyway, but
+ * this lets other consumers who connect directly (xpcshell tests, Gecko Profiler) to
+ * have unchanged behaviour.
+ */
+ getProfile: custom(function (options) {
+ return this._getProfile(extend({ stringify: true }, options));
+ }, {
+ impl: "_getProfile"
+ }),
+
+ /**
+ * Also emit an old `eventNotification` for older consumers of the profiler.
+ */
+ _onProfilerEvent: function (eventName, data) {
+ // If this event already passed through once, don't repropagate
+ if (data.relayed) {
+ return;
+ }
+ data.relayed = true;
+
+ if (eventName === "eventNotification") {
+ // If this is `eventNotification`, this is coming from an older Gecko (<Fx42)
+ // that doesn't use protocol.js style events. Massage it to emit a protocol.js
+ // style event as well.
+ events.emit(this, data.topic, data);
+ } else {
+ // Otherwise if a modern protocol.js event, emit it also as `eventNotification`
+ // for compatibility reasons on the client (like for any add-ons/Gecko Profiler
+ // using this event) and log a deprecation message if there is a listener.
+ this.conn.emit("eventNotification", {
+ subject: data.subject,
+ topic: data.topic,
+ data: data.data,
+ details: data.details
+ });
+ if (this.conn._getListeners("eventNotification").length) {
+ Cu.reportError(`
+ ProfilerActor's "eventNotification" on the DebuggerClient has been deprecated.
+ Use the ProfilerFront found in "devtools/server/actors/profiler".`);
+ }
+ }
+ },
+});
diff --git a/devtools/shared/fronts/promises.js b/devtools/shared/fronts/promises.js
new file mode 100644
index 000000000..72896455d
--- /dev/null
+++ b/devtools/shared/fronts/promises.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Front,
+ FrontClassWithSpec,
+} = require("devtools/shared/protocol");
+const { promisesSpec } = require("devtools/shared/specs/promises");
+
+/**
+ * PromisesFront, the front for the PromisesActor.
+ */
+const PromisesFront = FrontClassWithSpec(promisesSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+ this.actorID = form.promisesActor;
+ this.manage(this);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ }
+});
+
+exports.PromisesFront = PromisesFront;
diff --git a/devtools/shared/fronts/reflow.js b/devtools/shared/fronts/reflow.js
new file mode 100644
index 000000000..69c513270
--- /dev/null
+++ b/devtools/shared/fronts/reflow.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {reflowSpec} = require("devtools/shared/specs/reflow");
+const protocol = require("devtools/shared/protocol");
+
+/**
+ * Usage example of the reflow front:
+ *
+ * let front = ReflowFront(toolbox.target.client, toolbox.target.form);
+ * front.on("reflows", this._onReflows);
+ * front.start();
+ * // now wait for events to come
+ */
+const ReflowFront = protocol.FrontClassWithSpec(reflowSpec, {
+ initialize: function (client, {reflowActor}) {
+ protocol.Front.prototype.initialize.call(this, client, {actor: reflowActor});
+ this.manage(this);
+ },
+
+ destroy: function () {
+ protocol.Front.prototype.destroy.call(this);
+ },
+});
+
+exports.ReflowFront = ReflowFront;
diff --git a/devtools/shared/fronts/settings.js b/devtools/shared/fronts/settings.js
new file mode 100644
index 000000000..158425364
--- /dev/null
+++ b/devtools/shared/fronts/settings.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {settingsSpec} = require("devtools/shared/specs/settings");
+const protocol = require("devtools/shared/protocol");
+
+const SettingsFront = protocol.FrontClassWithSpec(settingsSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client);
+ this.actorID = form.settingsActor;
+ this.manage(this);
+ },
+});
+
+const _knownSettingsFronts = new WeakMap();
+
+exports.getSettingsFront = function (client, form) {
+ if (!form.settingsActor) {
+ return null;
+ }
+ if (_knownSettingsFronts.has(client)) {
+ return _knownSettingsFronts.get(client);
+ }
+ let front = new SettingsFront(client, form);
+ _knownSettingsFronts.set(client, front);
+ return front;
+};
diff --git a/devtools/shared/fronts/storage.js b/devtools/shared/fronts/storage.js
new file mode 100644
index 000000000..304e57c6f
--- /dev/null
+++ b/devtools/shared/fronts/storage.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const specs = require("devtools/shared/specs/storage");
+
+for (let childSpec of Object.values(specs.childSpecs)) {
+ protocol.FrontClassWithSpec(childSpec, {
+ form(form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return null;
+ }
+
+ this.actorID = form.actor;
+ this.hosts = form.hosts;
+ return null;
+ }
+ });
+}
+
+const StorageFront = protocol.FrontClassWithSpec(specs.storageSpec, {
+ initialize(client, tabForm) {
+ protocol.Front.prototype.initialize.call(this, client);
+ this.actorID = tabForm.storageActor;
+ this.manage(this);
+ }
+});
+
+exports.StorageFront = StorageFront;
diff --git a/devtools/shared/fronts/string.js b/devtools/shared/fronts/string.js
new file mode 100644
index 000000000..12036afe4
--- /dev/null
+++ b/devtools/shared/fronts/string.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {DebuggerServer} = require("devtools/server/main");
+const promise = require("promise");
+const {longStringSpec, SimpleStringFront} = require("devtools/shared/specs/string");
+const protocol = require("devtools/shared/protocol");
+
+const LongStringFront = protocol.FrontClassWithSpec(longStringSpec, {
+ initialize: function (client) {
+ protocol.Front.prototype.initialize.call(this, client);
+ },
+
+ destroy: function () {
+ this.initial = null;
+ this.length = null;
+ this.strPromise = null;
+ protocol.Front.prototype.destroy.call(this);
+ },
+
+ form: function (form) {
+ this.actorID = form.actor;
+ this.initial = form.initial;
+ this.length = form.length;
+ },
+
+ string: function () {
+ if (!this.strPromise) {
+ let promiseRest = (thusFar) => {
+ if (thusFar.length === this.length) {
+ return promise.resolve(thusFar);
+ }
+ return this.substring(thusFar.length,
+ thusFar.length + DebuggerServer.LONG_STRING_READ_LENGTH)
+ .then((next) => promiseRest(thusFar + next));
+ };
+
+ this.strPromise = promiseRest(this.initial);
+ }
+ return this.strPromise;
+ }
+});
+
+exports.LongStringFront = LongStringFront;
+exports.SimpleStringFront = SimpleStringFront;
diff --git a/devtools/shared/fronts/styleeditor.js b/devtools/shared/fronts/styleeditor.js
new file mode 100644
index 000000000..5feb6df1b
--- /dev/null
+++ b/devtools/shared/fronts/styleeditor.js
@@ -0,0 +1,113 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { SimpleStringFront } = require("devtools/shared/fronts/string");
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const {
+ oldStyleSheetSpec,
+ styleEditorSpec
+} = require("devtools/shared/specs/styleeditor");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const events = require("sdk/event/core");
+
+/**
+ * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
+ */
+const OldStyleSheetFront = FrontClassWithSpec(oldStyleSheetSpec, {
+ initialize: function (conn, form, ctx, detail) {
+ Front.prototype.initialize.call(this, conn, form, ctx, detail);
+
+ this._onPropertyChange = this._onPropertyChange.bind(this);
+ events.on(this, "property-change", this._onPropertyChange);
+ },
+
+ destroy: function () {
+ events.off(this, "property-change", this._onPropertyChange);
+
+ Front.prototype.destroy.call(this);
+ },
+
+ _onPropertyChange: function (property, value) {
+ this._form[property] = value;
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ },
+
+ getText: function () {
+ let deferred = defer();
+
+ events.once(this, "source-load", (source) => {
+ let longStr = new SimpleStringFront(source);
+ deferred.resolve(longStr);
+ });
+ this.fetchSource();
+
+ return deferred.promise;
+ },
+
+ getOriginalSources: function () {
+ return promise.resolve([]);
+ },
+
+ get href() {
+ return this._form.href;
+ },
+ get nodeHref() {
+ return this._form.nodeHref;
+ },
+ get disabled() {
+ return !!this._form.disabled;
+ },
+ get title() {
+ return this._form.title;
+ },
+ get isSystem() {
+ return this._form.system;
+ },
+ get styleSheetIndex() {
+ return this._form.styleSheetIndex;
+ },
+ get ruleCount() {
+ return this._form.ruleCount;
+ }
+});
+
+exports.OldStyleSheetFront = OldStyleSheetFront;
+
+/**
+ * The corresponding Front object for the StyleEditorActor.
+ */
+const StyleEditorFront = FrontClassWithSpec(styleEditorSpec, {
+ initialize: function (client, tabForm) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = tabForm.styleEditorActor;
+ this.manage(this);
+ },
+
+ getStyleSheets: function () {
+ let deferred = defer();
+
+ events.once(this, "document-load", (styleSheets) => {
+ deferred.resolve(styleSheets);
+ });
+ this.newDocument();
+
+ return deferred.promise;
+ },
+
+ addStyleSheet: function (text) {
+ return this.newStyleSheet(text);
+ }
+});
+
+exports.StyleEditorFront = StyleEditorFront;
diff --git a/devtools/shared/fronts/styles.js b/devtools/shared/fronts/styles.js
new file mode 100644
index 000000000..116bb1f75
--- /dev/null
+++ b/devtools/shared/fronts/styles.js
@@ -0,0 +1,421 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+require("devtools/shared/fronts/stylesheets");
+const {
+ Front,
+ FrontClassWithSpec,
+ custom,
+ preEvent
+} = require("devtools/shared/protocol");
+const {
+ pageStyleSpec,
+ styleRuleSpec
+} = require("devtools/shared/specs/styles");
+const promise = require("promise");
+const { Task } = require("devtools/shared/task");
+const { Class } = require("sdk/core/heritage");
+const { RuleRewriter } = require("devtools/shared/css/parsing-utils");
+
+/**
+ * PageStyleFront, the front object for the PageStyleActor
+ */
+const PageStyleFront = FrontClassWithSpec(pageStyleSpec, {
+ initialize: function (conn, form, ctx, detail) {
+ Front.prototype.initialize.call(this, conn, form, ctx, detail);
+ this.inspector = this.parent();
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this._form = form;
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ get walker() {
+ return this.inspector.walker;
+ },
+
+ get supportsAuthoredStyles() {
+ return this._form.traits && this._form.traits.authoredStyles;
+ },
+
+ getMatchedSelectors: custom(function (node, property, options) {
+ return this._getMatchedSelectors(node, property, options).then(ret => {
+ return ret.matched;
+ });
+ }, {
+ impl: "_getMatchedSelectors"
+ }),
+
+ getApplied: custom(Task.async(function* (node, options = {}) {
+ // If the getApplied method doesn't recreate the style cache itself, this
+ // means a call to cssLogic.highlight is required before trying to access
+ // the applied rules. Issue a request to getLayout if this is the case.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1103993#c16.
+ if (!this._form.traits || !this._form.traits.getAppliedCreatesStyleCache) {
+ yield this.getLayout(node);
+ }
+ let ret = yield this._getApplied(node, options);
+ return ret.entries;
+ }), {
+ impl: "_getApplied"
+ }),
+
+ addNewRule: custom(function (node, pseudoClasses) {
+ let addPromise;
+ if (this.supportsAuthoredStyles) {
+ addPromise = this._addNewRule(node, pseudoClasses, true);
+ } else {
+ addPromise = this._addNewRule(node, pseudoClasses);
+ }
+ return addPromise.then(ret => {
+ return ret.entries[0];
+ });
+ }, {
+ impl: "_addNewRule"
+ })
+});
+
+exports.PageStyleFront = PageStyleFront;
+
+/**
+ * StyleRuleFront, the front for the StyleRule actor.
+ */
+const StyleRuleFront = FrontClassWithSpec(styleRuleSpec, {
+ initialize: function (client, form, ctx, detail) {
+ Front.prototype.initialize.call(this, client, form, ctx, detail);
+ },
+
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ if (this._mediaText) {
+ this._mediaText = null;
+ }
+ },
+
+ /**
+ * Ensure _form is updated when location-changed is emitted.
+ */
+ _locationChangedPre: preEvent("location-changed", function (line, column) {
+ this._clearOriginalLocation();
+ this._form.line = line;
+ this._form.column = column;
+ }),
+
+ /**
+ * Return a new RuleModificationList or RuleRewriter for this node.
+ * A RuleRewriter will be returned when the rule's canSetRuleText
+ * trait is true; otherwise a RuleModificationList will be
+ * returned.
+ *
+ * @param {CssPropertiesFront} cssProperties
+ * This is needed by the RuleRewriter.
+ * @return {RuleModificationList}
+ */
+ startModifyingProperties: function (cssProperties) {
+ if (this.canSetRuleText) {
+ return new RuleRewriter(cssProperties.isKnown, this, this.authoredText);
+ }
+ return new RuleModificationList(this);
+ },
+
+ get type() {
+ return this._form.type;
+ },
+ get line() {
+ return this._form.line || -1;
+ },
+ get column() {
+ return this._form.column || -1;
+ },
+ get cssText() {
+ return this._form.cssText;
+ },
+ get authoredText() {
+ return this._form.authoredText || this._form.cssText;
+ },
+ get declarations() {
+ return this._form.declarations || [];
+ },
+ get keyText() {
+ return this._form.keyText;
+ },
+ get name() {
+ return this._form.name;
+ },
+ get selectors() {
+ return this._form.selectors;
+ },
+ get media() {
+ return this._form.media;
+ },
+ get mediaText() {
+ if (!this._form.media) {
+ return null;
+ }
+ if (this._mediaText) {
+ return this._mediaText;
+ }
+ this._mediaText = this.media.join(", ");
+ return this._mediaText;
+ },
+
+ get parentRule() {
+ return this.conn.getActor(this._form.parentRule);
+ },
+
+ get parentStyleSheet() {
+ return this.conn.getActor(this._form.parentStyleSheet);
+ },
+
+ get element() {
+ return this.conn.getActor(this._form.element);
+ },
+
+ get href() {
+ if (this._form.href) {
+ return this._form.href;
+ }
+ let sheet = this.parentStyleSheet;
+ return sheet ? sheet.href : "";
+ },
+
+ get nodeHref() {
+ let sheet = this.parentStyleSheet;
+ return sheet ? sheet.nodeHref : "";
+ },
+
+ get supportsModifySelectorUnmatched() {
+ return this._form.traits && this._form.traits.modifySelectorUnmatched;
+ },
+
+ get canSetRuleText() {
+ return this._form.traits && this._form.traits.canSetRuleText;
+ },
+
+ get location() {
+ return {
+ source: this.parentStyleSheet,
+ href: this.href,
+ line: this.line,
+ column: this.column
+ };
+ },
+
+ _clearOriginalLocation: function () {
+ this._originalLocation = null;
+ },
+
+ getOriginalLocation: function () {
+ if (this._originalLocation) {
+ return promise.resolve(this._originalLocation);
+ }
+ let parentSheet = this.parentStyleSheet;
+ if (!parentSheet) {
+ // This rule doesn't belong to a stylesheet so it is an inline style.
+ // Inline styles do not have any mediaText so we can return early.
+ return promise.resolve(this.location);
+ }
+ return parentSheet.getOriginalLocation(this.line, this.column)
+ .then(({ fromSourceMap, source, line, column }) => {
+ let location = {
+ href: source,
+ line: line,
+ column: column,
+ mediaText: this.mediaText
+ };
+ if (fromSourceMap === false) {
+ location.source = this.parentStyleSheet;
+ }
+ if (!source) {
+ location.href = this.href;
+ }
+ this._originalLocation = location;
+ return location;
+ });
+ },
+
+ modifySelector: custom(Task.async(function* (node, value) {
+ let response;
+ if (this.supportsModifySelectorUnmatched) {
+ // If the debugee supports adding unmatched rules (post FF41)
+ if (this.canSetRuleText) {
+ response = yield this.modifySelector2(node, value, true);
+ } else {
+ response = yield this.modifySelector2(node, value);
+ }
+ } else {
+ response = yield this._modifySelector(value);
+ }
+
+ if (response.ruleProps) {
+ response.ruleProps = response.ruleProps.entries[0];
+ }
+ return response;
+ }), {
+ impl: "_modifySelector"
+ }),
+
+ setRuleText: custom(function (newText) {
+ this._form.authoredText = newText;
+ return this._setRuleText(newText);
+ }, {
+ impl: "_setRuleText"
+ })
+});
+
+exports.StyleRuleFront = StyleRuleFront;
+
+/**
+ * Convenience API for building a list of attribute modifications
+ * for the `modifyProperties` request. A RuleModificationList holds a
+ * list of modifications that will be applied to a StyleRuleActor.
+ * The modifications are processed in the order in which they are
+ * added to the RuleModificationList.
+ *
+ * Objects of this type expose the same API as @see RuleRewriter.
+ * This lets the inspector use (mostly) the same code, regardless of
+ * whether the server implements setRuleText.
+ */
+var RuleModificationList = Class({
+ /**
+ * Initialize a RuleModificationList.
+ * @param {StyleRuleFront} rule the associated rule
+ */
+ initialize: function (rule) {
+ this.rule = rule;
+ this.modifications = [];
+ },
+
+ /**
+ * Apply the modifications in this object to the associated rule.
+ *
+ * @return {Promise} A promise which will be resolved when the modifications
+ * are complete; @see StyleRuleActor.modifyProperties.
+ */
+ apply: function () {
+ return this.rule.modifyProperties(this.modifications);
+ },
+
+ /**
+ * Add a "set" entry to the modification list.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name the property's name
+ * @param {String} value the property's value
+ * @param {String} priority the property's priority, either the empty
+ * string or "important"
+ */
+ setProperty: function (index, name, value, priority) {
+ this.modifications.push({
+ type: "set",
+ name: name,
+ value: value,
+ priority: priority
+ });
+ },
+
+ /**
+ * Add a "remove" entry to the modification list.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name the name of the property to remove
+ */
+ removeProperty: function (index, name) {
+ this.modifications.push({
+ type: "remove",
+ name: name
+ });
+ },
+
+ /**
+ * Rename a property. This implementation acts like
+ * |removeProperty|, because |setRuleText| is not available.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name current name of the property
+ *
+ * This parameter is also passed, but as it is not used in this
+ * implementation, it is omitted. It is documented here as this
+ * code also defined the interface implemented by @see RuleRewriter.
+ * @param {String} newName new name of the property
+ */
+ renameProperty: function (index, name) {
+ this.removeProperty(index, name);
+ },
+
+ /**
+ * Enable or disable a property. This implementation acts like
+ * |removeProperty| when disabling, or a no-op when enabling,
+ * because |setRuleText| is not available.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name current name of the property
+ * @param {Boolean} isEnabled true if the property should be enabled;
+ * false if it should be disabled
+ */
+ setPropertyEnabled: function (index, name, isEnabled) {
+ if (!isEnabled) {
+ this.removeProperty(index, name);
+ }
+ },
+
+ /**
+ * Create a new property. This implementation does nothing, because
+ * |setRuleText| is not available.
+ *
+ * These parameters are passed, but as they are not used in this
+ * implementation, they are omitted. They are documented here as
+ * this code also defined the interface implemented by @see
+ * RuleRewriter.
+ *
+ * @param {Number} index index of the property in the rule.
+ * This can be -1 in the case where
+ * the rule does not support setRuleText;
+ * generally for setting properties
+ * on an element's style.
+ * @param {String} name name of the new property
+ * @param {String} value value of the new property
+ * @param {String} priority priority of the new property; either
+ * the empty string or "important"
+ * @param {Boolean} enabled True if the new property should be
+ * enabled, false if disabled
+ */
+ createProperty: function () {
+ // Nothing.
+ },
+});
diff --git a/devtools/shared/fronts/stylesheets.js b/devtools/shared/fronts/stylesheets.js
new file mode 100644
index 000000000..6df8a9bd4
--- /dev/null
+++ b/devtools/shared/fronts/stylesheets.js
@@ -0,0 +1,184 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Front, FrontClassWithSpec } = require("devtools/shared/protocol");
+const {
+ getIndentationFromPrefs,
+ getIndentationFromString
+} = require("devtools/shared/indentation");
+const {
+ originalSourceSpec,
+ mediaRuleSpec,
+ styleSheetSpec,
+ styleSheetsSpec
+} = require("devtools/shared/specs/stylesheets");
+const promise = require("promise");
+const { Task } = require("devtools/shared/task");
+const events = require("sdk/event/core");
+
+/**
+ * The client-side counterpart for an OriginalSourceActor.
+ */
+const OriginalSourceFront = FrontClassWithSpec(originalSourceSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+
+ this.isOriginalSource = true;
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ },
+
+ get href() {
+ return this._form.url;
+ },
+ get url() {
+ return this._form.url;
+ }
+});
+
+exports.OriginalSourceFront = OriginalSourceFront;
+
+/**
+ * Corresponding client-side front for a MediaRuleActor.
+ */
+const MediaRuleFront = FrontClassWithSpec(mediaRuleSpec, {
+ initialize: function (client, form) {
+ Front.prototype.initialize.call(this, client, form);
+
+ this._onMatchesChange = this._onMatchesChange.bind(this);
+ events.on(this, "matches-change", this._onMatchesChange);
+ },
+
+ _onMatchesChange: function (matches) {
+ this._form.matches = matches;
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ },
+
+ get mediaText() {
+ return this._form.mediaText;
+ },
+ get conditionText() {
+ return this._form.conditionText;
+ },
+ get matches() {
+ return this._form.matches;
+ },
+ get line() {
+ return this._form.line || -1;
+ },
+ get column() {
+ return this._form.column || -1;
+ },
+ get parentStyleSheet() {
+ return this.conn.getActor(this._form.parentStyleSheet);
+ }
+});
+
+exports.MediaRuleFront = MediaRuleFront;
+
+/**
+ * StyleSheetFront is the client-side counterpart to a StyleSheetActor.
+ */
+const StyleSheetFront = FrontClassWithSpec(styleSheetSpec, {
+ initialize: function (conn, form) {
+ Front.prototype.initialize.call(this, conn, form);
+
+ this._onPropertyChange = this._onPropertyChange.bind(this);
+ events.on(this, "property-change", this._onPropertyChange);
+ },
+
+ destroy: function () {
+ events.off(this, "property-change", this._onPropertyChange);
+ Front.prototype.destroy.call(this);
+ },
+
+ _onPropertyChange: function (property, value) {
+ this._form[property] = value;
+ },
+
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+ this.actorID = form.actor;
+ this._form = form;
+ },
+
+ get href() {
+ return this._form.href;
+ },
+ get nodeHref() {
+ return this._form.nodeHref;
+ },
+ get disabled() {
+ return !!this._form.disabled;
+ },
+ get title() {
+ return this._form.title;
+ },
+ get isSystem() {
+ return this._form.system;
+ },
+ get styleSheetIndex() {
+ return this._form.styleSheetIndex;
+ },
+ get ruleCount() {
+ return this._form.ruleCount;
+ },
+
+ /**
+ * Get the indentation to use for edits to this style sheet.
+ *
+ * @return {Promise} A promise that will resolve to a string that
+ * should be used to indent a block in this style sheet.
+ */
+ guessIndentation: function () {
+ let prefIndent = getIndentationFromPrefs();
+ if (prefIndent) {
+ let {indentUnit, indentWithTabs} = prefIndent;
+ return promise.resolve(indentWithTabs ? "\t" : " ".repeat(indentUnit));
+ }
+
+ return Task.spawn(function* () {
+ let longStr = yield this.getText();
+ let source = yield longStr.string();
+
+ let {indentUnit, indentWithTabs} = getIndentationFromString(source);
+
+ return indentWithTabs ? "\t" : " ".repeat(indentUnit);
+ }.bind(this));
+ }
+});
+
+exports.StyleSheetFront = StyleSheetFront;
+
+/**
+ * The corresponding Front object for the StyleSheetsActor.
+ */
+const StyleSheetsFront = FrontClassWithSpec(styleSheetsSpec, {
+ initialize: function (client, tabForm) {
+ Front.prototype.initialize.call(this, client);
+ this.actorID = tabForm.styleSheetsActor;
+ this.manage(this);
+ }
+});
+
+exports.StyleSheetsFront = StyleSheetsFront;
diff --git a/devtools/shared/fronts/timeline.js b/devtools/shared/fronts/timeline.js
new file mode 100644
index 000000000..cd6bc54e8
--- /dev/null
+++ b/devtools/shared/fronts/timeline.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Front,
+ FrontClassWithSpec,
+} = require("devtools/shared/protocol");
+const { timelineSpec } = require("devtools/shared/specs/timeline");
+
+/**
+ * TimelineFront, the front for the TimelineActor.
+ */
+const TimelineFront = FrontClassWithSpec(timelineSpec, {
+ initialize: function (client, { timelineActor }) {
+ Front.prototype.initialize.call(this, client, { actor: timelineActor });
+ this.manage(this);
+ },
+ destroy: function () {
+ Front.prototype.destroy.call(this);
+ },
+});
+
+exports.TimelineFront = TimelineFront;
diff --git a/devtools/shared/fronts/webaudio.js b/devtools/shared/fronts/webaudio.js
new file mode 100644
index 000000000..73594c513
--- /dev/null
+++ b/devtools/shared/fronts/webaudio.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ audionodeSpec,
+ webAudioSpec,
+ AUTOMATION_METHODS,
+ NODE_CREATION_METHODS,
+ NODE_ROUTING_METHODS,
+} = require("devtools/shared/specs/webaudio");
+const protocol = require("devtools/shared/protocol");
+const AUDIO_NODE_DEFINITION = require("devtools/server/actors/utils/audionodes.json");
+
+/**
+ * The corresponding Front object for the AudioNodeActor.
+ *
+ * @attribute {String} type
+ * The type of audio node, like "OscillatorNode", "MediaElementAudioSourceNode"
+ * @attribute {Boolean} source
+ * Boolean indicating if the node is a source node, like BufferSourceNode,
+ * MediaElementAudioSourceNode, OscillatorNode, etc.
+ * @attribute {Boolean} bypassable
+ * Boolean indicating if the audio node is bypassable (splitter,
+ * merger and destination nodes, for example, are not)
+ */
+const AudioNodeFront = protocol.FrontClassWithSpec(audionodeSpec, {
+ form: function (form, detail) {
+ if (detail === "actorid") {
+ this.actorID = form;
+ return;
+ }
+
+ this.actorID = form.actor;
+ this.type = form.type;
+ this.source = form.source;
+ this.bypassable = form.bypassable;
+ },
+
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ // if we were manually passed a form, this was created manually and
+ // needs to own itself for now.
+ if (form) {
+ this.manage(this);
+ }
+ }
+});
+
+exports.AudioNodeFront = AudioNodeFront;
+
+/**
+ * The corresponding Front object for the WebAudioActor.
+ */
+const WebAudioFront = protocol.FrontClassWithSpec(webAudioSpec, {
+ initialize: function (client, { webaudioActor }) {
+ protocol.Front.prototype.initialize.call(this, client, { actor: webaudioActor });
+ this.manage(this);
+ },
+
+ /**
+ * If connecting to older geckos (<Fx43), where audio node actor's do not
+ * contain `type`, `source` and `bypassable` properties, fetch
+ * them manually here.
+ */
+ _onCreateNode: protocol.preEvent("create-node", function (audionode) {
+ if (!audionode.type) {
+ return audionode.getType().then(type => {
+ audionode.type = type;
+ audionode.source = !!AUDIO_NODE_DEFINITION[type].source;
+ audionode.bypassable = !AUDIO_NODE_DEFINITION[type].unbypassable;
+ });
+ }
+ return null;
+ }),
+});
+
+WebAudioFront.AUTOMATION_METHODS = new Set(AUTOMATION_METHODS);
+WebAudioFront.NODE_CREATION_METHODS = new Set(NODE_CREATION_METHODS);
+WebAudioFront.NODE_ROUTING_METHODS = new Set(NODE_ROUTING_METHODS);
+
+exports.WebAudioFront = WebAudioFront;
diff --git a/devtools/shared/fronts/webgl.js b/devtools/shared/fronts/webgl.js
new file mode 100644
index 000000000..b5f7afac8
--- /dev/null
+++ b/devtools/shared/fronts/webgl.js
@@ -0,0 +1,45 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ shaderSpec,
+ programSpec,
+ webGLSpec,
+} = require("devtools/shared/specs/webgl");
+const protocol = require("devtools/shared/protocol");
+
+/**
+ * The corresponding Front object for the ShaderActor.
+ */
+const ShaderFront = protocol.FrontClassWithSpec(shaderSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+exports.ShaderFront = ShaderFront;
+
+/**
+ * The corresponding Front object for the ProgramActor.
+ */
+const ProgramFront = protocol.FrontClassWithSpec(programSpec, {
+ initialize: function (client, form) {
+ protocol.Front.prototype.initialize.call(this, client, form);
+ }
+});
+
+exports.ProgramFront = ProgramFront;
+
+/**
+ * The corresponding Front object for the WebGLActor.
+ */
+const WebGLFront = protocol.FrontClassWithSpec(webGLSpec, {
+ initialize: function (client, { webglActor }) {
+ protocol.Front.prototype.initialize.call(this, client, { actor: webglActor });
+ this.manage(this);
+ }
+});
+
+exports.WebGLFront = WebGLFront;
diff --git a/devtools/shared/gcli/commands/addon.js b/devtools/shared/gcli/commands/addon.js
new file mode 100644
index 000000000..9a38142a3
--- /dev/null
+++ b/devtools/shared/gcli/commands/addon.js
@@ -0,0 +1,320 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * You can't require the AddonManager in a child process, but GCLI wants to
+ * check for 'items' in all processes, so we return empty array if the
+ * AddonManager is not available
+ */
+function getAddonManager() {
+ try {
+ return {
+ AddonManager: require("resource://gre/modules/AddonManager.jsm").AddonManager,
+ addonManagerActive: true
+ };
+ }
+ catch (ex) {
+ // Fake up an AddonManager just enough to let the file load
+ return {
+ AddonManager: {
+ getAllAddons() {},
+ getAddonsByTypes() {}
+ },
+ addonManagerActive: false
+ };
+ }
+}
+
+const { Cc, Ci, Cu } = require("chrome");
+const { AddonManager, addonManagerActive } = getAddonManager();
+const l10n = require("gcli/l10n");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+/**
+ * Takes a function that uses a callback as its last parameter, and returns a
+ * new function that returns a promise instead.
+ * This should probably live in async-util
+ */
+const promiseify = function(scope, functionWithLastParamCallback) {
+ return (...args) => {
+ return new Promise(resolve => {
+ args.push((...results) => {
+ resolve(results.length > 1 ? results : results[0]);
+ });
+ functionWithLastParamCallback.apply(scope, args);
+ });
+ }
+};
+
+// Convert callback based functions to promise based ones
+const getAllAddons = promiseify(AddonManager, AddonManager.getAllAddons);
+const getAddonsByTypes = promiseify(AddonManager, AddonManager.getAddonsByTypes);
+
+/**
+ * Return a string array containing the pending operations on an addon
+ */
+function pendingOperations(addon) {
+ let allOperations = [
+ "PENDING_ENABLE", "PENDING_DISABLE", "PENDING_UNINSTALL",
+ "PENDING_INSTALL", "PENDING_UPGRADE"
+ ];
+ return allOperations.reduce(function(operations, opName) {
+ return addon.pendingOperations & AddonManager[opName] ?
+ operations.concat(opName) :
+ operations;
+ }, []);
+}
+
+var items = [
+ {
+ item: "type",
+ name: "addon",
+ parent: "selection",
+ stringifyProperty: "name",
+ cacheable: true,
+ constructor: function() {
+ // Tell GCLI to clear the cache of addons when one is added or removed
+ let listener = {
+ onInstalled: addon => { this.clearCache(); },
+ onUninstalled: addon => { this.clearCache(); },
+ };
+ AddonManager.addAddonListener(listener);
+ },
+ lookup: function() {
+ return getAllAddons().then(addons => {
+ return addons.map(addon => {
+ let name = addon.name + " " + addon.version;
+ name = name.trim().replace(/\s/g, "_");
+ return { name: name, value: addon };
+ });
+ });
+ }
+ },
+ {
+ name: "addon",
+ description: l10n.lookup("addonDesc")
+ },
+ {
+ name: "addon list",
+ description: l10n.lookup("addonListDesc"),
+ returnType: "addonsInfo",
+ params: [{
+ name: "type",
+ type: {
+ name: "selection",
+ data: [ "dictionary", "extension", "locale", "plugin", "theme", "all" ]
+ },
+ defaultValue: "all",
+ description: l10n.lookup("addonListTypeDesc")
+ }],
+ exec: function(args, context) {
+ let types = (args.type === "all") ? null : [ args.type ];
+ return getAddonsByTypes(types).then(addons => {
+ addons = addons.map(function(addon) {
+ return {
+ name: addon.name,
+ version: addon.version,
+ isActive: addon.isActive,
+ pendingOperations: pendingOperations(addon)
+ };
+ });
+ return { addons: addons, type: args.type };
+ });
+ }
+ },
+ {
+ item: "converter",
+ from: "addonsInfo",
+ to: "view",
+ exec: function(addonsInfo, context) {
+ if (!addonsInfo.addons.length) {
+ return context.createView({
+ html: "<p>${message}</p>",
+ data: { message: l10n.lookup("addonNoneOfType") }
+ });
+ }
+
+ let headerLookups = {
+ "dictionary": "addonListDictionaryHeading",
+ "extension": "addonListExtensionHeading",
+ "locale": "addonListLocaleHeading",
+ "plugin": "addonListPluginHeading",
+ "theme": "addonListThemeHeading",
+ "all": "addonListAllHeading"
+ };
+ let header = l10n.lookup(headerLookups[addonsInfo.type] ||
+ "addonListUnknownHeading");
+
+ let operationLookups = {
+ "PENDING_ENABLE": "addonPendingEnable",
+ "PENDING_DISABLE": "addonPendingDisable",
+ "PENDING_UNINSTALL": "addonPendingUninstall",
+ "PENDING_INSTALL": "addonPendingInstall",
+ "PENDING_UPGRADE": "addonPendingUpgrade"
+ };
+ function lookupOperation(opName) {
+ let lookupName = operationLookups[opName];
+ return lookupName ? l10n.lookup(lookupName) : opName;
+ }
+
+ function arrangeAddons(addons) {
+ let enabledAddons = [];
+ let disabledAddons = [];
+ addons.forEach(function(addon) {
+ if (addon.isActive) {
+ enabledAddons.push(addon);
+ } else {
+ disabledAddons.push(addon);
+ }
+ });
+
+ function compareAddonNames(nameA, nameB) {
+ return String.localeCompare(nameA.name, nameB.name);
+ }
+ enabledAddons.sort(compareAddonNames);
+ disabledAddons.sort(compareAddonNames);
+
+ return enabledAddons.concat(disabledAddons);
+ }
+
+ function isActiveForToggle(addon) {
+ return (addon.isActive && ~~addon.pendingOperations.indexOf("PENDING_DISABLE"));
+ }
+
+ return context.createView({
+ html:
+ "<table>" +
+ " <caption>${header}</caption>" +
+ " <tbody>" +
+ " <tr foreach='addon in ${addons}'" +
+ " class=\"gcli-addon-${addon.status}\">" +
+ " <td>${addon.name} ${addon.version}</td>" +
+ " <td>${addon.pendingOperations}</td>" +
+ " <td>" +
+ " <span class='gcli-out-shortcut'" +
+ " data-command='addon ${addon.toggleActionName} ${addon.label}'" +
+ " onclick='${onclick}' ondblclick='${ondblclick}'" +
+ " >${addon.toggleActionMessage}</span>" +
+ " </td>" +
+ " </tr>" +
+ " </tbody>" +
+ "</table>",
+ data: {
+ header: header,
+ addons: arrangeAddons(addonsInfo.addons).map(function(addon) {
+ return {
+ name: addon.name,
+ label: addon.name.replace(/\s/g, "_") +
+ (addon.version ? "_" + addon.version : ""),
+ status: addon.isActive ? "enabled" : "disabled",
+ version: addon.version,
+ pendingOperations: addon.pendingOperations.length ?
+ (" (" + l10n.lookup("addonPending") + ": "
+ + addon.pendingOperations.map(lookupOperation).join(", ")
+ + ")") :
+ "",
+ toggleActionName: isActiveForToggle(addon) ? "disable": "enable",
+ toggleActionMessage: isActiveForToggle(addon) ?
+ l10n.lookup("addonListOutDisable") :
+ l10n.lookup("addonListOutEnable")
+ };
+ }),
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon enable",
+ description: l10n.lookup("addonEnableDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (args.addon.userDisabled) {
+ args.addon.userDisabled = false;
+ return l10n.lookupFormat("addonEnabled", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyEnabled", [ name ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon disable",
+ description: l10n.lookup("addonDisableDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ // If the addon is not disabled or is set to "click to play" then
+ // disable it. Otherwise display the message "Add-on is already
+ // disabled."
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (!args.addon.userDisabled ||
+ args.addon.userDisabled === AddonManager.STATE_ASK_TO_ACTIVATE) {
+ args.addon.userDisabled = true;
+ return l10n.lookupFormat("addonDisabled", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyDisabled", [ name ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "addon ctp",
+ description: l10n.lookup("addonCtpDesc"),
+ params: [
+ {
+ name: "addon",
+ type: "addon",
+ description: l10n.lookup("addonNameDesc")
+ }
+ ],
+ exec: function(args, context) {
+ let name = (args.addon.name + " " + args.addon.version).trim();
+ if (args.addon.type !== "plugin") {
+ return l10n.lookupFormat("addonCantCtp", [ name ]);
+ }
+
+ if (!args.addon.userDisabled ||
+ args.addon.userDisabled === true) {
+ args.addon.userDisabled = AddonManager.STATE_ASK_TO_ACTIVATE;
+
+ if (args.addon.userDisabled !== AddonManager.STATE_ASK_TO_ACTIVATE) {
+ // Some plugins (e.g. OpenH264 shipped with Firefox) cannot be set to
+ // click-to-play. Handle this.
+
+ return l10n.lookupFormat("addonNoCtp", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonCtp", [ name ]);
+ }
+
+ return l10n.lookupFormat("addonAlreadyCtp", [ name ]);
+ }
+ }
+];
+
+exports.items = addonManagerActive ? items : [];
diff --git a/devtools/shared/gcli/commands/appcache.js b/devtools/shared/gcli/commands/appcache.js
new file mode 100644
index 000000000..0789fb5a0
--- /dev/null
+++ b/devtools/shared/gcli/commands/appcache.js
@@ -0,0 +1,186 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const l10n = require("gcli/l10n");
+
+loader.lazyImporter(this, "AppCacheUtils", "resource://devtools/client/shared/AppCacheUtils.jsm");
+
+exports.items = [
+ {
+ item: "command",
+ name: "appcache",
+ description: l10n.lookup("appCacheDesc")
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "appcache validate",
+ description: l10n.lookup("appCacheValidateDesc"),
+ manual: l10n.lookup("appCacheValidateManual"),
+ returnType: "appcacheerrors",
+ params: [{
+ group: "options",
+ params: [
+ {
+ type: "string",
+ name: "uri",
+ description: l10n.lookup("appCacheValidateUriDesc"),
+ defaultValue: null,
+ }
+ ]
+ }],
+ exec: function(args, context) {
+ let utils;
+ let deferred = context.defer();
+
+ if (args.uri) {
+ utils = new AppCacheUtils(args.uri);
+ } else {
+ utils = new AppCacheUtils(context.environment.document);
+ }
+
+ utils.validateManifest().then(function(errors) {
+ deferred.resolve([errors, utils.manifestURI || "-"]);
+ });
+
+ return deferred.promise;
+ }
+ },
+ {
+ item: "converter",
+ from: "appcacheerrors",
+ to: "view",
+ exec: function([errors, manifestURI], context) {
+ if (errors.length == 0) {
+ return context.createView({
+ html: "<span>" + l10n.lookup("appCacheValidatedSuccessfully") + "</span>"
+ });
+ }
+
+ return context.createView({
+ html:
+ "<div>" +
+ " <h4>Manifest URI: ${manifestURI}</h4>" +
+ " <ol>" +
+ " <li foreach='error in ${errors}'>${error.msg}</li>" +
+ " </ol>" +
+ "</div>",
+ data: {
+ errors: errors,
+ manifestURI: manifestURI
+ }
+ });
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "appcache clear",
+ description: l10n.lookup("appCacheClearDesc"),
+ manual: l10n.lookup("appCacheClearManual"),
+ exec: function(args, context) {
+ let utils = new AppCacheUtils(args.uri);
+ utils.clearAll();
+
+ return l10n.lookup("appCacheClearCleared");
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "appcache list",
+ description: l10n.lookup("appCacheListDesc"),
+ manual: l10n.lookup("appCacheListManual"),
+ returnType: "appcacheentries",
+ params: [{
+ group: "options",
+ params: [
+ {
+ type: "string",
+ name: "search",
+ description: l10n.lookup("appCacheListSearchDesc"),
+ defaultValue: null,
+ },
+ ]
+ }],
+ exec: function(args, context) {
+ let utils = new AppCacheUtils();
+ return utils.listEntries(args.search);
+ }
+ },
+ {
+ item: "converter",
+ from: "appcacheentries",
+ to: "view",
+ exec: function(entries, context) {
+ return context.createView({
+ html: "" +
+ "<ul class='gcli-appcache-list'>" +
+ " <li foreach='entry in ${entries}'>" +
+ " <table class='gcli-appcache-detail'>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListKey") + "</td>" +
+ " <td>${entry.key}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListFetchCount") + "</td>" +
+ " <td>${entry.fetchCount}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListLastFetched") + "</td>" +
+ " <td>${entry.lastFetched}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListLastModified") + "</td>" +
+ " <td>${entry.lastModified}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListExpirationTime") + "</td>" +
+ " <td>${entry.expirationTime}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListDataSize") + "</td>" +
+ " <td>${entry.dataSize}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("appCacheListDeviceID") + "</td>" +
+ " <td>${entry.deviceID} <span class='gcli-out-shortcut' " +
+ "onclick='${onclick}' ondblclick='${ondblclick}' " +
+ "data-command='appcache viewentry ${entry.key}'" +
+ ">" + l10n.lookup("appCacheListViewEntry") + "</span>" +
+ " </td>" +
+ " </tr>" +
+ " </table>" +
+ " </li>" +
+ "</ul>",
+ data: {
+ entries: entries,
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "appcache viewentry",
+ description: l10n.lookup("appCacheViewEntryDesc"),
+ manual: l10n.lookup("appCacheViewEntryManual"),
+ params: [
+ {
+ type: "string",
+ name: "key",
+ description: l10n.lookup("appCacheViewEntryKey"),
+ defaultValue: null,
+ }
+ ],
+ exec: function(args, context) {
+ let utils = new AppCacheUtils();
+ return utils.viewEntry(args.key);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/calllog.js b/devtools/shared/gcli/commands/calllog.js
new file mode 100644
index 000000000..c0f21aeab
--- /dev/null
+++ b/devtools/shared/gcli/commands/calllog.js
@@ -0,0 +1,219 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+const gcli = require("gcli/index");
+const Debugger = require("Debugger");
+
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
+
+var debuggers = [];
+var chromeDebuggers = [];
+var sandboxes = [];
+
+exports.items = [
+ {
+ name: "calllog",
+ description: l10n.lookup("calllogDesc")
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "calllog start",
+ description: l10n.lookup("calllogStartDesc"),
+
+ exec: function(args, context) {
+ let contentWindow = context.environment.window;
+
+ let dbg = new Debugger(contentWindow);
+ dbg.onEnterFrame = function(frame) {
+ // BUG 773652 - Make the output from the GCLI calllog command nicer
+ contentWindow.console.log("Method call: " + this.callDescription(frame));
+ }.bind(this);
+
+ debuggers.push(dbg);
+
+ let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ gDevTools.showToolbox(target, "webconsole");
+
+ return l10n.lookup("calllogStartReply");
+ },
+
+ callDescription: function(frame) {
+ let name = "<anonymous>";
+ if (frame.callee.name) {
+ name = frame.callee.name;
+ }
+ else {
+ let desc = frame.callee.getOwnPropertyDescriptor("displayName");
+ if (desc && desc.value && typeof desc.value == "string") {
+ name = desc.value;
+ }
+ }
+
+ let args = frame.arguments.map(this.valueToString).join(", ");
+ return name + "(" + args + ")";
+ },
+
+ valueToString: function(value) {
+ if (typeof value !== "object" || value === null) {
+ return uneval(value);
+ }
+ return "[object " + value.class + "]";
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "calllog stop",
+ description: l10n.lookup("calllogStopDesc"),
+
+ exec: function(args, context) {
+ let numDebuggers = debuggers.length;
+ if (numDebuggers == 0) {
+ return l10n.lookup("calllogStopNoLogging");
+ }
+
+ for (let dbg of debuggers) {
+ dbg.onEnterFrame = undefined;
+ }
+ debuggers = [];
+
+ return l10n.lookupFormat("calllogStopReply", [ numDebuggers ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "calllog chromestart",
+ description: l10n.lookup("calllogChromeStartDesc"),
+ get hidden() {
+ return gcli.hiddenByChromePref();
+ },
+ params: [
+ {
+ name: "sourceType",
+ type: {
+ name: "selection",
+ data: ["content-variable", "chrome-variable", "jsm", "javascript"]
+ }
+ },
+ {
+ name: "source",
+ type: "string",
+ description: l10n.lookup("calllogChromeSourceTypeDesc"),
+ manual: l10n.lookup("calllogChromeSourceTypeManual"),
+ }
+ ],
+ exec: function(args, context) {
+ let globalObj;
+ let contentWindow = context.environment.window;
+
+ if (args.sourceType == "jsm") {
+ try {
+ globalObj = Cu.import(args.source, {});
+ } catch (e) {
+ return l10n.lookup("callLogChromeInvalidJSM");
+ }
+ } else if (args.sourceType == "content-variable") {
+ if (args.source in contentWindow) {
+ globalObj = Cu.getGlobalForObject(contentWindow[args.source]);
+ } else {
+ throw new Error(l10n.lookup("callLogChromeVarNotFoundContent"));
+ }
+ } else if (args.sourceType == "chrome-variable") {
+ let chromeWin = context.environment.chromeDocument.defaultView;
+ if (args.source in chromeWin) {
+ globalObj = Cu.getGlobalForObject(chromeWin[args.source]);
+ } else {
+ return l10n.lookup("callLogChromeVarNotFoundChrome");
+ }
+ } else {
+ let chromeWin = context.environment.chromeDocument.defaultView;
+ let sandbox = new Cu.Sandbox(chromeWin,
+ {
+ sandboxPrototype: chromeWin,
+ wantXrays: false,
+ sandboxName: "gcli-cmd-calllog-chrome"
+ });
+ let returnVal;
+ try {
+ returnVal = Cu.evalInSandbox(args.source, sandbox, "ECMAv5");
+ sandboxes.push(sandbox);
+ } catch(e) {
+ // We need to save the message before cleaning up else e contains a dead
+ // object.
+ let msg = l10n.lookup("callLogChromeEvalException") + ": " + e;
+ Cu.nukeSandbox(sandbox);
+ return msg;
+ }
+
+ if (typeof returnVal == "undefined") {
+ return l10n.lookup("callLogChromeEvalNeedsObject");
+ }
+
+ globalObj = Cu.getGlobalForObject(returnVal);
+ }
+
+ let dbg = new Debugger(globalObj);
+ chromeDebuggers.push(dbg);
+
+ dbg.onEnterFrame = function(frame) {
+ // BUG 773652 - Make the output from the GCLI calllog command nicer
+ contentWindow.console.log(l10n.lookup("callLogChromeMethodCall") +
+ ": " + this.callDescription(frame));
+ }.bind(this);
+
+ let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+ let target = TargetFactory.forTab(gBrowser.selectedTab);
+ gDevTools.showToolbox(target, "webconsole");
+
+ return l10n.lookup("calllogChromeStartReply");
+ },
+
+ valueToString: function(value) {
+ if (typeof value !== "object" || value === null)
+ return uneval(value);
+ return "[object " + value.class + "]";
+ },
+
+ callDescription: function(frame) {
+ let name = frame.callee.name || l10n.lookup("callLogChromeAnonFunction");
+ let args = frame.arguments.map(this.valueToString).join(", ");
+ return name + "(" + args + ")";
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "calllog chromestop",
+ description: l10n.lookup("calllogChromeStopDesc"),
+ get hidden() {
+ return gcli.hiddenByChromePref();
+ },
+ exec: function(args, context) {
+ let numDebuggers = chromeDebuggers.length;
+ if (numDebuggers == 0) {
+ return l10n.lookup("calllogChromeStopNoLogging");
+ }
+
+ for (let dbg of chromeDebuggers) {
+ dbg.onEnterFrame = undefined;
+ dbg.enabled = false;
+ }
+ for (let sandbox of sandboxes) {
+ Cu.nukeSandbox(sandbox);
+ }
+ chromeDebuggers = [];
+ sandboxes = [];
+
+ return l10n.lookupFormat("calllogChromeStopReply", [ numDebuggers ]);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/cmd.js b/devtools/shared/gcli/commands/cmd.js
new file mode 100644
index 000000000..1777ed960
--- /dev/null
+++ b/devtools/shared/gcli/commands/cmd.js
@@ -0,0 +1,178 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+
+const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+const { TextEncoder, TextDecoder } = Cu.import('resource://gre/modules/commonjs/toolkit/loader.js', {});
+const gcli = require("gcli/index");
+const l10n = require("gcli/l10n");
+
+loader.lazyGetter(this, "prefBranch", function() {
+ let prefService = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+ return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+});
+
+loader.lazyGetter(this, "supportsString", function() {
+ return Cc["@mozilla.org/supports-string;1"].createInstance(Ci.nsISupportsString);
+});
+
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+
+const PREF_DIR = "devtools.commands.dir";
+
+/**
+ * Load all the .mozcmd files in the directory pointed to by PREF_DIR
+ * @return A promise of an array of items suitable for gcli.addItems or
+ * using in gcli.addItemsByModule
+ */
+function loadItemsFromMozDir() {
+ let dirName = prefBranch.getComplexValue(PREF_DIR,
+ Ci.nsISupportsString).data.trim();
+ if (dirName == "") {
+ return Promise.resolve([]);
+ }
+
+ // replaces ~ with the home directory path in unix and windows
+ if (dirName.indexOf("~") == 0) {
+ let dirService = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ let homeDirFile = dirService.get("Home", Ci.nsIFile);
+ let homeDir = homeDirFile.path;
+ dirName = dirName.substr(1);
+ dirName = homeDir + dirName;
+ }
+
+ // statPromise resolves to nothing if dirName is a directory, or it
+ // rejects with an error message otherwise
+ let statPromise = OS.File.stat(dirName);
+ statPromise = statPromise.then(
+ function onSuccess(stat) {
+ if (!stat.isDir) {
+ throw new Error("'" + dirName + "' is not a directory.");
+ }
+ },
+ function onFailure(reason) {
+ if (reason instanceof OS.File.Error && reason.becauseNoSuchFile) {
+ throw new Error("'" + dirName + "' does not exist.");
+ } else {
+ throw reason;
+ }
+ }
+ );
+
+ // We need to return (a promise of) an array of items from the *.mozcmd
+ // files in dirName (which we can assume to be a valid directory now)
+ return statPromise.then(() => {
+ let itemPromises = [];
+
+ let iterator = new OS.File.DirectoryIterator(dirName);
+ let iterPromise = iterator.forEach(entry => {
+ if (entry.name.match(/.*\.mozcmd$/) && !entry.isDir) {
+ itemPromises.push(loadCommandFile(entry));
+ }
+ });
+
+ return iterPromise.then(() => {
+ iterator.close();
+ return Promise.all(itemPromises).then((itemsArray) => {
+ return itemsArray.reduce((prev, curr) => {
+ return prev.concat(curr);
+ }, []);
+ });
+ }, reason => { iterator.close(); throw reason; });
+ });
+}
+
+exports.mozDirLoader = function(name) {
+ return loadItemsFromMozDir().then(items => {
+ return { items: items };
+ });
+};
+
+/**
+ * Load the commands from a single file
+ * @param OS.File.DirectoryIterator.Entry entry The DirectoryIterator
+ * Entry of the file containing the commands that we should read
+ */
+function loadCommandFile(entry) {
+ let readPromise = OS.File.read(entry.path);
+ return readPromise = readPromise.then(array => {
+ let decoder = new TextDecoder();
+ let source = decoder.decode(array);
+ var principal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+
+ let sandbox = new Cu.Sandbox(principal, {
+ sandboxName: entry.path
+ });
+ let data = Cu.evalInSandbox(source, sandbox, "1.8", entry.name, 1);
+
+ if (!Array.isArray(data)) {
+ console.error("Command file '" + entry.name + "' does not have top level array.");
+ return;
+ }
+
+ return data;
+ });
+}
+
+exports.items = [
+ {
+ name: "cmd",
+ get hidden() {
+ return !prefBranch.prefHasUserValue(PREF_DIR);
+ },
+ description: l10n.lookup("cmdDesc")
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "cmd refresh",
+ description: l10n.lookup("cmdRefreshDesc"),
+ get hidden() {
+ return !prefBranch.prefHasUserValue(PREF_DIR);
+ },
+ exec: function(args, context) {
+ gcli.load();
+
+ let dirName = prefBranch.getComplexValue(PREF_DIR,
+ Ci.nsISupportsString).data.trim();
+ return l10n.lookupFormat("cmdStatus3", [ dirName ]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "cmd setdir",
+ description: l10n.lookup("cmdSetdirDesc"),
+ manual: l10n.lookup("cmdSetdirManual3"),
+ params: [
+ {
+ name: "directory",
+ description: l10n.lookup("cmdSetdirDirectoryDesc"),
+ type: {
+ name: "file",
+ filetype: "directory",
+ existing: "yes"
+ },
+ defaultValue: null
+ }
+ ],
+ returnType: "string",
+ get hidden() {
+ return true; // !prefBranch.prefHasUserValue(PREF_DIR);
+ },
+ exec: function(args, context) {
+ supportsString.data = args.directory;
+ prefBranch.setComplexValue(PREF_DIR, Ci.nsISupportsString, supportsString);
+
+ gcli.load();
+
+ return l10n.lookupFormat("cmdStatus3", [ args.directory ]);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/cookie.js b/devtools/shared/gcli/commands/cookie.js
new file mode 100644
index 000000000..f1680042f
--- /dev/null
+++ b/devtools/shared/gcli/commands/cookie.js
@@ -0,0 +1,300 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * XXX: bug 1221488 is required to make these commands run on the server.
+ * If we want these commands to run on remote devices/connections, they need to
+ * run on the server (runAt=server). Unfortunately, cookie commands not only
+ * need to run on the server, they also need to access to the parent process to
+ * retrieve and manipulate cookies via nsICookieManager2.
+ * However, server-running commands have no way of accessing the parent process
+ * for now.
+ *
+ * So, because these cookie commands, as of today, only run in the developer
+ * toolbar (the gcli command bar), and because this toolbar is only available on
+ * a local Firefox desktop tab (not in webide or the browser toolbox), we can
+ * make the commands run on the client.
+ * This way, they'll always run in the parent process.
+ */
+
+const { Ci, Cc } = require("chrome");
+const l10n = require("gcli/l10n");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "cookieMgr", function() {
+ return Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager2);
+});
+
+/**
+ * Check host value and remove port part as it is not used
+ * for storing cookies.
+ *
+ * Parameter will usually be `new URL(context.environment.target.url).host`
+ */
+function sanitizeHost(host) {
+ if (host == null || host == "") {
+ throw new Error(l10n.lookup("cookieListOutNonePage"));
+ }
+ return host.split(":")[0];
+}
+
+/**
+ * The cookie 'expires' value needs converting into something more readable.
+ *
+ * And the unit of expires is sec, the unit that in argument of Date() needs
+ * millisecond.
+ */
+function translateExpires(expires) {
+ if (expires == 0) {
+ return l10n.lookup("cookieListOutSession");
+ }
+
+ let expires_msec = expires * 1000;
+
+ return (new Date(expires_msec)).toLocaleString();
+}
+
+/**
+ * Check if a given cookie matches a given host
+ */
+function isCookieAtHost(cookie, host) {
+ if (cookie.host == null) {
+ return host == null;
+ }
+ if (cookie.host.startsWith(".")) {
+ return ("." + host).endsWith(cookie.host);
+ }
+ if (cookie.host === "") {
+ return host.startsWith("file://" + cookie.path);
+ }
+ return cookie.host == host;
+}
+
+exports.items = [
+ {
+ name: "cookie",
+ description: l10n.lookup("cookieDesc"),
+ manual: l10n.lookup("cookieManual")
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "cookie list",
+ description: l10n.lookup("cookieListDesc"),
+ manual: l10n.lookup("cookieListManual"),
+ returnType: "cookies",
+ exec: function(args, context) {
+ if (context.environment.target.isRemote) {
+ throw new Error("The cookie gcli commands only work in a local tab, " +
+ "see bug 1221488");
+ }
+ let host = new URL(context.environment.target.url).host;
+ let contentWindow = context.environment.window;
+ host = sanitizeHost(host);
+ let enm = cookieMgr.getCookiesFromHost(host, contentWindow.document.
+ nodePrincipal.
+ originAttributes);
+
+ let cookies = [];
+ while (enm.hasMoreElements()) {
+ let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
+ if (isCookieAtHost(cookie, host)) {
+ cookies.push({
+ host: cookie.host,
+ name: cookie.name,
+ value: cookie.value,
+ path: cookie.path,
+ expires: cookie.expires,
+ secure: cookie.secure,
+ httpOnly: cookie.httpOnly,
+ sameDomain: cookie.sameDomain
+ });
+ }
+ }
+
+ return cookies;
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "cookie remove",
+ description: l10n.lookup("cookieRemoveDesc"),
+ manual: l10n.lookup("cookieRemoveManual"),
+ params: [
+ {
+ name: "name",
+ type: "string",
+ description: l10n.lookup("cookieRemoveKeyDesc"),
+ }
+ ],
+ exec: function(args, context) {
+ if (context.environment.target.isRemote) {
+ throw new Error("The cookie gcli commands only work in a local tab, " +
+ "see bug 1221488");
+ }
+ let host = new URL(context.environment.target.url).host;
+ let contentWindow = context.environment.window;
+ host = sanitizeHost(host);
+ let enm = cookieMgr.getCookiesFromHost(host, contentWindow.document.
+ nodePrincipal.
+ originAttributes);
+
+ while (enm.hasMoreElements()) {
+ let cookie = enm.getNext().QueryInterface(Ci.nsICookie);
+ if (isCookieAtHost(cookie, host)) {
+ if (cookie.name == args.name) {
+ cookieMgr.remove(cookie.host, cookie.name, cookie.path,
+ false, cookie.originAttributes);
+ }
+ }
+ }
+ }
+ },
+ {
+ item: "converter",
+ from: "cookies",
+ to: "view",
+ exec: function(cookies, context) {
+ if (cookies.length == 0) {
+ let host = new URL(context.environment.target.url).host;
+ host = sanitizeHost(host);
+ let msg = l10n.lookupFormat("cookieListOutNoneHost", [ host ]);
+ return context.createView({ html: "<span>" + msg + "</span>" });
+ }
+
+ for (let cookie of cookies) {
+ cookie.expires = translateExpires(cookie.expires);
+
+ let noAttrs = !cookie.secure && !cookie.httpOnly && !cookie.sameDomain;
+ cookie.attrs = (cookie.secure ? "secure" : " ") +
+ (cookie.httpOnly ? "httpOnly" : " ") +
+ (cookie.sameDomain ? "sameDomain" : " ") +
+ (noAttrs ? l10n.lookup("cookieListOutNone") : " ");
+ }
+
+ return context.createView({
+ html:
+ "<ul class='gcli-cookielist-list'>" +
+ " <li foreach='cookie in ${cookies}'>" +
+ " <div>${cookie.name}=${cookie.value}</div>" +
+ " <table class='gcli-cookielist-detail'>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("cookieListOutHost") + "</td>" +
+ " <td>${cookie.host}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("cookieListOutPath") + "</td>" +
+ " <td>${cookie.path}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("cookieListOutExpires") + "</td>" +
+ " <td>${cookie.expires}</td>" +
+ " </tr>" +
+ " <tr>" +
+ " <td>" + l10n.lookup("cookieListOutAttributes") + "</td>" +
+ " <td>${cookie.attrs}</td>" +
+ " </tr>" +
+ " <tr><td colspan='2'>" +
+ " <span class='gcli-out-shortcut' onclick='${onclick}'" +
+ " data-command='cookie set ${cookie.name} '" +
+ " >" + l10n.lookup("cookieListOutEdit") + "</span>" +
+ " <span class='gcli-out-shortcut'" +
+ " onclick='${onclick}' ondblclick='${ondblclick}'" +
+ " data-command='cookie remove ${cookie.name}'" +
+ " >" + l10n.lookup("cookieListOutRemove") + "</span>" +
+ " </td></tr>" +
+ " </table>" +
+ " </li>" +
+ "</ul>",
+ data: {
+ options: { allowEval: true },
+ cookies: cookies,
+ onclick: context.update,
+ ondblclick: context.updateExec
+ }
+ });
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "cookie set",
+ description: l10n.lookup("cookieSetDesc"),
+ manual: l10n.lookup("cookieSetManual"),
+ params: [
+ {
+ name: "name",
+ type: "string",
+ description: l10n.lookup("cookieSetKeyDesc")
+ },
+ {
+ name: "value",
+ type: "string",
+ description: l10n.lookup("cookieSetValueDesc")
+ },
+ {
+ group: l10n.lookup("cookieSetOptionsDesc"),
+ params: [
+ {
+ name: "path",
+ type: { name: "string", allowBlank: true },
+ defaultValue: "/",
+ description: l10n.lookup("cookieSetPathDesc")
+ },
+ {
+ name: "domain",
+ type: "string",
+ defaultValue: null,
+ description: l10n.lookup("cookieSetDomainDesc")
+ },
+ {
+ name: "secure",
+ type: "boolean",
+ description: l10n.lookup("cookieSetSecureDesc")
+ },
+ {
+ name: "httpOnly",
+ type: "boolean",
+ description: l10n.lookup("cookieSetHttpOnlyDesc")
+ },
+ {
+ name: "session",
+ type: "boolean",
+ description: l10n.lookup("cookieSetSessionDesc")
+ },
+ {
+ name: "expires",
+ type: "string",
+ defaultValue: "Jan 17, 2038",
+ description: l10n.lookup("cookieSetExpiresDesc")
+ },
+ ]
+ }
+ ],
+ exec: function(args, context) {
+ if (context.environment.target.isRemote) {
+ throw new Error("The cookie gcli commands only work in a local tab, " +
+ "see bug 1221488");
+ }
+ let host = new URL(context.environment.target.url).host;
+ host = sanitizeHost(host);
+ let time = Date.parse(args.expires) / 1000;
+ let contentWindow = context.environment.window;
+ cookieMgr.add(args.domain ? "." + args.domain : host,
+ args.path ? args.path : "/",
+ args.name,
+ args.value,
+ args.secure,
+ args.httpOnly,
+ args.session,
+ time,
+ contentWindow.document.
+ nodePrincipal.
+ originAttributes);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/csscoverage.js b/devtools/shared/gcli/commands/csscoverage.js
new file mode 100644
index 000000000..ebbf0baca
--- /dev/null
+++ b/devtools/shared/gcli/commands/csscoverage.js
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+
+const domtemplate = require("gcli/util/domtemplate");
+const csscoverage = require("devtools/shared/fronts/csscoverage");
+const l10n = csscoverage.l10n;
+
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+
+loader.lazyImporter(this, "Chart", "resource://devtools/client/shared/widgets/Chart.jsm");
+
+/**
+ * The commands/converters for GCLI
+ */
+exports.items = [
+ {
+ name: "csscoverage",
+ hidden: true,
+ description: l10n.lookup("csscoverageDesc"),
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "csscoverage start",
+ hidden: true,
+ description: l10n.lookup("csscoverageStartDesc2"),
+ params: [
+ {
+ name: "noreload",
+ type: "boolean",
+ description: l10n.lookup("csscoverageStartNoReloadDesc"),
+ manual: l10n.lookup("csscoverageStartNoReloadManual")
+ }
+ ],
+ exec: function*(args, context) {
+ let usage = yield csscoverage.getUsage(context.environment.target);
+ if (usage == null) {
+ throw new Error(l10n.lookup("csscoverageNoRemoteError"));
+ }
+ yield usage.start(context.environment.chromeWindow,
+ context.environment.target, args.noreload);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "csscoverage stop",
+ hidden: true,
+ description: l10n.lookup("csscoverageStopDesc2"),
+ exec: function*(args, context) {
+ let target = context.environment.target;
+ let usage = yield csscoverage.getUsage(target);
+ if (usage == null) {
+ throw new Error(l10n.lookup("csscoverageNoRemoteError"));
+ }
+ yield usage.stop();
+ yield gDevTools.showToolbox(target, "styleeditor");
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "csscoverage oneshot",
+ hidden: true,
+ description: l10n.lookup("csscoverageOneShotDesc2"),
+ exec: function*(args, context) {
+ let target = context.environment.target;
+ let usage = yield csscoverage.getUsage(target);
+ if (usage == null) {
+ throw new Error(l10n.lookup("csscoverageNoRemoteError"));
+ }
+ yield usage.oneshot();
+ yield gDevTools.showToolbox(target, "styleeditor");
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "csscoverage toggle",
+ hidden: true,
+ description: l10n.lookup("csscoverageToggleDesc2"),
+ state: {
+ isChecked: function(target) {
+ return csscoverage.getUsage(target).then(usage => {
+ return usage.isRunning();
+ });
+ },
+ onChange: function(target, handler) {
+ csscoverage.getUsage(target).then(usage => {
+ this.handler = ev => { handler("state-change", ev); };
+ usage.on("state-change", this.handler);
+ });
+ },
+ offChange: function(target, handler) {
+ csscoverage.getUsage(target).then(usage => {
+ usage.off("state-change", this.handler);
+ this.handler = undefined;
+ });
+ },
+ },
+ exec: function*(args, context) {
+ let target = context.environment.target;
+ let usage = yield csscoverage.getUsage(target);
+ if (usage == null) {
+ throw new Error(l10n.lookup("csscoverageNoRemoteError"));
+ }
+
+ yield usage.toggle(context.environment.chromeWindow,
+ context.environment.target);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "csscoverage report",
+ hidden: true,
+ description: l10n.lookup("csscoverageReportDesc2"),
+ exec: function*(args, context) {
+ let usage = yield csscoverage.getUsage(context.environment.target);
+ if (usage == null) {
+ throw new Error(l10n.lookup("csscoverageNoRemoteError"));
+ }
+
+ return {
+ isTypedData: true,
+ type: "csscoveragePageReport",
+ data: yield usage.createPageReport()
+ };
+ }
+ },
+ {
+ item: "converter",
+ from: "csscoveragePageReport",
+ to: "dom",
+ exec: function*(csscoveragePageReport, context) {
+ let target = context.environment.target;
+
+ let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
+ let panel = toolbox.getCurrentPanel();
+
+ let host = panel._panelDoc.querySelector(".csscoverage-report");
+ let templ = panel._panelDoc.querySelector(".csscoverage-template");
+
+ templ = templ.cloneNode(true);
+ templ.hidden = false;
+
+ let data = {
+ preload: csscoveragePageReport.preload,
+ unused: csscoveragePageReport.unused,
+ summary: csscoveragePageReport.summary,
+ onback: () => {
+ // The back button clears and hides .csscoverage-report
+ while (host.hasChildNodes()) {
+ host.removeChild(host.firstChild);
+ }
+ host.hidden = true;
+ }
+ };
+
+ let addOnClick = rule => {
+ rule.onclick = () => {
+ panel.selectStyleSheet(rule.url, rule.start.line);
+ };
+ };
+
+ data.preload.forEach(page => {
+ page.rules.forEach(addOnClick);
+ });
+ data.unused.forEach(page => {
+ page.rules.forEach(addOnClick);
+ });
+
+ let options = { allowEval: true, stack: "styleeditor.xul" };
+ domtemplate.template(templ, data, options);
+
+ while (templ.hasChildNodes()) {
+ host.appendChild(templ.firstChild);
+ }
+
+ // Create a new chart.
+ let container = host.querySelector(".csscoverage-report-chart");
+ let chart = Chart.PieTable(panel._panelDoc, {
+ diameter: 200, // px
+ title: "CSS Usage",
+ data: [
+ { size: data.summary.preload, label: "Used Preload" },
+ { size: data.summary.used, label: "Used" },
+ { size: data.summary.unused, label: "Unused" }
+ ]
+ });
+ container.appendChild(chart.node);
+
+ host.hidden = false;
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/folder.js b/devtools/shared/gcli/commands/folder.js
new file mode 100644
index 000000000..22a51420d
--- /dev/null
+++ b/devtools/shared/gcli/commands/folder.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu, CC } = require("chrome");
+const Services = require("Services");
+const l10n = require("gcli/l10n");
+const dirService = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+
+function showFolder(aPath) {
+ let nsLocalFile = CC("@mozilla.org/file/local;1", "nsILocalFile",
+ "initWithPath");
+
+ try {
+ let file = new nsLocalFile(aPath);
+
+ if (file.exists()) {
+ file.reveal();
+ return l10n.lookupFormat("folderOpenDirResult", [aPath]);
+ } else {
+ return l10n.lookup("folderInvalidPath");
+ }
+ } catch (e) {
+ return l10n.lookup("folderInvalidPath");
+ }
+}
+
+exports.items = [
+ {
+ name: "folder",
+ description: l10n.lookup("folderDesc")
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "folder open",
+ description: l10n.lookup("folderOpenDesc"),
+ params: [
+ {
+ name: "path",
+ type: { name: "string", allowBlank: true },
+ defaultValue: "~",
+ description: l10n.lookup("folderOpenDir")
+ }
+ ],
+ returnType: "string",
+ exec: function(args, context) {
+ let dirName = args.path;
+
+ // replaces ~ with the home directory path in unix and windows
+ if (dirName.indexOf("~") == 0) {
+ let homeDirFile = dirService.get("Home", Ci.nsIFile);
+ let homeDir = homeDirFile.path;
+ dirName = dirName.substr(1);
+ dirName = homeDir + dirName;
+ }
+
+ return showFolder(dirName);
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "folder openprofile",
+ description: l10n.lookup("folderOpenProfileDesc"),
+ returnType: "string",
+ exec: function(args, context) {
+ // Get the profile directory.
+ let currProfD = Services.dirsvc.get("ProfD", Ci.nsIFile);
+ let profileDir = currProfD.path;
+ return showFolder(profileDir);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/highlight.js b/devtools/shared/gcli/commands/highlight.js
new file mode 100644
index 000000000..cc2353b3b
--- /dev/null
+++ b/devtools/shared/gcli/commands/highlight.js
@@ -0,0 +1,158 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const l10n = require("gcli/l10n");
+require("devtools/server/actors/inspector");
+const {
+ BoxModelHighlighter,
+ HighlighterEnvironment
+} = require("devtools/server/actors/highlighters");
+
+const {PluralForm} = require("devtools/shared/plural-form");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/shared/locales/gclicommands.properties");
+
+// How many maximum nodes can be highlighted in parallel
+const MAX_HIGHLIGHTED_ELEMENTS = 100;
+
+// Store the environment object used to create highlighters so it can be
+// destroyed later.
+var highlighterEnv;
+
+// Stores the highlighters instances so they can be destroyed later.
+// also export them so tests can access those and assert they got created
+// correctly.
+exports.highlighters = [];
+
+/**
+ * Destroy all existing highlighters
+ */
+function unhighlightAll() {
+ for (let highlighter of exports.highlighters) {
+ highlighter.destroy();
+ }
+ exports.highlighters.length = 0;
+
+ if (highlighterEnv) {
+ highlighterEnv.destroy();
+ highlighterEnv = null;
+ }
+}
+
+exports.items = [
+ {
+ item: "command",
+ runAt: "server",
+ name: "highlight",
+ description: l10n.lookup("highlightDesc"),
+ manual: l10n.lookup("highlightManual"),
+ params: [
+ {
+ name: "selector",
+ type: "nodelist",
+ description: l10n.lookup("highlightSelectorDesc"),
+ manual: l10n.lookup("highlightSelectorManual")
+ },
+ {
+ group: l10n.lookup("highlightOptionsDesc"),
+ params: [
+ {
+ name: "hideguides",
+ type: "boolean",
+ description: l10n.lookup("highlightHideGuidesDesc"),
+ manual: l10n.lookup("highlightHideGuidesManual")
+ },
+ {
+ name: "showinfobar",
+ type: "boolean",
+ description: l10n.lookup("highlightShowInfoBarDesc"),
+ manual: l10n.lookup("highlightShowInfoBarManual")
+ },
+ {
+ name: "showall",
+ type: "boolean",
+ description: l10n.lookup("highlightShowAllDesc"),
+ manual: l10n.lookup("highlightShowAllManual")
+ },
+ {
+ name: "region",
+ type: {
+ name: "selection",
+ data: ["content", "padding", "border", "margin"]
+ },
+ description: l10n.lookup("highlightRegionDesc"),
+ manual: l10n.lookup("highlightRegionManual"),
+ defaultValue: "border"
+ },
+ {
+ name: "fill",
+ type: "string",
+ description: l10n.lookup("highlightFillDesc"),
+ manual: l10n.lookup("highlightFillManual"),
+ defaultValue: null
+ },
+ {
+ name: "keep",
+ type: "boolean",
+ description: l10n.lookup("highlightKeepDesc"),
+ manual: l10n.lookup("highlightKeepManual")
+ }
+ ]
+ }
+ ],
+ exec: function(args, context) {
+ // Remove all existing highlighters unless told otherwise
+ if (!args.keep) {
+ unhighlightAll();
+ }
+
+ let env = context.environment;
+ highlighterEnv = new HighlighterEnvironment();
+ highlighterEnv.initFromWindow(env.window);
+
+ // Unhighlight on navigate
+ highlighterEnv.once("will-navigate", unhighlightAll);
+
+ let i = 0;
+ for (let node of args.selector) {
+ if (!args.showall && i >= MAX_HIGHLIGHTED_ELEMENTS) {
+ break;
+ }
+
+ let highlighter = new BoxModelHighlighter(highlighterEnv);
+ if (args.fill) {
+ highlighter.regionFill[args.region] = args.fill;
+ }
+ highlighter.show(node, {
+ region: args.region,
+ hideInfoBar: !args.showinfobar,
+ hideGuides: args.hideguides,
+ showOnly: args.region
+ });
+ exports.highlighters.push(highlighter);
+ i++;
+ }
+
+ let highlightText = L10N.getStr("highlightOutputConfirm2");
+ let output = PluralForm.get(args.selector.length, highlightText)
+ .replace("%1$S", args.selector.length);
+ if (args.selector.length > i) {
+ output = l10n.lookupFormat("highlightOutputMaxReached",
+ ["" + args.selector.length, "" + i]);
+ }
+
+ return output;
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "unhighlight",
+ description: l10n.lookup("unhighlightDesc"),
+ manual: l10n.lookup("unhighlightManual"),
+ exec: unhighlightAll
+ }
+];
diff --git a/devtools/shared/gcli/commands/index.js b/devtools/shared/gcli/commands/index.js
new file mode 100644
index 000000000..8fe77482e
--- /dev/null
+++ b/devtools/shared/gcli/commands/index.js
@@ -0,0 +1,179 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createSystem, connectFront, disconnectFront } = require("gcli/system");
+const { GcliFront } = require("devtools/shared/fronts/gcli");
+
+/**
+ * This is the basic list of modules that should be loaded into each
+ * requisition instance whether server side or client side
+ */
+exports.baseModules = [
+ "gcli/types/delegate",
+ "gcli/types/selection",
+ "gcli/types/array",
+
+ "gcli/types/boolean",
+ "gcli/types/command",
+ "gcli/types/date",
+ "gcli/types/file",
+ "gcli/types/javascript",
+ "gcli/types/node",
+ "gcli/types/number",
+ "gcli/types/resource",
+ "gcli/types/setting",
+ "gcli/types/string",
+ "gcli/types/union",
+ "gcli/types/url",
+
+ "gcli/fields/fields",
+ "gcli/fields/delegate",
+ "gcli/fields/selection",
+
+ "gcli/ui/focus",
+ "gcli/ui/intro",
+
+ "gcli/converters/converters",
+ "gcli/converters/basic",
+ "gcli/converters/terminal",
+
+ "gcli/languages/command",
+ "gcli/languages/javascript",
+
+ "gcli/commands/clear",
+ "gcli/commands/context",
+ "gcli/commands/help",
+ "gcli/commands/pref",
+];
+
+/**
+ * Some commands belong to a tool (see getToolModules). This is a list of the
+ * modules that are *not* owned by a tool.
+ */
+exports.devtoolsModules = [
+ "devtools/shared/gcli/commands/addon",
+ "devtools/shared/gcli/commands/appcache",
+ "devtools/shared/gcli/commands/calllog",
+ "devtools/shared/gcli/commands/cmd",
+ "devtools/shared/gcli/commands/cookie",
+ "devtools/shared/gcli/commands/csscoverage",
+ "devtools/shared/gcli/commands/folder",
+ "devtools/shared/gcli/commands/highlight",
+ "devtools/shared/gcli/commands/inject",
+ "devtools/shared/gcli/commands/jsb",
+ "devtools/shared/gcli/commands/listen",
+ "devtools/shared/gcli/commands/mdn",
+ "devtools/shared/gcli/commands/measure",
+ "devtools/shared/gcli/commands/media",
+ "devtools/shared/gcli/commands/pagemod",
+ "devtools/shared/gcli/commands/paintflashing",
+ "devtools/shared/gcli/commands/qsa",
+ "devtools/shared/gcli/commands/restart",
+ "devtools/shared/gcli/commands/rulers",
+ "devtools/shared/gcli/commands/screenshot",
+ "devtools/shared/gcli/commands/security",
+];
+
+/**
+ * Register commands from tools with 'command: [ "some/module" ]' definitions.
+ * The map/reduce incantation squashes the array of arrays to a single array.
+ */
+try {
+ const { defaultTools } = require("devtools/client/definitions");
+ exports.devtoolsToolModules = defaultTools.map(def => def.commands || [])
+ .reduce((prev, curr) => prev.concat(curr), []);
+} catch (e) {
+ // "devtools/client/definitions" is only accessible from Firefox
+ exports.devtoolsToolModules = [];
+}
+
+/**
+ * Register commands from toolbox buttons with 'command: [ "some/module" ]'
+ * definitions. The map/reduce incantation squashes the array of arrays to a
+ * single array.
+ */
+try {
+ const { ToolboxButtons } = require("devtools/client/definitions");
+ exports.devtoolsButtonModules = ToolboxButtons.map(def => def.commands || [])
+ .reduce((prev, curr) => prev.concat(curr), []);
+} catch (e) {
+ // "devtools/client/definitions" is only accessible from Firefox
+ exports.devtoolsButtonModules = [];
+}
+
+/**
+ * Add modules to a system for use in a content process (but don't call load)
+ */
+exports.addAllItemsByModule = function(system) {
+ system.addItemsByModule(exports.baseModules, { delayedLoad: true });
+ system.addItemsByModule(exports.devtoolsModules, { delayedLoad: true });
+ system.addItemsByModule(exports.devtoolsToolModules, { delayedLoad: true });
+ system.addItemsByModule(exports.devtoolsButtonModules, { delayedLoad: true });
+
+ const { mozDirLoader } = require("devtools/shared/gcli/commands/cmd");
+ system.addItemsByModule("mozcmd", { delayedLoad: true, loader: mozDirLoader });
+};
+
+/**
+ * This is WeakMap<Target, Links> where Links is an object that looks like
+ * { refs: number, promise: Promise<System>, front: GcliFront }
+ */
+var linksForTarget = new WeakMap();
+
+/**
+ * The toolbox uses the following properties on a command to allow it to be
+ * added to the toolbox toolbar
+ */
+var customProperties = [ "buttonId", "buttonClass", "tooltipText" ];
+
+/**
+ * Create a system which connects to a GCLI in a remote target
+ * @return Promise<System> for the given target
+ */
+exports.getSystem = function(target) {
+ const existingLinks = linksForTarget.get(target);
+ if (existingLinks != null) {
+ existingLinks.refs++;
+ return existingLinks.promise;
+ }
+
+ const system = createSystem({ location: "client" });
+
+ exports.addAllItemsByModule(system);
+
+ // Load the client system
+ const links = {
+ refs: 1,
+ system,
+ promise: system.load().then(() => {
+ return GcliFront.create(target).then(front => {
+ links.front = front;
+ return connectFront(system, front, customProperties).then(() => system);
+ });
+ })
+ };
+
+ linksForTarget.set(target, links);
+ return links.promise;
+};
+
+/**
+ * Someone that called getSystem doesn't need it any more, so decrement the
+ * count of users of the system for that target, and destroy if needed
+ */
+exports.releaseSystem = function(target) {
+ const links = linksForTarget.get(target);
+ if (links == null) {
+ throw new Error("releaseSystem called for unknown target");
+ }
+
+ links.refs--;
+ if (links.refs === 0) {
+ disconnectFront(links.system, links.front);
+ links.system.destroy();
+ linksForTarget.delete(target);
+ }
+};
diff --git a/devtools/shared/gcli/commands/inject.js b/devtools/shared/gcli/commands/inject.js
new file mode 100644
index 000000000..85e995eed
--- /dev/null
+++ b/devtools/shared/gcli/commands/inject.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Services = require("Services");
+const { listenOnce } = require("devtools/shared/async-utils");
+const l10n = require("gcli/l10n");
+
+exports.items = [
+ {
+ item: "command",
+ runAt: "server",
+ name: "inject",
+ description: l10n.lookup("injectDesc"),
+ manual: l10n.lookup("injectManual2"),
+ params: [{
+ name: "library",
+ type: {
+ name: "union",
+ alternatives: [
+ {
+ name: "selection",
+ lookup: [
+ {
+ name: "jQuery",
+ value: {
+ name: "jQuery",
+ src: Services.prefs.getCharPref("devtools.gcli.jquerySrc")
+ }
+ },
+ {
+ name: "lodash",
+ value: {
+ name: "lodash",
+ src: Services.prefs.getCharPref("devtools.gcli.lodashSrc")
+ }
+ },
+ {
+ name: "underscore",
+ value: {
+ name: "underscore",
+ src: Services.prefs.getCharPref("devtools.gcli.underscoreSrc")
+ }
+ }
+ ]
+ },
+ {
+ name: "url"
+ }
+ ]
+ },
+ description: l10n.lookup("injectLibraryDesc")
+ }],
+ exec: function*(args, context) {
+ let document = context.environment.document;
+ let library = args.library;
+ let name = (library.type === "selection") ?
+ library.selection.name : library.url;
+ let src = (library.type === "selection") ?
+ library.selection.src : library.url;
+
+ if (context.environment.window.location.protocol == "https:") {
+ src = src.replace(/^http:/, "https:");
+ }
+
+ try {
+ // Check if URI is valid
+ Services.io.newURI(src, null, null);
+ } catch(e) {
+ return l10n.lookupFormat("injectFailed", [name]);
+ }
+
+ let newSource = document.createElement("script");
+ newSource.setAttribute("src", src);
+
+ let loadPromise = listenOnce(newSource, "load");
+ document.head.appendChild(newSource);
+
+ yield loadPromise;
+
+ return l10n.lookupFormat("injectLoaded", [name]);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/jsb.js b/devtools/shared/gcli/commands/jsb.js
new file mode 100644
index 000000000..b56e079d2
--- /dev/null
+++ b/devtools/shared/gcli/commands/jsb.js
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+const XMLHttpRequest = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"];
+
+loader.lazyImporter(this, "Preferences", "resource://gre/modules/Preferences.jsm");
+loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
+
+loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
+
+exports.items = [
+ {
+ item: "command",
+ runAt: "client",
+ name: "jsb",
+ description: l10n.lookup("jsbDesc"),
+ returnValue:"string",
+ params: [
+ {
+ name: "url",
+ type: "string",
+ description: l10n.lookup("jsbUrlDesc")
+ },
+ {
+ group: l10n.lookup("jsbOptionsDesc"),
+ params: [
+ {
+ name: "indentSize",
+ type: "number",
+ description: l10n.lookup("jsbIndentSizeDesc"),
+ manual: l10n.lookup("jsbIndentSizeManual"),
+ defaultValue: Preferences.get("devtools.editor.tabsize", 2),
+ },
+ {
+ name: "indentChar",
+ type: {
+ name: "selection",
+ lookup: [
+ { name: "space", value: " " },
+ { name: "tab", value: "\t" }
+ ]
+ },
+ description: l10n.lookup("jsbIndentCharDesc"),
+ manual: l10n.lookup("jsbIndentCharManual"),
+ defaultValue: " ",
+ },
+ {
+ name: "doNotPreserveNewlines",
+ type: "boolean",
+ description: l10n.lookup("jsbDoNotPreserveNewlinesDesc")
+ },
+ {
+ name: "preserveMaxNewlines",
+ type: "number",
+ description: l10n.lookup("jsbPreserveMaxNewlinesDesc"),
+ manual: l10n.lookup("jsbPreserveMaxNewlinesManual"),
+ defaultValue: -1
+ },
+ {
+ name: "jslintHappy",
+ type: "boolean",
+ description: l10n.lookup("jsbJslintHappyDesc"),
+ manual: l10n.lookup("jsbJslintHappyManual")
+ },
+ {
+ name: "braceStyle",
+ type: {
+ name: "selection",
+ data: ["collapse", "expand", "end-expand", "expand-strict"]
+ },
+ description: l10n.lookup("jsbBraceStyleDesc2"),
+ manual: l10n.lookup("jsbBraceStyleManual2"),
+ defaultValue: "collapse"
+ },
+ {
+ name: "noSpaceBeforeConditional",
+ type: "boolean",
+ description: l10n.lookup("jsbNoSpaceBeforeConditionalDesc")
+ },
+ {
+ name: "unescapeStrings",
+ type: "boolean",
+ description: l10n.lookup("jsbUnescapeStringsDesc"),
+ manual: l10n.lookup("jsbUnescapeStringsManual")
+ }
+ ]
+ }
+ ],
+ exec: function(args, context) {
+ let opts = {
+ indent_size: args.indentSize,
+ indent_char: args.indentChar,
+ preserve_newlines: !args.doNotPreserveNewlines,
+ max_preserve_newlines: args.preserveMaxNewlines == -1 ?
+ undefined : args.preserveMaxNewlines,
+ jslint_happy: args.jslintHappy,
+ brace_style: args.braceStyle,
+ space_before_conditional: !args.noSpaceBeforeConditional,
+ unescape_strings: args.unescapeStrings
+ };
+
+ let xhr = new XMLHttpRequest();
+
+ let deferred = context.defer();
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200 || xhr.status == 0) {
+ let result = beautify.js(xhr.responseText, opts);
+
+ ScratchpadManager.openScratchpad({text: result});
+
+ deferred.resolve();
+ } else {
+ deferred.reject("Unable to load page to beautify: " + args.url + " " +
+ xhr.status + " " + xhr.statusText);
+ }
+ };
+ }
+ try {
+ xhr.open("GET", args.url, true);
+ xhr.send(null);
+ } catch(e) {
+ return l10n.lookup("jsbInvalidURL");
+ }
+ return deferred.promise;
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/listen.js b/devtools/shared/gcli/commands/listen.js
new file mode 100644
index 000000000..7878577fb
--- /dev/null
+++ b/devtools/shared/gcli/commands/listen.js
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci } = require("chrome");
+const Services = require("Services");
+const l10n = require("gcli/l10n");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "DevToolsLoader",
+ "resource://devtools/shared/Loader.jsm");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+XPCOMUtils.defineLazyGetter(this, "debuggerServer", () => {
+ // Create a separate loader instance, so that we can be sure to receive
+ // a separate instance of the DebuggingServer from the rest of the
+ // devtools. This allows us to safely use the tools against even the
+ // actors and DebuggingServer itself, especially since we can mark
+ // serverLoader as invisible to the debugger (unlike the usual loader
+ // settings).
+ let serverLoader = new DevToolsLoader();
+ serverLoader.invisibleToDebugger = true;
+ let { DebuggerServer: debuggerServer } = serverLoader.require("devtools/server/main");
+ debuggerServer.init();
+ debuggerServer.addBrowserActors();
+ debuggerServer.allowChromeProcess = !l10n.hiddenByChromePref();
+ return debuggerServer;
+});
+
+exports.items = [
+ {
+ item: "command",
+ runAt: "client",
+ name: "listen",
+ description: l10n.lookup("listenDesc"),
+ manual: l10n.lookupFormat("listenManual2", [ BRAND_SHORT_NAME ]),
+ params: [
+ {
+ name: "port",
+ type: "number",
+ get defaultValue() {
+ return Services.prefs.getIntPref("devtools.debugger.remote-port");
+ },
+ description: l10n.lookup("listenPortDesc"),
+ },
+ {
+ name: "protocol",
+ get defaultValue() {
+ let webSocket = Services.prefs
+ .getBoolPref("devtools.debugger.remote-websocket");
+ let protocol;
+ if (webSocket === true) {
+ protocol = "websocket";
+ } else {
+ protocol = "mozilla-rdp";
+ }
+ return protocol;
+ },
+ type: {
+ name: "selection",
+ data: [ "mozilla-rdp", "websocket"],
+ },
+ description: l10n.lookup("listenProtocolDesc"),
+ },
+ ],
+ exec: function (args, context) {
+ var listener = debuggerServer.createListener();
+ if (!listener) {
+ throw new Error(l10n.lookup("listenDisabledOutput"));
+ }
+
+ let webSocket = false;
+ if (args.protocol === "websocket") {
+ webSocket = true;
+ } else if (args.protocol === "mozilla-rdp") {
+ webSocket = false;
+ }
+
+ listener.portOrPath = args.port;
+ listener.webSocket = webSocket;
+ listener.open();
+
+ if (debuggerServer.initialized) {
+ return l10n.lookupFormat("listenInitOutput", [ "" + args.port ]);
+ }
+
+ return l10n.lookup("listenNoInitOutput");
+ },
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "unlisten",
+ description: l10n.lookup("unlistenDesc"),
+ manual: l10n.lookup("unlistenManual"),
+ exec: function (args, context) {
+ debuggerServer.closeAllListeners();
+ return l10n.lookup("unlistenOutput");
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/mdn.js b/devtools/shared/gcli/commands/mdn.js
new file mode 100644
index 000000000..57e582e40
--- /dev/null
+++ b/devtools/shared/gcli/commands/mdn.js
@@ -0,0 +1,83 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const l10n = require("gcli/l10n");
+
+var MdnDocsWidget;
+try {
+ MdnDocsWidget = require("devtools/client/shared/widgets/MdnDocsWidget");
+} catch (e) {
+ // DevTools MdnDocsWidget only available in Firefox Desktop
+}
+
+exports.items = [{
+ name: "mdn",
+ description: l10n.lookup("mdnDesc")
+}, {
+ item: "command",
+ runAt: "client",
+ name: "mdn css",
+ description: l10n.lookup("mdnCssDesc"),
+ returnType: "cssPropertyOutput",
+ params: [{
+ name: "property",
+ type: { name: "string" },
+ defaultValue: null,
+ description: l10n.lookup("mdnCssProp")
+ }],
+ exec: function(args) {
+ if (!MdnDocsWidget) {
+ return null;
+ }
+
+ return MdnDocsWidget.getCssDocs(args.property).then(result => {
+ return {
+ data: result,
+ url: MdnDocsWidget.PAGE_LINK_URL + args.property,
+ property: args.property
+ };
+ }, error => {
+ return { error, property: args.property };
+ });
+ }
+}, {
+ item: "converter",
+ from: "cssPropertyOutput",
+ to: "dom",
+ exec: function(result, context) {
+ let propertyName = result.property;
+
+ let document = context.document;
+ let root = document.createElement("div");
+
+ if (result.error) {
+ // The css property specified doesn't exist.
+ root.appendChild(document.createTextNode(
+ l10n.lookupFormat("mdnCssPropertyNotFound", [ propertyName ]) +
+ " (" + result.error + ")"));
+ } else {
+ let title = document.createElement("h2");
+ title.textContent = propertyName;
+ root.appendChild(title);
+
+ let link = document.createElement("p");
+ link.classList.add("gcli-mdn-url");
+ link.textContent = l10n.lookup("mdnCssVisitPage");
+ root.appendChild(link);
+
+ link.addEventListener("click", () => {
+ let mainWindow = context.environment.chromeWindow;
+ mainWindow.openUILinkIn(result.url, "tab");
+ });
+
+ let summary = document.createElement("p");
+ summary.textContent = result.data.summary;
+ root.appendChild(summary);
+ }
+
+ return root;
+ }
+}];
diff --git a/devtools/shared/gcli/commands/measure.js b/devtools/shared/gcli/commands/measure.js
new file mode 100644
index 000000000..7f6233a95
--- /dev/null
+++ b/devtools/shared/gcli/commands/measure.js
@@ -0,0 +1,112 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ /* globals getOuterId, getBrowserForTab */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const eventEmitter = new EventEmitter();
+const events = require("sdk/event/core");
+
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+const l10n = require("gcli/l10n");
+require("devtools/server/actors/inspector");
+const { MeasuringToolHighlighter, HighlighterEnvironment } =
+ require("devtools/server/actors/highlighters");
+
+const highlighters = new WeakMap();
+const visibleHighlighters = new Set();
+
+const isCheckedFor = (tab) =>
+ tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
+
+exports.items = [
+ // The client measure command is used to maintain the toolbar button state
+ // only and redirects to the server command to actually toggle the measuring
+ // tool (see `measure_server` below).
+ {
+ name: "measure",
+ runAt: "client",
+ description: l10n.lookup("measureDesc"),
+ manual: l10n.lookup("measureManual"),
+ buttonId: "command-button-measure",
+ buttonClass: "command-button command-button-invertable",
+ tooltipText: l10n.lookup("measureTooltip"),
+ state: {
+ isChecked: ({_tab}) => isCheckedFor(_tab),
+ onChange: (target, handler) => eventEmitter.on("changed", handler),
+ offChange: (target, handler) => eventEmitter.off("changed", handler)
+ },
+ exec: function*(args, context) {
+ let { target } = context.environment;
+
+ // Pipe the call to the server command.
+ let response = yield context.updateExec("measure_server");
+ let { visible, id } = response.data;
+
+ if (visible) {
+ visibleHighlighters.add(id);
+ } else {
+ visibleHighlighters.delete(id);
+ }
+
+ eventEmitter.emit("changed", { target });
+
+ // Toggle off the button when the page navigates because the measuring
+ // tool is removed automatically by the MeasuringToolHighlighter on the
+ // server then.
+ let onNavigate = () => {
+ visibleHighlighters.delete(id);
+ eventEmitter.emit("changed", { target });
+ };
+ target.off("will-navigate", onNavigate);
+ target.once("will-navigate", onNavigate);
+ }
+ },
+ // The server measure command is hidden by default, it's just used by the
+ // client command.
+ {
+ name: "measure_server",
+ runAt: "server",
+ hidden: true,
+ returnType: "highlighterVisibility",
+ exec: function(args, context) {
+ let env = context.environment;
+ let { document } = env;
+ let id = getOuterId(env.window);
+
+ // Calling the command again after the measuring tool has been shown once,
+ // hides it.
+ if (highlighters.has(document)) {
+ let { highlighter } = highlighters.get(document);
+ highlighter.destroy();
+ return {visible: false, id};
+ }
+
+ // Otherwise, display the measuring tool.
+ let environment = new HighlighterEnvironment();
+ environment.initFromWindow(env.window);
+ let highlighter = new MeasuringToolHighlighter(environment);
+
+ // Store the instance of the measuring tool highlighter for this document
+ // so we can hide it later.
+ highlighters.set(document, { highlighter, environment });
+
+ // Listen to the highlighter's destroy event which may happen if the
+ // window is refreshed or closed with the measuring tool shown.
+ events.once(highlighter, "destroy", () => {
+ if (highlighters.has(document)) {
+ let { environment } = highlighters.get(document);
+ environment.destroy();
+ highlighters.delete(document);
+ }
+ });
+
+ highlighter.show();
+ return {visible: true, id};
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/media.js b/devtools/shared/gcli/commands/media.js
new file mode 100644
index 000000000..908e9eb5e
--- /dev/null
+++ b/devtools/shared/gcli/commands/media.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Ci} = require("chrome");
+const l10n = require("gcli/l10n");
+
+function getContentViewer(context) {
+ let {window} = context.environment;
+ return window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell)
+ .contentViewer;
+}
+
+exports.items = [
+ {
+ name: "media",
+ description: l10n.lookup("mediaDesc")
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "media emulate",
+ description: l10n.lookup("mediaEmulateDesc"),
+ manual: l10n.lookup("mediaEmulateManual"),
+ params: [
+ {
+ name: "type",
+ description: l10n.lookup("mediaEmulateType"),
+ type: {
+ name: "selection",
+ data: [
+ "braille", "embossed", "handheld", "print", "projection",
+ "screen", "speech", "tty", "tv"
+ ]
+ }
+ }
+ ],
+ exec: function(args, context) {
+ let contentViewer = getContentViewer(context);
+ contentViewer.emulateMedium(args.type);
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "media reset",
+ description: l10n.lookup("mediaResetDesc"),
+ exec: function(args, context) {
+ let contentViewer = getContentViewer(context);
+ contentViewer.stopEmulatingMedium();
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/moz.build b/devtools/shared/gcli/commands/moz.build
new file mode 100644
index 000000000..e5f3fe91c
--- /dev/null
+++ b/devtools/shared/gcli/commands/moz.build
@@ -0,0 +1,30 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'addon.js',
+ 'appcache.js',
+ 'calllog.js',
+ 'cmd.js',
+ 'cookie.js',
+ 'csscoverage.js',
+ 'folder.js',
+ 'highlight.js',
+ 'index.js',
+ 'inject.js',
+ 'jsb.js',
+ 'listen.js',
+ 'mdn.js',
+ 'measure.js',
+ 'media.js',
+ 'pagemod.js',
+ 'paintflashing.js',
+ 'qsa.js',
+ 'restart.js',
+ 'rulers.js',
+ 'screenshot.js',
+ 'security.js',
+)
diff --git a/devtools/shared/gcli/commands/pagemod.js b/devtools/shared/gcli/commands/pagemod.js
new file mode 100644
index 000000000..184ab1ea3
--- /dev/null
+++ b/devtools/shared/gcli/commands/pagemod.js
@@ -0,0 +1,276 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+
+exports.items = [
+ {
+ name: "pagemod",
+ description: l10n.lookup("pagemodDesc"),
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "pagemod replace",
+ description: l10n.lookup("pagemodReplaceDesc"),
+ params: [
+ {
+ name: "search",
+ type: "string",
+ description: l10n.lookup("pagemodReplaceSearchDesc"),
+ },
+ {
+ name: "replace",
+ type: "string",
+ description: l10n.lookup("pagemodReplaceReplaceDesc"),
+ },
+ {
+ name: "ignoreCase",
+ type: "boolean",
+ description: l10n.lookup("pagemodReplaceIgnoreCaseDesc"),
+ },
+ {
+ name: "selector",
+ type: "string",
+ description: l10n.lookup("pagemodReplaceSelectorDesc"),
+ defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)",
+ },
+ {
+ name: "root",
+ type: "node",
+ description: l10n.lookup("pagemodReplaceRootDesc"),
+ defaultValue: null,
+ },
+ {
+ name: "attrOnly",
+ type: "boolean",
+ description: l10n.lookup("pagemodReplaceAttrOnlyDesc"),
+ },
+ {
+ name: "contentOnly",
+ type: "boolean",
+ description: l10n.lookup("pagemodReplaceContentOnlyDesc"),
+ },
+ {
+ name: "attributes",
+ type: "string",
+ description: l10n.lookup("pagemodReplaceAttributesDesc"),
+ defaultValue: null,
+ },
+ ],
+ // Make a given string safe to use in a regular expression.
+ escapeRegex: function(aString) {
+ return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+ },
+ exec: function(args, context) {
+ let searchTextNodes = !args.attrOnly;
+ let searchAttributes = !args.contentOnly;
+ let regexOptions = args.ignoreCase ? "ig" : "g";
+ let search = new RegExp(this.escapeRegex(args.search), regexOptions);
+ let attributeRegex = null;
+ if (args.attributes) {
+ attributeRegex = new RegExp(args.attributes, regexOptions);
+ }
+
+ let root = args.root || context.environment.document;
+ let elements = root.querySelectorAll(args.selector);
+ elements = Array.prototype.slice.call(elements);
+
+ let replacedTextNodes = 0;
+ let replacedAttributes = 0;
+
+ function replaceAttribute() {
+ replacedAttributes++;
+ return args.replace;
+ }
+ function replaceTextNode() {
+ replacedTextNodes++;
+ return args.replace;
+ }
+
+ for (let i = 0; i < elements.length; i++) {
+ let element = elements[i];
+ if (searchTextNodes) {
+ for (let y = 0; y < element.childNodes.length; y++) {
+ let node = element.childNodes[y];
+ if (node.nodeType == node.TEXT_NODE) {
+ node.textContent = node.textContent.replace(search, replaceTextNode);
+ }
+ }
+ }
+
+ if (searchAttributes) {
+ if (!element.attributes) {
+ continue;
+ }
+ for (let y = 0; y < element.attributes.length; y++) {
+ let attr = element.attributes[y];
+ if (!attributeRegex || attributeRegex.test(attr.name)) {
+ attr.value = attr.value.replace(search, replaceAttribute);
+ }
+ }
+ }
+ }
+
+ return l10n.lookupFormat("pagemodReplaceResult",
+ [elements.length, replacedTextNodes,
+ replacedAttributes]);
+ }
+ },
+ {
+ name: "pagemod remove",
+ description: l10n.lookup("pagemodRemoveDesc"),
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "pagemod remove element",
+ description: l10n.lookup("pagemodRemoveElementDesc"),
+ params: [
+ {
+ name: "search",
+ type: "string",
+ description: l10n.lookup("pagemodRemoveElementSearchDesc"),
+ },
+ {
+ name: "root",
+ type: "node",
+ description: l10n.lookup("pagemodRemoveElementRootDesc"),
+ defaultValue: null,
+ },
+ {
+ name: "stripOnly",
+ type: "boolean",
+ description: l10n.lookup("pagemodRemoveElementStripOnlyDesc"),
+ },
+ {
+ name: "ifEmptyOnly",
+ type: "boolean",
+ description: l10n.lookup("pagemodRemoveElementIfEmptyOnlyDesc"),
+ },
+ ],
+ exec: function(args, context) {
+ let root = args.root || context.environment.document;
+ let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
+
+ let removed = 0;
+ for (let i = 0; i < elements.length; i++) {
+ let element = elements[i];
+ let parentNode = element.parentNode;
+ if (!parentNode || !element.removeChild) {
+ continue;
+ }
+ if (args.stripOnly) {
+ while (element.hasChildNodes()) {
+ parentNode.insertBefore(element.childNodes[0], element);
+ }
+ }
+ if (!args.ifEmptyOnly || !element.hasChildNodes()) {
+ element.parentNode.removeChild(element);
+ removed++;
+ }
+ }
+
+ return l10n.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements",
+ [elements.length, removed]);
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "pagemod remove attribute",
+ description: l10n.lookup("pagemodRemoveAttributeDesc"),
+ params: [
+ {
+ name: "searchAttributes",
+ type: "string",
+ description: l10n.lookup("pagemodRemoveAttributeSearchAttributesDesc"),
+ },
+ {
+ name: "searchElements",
+ type: "string",
+ description: l10n.lookup("pagemodRemoveAttributeSearchElementsDesc"),
+ },
+ {
+ name: "root",
+ type: "node",
+ description: l10n.lookup("pagemodRemoveAttributeRootDesc"),
+ defaultValue: null,
+ },
+ {
+ name: "ignoreCase",
+ type: "boolean",
+ description: l10n.lookup("pagemodRemoveAttributeIgnoreCaseDesc"),
+ },
+ ],
+ exec: function(args, context) {
+ let root = args.root || context.environment.document;
+ let regexOptions = args.ignoreCase ? "ig" : "g";
+ let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
+ let elements = root.querySelectorAll(args.searchElements);
+ elements = Array.prototype.slice.call(elements);
+
+ let removed = 0;
+ for (let i = 0; i < elements.length; i++) {
+ let element = elements[i];
+ if (!element.attributes) {
+ continue;
+ }
+
+ var attrs = Array.prototype.slice.call(element.attributes);
+ for (let y = 0; y < attrs.length; y++) {
+ let attr = attrs[y];
+ if (attributeRegex.test(attr.name)) {
+ element.removeAttribute(attr.name);
+ removed++;
+ }
+ }
+ }
+
+ return l10n.lookupFormat("pagemodRemoveAttributeResult",
+ [elements.length, removed]);
+ }
+ },
+ // This command allows the user to export the page to HTML after DOM changes
+ {
+ name: "export",
+ description: l10n.lookup("exportDesc"),
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "export html",
+ description: l10n.lookup("exportHtmlDesc"),
+ params: [
+ {
+ name: "destination",
+ type: {
+ name: "selection",
+ data: [ "window", "stdout", "clipboard" ]
+ },
+ defaultValue: "window"
+ }
+ ],
+ exec: function(args, context) {
+ let html = context.environment.document.documentElement.outerHTML;
+ if (args.destination === "stdout") {
+ return html;
+ }
+
+ if (args.desination === "clipboard") {
+ let clipboard = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+ clipboard.copyString(url);
+ return '';
+ }
+
+ let url = "data:text/plain;charset=utf8," + encodeURIComponent(html);
+ context.environment.window.open(url);
+ return '';
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/paintflashing.js b/devtools/shared/gcli/commands/paintflashing.js
new file mode 100644
index 000000000..7e21911a7
--- /dev/null
+++ b/devtools/shared/gcli/commands/paintflashing.js
@@ -0,0 +1,201 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Ci } = require("chrome");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+var telemetry;
+try {
+ const Telemetry = require("devtools/client/shared/telemetry");
+ telemetry = new Telemetry();
+} catch(e) {
+ // DevTools Telemetry module only available in Firefox
+}
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const eventEmitter = new EventEmitter();
+
+const gcli = require("gcli/index");
+const l10n = require("gcli/l10n");
+
+const enabledPaintFlashing = new Set();
+
+const isCheckedFor = (tab) =>
+ tab ? enabledPaintFlashing.has(getBrowserForTab(tab).outerWindowID) : false;
+
+/**
+ * Fire events and telemetry when paintFlashing happens
+ */
+function onPaintFlashingChanged(target, state) {
+ const { flashing, id } = state;
+
+ if (flashing) {
+ enabledPaintFlashing.add(id);
+ } else {
+ enabledPaintFlashing.delete(id);
+ }
+
+ eventEmitter.emit("changed", { target: target });
+ function fireChange() {
+ eventEmitter.emit("changed", { target: target });
+ }
+
+ target.off("navigate", fireChange);
+ target.once("navigate", fireChange);
+
+ if (!telemetry) {
+ return;
+ }
+ if (flashing) {
+ telemetry.toolOpened("paintflashing");
+ } else {
+ telemetry.toolClosed("paintflashing");
+ }
+}
+
+/**
+ * Alter the paintFlashing state of a window and report on the new value.
+ * This works with chrome or content windows.
+ *
+ * This is a bizarre method that you could argue should be broken up into
+ * separate getter and setter functions, however keeping it as one helps
+ * to simplify the commands below.
+ *
+ * @param state {string} One of:
+ * - "on" which does window.paintFlashing = true
+ * - "off" which does window.paintFlashing = false
+ * - "toggle" which does window.paintFlashing = !window.paintFlashing
+ * - "query" which does nothing
+ * @return The new value of the window.paintFlashing flag
+ */
+function setPaintFlashing(window, state) {
+ const winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ if (!["on", "off", "toggle", "query"].includes(state)) {
+ throw new Error(`Unsupported state: ${state}`);
+ }
+
+ if (state === "on") {
+ winUtils.paintFlashing = true;
+ } else if (state === "off") {
+ winUtils.paintFlashing = false;
+ } else if (state === "toggle") {
+ winUtils.paintFlashing = !winUtils.paintFlashing;
+ }
+
+ return winUtils.paintFlashing;
+}
+
+exports.items = [
+ {
+ name: "paintflashing",
+ description: l10n.lookup("paintflashingDesc")
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "paintflashing on",
+ description: l10n.lookup("paintflashingOnDesc"),
+ manual: l10n.lookup("paintflashingManual"),
+ params: [{
+ group: "options",
+ params: [
+ {
+ type: "boolean",
+ name: "chrome",
+ get hidden() {
+ return gcli.hiddenByChromePref();
+ },
+ description: l10n.lookup("paintflashingChromeDesc"),
+ }
+ ]
+ }],
+ exec: function*(args, context) {
+ if (!args.chrome) {
+ const output = yield context.updateExec("paintflashing_server --state on");
+
+ onPaintFlashingChanged(context.environment.target, output.data);
+ } else {
+ setPaintFlashing(context.environment.chromeWindow, "on");
+ }
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "paintflashing off",
+ description: l10n.lookup("paintflashingOffDesc"),
+ manual: l10n.lookup("paintflashingManual"),
+ params: [{
+ group: "options",
+ params: [
+ {
+ type: "boolean",
+ name: "chrome",
+ get hidden() {
+ return gcli.hiddenByChromePref();
+ },
+ description: l10n.lookup("paintflashingChromeDesc"),
+ }
+ ]
+ }],
+ exec: function*(args, context) {
+ if (!args.chrome) {
+ const output = yield context.updateExec("paintflashing_server --state off");
+
+ onPaintFlashingChanged(context.environment.target, output.data);
+ } else {
+ setPaintFlashing(context.environment.chromeWindow, "off");
+ }
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "paintflashing toggle",
+ hidden: true,
+ buttonId: "command-button-paintflashing",
+ buttonClass: "command-button command-button-invertable",
+ state: {
+ isChecked: ({_tab}) => isCheckedFor(_tab),
+ onChange: (_, handler) => eventEmitter.on("changed", handler),
+ offChange: (_, handler) => eventEmitter.off("changed", handler),
+ },
+ tooltipText: l10n.lookup("paintflashingTooltip"),
+ description: l10n.lookup("paintflashingToggleDesc"),
+ manual: l10n.lookup("paintflashingManual"),
+ exec: function*(args, context) {
+ const output = yield context.updateExec("paintflashing_server --state toggle");
+
+ onPaintFlashingChanged(context.environment.target, output.data);
+ }
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "paintflashing_server",
+ hidden: true,
+ params: [
+ {
+ name: "state",
+ type: {
+ name: "selection",
+ data: [ "on", "off", "toggle", "query" ]
+ }
+ },
+ ],
+ returnType: "paintFlashingState",
+ exec: function(args, context) {
+ let { window } = context.environment;
+ let id = getOuterId(window);
+ let flashing = setPaintFlashing(window, args.state);
+
+ return { flashing, id };
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/qsa.js b/devtools/shared/gcli/commands/qsa.js
new file mode 100644
index 000000000..939991f18
--- /dev/null
+++ b/devtools/shared/gcli/commands/qsa.js
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const l10n = require("gcli/l10n");
+
+exports.items = [
+ {
+ item: "command",
+ runAt: "server",
+ name: "qsa",
+ description: l10n.lookup("qsaDesc"),
+ params: [{
+ name: "query",
+ type: "nodelist",
+ description: l10n.lookup("qsaQueryDesc")
+ }],
+ exec: function(args, context) {
+ return args.query.length;
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/restart.js b/devtools/shared/gcli/commands/restart.js
new file mode 100644
index 000000000..cf0e688d3
--- /dev/null
+++ b/devtools/shared/gcli/commands/restart.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+const Services = require("Services");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+/**
+ * Restart command
+ *
+ * @param boolean nocache
+ * Disables loading content from cache upon restart.
+ *
+ * Examples :
+ * >> restart
+ * - restarts browser immediately
+ * >> restart --nocache
+ * - restarts immediately and starts Firefox without using cache
+ */
+exports.items = [
+ {
+ item: "command",
+ runAt: "client",
+ name: "restart",
+ description: l10n.lookupFormat("restartBrowserDesc", [ BRAND_SHORT_NAME ]),
+ params: [{
+ group: l10n.lookup("restartBrowserGroupOptions"),
+ params: [
+ {
+ name: "nocache",
+ type: "boolean",
+ description: l10n.lookup("restartBrowserNocacheDesc")
+ },
+ {
+ name: "safemode",
+ type: "boolean",
+ description: l10n.lookup("restartBrowserSafemodeDesc")
+ }
+ ]
+ }],
+ returnType: "string",
+ exec: function Restart(args, context) {
+ let canceled = Cc["@mozilla.org/supports-PRBool;1"]
+ .createInstance(Ci.nsISupportsPRBool);
+ Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");
+ if (canceled.data) {
+ return l10n.lookup("restartBrowserRequestCancelled");
+ }
+
+ // disable loading content from cache.
+ if (args.nocache) {
+ Services.appinfo.invalidateCachesOnRestart();
+ }
+
+ const appStartup = Cc["@mozilla.org/toolkit/app-startup;1"]
+ .getService(Ci.nsIAppStartup);
+
+ if (args.safemode) {
+ // restart in safemode
+ appStartup.restartInSafeMode(Ci.nsIAppStartup.eAttemptQuit);
+ } else {
+ // restart normally
+ appStartup.quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+ }
+
+ return l10n.lookupFormat("restartBrowserRestarting", [ BRAND_SHORT_NAME ]);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/rulers.js b/devtools/shared/gcli/commands/rulers.js
new file mode 100644
index 000000000..121e975bc
--- /dev/null
+++ b/devtools/shared/gcli/commands/rulers.js
@@ -0,0 +1,110 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals getBrowserForTab */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const eventEmitter = new EventEmitter();
+const events = require("sdk/event/core");
+loader.lazyRequireGetter(this, "getOuterId", "sdk/window/utils", true);
+loader.lazyRequireGetter(this, "getBrowserForTab", "sdk/tabs/utils", true);
+
+const l10n = require("gcli/l10n");
+require("devtools/server/actors/inspector");
+const { RulersHighlighter, HighlighterEnvironment } =
+ require("devtools/server/actors/highlighters");
+
+const highlighters = new WeakMap();
+const visibleHighlighters = new Set();
+
+const isCheckedFor = (tab) =>
+ tab ? visibleHighlighters.has(getBrowserForTab(tab).outerWindowID) : false;
+
+exports.items = [
+ // The client rulers command is used to maintain the toolbar button state only
+ // and redirects to the server command to actually toggle the rulers (see
+ // rulers_server below).
+ {
+ name: "rulers",
+ runAt: "client",
+ description: l10n.lookup("rulersDesc"),
+ manual: l10n.lookup("rulersManual"),
+ buttonId: "command-button-rulers",
+ buttonClass: "command-button command-button-invertable",
+ tooltipText: l10n.lookup("rulersTooltip"),
+ state: {
+ isChecked: ({_tab}) => isCheckedFor(_tab),
+ onChange: (target, handler) => eventEmitter.on("changed", handler),
+ offChange: (target, handler) => eventEmitter.off("changed", handler)
+ },
+ exec: function*(args, context) {
+ let { target } = context.environment;
+
+ // Pipe the call to the server command.
+ let response = yield context.updateExec("rulers_server");
+ let { visible, id } = response.data;
+
+ if (visible) {
+ visibleHighlighters.add(id);
+ } else {
+ visibleHighlighters.delete(id);
+ }
+
+ eventEmitter.emit("changed", { target });
+
+ // Toggle off the button when the page navigates because the rulers are
+ // removed automatically by the RulersHighlighter on the server then.
+ let onNavigate = () => {
+ visibleHighlighters.delete(id);
+ eventEmitter.emit("changed", { target });
+ };
+ target.off("will-navigate", onNavigate);
+ target.once("will-navigate", onNavigate);
+ }
+ },
+ // The server rulers command is hidden by default, it's just used by the
+ // client command.
+ {
+ name: "rulers_server",
+ runAt: "server",
+ hidden: true,
+ returnType: "highlighterVisibility",
+ exec: function(args, context) {
+ let env = context.environment;
+ let { document } = env;
+ let id = getOuterId(env.window);
+
+ // Calling the command again after the rulers have been shown once hides
+ // them.
+ if (highlighters.has(document)) {
+ let { highlighter } = highlighters.get(document);
+ highlighter.destroy();
+ return {visible: false, id};
+ }
+
+ // Otherwise, display the rulers.
+ let environment = new HighlighterEnvironment();
+ environment.initFromWindow(env.window);
+ let highlighter = new RulersHighlighter(environment);
+
+ // Store the instance of the rulers highlighter for this document so we
+ // can hide it later.
+ highlighters.set(document, { highlighter, environment });
+
+ // Listen to the highlighter's destroy event which may happen if the
+ // window is refreshed or closed with the rulers shown.
+ events.once(highlighter, "destroy", () => {
+ if (highlighters.has(document)) {
+ let { environment } = highlighters.get(document);
+ environment.destroy();
+ highlighters.delete(document);
+ }
+ });
+
+ highlighter.show();
+ return {visible: true, id};
+ }
+ }
+];
diff --git a/devtools/shared/gcli/commands/screenshot.js b/devtools/shared/gcli/commands/screenshot.js
new file mode 100644
index 000000000..e2f38b6d9
--- /dev/null
+++ b/devtools/shared/gcli/commands/screenshot.js
@@ -0,0 +1,579 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cc, Ci, Cr, Cu } = require("chrome");
+const l10n = require("gcli/l10n");
+const Services = require("Services");
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const { getRect } = require("devtools/shared/layout/utils");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+loader.lazyImporter(this, "Downloads", "resource://gre/modules/Downloads.jsm");
+loader.lazyImporter(this, "OS", "resource://gre/modules/osfile.jsm");
+loader.lazyImporter(this, "FileUtils", "resource://gre/modules/FileUtils.jsm");
+loader.lazyImporter(this, "PrivateBrowsingUtils",
+ "resource://gre/modules/PrivateBrowsingUtils.jsm");
+
+const BRAND_SHORT_NAME = Cc["@mozilla.org/intl/stringbundle;1"]
+ .getService(Ci.nsIStringBundleService)
+ .createBundle("chrome://branding/locale/brand.properties")
+ .GetStringFromName("brandShortName");
+
+// String used as an indication to generate default file name in the following
+// format: "Screen Shot yyyy-mm-dd at HH.MM.SS.png"
+const FILENAME_DEFAULT_VALUE = " ";
+
+/*
+ * There are 2 commands and 1 converter here. The 2 commands are nearly
+ * identical except that one runs on the client and one in the server.
+ *
+ * The server command is hidden, and is designed to be called from the client
+ * command.
+ */
+
+/**
+ * Both commands have the same initial filename parameter
+ */
+const filenameParam = {
+ name: "filename",
+ type: {
+ name: "file",
+ filetype: "file",
+ existing: "maybe",
+ },
+ defaultValue: FILENAME_DEFAULT_VALUE,
+ description: l10n.lookup("screenshotFilenameDesc"),
+ manual: l10n.lookup("screenshotFilenameManual")
+};
+
+/**
+ * Both commands have the same set of standard optional parameters
+ */
+const standardParams = {
+ group: l10n.lookup("screenshotGroupOptions"),
+ params: [
+ {
+ name: "clipboard",
+ type: "boolean",
+ description: l10n.lookup("screenshotClipboardDesc"),
+ manual: l10n.lookup("screenshotClipboardManual")
+ },
+ {
+ name: "imgur",
+ type: "boolean",
+ description: l10n.lookup("screenshotImgurDesc"),
+ manual: l10n.lookup("screenshotImgurManual")
+ },
+ {
+ name: "delay",
+ type: { name: "number", min: 0 },
+ defaultValue: 0,
+ description: l10n.lookup("screenshotDelayDesc"),
+ manual: l10n.lookup("screenshotDelayManual")
+ },
+ {
+ name: "dpr",
+ type: { name: "number", min: 0, allowFloat: true },
+ defaultValue: 0,
+ description: l10n.lookup("screenshotDPRDesc"),
+ manual: l10n.lookup("screenshotDPRManual")
+ },
+ {
+ name: "fullpage",
+ type: "boolean",
+ description: l10n.lookup("screenshotFullPageDesc"),
+ manual: l10n.lookup("screenshotFullPageManual")
+ },
+ {
+ name: "selector",
+ type: "node",
+ defaultValue: null,
+ description: l10n.lookup("inspectNodeDesc"),
+ manual: l10n.lookup("inspectNodeManual")
+ }
+ ]
+};
+
+exports.items = [
+ {
+ /**
+ * Format an 'imageSummary' (as output by the screenshot command).
+ * An 'imageSummary' is a simple JSON object that looks like this:
+ *
+ * {
+ * destinations: [ "..." ], // Required array of descriptions of the
+ * // locations of the result image (the command
+ * // can have multiple outputs)
+ * data: "...", // Optional Base64 encoded image data
+ * width:1024, height:768, // Dimensions of the image data, required
+ * // if data != null
+ * filename: "...", // If set, clicking the image will open the
+ * // folder containing the given file
+ * href: "...", // If set, clicking the image will open the
+ * // link in a new tab
+ * }
+ */
+ item: "converter",
+ from: "imageSummary",
+ to: "dom",
+ exec: function(imageSummary, context) {
+ const document = context.document;
+ const root = document.createElement("div");
+
+ // Add a line to the result for each destination
+ imageSummary.destinations.forEach(destination => {
+ const title = document.createElement("div");
+ title.textContent = destination;
+ root.appendChild(title);
+ });
+
+ // Add the thumbnail image
+ if (imageSummary.data != null) {
+ const image = context.document.createElement("div");
+ const previewHeight = parseInt(256 * imageSummary.height / imageSummary.width);
+ const style = "" +
+ "width: 256px;" +
+ "height: " + previewHeight + "px;" +
+ "max-height: 256px;" +
+ "background-image: url('" + imageSummary.data + "');" +
+ "background-size: 256px " + previewHeight + "px;" +
+ "margin: 4px;" +
+ "display: block;";
+ image.setAttribute("style", style);
+ root.appendChild(image);
+ }
+
+ // Click handler
+ if (imageSummary.href || imageSummary.filename) {
+ root.style.cursor = "pointer";
+ root.addEventListener("click", () => {
+ if (imageSummary.href) {
+ let mainWindow = context.environment.chromeWindow;
+ mainWindow.openUILinkIn(imageSummary.href, "tab");
+ } else if (imageSummary.filename) {
+ const file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+ file.initWithPath(imageSummary.filename);
+ file.reveal();
+ }
+ });
+ }
+
+ return root;
+ }
+ },
+ {
+ item: "command",
+ runAt: "client",
+ name: "screenshot",
+ description: l10n.lookup("screenshotDesc"),
+ manual: l10n.lookup("screenshotManual"),
+ returnType: "imageSummary",
+ buttonId: "command-button-screenshot",
+ buttonClass: "command-button command-button-invertable",
+ tooltipText: l10n.lookup("screenshotTooltipPage"),
+ params: [
+ filenameParam,
+ standardParams,
+ ],
+ exec: function (args, context) {
+ // Re-execute the command on the server
+ const command = context.typed.replace(/^screenshot/, "screenshot_server");
+ let capture = context.updateExec(command).then(output => {
+ return output.error ? Promise.reject(output.data) : output.data;
+ });
+
+ simulateCameraEffect(context.environment.chromeDocument, "shutter");
+ return capture.then(saveScreenshot.bind(null, args, context));
+ },
+ },
+ {
+ item: "command",
+ runAt: "server",
+ name: "screenshot_server",
+ hidden: true,
+ returnType: "imageSummary",
+ params: [ filenameParam, standardParams ],
+ exec: function (args, context) {
+ return captureScreenshot(args, context.environment.document);
+ },
+ }
+];
+
+/**
+ * This function is called to simulate camera effects
+ */
+function simulateCameraEffect(document, effect) {
+ let window = document.defaultView;
+ if (effect === "shutter") {
+ const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
+ audioCamera.play();
+ }
+ if (effect == "flash") {
+ const frames = Cu.cloneInto({ opacity: [ 0, 1 ] }, window);
+ document.documentElement.animate(frames, 500);
+ }
+}
+
+/**
+ * This function simply handles the --delay argument before calling
+ * createScreenshotData
+ */
+function captureScreenshot(args, document) {
+ if (args.delay > 0) {
+ return new Promise((resolve, reject) => {
+ document.defaultView.setTimeout(() => {
+ createScreenshotData(document, args).then(resolve, reject);
+ }, args.delay * 1000);
+ });
+ }
+ else {
+ return createScreenshotData(document, args);
+ }
+}
+
+/**
+ * There are several possible destinations for the screenshot, SKIP is used
+ * in saveScreenshot() whenever one of them is not used
+ */
+const SKIP = Promise.resolve();
+
+/**
+ * Save the captured screenshot to one of several destinations.
+ */
+function saveScreenshot(args, context, reply) {
+ const fileNeeded = args.filename != FILENAME_DEFAULT_VALUE ||
+ (!args.imgur && !args.clipboard);
+
+ return Promise.all([
+ args.clipboard ? saveToClipboard(context, reply) : SKIP,
+ args.imgur ? uploadToImgur(reply) : SKIP,
+ fileNeeded ? saveToFile(context, reply) : SKIP,
+ ]).then(() => reply);
+}
+
+/**
+ * This does the dirty work of creating a base64 string out of an
+ * area of the browser window
+ */
+function createScreenshotData(document, args) {
+ const window = document.defaultView;
+ let left = 0;
+ let top = 0;
+ let width;
+ let height;
+ const currentX = window.scrollX;
+ const currentY = window.scrollY;
+
+ let filename = getFilename(args.filename);
+
+ if (args.fullpage) {
+ // Bug 961832: GCLI screenshot shows fixed position element in wrong
+ // position if we don't scroll to top
+ window.scrollTo(0,0);
+ width = window.innerWidth + window.scrollMaxX - window.scrollMinX;
+ height = window.innerHeight + window.scrollMaxY - window.scrollMinY;
+ filename = filename.replace(".png", "-fullpage.png");
+ }
+ else if (args.selector) {
+ ({ top, left, width, height } = getRect(window, args.selector, window));
+ }
+ else {
+ left = window.scrollX;
+ top = window.scrollY;
+ width = window.innerWidth;
+ height = window.innerHeight;
+ }
+
+ // Only adjust for scrollbars when considering the full window
+ if (!args.selector) {
+ const winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ const scrollbarHeight = {};
+ const scrollbarWidth = {};
+ winUtils.getScrollbarSize(false, scrollbarWidth, scrollbarHeight);
+ width -= scrollbarWidth.value;
+ height -= scrollbarHeight.value;
+ }
+
+ const canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+ const ctx = canvas.getContext("2d");
+ const ratio = args.dpr ? args.dpr : window.devicePixelRatio;
+ canvas.width = width * ratio;
+ canvas.height = height * ratio;
+ ctx.scale(ratio, ratio);
+ ctx.drawWindow(window, left, top, width, height, "#fff");
+ const data = canvas.toDataURL("image/png", "");
+
+ // See comment above on bug 961832
+ if (args.fullpage) {
+ window.scrollTo(currentX, currentY);
+ }
+
+ simulateCameraEffect(document, "flash");
+
+ return Promise.resolve({
+ destinations: [],
+ data: data,
+ height: height,
+ width: width,
+ filename: filename,
+ });
+}
+
+/**
+ * We may have a filename specified in args, or we might have to generate
+ * one.
+ */
+function getFilename(defaultName) {
+ // Create a name for the file if not present
+ if (defaultName != FILENAME_DEFAULT_VALUE) {
+ return defaultName;
+ }
+
+ const date = new Date();
+ let dateString = date.getFullYear() + "-" + (date.getMonth() + 1) +
+ "-" + date.getDate();
+ dateString = dateString.split("-").map(function(part) {
+ if (part.length == 1) {
+ part = "0" + part;
+ }
+ return part;
+ }).join("-");
+
+ const timeString = date.toTimeString().replace(/:/g, ".").split(" ")[0];
+ return l10n.lookupFormat("screenshotGeneratedFilename",
+ [ dateString, timeString ]) + ".png";
+}
+
+/**
+ * Save the image data to the clipboard. This returns a promise, so it can
+ * be treated exactly like imgur / file processing, but it's really sync
+ * for now.
+ */
+function saveToClipboard(context, reply) {
+ try {
+ const channel = NetUtil.newChannel({
+ uri: reply.data,
+ loadUsingSystemPrincipal: true,
+ contentPolicyType: Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE
+ });
+ const input = channel.open2();
+
+ const loadContext = context.environment.chromeWindow
+ .QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsILoadContext);
+
+ const imgTools = Cc["@mozilla.org/image/tools;1"]
+ .getService(Ci.imgITools);
+
+ const container = {};
+ imgTools.decodeImageData(input, channel.contentType, container);
+
+ const wrapped = Cc["@mozilla.org/supports-interface-pointer;1"]
+ .createInstance(Ci.nsISupportsInterfacePointer);
+ wrapped.data = container.value;
+
+ const trans = Cc["@mozilla.org/widget/transferable;1"]
+ .createInstance(Ci.nsITransferable);
+ trans.init(loadContext);
+ trans.addDataFlavor(channel.contentType);
+ trans.setTransferData(channel.contentType, wrapped, -1);
+
+ const clip = Cc["@mozilla.org/widget/clipboard;1"]
+ .getService(Ci.nsIClipboard);
+ clip.setData(trans, null, Ci.nsIClipboard.kGlobalClipboard);
+
+ reply.destinations.push(l10n.lookup("screenshotCopied"));
+ }
+ catch (ex) {
+ console.error(ex);
+ reply.destinations.push(l10n.lookup("screenshotErrorCopying"));
+ }
+
+ return Promise.resolve();
+}
+
+/**
+ * Upload screenshot data to Imgur, returning a promise of a URL (as a string)
+ */
+function uploadToImgur(reply) {
+ return new Promise((resolve, reject) => {
+ const xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
+ .createInstance(Ci.nsIXMLHttpRequest);
+ const fd = Cc["@mozilla.org/files/formdata;1"]
+ .createInstance(Ci.nsIDOMFormData);
+ fd.append("image", reply.data.split(",")[1]);
+ fd.append("type", "base64");
+ fd.append("title", reply.filename);
+
+ const postURL = Services.prefs.getCharPref("devtools.gcli.imgurUploadURL");
+ const clientID = "Client-ID " + Services.prefs.getCharPref("devtools.gcli.imgurClientID");
+
+ xhr.open("POST", postURL);
+ xhr.setRequestHeader("Authorization", clientID);
+ xhr.send(fd);
+ xhr.responseType = "json";
+
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState == 4) {
+ if (xhr.status == 200) {
+ reply.href = xhr.response.data.link;
+ reply.destinations.push(l10n.lookupFormat("screenshotImgurUploaded",
+ [ reply.href ]));
+ } else {
+ reply.destinations.push(l10n.lookup("screenshotImgurError"));
+ }
+
+ resolve();
+ }
+ };
+ });
+}
+
+/**
+ * Progress listener that forwards calls to a transfer object.
+ *
+ * This is used below in saveToFile to forward progress updates from the
+ * nsIWebBrowserPersist object that does the actual saving to the nsITransfer
+ * which just represents the operation for the Download Manager. This keeps the
+ * Download Manager updated on saving progress and completion, so that it gives
+ * visual feedback from the downloads toolbar button when the save is done.
+ *
+ * It also allows the browser window to show auth prompts if needed (should not
+ * be needed for saving screenshots).
+ *
+ * This code is borrowed directly from contentAreaUtils.js.
+ */
+function DownloadListener(win, transfer) {
+ this.window = win;
+ this.transfer = transfer;
+
+ // For most method calls, forward to the transfer object.
+ for (let name in transfer) {
+ if (name != "QueryInterface" &&
+ name != "onStateChange") {
+ this[name] = (...args) => transfer[name].apply(transfer, args);
+ }
+ }
+
+ // Allow saveToFile to await completion for error handling
+ this._completedDeferred = defer();
+ this.completed = this._completedDeferred.promise;
+}
+
+DownloadListener.prototype = {
+ QueryInterface: function(iid) {
+ if (iid.equals(Ci.nsIInterfaceRequestor) ||
+ iid.equals(Ci.nsIWebProgressListener) ||
+ iid.equals(Ci.nsIWebProgressListener2) ||
+ iid.equals(Ci.nsISupports)) {
+ return this;
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ getInterface: function(iid) {
+ if (iid.equals(Ci.nsIAuthPrompt) ||
+ iid.equals(Ci.nsIAuthPrompt2)) {
+ let ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIPromptFactory);
+ return ww.getPrompt(this.window, iid);
+ }
+
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ onStateChange: function(webProgress, request, state, status) {
+ // Check if the download has completed
+ if ((state & Ci.nsIWebProgressListener.STATE_STOP) &&
+ (state & Ci.nsIWebProgressListener.STATE_IS_NETWORK)) {
+ if (status == Cr.NS_OK) {
+ this._completedDeferred.resolve();
+ } else {
+ this._completedDeferred.reject();
+ }
+ }
+
+ this.transfer.onStateChange.apply(this.transfer, arguments);
+ }
+};
+
+/**
+ * Save the screenshot data to disk, returning a promise which is resolved on
+ * completion.
+ */
+var saveToFile = Task.async(function*(context, reply) {
+ let document = context.environment.chromeDocument;
+ let window = context.environment.chromeWindow;
+
+ // Check there is a .png extension to filename
+ if (!reply.filename.match(/.png$/i)) {
+ reply.filename += ".png";
+ }
+
+ let downloadsDir = yield Downloads.getPreferredDownloadsDirectory();
+ let downloadsDirExists = yield OS.File.exists(downloadsDir);
+ if (downloadsDirExists) {
+ // If filename is absolute, it will override the downloads directory and
+ // still be applied as expected.
+ reply.filename = OS.Path.join(downloadsDir, reply.filename);
+ }
+
+ let sourceURI = Services.io.newURI(reply.data, null, null);
+ let targetFile = new FileUtils.File(reply.filename);
+ let targetFileURI = Services.io.newFileURI(targetFile);
+
+ // Create download and track its progress.
+ // This is adapted from saveURL in contentAreaUtils.js, but simplified greatly
+ // and modified to allow saving to arbitrary paths on disk. Using these
+ // objects as opposed to just writing with OS.File allows us to tie into the
+ // download manager to record a download entry and to get visual feedback from
+ // the downloads toolbar button when the save is done.
+ const nsIWBP = Ci.nsIWebBrowserPersist;
+ const flags = nsIWBP.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+ nsIWBP.PERSIST_FLAGS_FORCE_ALLOW_COOKIES |
+ nsIWBP.PERSIST_FLAGS_BYPASS_CACHE |
+ nsIWBP.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
+ let isPrivate =
+ PrivateBrowsingUtils.isContentWindowPrivate(document.defaultView);
+ let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+ .createInstance(Ci.nsIWebBrowserPersist);
+ persist.persistFlags = flags;
+ let tr = Cc["@mozilla.org/transfer;1"].createInstance(Ci.nsITransfer);
+ tr.init(sourceURI,
+ targetFileURI,
+ "",
+ null,
+ null,
+ null,
+ persist,
+ isPrivate);
+ let listener = new DownloadListener(window, tr);
+ persist.progressListener = listener;
+ persist.savePrivacyAwareURI(sourceURI,
+ null,
+ document.documentURIObject,
+ Ci.nsIHttpChannel
+ .REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE,
+ null,
+ null,
+ targetFileURI,
+ isPrivate);
+
+ try {
+ // Await successful completion of the save via the listener
+ yield listener.completed;
+ reply.destinations.push(l10n.lookup("screenshotSavedToFile") +
+ ` "${reply.filename}"`);
+ } catch (ex) {
+ console.error(ex);
+ reply.destinations.push(l10n.lookup("screenshotErrorSavingToFile") + " " +
+ reply.filename);
+ }
+});
diff --git a/devtools/shared/gcli/commands/security.js b/devtools/shared/gcli/commands/security.js
new file mode 100644
index 000000000..eeed03c61
--- /dev/null
+++ b/devtools/shared/gcli/commands/security.js
@@ -0,0 +1,328 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * The Security devtool supports the following arguments:
+ * * Security CSP
+ * Provides feedback about the current CSP
+ *
+ * * Security referrer
+ * Provides information about the current referrer policy
+ */
+
+"use strict";
+
+const { Cc, Ci, Cu, CC } = require("chrome");
+const l10n = require("gcli/l10n");
+const CSP = Cc["@mozilla.org/cspcontext;1"].getService(Ci.nsIContentSecurityPolicy);
+
+const GOOD_IMG_SRC = "chrome://browser/content/gcli_sec_good.svg";
+const MOD_IMG_SRC = "chrome://browser/content/gcli_sec_moderate.svg";
+const BAD_IMG_SRC = "chrome://browser/content/gcli_sec_bad.svg";
+
+
+// special handling within policy
+const POLICY_REPORT_ONLY = "report-only"
+
+// special handling of directives
+const DIR_UPGRADE_INSECURE = "upgrade-insecure-requests";
+const DIR_BLOCK_ALL_MIXED_CONTENT = "block-all-mixed-content";
+
+// special handling of sources
+const SRC_UNSAFE_INLINE = "'unsafe-inline'";
+const SRC_UNSAFE_EVAL = "'unsafe-eval'";
+
+const WILDCARD_MSG = l10n.lookup("securityCSPRemWildCard");
+const XSS_WARNING_MSG = l10n.lookup("securityCSPPotentialXSS");
+const NO_CSP_ON_PAGE_MSG = l10n.lookup("securityCSPNoCSPOnPage");
+const CONTENT_SECURITY_POLICY_MSG = l10n.lookup("securityCSPHeaderOnPage");
+const CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG = l10n.lookup("securityCSPROHeaderOnPage");
+
+const NEXT_URI_HEADER = l10n.lookup("securityReferrerNextURI");
+const CALCULATED_REFERRER_HEADER = l10n.lookup("securityReferrerCalculatedReferrer");
+/* The official names from the W3C Referrer Policy Draft http://www.w3.org/TR/referrer-policy/ */
+const REFERRER_POLICY_NAMES = [ "None When Downgrade (default)", "None", "Origin Only", "Origin When Cross-Origin", "Unsafe URL" ];
+
+exports.items = [
+ {
+ // --- General Security information
+ name: "security",
+ description: l10n.lookup("securityPrivacyDesc"),
+ manual: l10n.lookup("securityManual")
+ },
+ {
+ // --- CSP specific Security information
+ item: "command",
+ runAt: "server",
+ name: "security csp",
+ description: l10n.lookup("securityCSPDesc"),
+ manual: l10n.lookup("securityCSPManual"),
+ returnType: "securityCSPInfo",
+ exec: function(args, context) {
+
+ var cspJSON = context.environment.document.nodePrincipal.cspJSON;
+ var cspOBJ = JSON.parse(cspJSON);
+
+ var outPolicies = [];
+
+ var policies = cspOBJ["csp-policies"];
+
+ // loop over all the different policies
+ for (var csp in policies) {
+ var curPolicy = policies[csp];
+
+ // loop over all the directive-values within that policy
+ var outDirectives = [];
+ var outHeader = CONTENT_SECURITY_POLICY_MSG;
+ for (var dir in curPolicy) {
+ var curDir = curPolicy[dir];
+
+ // when iterating properties within the obj we might also
+ // encounter the 'report-only' flag, which is not a csp directive.
+ if (dir === POLICY_REPORT_ONLY) {
+ outHeader = curPolicy[POLICY_REPORT_ONLY] === true ?
+ CONTENT_SECURITY_POLICY_REPORT_ONLY_MSG :
+ CONTENT_SECURITY_POLICY_MSG;
+ continue;
+ }
+
+ // loop over all the directive-sources within that directive
+ var outSrcs = [];
+
+ // special case handling for the directives
+ // upgrade-insecure-requests and block-all-mixed-content
+ // which do not include any srcs
+ if (dir === DIR_UPGRADE_INSECURE ||
+ dir === DIR_BLOCK_ALL_MIXED_CONTENT) {
+ outSrcs.push({
+ icon: GOOD_IMG_SRC,
+ src: "", // no src
+ desc: "" // no description
+ });
+ }
+
+ for (var src in curDir) {
+ var curSrc = curDir[src];
+
+ // the default icon and descritpion of the directive-src
+ var outIcon = GOOD_IMG_SRC;
+ var outDesc = "";
+
+ if (curSrc.indexOf("*") > -1) {
+ outIcon = MOD_IMG_SRC;
+ outDesc = WILDCARD_MSG;
+ }
+ if (curSrc == SRC_UNSAFE_INLINE || curSrc == SRC_UNSAFE_EVAL) {
+ outIcon = BAD_IMG_SRC;
+ outDesc = XSS_WARNING_MSG;
+ }
+ outSrcs.push({
+ icon: outIcon,
+ src: curSrc,
+ desc: outDesc
+ });
+ }
+ // append info about that directive to the directives array
+ outDirectives.push({
+ dirValue: dir,
+ dirSrc: outSrcs
+ });
+ }
+ // append info about the policy to the policies array
+ outPolicies.push({
+ header: outHeader,
+ directives: outDirectives
+ });
+ }
+ return outPolicies;
+ }
+ },
+ {
+ item: "converter",
+ from: "securityCSPInfo",
+ to: "view",
+ exec: function(cspInfo, context) {
+ var url = context.environment.target.url;
+
+ if (cspInfo.length == 0) {
+ return context.createView({
+ html:
+ "<table class='gcli-csp-detail' cellspacing='10' valign='top'>" +
+ " <tr>" +
+ " <td> <img src='chrome://browser/content/gcli_sec_bad.svg' width='20px' /> </td> " +
+ " <td>" + NO_CSP_ON_PAGE_MSG + " <b>" + url + "</b></td>" +
+ " </tr>" +
+ "</table>"});
+ }
+
+ return context.createView({
+ html:
+ "<table class='gcli-csp-detail' cellspacing='10' valign='top'>" +
+ // iterate all policies
+ " <tr foreach='csp in ${cspinfo}' >" +
+ " <td> ${csp.header} <b>" + url + "</b><br/><br/>" +
+ " <table class='gcli-csp-dir-detail' valign='top'>" +
+ // >> iterate all directives
+ " <tr foreach='dir in ${csp.directives}' >" +
+ " <td valign='top'> ${dir.dirValue} </td>" +
+ " <td valign='top'>" +
+ " <table class='gcli-csp-src-detail' valign='top'>" +
+ // >> >> iterate all srs
+ " <tr foreach='src in ${dir.dirSrc}' >" +
+ " <td valign='center' width='20px'> <img src= \"${src.icon}\" width='20px' /> </td> " +
+ " <td valign='center' width='200px'> ${src.src} </td>" +
+ " <td valign='center'> ${src.desc} </td>" +
+ " </tr>" +
+ " </table>" +
+ " </td>" +
+ " </tr>" +
+ " </table>" +
+ " </td>" +
+ " </tr>" +
+ "</table>",
+ data: {
+ cspinfo: cspInfo,
+ }
+ });
+ }
+ },
+ {
+ // --- Referrer Policy specific Security information
+ item: "command",
+ runAt: "server",
+ name: "security referrer",
+ description: l10n.lookup("securityReferrerPolicyDesc"),
+ manual: l10n.lookup("securityReferrerPolicyManual"),
+ returnType: "securityReferrerPolicyInfo",
+ exec: function(args, context) {
+ var doc = context.environment.document;
+
+ var referrerPolicy = doc.referrerPolicy;
+
+ var pageURI = doc.documentURIObject;
+ var sameDomainReferrer = "";
+ var otherDomainReferrer = "";
+ var downgradeReferrer = "";
+ var otherDowngradeReferrer = "";
+ var origin = pageURI.prePath;
+
+ switch (referrerPolicy) {
+ case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER:
+ // sends no referrer
+ sameDomainReferrer
+ = otherDomainReferrer
+ = downgradeReferrer
+ = otherDowngradeReferrer
+ = "(no referrer)";
+ break;
+ case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN:
+ // only sends the origin of the referring URL
+ sameDomainReferrer
+ = otherDomainReferrer
+ = downgradeReferrer
+ = otherDowngradeReferrer
+ = origin;
+ break;
+ case Ci.nsIHttpChannel.REFERRER_POLICY_ORIGIN_WHEN_XORIGIN:
+ // same as default, but reduced to ORIGIN when cross-origin.
+ sameDomainReferrer = pageURI.spec;
+ otherDomainReferrer
+ = downgradeReferrer
+ = otherDowngradeReferrer
+ = origin;
+ break;
+ case Ci.nsIHttpChannel.REFERRER_POLICY_UNSAFE_URL:
+ // always sends the referrer, even on downgrade.
+ sameDomainReferrer
+ = otherDomainReferrer
+ = downgradeReferrer
+ = otherDowngradeReferrer
+ = pageURI.spec;
+ break;
+ case Ci.nsIHttpChannel.REFERRER_POLICY_NO_REFERRER_WHEN_DOWNGRADE:
+ // default state, doesn't send referrer from https->http
+ sameDomainReferrer = otherDomainReferrer = pageURI.spec;
+ downgradeReferrer = otherDowngradeReferrer = "(no referrer)";
+ break;
+ default:
+ // this is a new referrer policy which we do not know about
+ sameDomainReferrer
+ = otherDomainReferrer
+ = downgradeReferrer
+ = otherDowngradeReferrer
+ = "(unknown Referrer Policy)";
+ break;
+ }
+
+ var sameDomainUri = origin + "/*";
+
+ var referrerUrls = [
+ // add the referrer uri 'referrer' we would send when visiting 'uri'
+ {
+ uri: pageURI.scheme+'://example.com/',
+ referrer: otherDomainReferrer,
+ description: l10n.lookup('securityReferrerPolicyOtherDomain')},
+ {
+ uri: sameDomainUri,
+ referrer: sameDomainReferrer,
+ description: l10n.lookup('securityReferrerPolicySameDomain')}
+ ];
+
+ if (pageURI.schemeIs('https')) {
+ // add the referrer we would send on downgrading http->https
+ if (sameDomainReferrer != downgradeReferrer) {
+ referrerUrls.push({
+ uri: "http://"+pageURI.hostPort+"/*",
+ referrer: downgradeReferrer,
+ description:
+ l10n.lookup('securityReferrerPolicySameDomainDowngrade')
+ });
+ }
+ if (otherDomainReferrer != otherDowngradeReferrer) {
+ referrerUrls.push({
+ uri: "http://example.com/",
+ referrer: otherDowngradeReferrer,
+ description:
+ l10n.lookup('securityReferrerPolicyOtherDomainDowngrade')
+ });
+ }
+ }
+
+ return {
+ header: l10n.lookupFormat("securityReferrerPolicyReportHeader",
+ [pageURI.spec]),
+ policyName: REFERRER_POLICY_NAMES[referrerPolicy],
+ urls: referrerUrls
+ }
+ }
+ },
+ {
+ item: "converter",
+ from: "securityReferrerPolicyInfo",
+ to: "view",
+ exec: function(referrerPolicyInfo, context) {
+ return context.createView({
+ html:
+ "<div class='gcli-referrer-policy'>" +
+ " <strong> ${rpi.header} </strong> <br />" +
+ " ${rpi.policyName} <br />" +
+ " <table class='gcli-referrer-policy-detail' cellspacing='10' >" +
+ " <tr>" +
+ " <th> " + NEXT_URI_HEADER + " </th>" +
+ " <th> " + CALCULATED_REFERRER_HEADER + " </th>" +
+ " </tr>" +
+ // iterate all policies
+ " <tr foreach='nextURI in ${rpi.urls}' >" +
+ " <td> ${nextURI.description} (e.g., ${nextURI.uri}) </td>" +
+ " <td> ${nextURI.referrer} </td>" +
+ " </tr>" +
+ " </table>" +
+ "</div>",
+ data: {
+ rpi: referrerPolicyInfo,
+ }
+ });
+ }
+ }
+];
diff --git a/devtools/shared/gcli/moz.build b/devtools/shared/gcli/moz.build
new file mode 100644
index 000000000..c1015569f
--- /dev/null
+++ b/devtools/shared/gcli/moz.build
@@ -0,0 +1,23 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'commands',
+ 'source/lib/gcli',
+ 'source/lib/gcli/connectors',
+ 'source/lib/gcli/converters',
+ 'source/lib/gcli/commands',
+ 'source/lib/gcli/fields',
+ 'source/lib/gcli/languages',
+ 'source/lib/gcli/mozui',
+ 'source/lib/gcli/types',
+ 'source/lib/gcli/ui',
+ 'source/lib/gcli/util',
+]
+
+DevToolsModules(
+ 'templater.js'
+)
diff --git a/devtools/shared/gcli/source/LICENSE b/devtools/shared/gcli/source/LICENSE
new file mode 100644
index 000000000..d64569567
--- /dev/null
+++ b/devtools/shared/gcli/source/LICENSE
@@ -0,0 +1,202 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/devtools/shared/gcli/source/docs/design.md b/devtools/shared/gcli/source/docs/design.md
new file mode 100644
index 000000000..5e3c0a2f3
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/design.md
@@ -0,0 +1,102 @@
+
+# The Design of GCLI
+
+## Design Goals
+
+GCLI should be:
+
+- primarily for technical users.
+- as fast as a traditional CLI. It should be possible to put your head down,
+ and look at the keyboard and use GCLI 'blind' at full speed without making
+ mistakes.
+- principled about the way it encourages people to build commands. There is
+ benefit from unifying the underlying concepts.
+- automatically helpful.
+
+GCLI should not attempt to:
+
+- convert existing GUI users to a CLI.
+- use natural language input. The closest we should get to natural language is
+ thinking of commands as ```verb noun --adjective```.
+- gain a touch based interface. Whilst it's possible (even probable) that touch
+ can provide further benefits to command line users, that can wait while we
+ catch up with 1985.
+- slavishly follow the syntax of existing commands, predictability is more
+ important.
+- be a programming language. Shell scripts are mini programming languages but
+ we have JavaScript sat just next door. It's better to integrate than compete.
+
+
+## Design Challenges
+
+What has changed since 1970 that might cause us to make changes to the design
+of the command line?
+
+
+### Connection limitations
+
+Unix pre-dates the Internet and treats almost everything as a file. Since the
+Internet it could be more useful to use URIs as ways to identify sources of data.
+
+
+### Memory limitations
+
+Modern computers have something like 6 orders of magnitude more memory than the
+PDP-7 on which Unix was developed. Innovations like stdin/stdout and pipes are
+ways to connect systems without long-term storage of the results. The ability
+to store results for some time (potentially in more than one format)
+significantly reduces the need for these concepts. We should make the results
+of past commands addressable for re-use at a later time.
+
+There are a number of possible policies for eviction of items from the history.
+We should investigate options other than a simple stack.
+
+
+### Multi-tasking limitations
+
+Multi-tasking was a problem in 1970; the problem was getting a computer to do
+many jobs on 1 core. Today the problem is getting a computer to do one job on
+many cores. However we're stuck with this legacy in 2 ways. Firstly that the
+default is to force everything to wait until the previous job is finished, but
+more importantly that output from parallel jobs frequently collides
+
+ $ find / -ctime 5d -print &
+ $ find / -uid 0 -print &
+ // good luck working out what came from where
+
+ $ tail -f logfile.txt &
+ $ vi main.c
+ // have a nice time editing that file
+
+GCLI should allow commands to be asynchronous and will provide UI elements to
+inform the user of job completion. It will also keep asynchronous command
+output contained within it's own display area.
+
+
+### Output limitations
+
+The PDP-7 had a teletype. There is something like 4 orders of magnitude more
+information that can be displayed on a modern display than a 80x24 character
+based console. We can use this flexibility to provide better help to the user
+in entering their command.
+
+The additional display richness can also allow interaction with result output.
+Command output can include links to follow-up commands, and even ask for
+additional input. (e.g. "your search returned zero results do you want to try
+again with a different search string")
+
+There is no reason why output must be static. For example, it could be
+informative to see the results of an "ls" command alter given changes made by
+subsequent commands. (It should be noted that there are times when historical
+information is important too)
+
+
+### Integration limitations
+
+In 1970, command execution meant retrieving a program from storage, and running
+it. This required minimal interaction between the command line processor and
+the program being run, and was good for resource constrained systems.
+This lack of interaction resulted in the processing of command line arguments
+being done everywhere, when the task was better suited to command line.
+We should provide metadata about the commands being run, to allow the command
+line to process, interpret and provide help on the input.
diff --git a/devtools/shared/gcli/source/docs/developing-gcli.md b/devtools/shared/gcli/source/docs/developing-gcli.md
new file mode 100644
index 000000000..113712655
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/developing-gcli.md
@@ -0,0 +1,213 @@
+
+# Developing GCLI
+
+## About the code
+
+The majority of the GCLI source is stored in the ``lib`` directory.
+
+The ``docs`` directory contains documentation.
+The ``scripts`` directory contains RequireJS that GCLI uses.
+The ``build`` directory contains files used when creating builds.
+The ``mozilla`` directory contains the mercurial patch queue of patches to apply
+to mozilla-central.
+The ``selenium-tests`` directory contains selenium web-page integration tests.
+
+The source in the ``lib`` directory is split into 4 sections:
+
+- ``lib/demo`` contains commands used in the demo page. It is not needed except
+ for demo purposes.
+- ``lib/test`` contains a small test harness for testing GCLI.
+- ``lib/gclitest`` contains tests that run in the test harness
+- ``lib/gcli`` contains the actual meat
+
+GCLI is split into a UI portion and a Model/Controller portion.
+
+
+## The GCLI Model
+
+The heart of GCLI is a ``Requisition``, which is an AST for the input. A
+``Requisition`` is a command that we'd like to execute, and we're filling out
+all the inputs required to execute the command.
+
+A ``Requisition`` has a ``Command`` that is to be executed. Each Command has a
+number of ``Parameter``s, each of which has a name and a type as detailed
+above.
+
+As you type, your input is split into ``Argument``s, which are then assigned to
+``Parameter``s using ``Assignment``s. Each ``Assignment`` has a ``Conversion``
+which stores the input argument along with the value that is was converted into
+according to the type of the parameter.
+
+There are special assignments called ``CommandAssignment`` which the
+``Requisition`` uses to link to the command to execute, and
+``UnassignedAssignment``used to store arguments that do not have a parameter
+to be assigned to.
+
+
+## The GCLI UI
+
+There are several components of the GCLI UI. Each can have a script portion,
+some template HTML and a CSS file. The template HTML is processed by
+``domtemplate`` before use.
+
+DomTemplate is fully documented in [it's own repository]
+(https://github.com/joewalker/domtemplate).
+
+The components are:
+
+- ``Inputter`` controls the input field, processing special keyboard events and
+ making sure that it stays in sync with the Requisition.
+- ``Completer`` updates a div that is located behind the input field and used
+ to display completion advice and hint highlights. It is stored in
+ completer.js.
+- ``Display`` is responsible for containing the popup hints that are displayed
+ above the command line. Typically Display contains a Hinter and a RequestsView
+ although these are not both required. Display itself is optional, and isn't
+ planned for use in the first release of GCLI in Firefox.
+- ``Hinter`` Is used to display input hints. It shows either a Menu or an
+ ArgFetch component depending on the state of the Requisition
+- ``Menu`` is used initially to select the command to be executed. It can act
+ somewhat like the Start menu on windows.
+- ``ArgFetch`` Once the command to be executed has been selected, ArgFetch
+ shows a 'dialog' allowing the user to enter the parameters to the selected
+ command.
+- ``RequestsView`` Contains a set of ``RequestView`` components, each of which
+ displays a command that has been invoked. RequestsView is a poor name, and
+ should better be called ReportView
+
+ArgFetch displays a number of Fields. There are fields for most of the Types
+discussed earlier. See 'Writing Fields' above for more information.
+
+
+## Testing
+
+GCLI contains 2 test suites:
+
+- JS level testing is run with the ``test`` command. The tests are located in
+ ``lib/gclitest`` and they use the test runner in ``lib/test``. This is fairly
+ comprehensive, however it does not do UI level testing.
+ If writing a new test it needs to be registered in ``lib/gclitest/index``.
+ For an example of how to write tests, see ``lib/gclitest/testSplit.js``.
+ The test functions are implemented in ``lib/test/assert``.
+- Browser integration tests are included in ``browser_webconsole_gcli_*.js``,
+ in ``toolkit/components/console/hudservice/tests/browser``. These are
+ run with the rest of the Mozilla test suite.
+
+
+## Coding Conventions
+
+The coding conventions for the GCLI project come from the Bespin/Skywriter and
+Ace projects. They are roughly [Crockford]
+(http://javascript.crockford.com/code.html) with a few exceptions and
+additions:
+
+* ``var`` does not need to be at the top of each function, we'd like to move
+ to ``let`` when it's generally available, and ``let`` doesn't have the same
+ semantic twists as ``var``.
+
+* Strings are generally enclosed in single quotes.
+
+* ``eval`` is to be avoided, but we don't declare it evil.
+
+The [Google JavaScript conventions]
+(https://google-styleguide.googlecode.com/svn/trunk/javascriptguide.xml) are
+more detailed, we tend to deviate in:
+
+* Custom exceptions: We generally just use ``throw new Error('message');``
+
+* Multi-level prototype hierarchies: Allowed; we don't have ``goog.inherits()``
+
+* ``else`` begins on a line by itself:
+
+ if (thing) {
+ doThis();
+ }
+ else {
+ doThat();
+ }
+
+
+## Startup
+
+Internally GCLI modules have ``startup()``/``shutdown()`` functions which are
+called on module init from the top level ``index.js`` of that 'package'.
+
+In order to initialize a package all that is needed is to require the package
+index (e.g. ``require('package/index')``).
+
+The ``shutdown()`` function was useful when GCLI was used in Bespin as part of
+dynamic registration/de-registration. It is not known if this feature will be
+useful in the future. So it has not been entirely removed, it may be at some
+future date.
+
+
+## Running the Unit Tests
+
+Start the GCLI static server:
+
+ cd path/to/gcli
+ node gcli.js
+
+Now point your browser to http://localhost:9999/localtest.html. When the page
+loads the tests will be automatically run outputting to the console, or you can
+enter the ``test`` command to run the unit tests.
+
+
+## Contributing Code
+
+Please could you do the following to help minimize the amount of rework that we
+do:
+
+1. Check the unit tests run correctly (see **Running the Unit Tests** above)
+2. Check the code follows the style guide. At a minimum it should look like the
+ code around it. For more detailed notes, see **Coding Conventions** above
+3. Help me review your work by using good commit comments. Which means 2 things
+ * Well formatted messages, i.e. 50 char summary including bug tag, followed
+ by a blank line followed by a more in-depth message wrapped to 72 chars
+ per line. This is basically the format used by the Linux Kernel. See the
+ [commit log](https://github.com/joewalker/gcli/commits/master) for
+ examples. The be extra helpful, please use the "shortdesc-BUGNUM: " if
+ possible which also helps in reviews.
+ * Commit your changes as a story. Make it easy for me to understand the
+ changes that you've made.
+4. Sign your work. To improve tracking of who did what, we follow the sign-off
+ procedure used in the Linux Kernel.
+ The sign-off is a simple line at the end of the explanation for the
+ patch, which certifies that you wrote it or otherwise have the right to
+ pass it on as an open-source patch. The rules are pretty simple: if you
+ can certify the below:
+
+ Developer's Certificate of Origin 1.1
+
+ By making a contribution to this project, I certify that:
+
+ (a) The contribution was created in whole or in part by me and I
+ have the right to submit it under the open source license
+ indicated in the file; or
+
+ (b) The contribution is based upon previous work that, to the best
+ of my knowledge, is covered under an appropriate open source
+ license and I have the right under that license to submit that
+ work with modifications, whether created in whole or in part
+ by me, under the same open source license (unless I am
+ permitted to submit under a different license), as indicated
+ in the file; or
+
+ (c) The contribution was provided directly to me by some other
+ person who certified (a), (b) or (c) and I have not modified
+ it.
+
+ (d) I understand and agree that this project and the contribution
+ are public and that a record of the contribution (including all
+ personal information I submit with it, including my sign-off) is
+ maintained indefinitely and may be redistributed consistent with
+ this project or the open source license(s) involved.
+
+ then you just add a line saying
+
+ Signed-off-by: Random J Developer <random@developer.example.org>
+
+ using your real name (sorry, no pseudonyms or anonymous contributions.)
+
+Thanks for wanting to contribute code.
+
diff --git a/devtools/shared/gcli/source/docs/index.md b/devtools/shared/gcli/source/docs/index.md
new file mode 100644
index 000000000..dce112e6d
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/index.md
@@ -0,0 +1,150 @@
+
+# About GCLI
+
+## GCLI is a Graphical Command Line Interpreter.
+
+GCLI is a command line for modern computers. When command lines were invented,
+computers were resource-limited, disconnected systems with slow multi-tasking
+and poor displays. The design of the Unix CLI made sense in 1970, but over 40
+years on, considering the pace of change, there are many improvements we can
+make.
+
+CLIs generally suffer from poor discoverability; It's hard when faced with a
+blank command line to work out what to do. As a result the majority of programs
+today use purely graphical user interfaces, however in doing so, they lose some
+of the benefits of CLIs. CLIs are still used because generally, in the hands of
+a skilled user they are faster, and have a wider range of available options.
+
+GCLI attempts to get the best of the GUI world and the CLI world to produce
+something that is both easy to use and learn as well as fast and powerful.
+
+GCLI has a type system to help ensure that users are inputting valid commands
+and to enable us to provide sensible context sensitive help. GCLI provides
+integration with JavaScript rather than being an alternative (like CoffeeScript).
+
+
+## History
+
+GCLI was born as part of the
+[Bespin](http://ajaxian.com/archives/canvas-for-a-text-editor) project and was
+[discussed at the time](http://j.mp/bespin-cli). The command line component
+survived the rename of Bepsin to Skywriter and the merger with Ace, got a name
+of it's own (Cockpit) which didn't last long before the project was named GCLI.
+It is now being used in the Firefox's web console where it doesn't have a
+separate identity but it's still called GCLI outside of Firefox. It is also
+used in [Eclipse Orion](http://www.eclipse.org/orion/).
+
+
+## Environments
+
+GCLI is designed to work in a number of environments:
+
+1. As a component of Firefox developer tools.
+2. As an adjunct to Orion/Ace and other online editors.
+3. As a plugin to any web-page wishing to provide its own set of commands.
+4. As part of a standalone web browser extension with it's own set of commands.
+
+
+## Related Pages
+
+Other sources of GCLI documentation:
+
+- [Writing Commands](writing-commands.md)
+- [Writing Types](writing-types.md)
+- [Developing GCLI](developing-gcli.md)
+- [Writing Tests](writing-tests.md) / [Running Tests](running-tests.md)
+- [The Design of GCLI](design.md)
+- Source
+ - The most up-to-date source is in [this Github repository](https://github.com/joewalker/gcli/).
+ - When a feature is 'done' it's merged into the [Mozilla clone](https://github.com/mozilla/gcli/).
+ - From which it flows into [Mozilla Central](https://hg.mozilla.org/mozilla-central/file/tip/devtools/client/commandline).
+- [Demo of GCLI](http://mozilla.github.com/gcli/) with an arbitrary set of demo
+ commands
+- Other Documentation
+ - [Embedding docs](https://github.com/mozilla/gcli/blob/master/docs/index.md)
+ - [Status page](http://mozilla.github.com/devtools/2011/status.html#gcli)
+
+
+## Accessibility
+
+GCLI uses ARIA roles to guide a screen-reader as to the important sections to
+voice. We welcome [feedback on how these roles are implemented](https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox&component=Developer+Tools:+Graphic+Commandline+and+Toolbar&rep_platform=All&op_sys=All&short_desc=GCLI).
+
+The command line uses TAB as a method of completing current input, this
+prevents use of TAB for keyboard navigation. Instead of using TAB to move to
+the next field you can use F6. In addition to F6, ALT+TAB, CTRL+TAB, META+TAB
+make an attempt to move the focus on. How well this works depends on your
+OS/browser combination.
+
+
+## Embedding GCLI
+
+There are 3 basic steps in using GCLI in your system.
+
+1. Import a GCLI JavaScript file.
+ For serious use of GCLI you are likely to be creating a custom build (see
+ below) however if you just want to have a quick play, you can use
+ ``gcli-uncompressed.js`` from [the gh-pages branch of GCLI]
+ (https://github.com/mozilla/gcli/tree/gh-pages)
+ Just place the following wherever you place your script files.
+
+ <script src="path/to/gcli-uncompressed.js" type="text/javascript"></script>
+
+2. Having imported GCLI, we need to tell it where to display. The simplest
+ method is to include an elements with the id of ``gcli-input`` and
+ ``gcli-display``.
+
+ <input id="gcli-input" type="text"/>
+ <div id="gcli-display"></div>
+
+3. Tell GCLI what commands to make available. See the sections on Writing
+ Commands, Writing Types and Writing Fields for more information.
+
+ GCLI uses the CommonJS AMD format for it's files, so a 'require' statement
+ is needed to get started.
+
+ require([ 'gcli/index' ], function(gcli) {
+ gcli.add(...); // Register custom commands/types/etc
+ gcli.createTerminal(); // Create a user interface
+ });
+
+ The createTerminal() function takes an ``options`` objects which allows
+ customization. At the current time the documentation of these object is left
+ to the source.
+
+
+## Backwards Compatibility
+
+The goals of the GCLI project are:
+
+- Aim for very good backwards compatibility with code required from an
+ 'index' module. This means we will not break code without a cycle of
+ deprecation warnings.
+
+ There are currently 3 'index' modules:
+ - gcli/index (all you need to get started with GCLI)
+ - demo/index (a number of demo commands)
+ - gclitest/index (GCLI test suite)
+
+ Code from these modules uses the module pattern to prevent access to internal
+ functions, so in essence, if you can get to it from an index module, you
+ should be ok.
+
+- We try to avoid needless change to other modules, however we don't make any
+ promises, and don't provide a deprecation cycle.
+
+ Code from other modules uses classes rather than modules, so member variables
+ are exposed. Many classes mark private members using the `_underscorePrefix`
+ pattern. Particular care should be taken if access is needed to a private
+ member.
+
+
+## Creating Custom Builds
+
+GCLI uses [DryIce](https://github.com/mozilla/dryice) to create custom builds.
+If dryice is installed (``npm install .``) then you can create a built
+version of GCLI simply using ``node gcli.js standard``. DryIce supplies a custom
+module loader to replace RequireJS for built applications.
+
+The build will be output to the ``built`` directory. The directory will be
+created if it doesn't exist.
diff --git a/devtools/shared/gcli/source/docs/running-tests.md b/devtools/shared/gcli/source/docs/running-tests.md
new file mode 100644
index 000000000..378c40837
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/running-tests.md
@@ -0,0 +1,60 @@
+
+# Running Tests
+
+GCLI has a test suite that can be run in a number of different environments.
+Some of the tests don't work in all environments. These should be automatically
+skipped when not applicable.
+
+
+## Web
+
+Running a limited set of test from the web is the easiest. Simply load
+'localtest.html' and the unit tests should be run automatically, with results
+displayed on the console. Tests can be re-run using the 'test' command.
+
+It also creates a function 'testCommands()' to be run at a JS prompt, which
+enables the test commands for debugging purposes.
+
+
+## Firefox
+
+GCLI's test suite integrates with Mochitest and runs automatically on each test
+run. Dryice packages the tests to format them for the Firefox build system.
+
+For more information about running Mochitest on Firefox (including GCLI) see
+[the MDN, Mochitest docs](https://developer.mozilla.org/en/Mochitest)
+
+
+# Node
+
+Running the test suite under node can be done as follows:
+
+ $ node gcli.js test
+
+Or, using the `test` command:
+
+ $ node gcli.js
+ Serving GCLI to http://localhost:9999/
+ This is also a limited GCLI prompt.
+ Type 'help' for a list of commands, CTRL+C twice to exit:
+ : test
+
+ testCli: Pass (funcs=9, checks=208)
+ testCompletion: Pass (funcs=1, checks=139)
+ testExec: Pass (funcs=1, checks=133)
+ testHistory: Pass (funcs=3, checks=13)
+ ....
+
+ Summary: Pass (951 checks)
+
+
+# Travis CI
+
+GCLI check-ins are automatically tested by [Travis CI](https://travis-ci.org/joewalker/gcli).
+
+
+# Test Case Generation
+
+GCLI can generate test cases automagically. Load ```localtest.html```, type a
+command to be tested into GCLI, and the press F2. GCLI will output to the
+console a template test case for the entered command.
diff --git a/devtools/shared/gcli/source/docs/writing-commands.md b/devtools/shared/gcli/source/docs/writing-commands.md
new file mode 100644
index 000000000..e73050279
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/writing-commands.md
@@ -0,0 +1,757 @@
+
+# Writing Commands
+
+## Basics
+
+GCLI has opinions about how commands should be written, and it encourages you
+to do The Right Thing. The opinions are based on helping users convert their
+intentions to commands and commands to what's actually going to happen.
+
+- Related commands should be sub-commands of a parent command. One of the goals
+ of GCLI is to support a large number of commands without things becoming
+ confusing, this will require some sort of namespacing or there will be
+ many people wanting to implement the ``add`` command. This style of
+ writing commands has become common place in Unix as the number of commands
+ has gone up.
+ The ```context``` command allows users to focus on a parent command, promoting
+ its sub-commands above others.
+
+- Each command should do exactly and only one thing. An example of a Unix
+ command that breaks this principle is the ``tar`` command
+
+ $ tar -zcf foo.tar.gz .
+ $ tar -zxf foo.tar.gz .
+
+ These 2 commands do exactly opposite things. Many a file has died as a result
+ of a x/c typo. In GCLI this would be better expressed:
+
+ $ tar create foo.tar.gz -z .
+ $ tar extract foo.tar.gz -z .
+
+ There may be commands (like tar) which have enough history behind them
+ that we shouldn't force everyone to re-learn a new syntax. The can be achieved
+ by having a single string parameter and parsing the input in the command)
+
+- Avoid errors. We try to avoid the user having to start again with a command
+ due to some problem. The majority of problems are simple typos which we can
+ catch using command metadata, but there are 2 things command authors can do
+ to prevent breakage.
+
+ - Where possible avoid the need to validate command line parameters in the
+ exec function. This can be done by good parameter design (see 'do exactly
+ and only one thing' above)
+
+ - If there is an obvious fix for an unpredictable problem, offer the
+ solution in the command output. So rather than use request.error (see
+ Request Object below) output some HTML which contains a link to a fixed
+ command line.
+
+Currently these concepts are not enforced at a code level, but they could be in
+the future.
+
+
+## How commands work
+
+This is how to create a basic ``greet`` command:
+
+ gcli.addItems([{
+ name: 'greet',
+ description: 'Show a greeting',
+ params: [
+ {
+ name: 'name',
+ type: 'string',
+ description: 'The name to greet'
+ }
+ ],
+ returnType: 'string',
+ exec: function(args, context) {
+ return 'Hello, ' + args.name;
+ }
+ }]);
+
+This command is used as follows:
+
+ : greet Joe
+ Hello, Joe
+
+Some terminology that isn't always obvious: a function has 'parameters', and
+when you call a function, you pass 'arguments' to it.
+
+
+## Internationalization (i18n)
+
+There are several ways that GCLI commands can be localized. The best method
+depends on what context you are writing your command for.
+
+### Firefox Embedding
+
+GCLI supports Mozilla style localization. To add a command that will only ever
+be used embedded in Firefox, this is the way to go. Your strings should be
+stored in ``toolkit/locales/en-US/chrome/global/devtools/gclicommands.properties``,
+And you should access them using ``let l10n = require("gcli/l10n")`` and then
+``l10n.lookup(...)`` or ``l10n.lookupFormat()``
+
+For examples of existing commands, take a look in
+``devtools/client/webconsole/GcliCommands.jsm``, which contains most of the
+current GCLI commands. If you will be adding a number of new commands, then
+consider starting a new JSM.
+
+Your command will then look something like this:
+
+ gcli.addItems([{
+ name: 'greet',
+ description: gcli.lookup("greetDesc")
+ ...
+ }]);
+
+### Web Commands
+
+There are 2 ways to provide translated strings for web use. The first is to
+supply the translated strings in the description:
+
+ gcli.addItems([{
+ name: 'greet',
+ description: {
+ 'root': 'Show a greeting',
+ 'fr-fr': 'Afficher un message d'accueil',
+ 'de-de': 'Zeige einen Gruß',
+ 'gk-gk': 'Εμφάνιση ένα χαιρετισμό',
+ ...
+ }
+ ...
+ }]);
+
+Each description should contain at least a 'root' entry which is the
+default if no better match is found. This method has the benefit of being
+compact and simple, however it has the significant drawback of being wasteful
+of memory and bandwidth to transmit and store a significant number of strings,
+the majority of which will never be used.
+
+More efficient is to supply a lookup key and ask GCLI to lookup the key from an
+appropriate localized strings file:
+
+ gcli.addItems([{
+ name: 'greet',
+ description: { 'key': 'demoGreetingDesc' }
+ ...
+ }]);
+
+For web usage, the central store of localized strings is
+``lib/gcli/nls/strings.js``. Other string files can be added using the
+``l10n.registerStringsSource(...)`` function.
+
+This method can be used both in Firefox and on the Web (see the help command
+for an example). However this method has the drawback that it will not work
+with DryIce built files until we fix bug 683844.
+
+
+## Default argument values
+
+The ``greet`` command requires the entry of the ``name`` parameter. This
+parameter can be made optional with the addition of a ``defaultValue`` to the
+parameter:
+
+ gcli.addItems([{
+ name: 'greet',
+ description: 'Show a message to someone',
+ params: [
+ {
+ name: 'name',
+ type: 'string',
+ description: 'The name to greet',
+ defaultValue: 'World!'
+ }
+ ],
+ returnType: 'string',
+ exec: function(args, context) {
+ return "Hello, " + args.name;
+ }
+ }]);
+
+Now we can also use the ``greet`` command as follows:
+
+ : greet
+ Hello, World!
+
+
+## Positional vs. named arguments
+
+Arguments can be entered either positionally or as named arguments. Generally
+users will prefer to type the positional version, however the named alternative
+can be more self documenting.
+
+For example, we can also invoke the greet command as follows:
+
+ : greet --name Joe
+ Hello, Joe
+
+
+## Short argument names
+
+GCLI allows you to specify a 'short' character for any parameter:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ {
+ name: 'name',
+ short: 'n',
+ type: 'string',
+ ...
+ }
+ ],
+ ...
+ }]);
+
+This is used as follows:
+
+ : greet -n Fred
+ Hello, Fred
+
+Currently GCLI does not allow short parameter merging (i.e. ```ls -la```)
+however this is planned.
+
+
+## Parameter types
+
+Initially the available types are:
+
+- string
+- boolean
+- number
+- selection
+- delegate
+- date
+- array
+- file
+- node
+- nodelist
+- resource
+- command
+- setting
+
+This list can be extended. See [Writing Types](writing-types.md) on types for
+more information.
+
+The following examples assume the following definition of the ```greet```
+command:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'name', type: 'string' },
+ { name: 'repeat', type: 'number' }
+ ],
+ ...
+ }]);
+
+Parameters can be specified either with named arguments:
+
+ : greet --name Joe --repeat 2
+
+And sometimes positionally:
+
+ : greet Joe 2
+
+Parameters can be specified positionally if they are considered 'important'.
+Unimportant parameters must be specified with a named argument.
+
+Named arguments can be specified anywhere on the command line (after the
+command itself) however positional arguments must be in order. So
+these examples are the same:
+
+ : greet --name Joe --repeat 2
+ : greet --repeat 2 --name Joe
+
+However (obviously) these are not the same:
+
+ : greet Joe 2
+ : greet 2 Joe
+
+(The second would be an error because 'Joe' is not a number).
+
+Named arguments are assigned first, then the remaining arguments are assigned
+to the remaining parameters. So the following is valid and unambiguous:
+
+ : greet 2 --name Joe
+
+Positional parameters quickly become unwieldy with long parameter lists so we
+recommend only having 2 or 3 important parameters. GCLI provides hints for
+important parameters more obviously than unimportant ones.
+
+Parameters are 'important' if they are not in a parameter group. The easiest way
+to achieve this is to use the ```option: true``` property.
+
+For example, using:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'name', type: 'string' },
+ { name: 'repeat', type: 'number', option: true, defaultValue: 1 }
+ ],
+ ...
+ }]);
+
+Would mean that this is an error
+
+ : greet Joe 2
+
+You would instead need to do the following:
+
+ : greet Joe --repeat 2
+
+For more on parameter groups, see below.
+
+In addition to being 'important' and 'unimportant' parameters can also be
+optional. If is possible to be important and optional, but it is not possible
+to be unimportant and non-optional.
+
+Parameters are optional if they either:
+- Have a ```defaultValue``` property
+- Are of ```type=boolean``` (boolean arguments automatically default to being false)
+
+There is currently no way to make parameters mutually exclusive.
+
+
+## Selection types
+
+Parameters can have a type of ``selection``. For example:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'name', ... },
+ {
+ name: 'lang',
+ description: 'In which language should we greet',
+ type: { name: 'selection', data: [ 'en', 'fr', 'de', 'es', 'gk' ] },
+ defaultValue: 'en'
+ }
+ ],
+ ...
+ }]);
+
+GCLI will enforce that the value of ``arg.lang`` was one of the values
+specified. Alternatively ``data`` can be a function which returns an array of
+strings.
+
+The ``data`` property is useful when the underlying type is a string but it
+doesn't work when the underlying type is something else. For this use the
+``lookup`` property as follows:
+
+ type: {
+ name: 'selection',
+ lookup: {
+ 'en': Locale.EN,
+ 'fr': Locale.FR,
+ ...
+ }
+ },
+
+Similarly, ``lookup`` can be a function returning the data of this type.
+
+
+## Number types
+
+Number types are mostly self explanatory, they have one special property which
+is the ability to specify upper and lower bounds for the number:
+
+ gcli.addItems([{
+ name: 'volume',
+ params: [
+ {
+ name: 'vol',
+ description: 'How loud should we go',
+ type: { name: 'number', min: 0, max: 11 }
+ }
+ ],
+ ...
+ }]);
+
+You can also specify a ``step`` property which specifies by what amount we
+should increment and decrement the values. The ``min``, ``max``, and ``step``
+properties are used by the command line when up and down are pressed and in
+the input type of a dialog generated from this command.
+
+
+## Delegate types
+
+Delegate types are needed when the type of some parameter depends on the type
+of another parameter. For example:
+
+ : set height 100
+ : set name "Joe Walker"
+
+We can achieve this as follows:
+
+ gcli.addItems([{
+ name: 'set',
+ params: [
+ {
+ name: 'setting',
+ type: { name: 'selection', values: [ 'height', 'name' ] }
+ },
+ {
+ name: 'value',
+ type: {
+ name: 'delegate',
+ delegateType: function() { ... }
+ }
+ }
+ ],
+ ...
+ }]);
+
+Several details are left out of this example, like how the delegateType()
+function knows what the current setting is. See the ``pref`` command for an
+example.
+
+
+## Array types
+
+Parameters can have a type of ``array``. For example:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ {
+ name: 'names',
+ type: { name: 'array', subtype: 'string' },
+ description: 'The names to greet',
+ defaultValue: [ 'World!' ]
+ }
+ ],
+ ...
+ exec: function(args, context) {
+ return "Hello, " + args.names.join(', ') + '.';
+ }
+ }]);
+
+This would be used as follows:
+
+ : greet Fred Jim Shiela
+ Hello, Fred, Jim, Shiela.
+
+Or using named arguments:
+
+ : greet --names Fred --names Jim --names Shiela
+ Hello, Fred, Jim, Shiela.
+
+There can only be one ungrouped parameter with an array type, and it must be
+at the end of the list of parameters (i.e. just before any parameter groups).
+This avoids confusion as to which parameter an argument should be assigned.
+
+
+## Sub-commands
+
+It is common for commands to be groups into those with similar functionality.
+Examples include virtually all VCS commands, ``apt-get``, etc. There are many
+examples of commands that should be structured as in a sub-command style -
+``tar`` being the obvious example, but others include ``crontab``.
+
+Groups of commands are specified with the top level command not having an
+exec function:
+
+ gcli.addItems([
+ {
+ name: 'tar',
+ description: 'Commands to manipulate archives',
+ },
+ {
+ name: 'tar create',
+ description: 'Create a new archive',
+ exec: function(args, context) { ... },
+ ...
+ },
+ {
+ name: 'tar extract',
+ description: 'Extract from an archive',
+ exec: function(args, context) { ... },
+ ...
+ }
+ ]);
+
+
+## Parameter groups
+
+Parameters can be grouped into sections.
+
+There are 3 ways to assign a parameter to a group.
+
+The simplest uses ```option: true``` to put a parameter into the default
+'Options' group:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'repeat', type: 'number', option: true }
+ ],
+ ...
+ }]);
+
+The ```option``` property can also take a string to use an alternative parameter
+group:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'repeat', type: 'number', option: 'Advanced' }
+ ],
+ ...
+ }]);
+
+An example of how this can be useful is 'git' which categorizes parameters into
+'porcelain' and 'plumbing'.
+
+Finally, parameters can be grouped together as follows:
+
+ gcli.addItems([{
+ name: 'greet',
+ params: [
+ { name: 'name', type: 'string', description: 'The name to greet' },
+ {
+ group: 'Advanced Options',
+ params: [
+ { name: 'repeat', type: 'number', defaultValue: 1 },
+ { name: 'debug', type: 'boolean' }
+ ]
+ }
+ ],
+ ...
+ }]);
+
+This could be used as follows:
+
+ : greet Joe --repeat 2 --debug
+ About to send greeting
+ Hello, Joe
+ Hello, Joe
+ Done!
+
+Parameter groups must come after non-grouped parameters because non-grouped
+parameters can be assigned positionally, so their index is important. We don't
+want 'holes' in the order caused by parameter groups.
+
+
+## Command metadata
+
+Each command should have the following properties:
+
+- A string ``name``.
+- A short ``description`` string. Generally no more than 20 characters without
+ a terminating period/fullstop.
+- A function to ``exec``ute. (Optional for the parent containing sub-commands)
+ See below for more details.
+
+And optionally the following extra properties:
+
+- A declaration of the accepted ``params``.
+- A ``hidden`` property to stop the command showing up in requests for help.
+- A ``context`` property which defines the scope of the function that we're
+ calling. Rather than simply call ``exec()``, we do ``exec.call(context)``.
+- A ``manual`` property which allows a fuller description of the purpose of the
+ command.
+- A ``returnType`` specifying how we should handle the value returned from the
+ exec function.
+
+The ``params`` property is an array of objects, one for each parameter. Each
+parameter object should have the following 3 properties:
+
+- A string ``name``.
+- A short string ``description`` as for the command.
+- A ``type`` which refers to an existing Type (see Writing Types).
+
+Optionally each parameter can have these properties:
+
+- A ``defaultValue`` (which should be in the type specified in ``type``).
+ The defaultValue will be used when there is no argument supplied for this
+ parameter on the command line.
+ If the parameter has a ``defaultValue``, other than ``undefined`` then the
+ parameter is optional, and if unspecified on the command line, the matching
+ argument will have this value when the function is called.
+ If ``defaultValue`` is missing, or if it is set to ``undefined``, then the
+ system will ensure that a value is provided before anything is executed.
+ There are 2 special cases:
+ - If the type is ``selection``, then defaultValue must not be undefined.
+ The defaultValue must either be ``null`` (meaning that a value must be
+ supplied by the user) or one of the selection values.
+ - If the type is ``boolean``, then ``defaultValue:false`` is implied and
+ can't be changed. Boolean toggles are assumed to be off by default, and
+ should be named to match.
+- A ``manual`` property for parameters is exactly analogous to the ``manual``
+ property for commands - descriptive text that is longer than than 20
+ characters.
+
+
+## The Command Function (exec)
+
+The parameters to the exec function are designed to be useful when you have a
+large number of parameters, and to give direct access to the environment (if
+used).
+
+ gcli.addItems([{
+ name: 'echo',
+ description: 'The message to display.',
+ params: [
+ {
+ name: 'message',
+ type: 'string',
+ description: 'The message to display.'
+ }
+ ],
+ returnType: 'string',
+ exec: function(args, context) {
+ return args.message;
+ }
+ }]);
+
+The ``args`` object contains the values specified on the params section and
+provided on the command line. In this example it would contain the message for
+display as ``args.message``.
+
+The ``context`` object has the following signature:
+
+ {
+ environment: ..., // environment object passed to createTerminal()
+ exec: ..., // function to execute a command
+ update: ..., // function to alter the text of the input area
+ createView: ..., // function to help creating rich output
+ defer: ..., // function to create a deferred promise
+ }
+
+The ``environment`` object is opaque to GCLI. It can be used for providing
+arbitrary data to your commands about their environment. It is most useful
+when more than one command line exists on a page with similar commands in both
+which should act in their own ways.
+An example use for ``environment`` would be a page with several tabs, each
+containing an editor with a command line. Commands executed in those editors
+should apply to the relevant editor.
+The ``environment`` object is passed to GCLI at startup (probably in the
+``createTerminal()`` function).
+
+The ``document`` object is also passed to GCLI at startup. In some environments
+(e.g. embedded in Firefox) there is no global ``document``. This object
+provides a way to create DOM nodes.
+
+``defer()`` allows commands to execute asynchronously.
+
+
+## Returning data
+
+The command meta-data specifies the type of data returned by the command using
+the ``returnValue`` setting.
+
+``returnValue`` processing is currently functioning, but incomplete, and being
+tracked in [Bug 657595](http://bugzil.la/657595). Currently you should specify
+a ``returnType`` of ``string`` or ``html``. If using HTML, you can return
+either an HTML string or a DOM node.
+
+In the future, JSON will be strongly encouraged as the return type, with some
+formatting functions to convert the JSON to HTML.
+
+Asynchronous output is achieved using a promise created from the ``context``
+parameter: ``context.defer()``.
+
+Some examples of this is practice:
+
+ { returnType: "string" }
+ ...
+ return "example";
+
+GCLI interprets the output as a plain string. It will be escaped before display
+and available as input to other commands as a plain string.
+
+ { returnType: "html" }
+ ...
+ return "<p>Hello</p>";
+
+GCLI will interpret this as HTML, and parse it for display.
+
+ { returnType: "dom" }
+ ...
+ return util.createElement(context.document, 'div');
+
+``util.createElement`` is a utility to ensure use of the XHTML namespace in XUL
+and other XML documents. In an HTML document it's functionally equivalent to
+``context.document.createElement('div')``. If your command is likely to be used
+in Firefox or another XML environment, you should use it. You can import it
+with ``var util = require('util/util');``.
+
+GCLI will use the returned HTML element as returned. See notes on ``context``
+above.
+
+ { returnType: "number" }
+ ...
+ return 42;
+
+GCLI will display the element in a similar way to a string, but it the value
+will be available to future commands as a number.
+
+ { returnType: "date" }
+ ...
+ return new Date();
+
+ { returnType: "file" }
+ ...
+ return new File();
+
+Both these examples return data as a given type, for which a converter will
+be required before the value can be displayed. The type system is likely to
+change before this is finalized. Please contact the author for more
+information.
+
+ { returnType: "string" }
+ ...
+ var deferred = context.defer();
+ setTimeout(function() {
+ deferred.resolve("hello");
+ }, 500);
+ return deferred.promise;
+
+Errors can be signaled by throwing an exception. GCLI will display the message
+property (or the toString() value if there is no message property). (However
+see *3 principles for writing commands* above for ways to avoid doing this).
+
+
+## Specifying Types
+
+Types are generally specified by a simple string, e.g. ``'string'``. For most
+types this is enough detail. There are a number of exceptions:
+
+* Array types. We declare a parameter to be an array of things using ``[]``,
+ for example: ``number[]``.
+* Selection types. There are 3 ways to specify the options in a selection:
+ * Using a lookup map
+
+ type: {
+ name: 'selection',
+ lookup: { one:1, two:2, three:3 }
+ }
+
+ (The boolean type is effectively just a selection that uses
+ ``lookup:{ 'true': true, 'false': false }``)
+
+ * Using given strings
+
+ type: {
+ name: 'selection',
+ data: [ 'left', 'center', 'right' ]
+ }
+
+ * Using named objects, (objects with a ``name`` property)
+
+ type: {
+ name: 'selection',
+ data: [
+ { name: 'Google', url: 'http://www.google.com/' },
+ { name: 'Microsoft', url: 'http://www.microsoft.com/' },
+ { name: 'Yahoo', url: 'http://www.yahoo.com/' }
+ ]
+ }
+
+* Delegate type. It is generally best to inherit from Delegate in order to
+ provide a customization of this type. See settingValue for an example.
+
+See below for more information.
diff --git a/devtools/shared/gcli/source/docs/writing-tests.md b/devtools/shared/gcli/source/docs/writing-tests.md
new file mode 100644
index 000000000..4d42142cd
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/writing-tests.md
@@ -0,0 +1,20 @@
+
+# Writing Tests
+
+There are several sources of GCLI tests and several environments in which they
+are run.
+
+The majority of GCLI tests are stored in
+[this repository](https://github.com/joewalker/gcli/) in files named like
+```./lib/gclitest/test*.js```. These tests run in Firefox, Chrome, Opera,
+and NodeJS/JsDom
+
+See [Running Tests](running-tests.md) for further details.
+
+GCLI comes with a generic unit test harness (in ```./lib/test/```) and a
+set of helpers for creating GCLI tests (in ```./lib/gclitest/helpers.js```).
+
+# GCLI tests in Firefox
+
+The build process converts the GCLI tests to run under Mochitest inside the
+Firefox unit tests. It also adds some
diff --git a/devtools/shared/gcli/source/docs/writing-types.md b/devtools/shared/gcli/source/docs/writing-types.md
new file mode 100644
index 000000000..ed2f75438
--- /dev/null
+++ b/devtools/shared/gcli/source/docs/writing-types.md
@@ -0,0 +1,106 @@
+
+# Writing Types
+
+Commands are a fundamental building block because they are what the users
+directly interacts with, however they are built on ``Type``s. There are a
+number of built in types:
+
+* string. This is a JavaScript string
+* number. A JavaScript number
+* boolean. A JavaScript boolean
+* selection. This is an selection from a number of alternatives
+* delegate. This type could change depending on other factors, but is well
+ defined when one of the conversion routines is called.
+
+There are a number of additional types defined by Pilot and GCLI as
+extensions to the ``selection`` and ``delegate`` types
+
+* setting. One of the defined settings
+* settingValue. A value that can be applied to an associated setting.
+* command. One of the defined commands
+
+Most of our types are 'static' e.g. there is only one type of 'string', however
+some types like 'selection' and 'delegate' are customizable.
+
+All types must inherit from Type and have the following methods:
+
+ /**
+ * Convert the given <tt>value</tt> to a string representation.
+ * Where possible, there should be round-tripping between values and their
+ * string representations.
+ */
+ stringify: function(value) { return 'string version of value'; },
+
+ /**
+ * Convert the given <tt>str</tt> to an instance of this type.
+ * Where possible, there should be round-tripping between values and their
+ * string representations.
+ * @return Conversion
+ */
+ parse: function(str) { return new Conversion(...); },
+
+ /**
+ * The plug-in system, and other things need to know what this type is
+ * called. The name alone is not enough to fully specify a type. Types like
+ * 'selection' and 'delegate' need extra data, however this function returns
+ * only the name, not the extra data.
+ * <p>In old bespin, equality was based on the name. This may turn out to be
+ * important in Ace too.
+ */
+ name: 'example',
+
+In addition, defining the following function can be helpful, although Type
+contains default implementations:
+
+* nudge(value, by)
+
+Type, Conversion and Status are all declared by commands.js.
+
+The values produced by the parse function can be of any type, but if you are
+producing your own, you are strongly encouraged to include properties called
+``name`` and ``description`` where it makes sense. There are a number of
+places in GCLI where the UI will be able to provide better help to users if
+your values include these properties.
+
+
+# Writing Fields
+
+Fields are visual representations of types. For simple types like string it is
+enough to use ``<input type=...>``, however more complex types we may wish to
+provide a custom widget to allow the user to enter values of the given type.
+
+This is an example of a very simple new password field type:
+
+ function PasswordField(doc) {
+ this.doc = doc;
+ }
+
+ PasswordField.prototype = Object.create(Field.prototype);
+
+ PasswordField.prototype.createElement = function(assignment) {
+ this.assignment = assignment;
+ this.input = dom.createElement(this.doc, 'input');
+ this.input.type = 'password';
+ this.input.value = assignment.arg ? assignment.arg.text : '';
+
+ this.onKeyup = function() {
+ this.assignment.setValue(this.input.value);
+ }.bind(this);
+ this.input.addEventListener('keyup', this.onKeyup, false);
+
+ this.onChange = function() {
+ this.input.value = this.assignment.arg.text;
+ };
+ this.assignment.onAssignmentChange.add(this.onChange, this);
+
+ return this.input;
+ };
+
+ PasswordField.prototype.destroy = function() {
+ this.input.removeEventListener('keyup', this.onKeyup, false);
+ this.assignment.onAssignmentChange.remove(this.onChange, this);
+ };
+
+ PasswordField.claim = function(type) {
+ return type.name === 'password' ? Field.claim.MATCH : Field.claim.NO_MATCH;
+ };
diff --git a/devtools/shared/gcli/source/lib/gcli/cli.js b/devtools/shared/gcli/source/lib/gcli/cli.js
new file mode 100644
index 000000000..4b7c115e2
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/cli.js
@@ -0,0 +1,2209 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('./util/util');
+var host = require('./util/host');
+var l10n = require('./util/l10n');
+
+var view = require('./ui/view');
+var Parameter = require('./commands/commands').Parameter;
+var CommandOutputManager = require('./commands/commands').CommandOutputManager;
+
+var Status = require('./types/types').Status;
+var Conversion = require('./types/types').Conversion;
+var commandModule = require('./types/command');
+var selectionModule = require('./types/selection');
+
+var Argument = require('./types/types').Argument;
+var ArrayArgument = require('./types/types').ArrayArgument;
+var NamedArgument = require('./types/types').NamedArgument;
+var TrueNamedArgument = require('./types/types').TrueNamedArgument;
+var MergedArgument = require('./types/types').MergedArgument;
+var ScriptArgument = require('./types/types').ScriptArgument;
+
+var RESOLVED = Promise.resolve(undefined);
+
+// Helper to produce a `deferred` object
+// using DOM Promise
+function defer() {
+ let resolve, reject;
+ let p = new Promise((a, b) => {
+ resolve = a;
+ reject = b;
+ });
+ return {
+ promise: p,
+ resolve: resolve,
+ reject: reject
+ };
+}
+
+/**
+ * This is a list of the known command line components to enable certain
+ * privileged commands to alter parts of a running command line. It is an array
+ * of objects shaped like:
+ * { conversionContext:..., executionContext:..., mapping:... }
+ * So lookup is O(n) where 'n' is the number of command lines.
+ */
+var instances = [];
+
+/**
+ * An indexOf that looks-up both types of context
+ */
+function instanceIndex(context) {
+ for (var i = 0; i < instances.length; i++) {
+ var instance = instances[i];
+ if (instance.conversionContext === context ||
+ instance.executionContext === context) {
+ return i;
+ }
+ }
+ return -1;
+}
+
+/**
+ * findInstance gets access to a Terminal object given a conversionContext or
+ * an executionContext (it doesn't have to be a terminal object, just whatever
+ * was passed into addMapping()
+ */
+exports.getMapping = function(context) {
+ var index = instanceIndex(context);
+ if (index === -1) {
+ console.log('Missing mapping for context: ', context);
+ console.log('Known contexts: ', instances);
+ throw new Error('Missing mapping for context');
+ }
+ return instances[index].mapping;
+};
+
+/**
+ * Add a requisition context->terminal mapping
+ */
+var addMapping = function(requisition) {
+ if (instanceIndex(requisition.conversionContext) !== -1) {
+ throw new Error('Remote existing mapping before adding a new one');
+ }
+
+ instances.push({
+ conversionContext: requisition.conversionContext,
+ executionContext: requisition.executionContext,
+ mapping: { requisition: requisition }
+ });
+};
+
+/**
+ * Remove a requisition context->terminal mapping
+ */
+var removeMapping = function(requisition) {
+ var index = instanceIndex(requisition.conversionContext);
+ instances.splice(index, 1);
+};
+
+/**
+ * Assignment is a link between a parameter and the data for that parameter.
+ * The data for the parameter is available as in the preferred type and as
+ * an Argument for the CLI.
+ * <p>We also record validity information where applicable.
+ * <p>For values, null and undefined have distinct definitions. null means
+ * that a value has been provided, undefined means that it has not.
+ * Thus, null is a valid default value, and common because it identifies an
+ * parameter that is optional. undefined means there is no value from
+ * the command line.
+ * @constructor
+ */
+function Assignment(param) {
+ // The parameter that we are assigning to
+ this.param = param;
+ this.conversion = undefined;
+}
+
+/**
+ * Easy accessor for conversion.arg.
+ * This is a read-only property because writes to arg should be done through
+ * the 'conversion' property.
+ */
+Object.defineProperty(Assignment.prototype, 'arg', {
+ get: function() {
+ return this.conversion == null ? undefined : this.conversion.arg;
+ },
+ enumerable: true
+});
+
+/**
+ * Easy accessor for conversion.value.
+ * This is a read-only property because writes to value should be done through
+ * the 'conversion' property.
+ */
+Object.defineProperty(Assignment.prototype, 'value', {
+ get: function() {
+ return this.conversion == null ? undefined : this.conversion.value;
+ },
+ enumerable: true
+});
+
+/**
+ * Easy (and safe) accessor for conversion.message
+ */
+Object.defineProperty(Assignment.prototype, 'message', {
+ get: function() {
+ if (this.conversion != null && this.conversion.message) {
+ return this.conversion.message;
+ }
+ // ERROR conversions have messages, VALID conversions don't need one, so
+ // we just need to consider INCOMPLETE conversions.
+ if (this.getStatus() === Status.INCOMPLETE) {
+ return l10n.lookupFormat('cliIncompleteParam', [ this.param.name ]);
+ }
+ return '';
+ },
+ enumerable: true
+});
+
+/**
+ * Easy (and safe) accessor for conversion.getPredictions()
+ * @return An array of objects with name and value elements. For example:
+ * [ { name:'bestmatch', value:foo1 }, { name:'next', value:foo2 }, ... ]
+ */
+Assignment.prototype.getPredictions = function(context) {
+ return this.conversion == null ? [] : this.conversion.getPredictions(context);
+};
+
+/**
+ * Accessor for a prediction by index.
+ * This is useful above <tt>getPredictions()[index]</tt> because it normalizes
+ * index to be within the bounds of the predictions, which means that the UI
+ * can maintain an index of which prediction to choose without caring how many
+ * predictions there are.
+ * @param rank The index of the prediction to choose
+ */
+Assignment.prototype.getPredictionRanked = function(context, rank) {
+ if (rank == null) {
+ rank = 0;
+ }
+
+ if (this.isInName()) {
+ return Promise.resolve(undefined);
+ }
+
+ return this.getPredictions(context).then(function(predictions) {
+ if (predictions.length === 0) {
+ return undefined;
+ }
+
+ rank = rank % predictions.length;
+ if (rank < 0) {
+ rank = predictions.length + rank;
+ }
+ return predictions[rank];
+ }.bind(this));
+};
+
+/**
+ * Some places want to take special action if we are in the name part of a
+ * named argument (i.e. the '--foo' bit).
+ * Currently this does not take actual cursor position into account, it just
+ * assumes that the cursor is at the end. In the future we will probably want
+ * to take this into account.
+ */
+Assignment.prototype.isInName = function() {
+ return this.conversion.arg.type === 'NamedArgument' &&
+ this.conversion.arg.prefix.slice(-1) !== ' ';
+};
+
+/**
+ * Work out what the status of the current conversion is which involves looking
+ * not only at the conversion, but also checking if data has been provided
+ * where it should.
+ * @param arg For assignments with multiple args (e.g. array assignments) we
+ * can narrow the search for status to a single argument.
+ */
+Assignment.prototype.getStatus = function(arg) {
+ if (this.param.isDataRequired && !this.conversion.isDataProvided()) {
+ return Status.INCOMPLETE;
+ }
+
+ // Selection/Boolean types with a defined range of values will say that
+ // '' is INCOMPLETE, but the parameter may be optional, so we don't ask
+ // if the user doesn't need to enter something and hasn't done so.
+ if (!this.param.isDataRequired && this.arg.type === 'BlankArgument') {
+ return Status.VALID;
+ }
+
+ return this.conversion.getStatus(arg);
+};
+
+/**
+ * Helper when we're rebuilding command lines.
+ */
+Assignment.prototype.toString = function() {
+ return this.conversion.toString();
+};
+
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Assignment.prototype, '_summaryJson', {
+ get: function() {
+ return {
+ param: this.param.name + '/' + this.param.type.name,
+ defaultValue: this.param.defaultValue,
+ arg: this.conversion.arg._summaryJson,
+ value: this.value,
+ message: this.message,
+ status: this.getStatus().toString()
+ };
+ },
+ enumerable: true
+});
+
+exports.Assignment = Assignment;
+
+
+/**
+ * How to dynamically execute JavaScript code
+ */
+var customEval = eval;
+
+/**
+ * Setup a function to be called in place of 'eval', generally for security
+ * reasons
+ */
+exports.setEvalFunction = function(newCustomEval) {
+ customEval = newCustomEval;
+};
+
+/**
+ * Remove the binding done by setEvalFunction().
+ * We purposely set customEval to undefined rather than to 'eval' because there
+ * is an implication of setEvalFunction that we're in a security sensitive
+ * situation. What if we can trick GCLI into calling unsetEvalFunction() at the
+ * wrong time?
+ * So to properly undo the effects of setEvalFunction(), you need to call
+ * setEvalFunction(eval) rather than unsetEvalFunction(), however the latter is
+ * preferred in most cases.
+ */
+exports.unsetEvalFunction = function() {
+ customEval = undefined;
+};
+
+/**
+ * 'eval' command
+ */
+var evalCmd = {
+ item: 'command',
+ name: '{',
+ params: [
+ {
+ name: 'javascript',
+ type: 'javascript',
+ description: ''
+ }
+ ],
+ hidden: true,
+ description: { key: 'cliEvalJavascript' },
+ exec: function(args, context) {
+ var reply = customEval(args.javascript);
+ return context.typedData(typeof reply, reply);
+ },
+ isCommandRegexp: /^\s*\{\s*/
+};
+
+exports.items = [ evalCmd ];
+
+/**
+ * This is a special assignment to reflect the command itself.
+ */
+function CommandAssignment(requisition) {
+ var commandParamMetadata = {
+ name: '__command',
+ type: { name: 'command', allowNonExec: false }
+ };
+ // This is a hack so that rather than reply with a generic description of the
+ // command assignment, we reply with the description of the assigned command,
+ // (using a generic term if there is no assigned command)
+ var self = this;
+ Object.defineProperty(commandParamMetadata, 'description', {
+ get: function() {
+ var value = self.value;
+ return value && value.description ?
+ value.description :
+ 'The command to execute';
+ },
+ enumerable: true
+ });
+ this.param = new Parameter(requisition.system.types, commandParamMetadata);
+}
+
+CommandAssignment.prototype = Object.create(Assignment.prototype);
+
+CommandAssignment.prototype.getStatus = function(arg) {
+ return Status.combine(
+ Assignment.prototype.getStatus.call(this, arg),
+ this.conversion.value && this.conversion.value.exec ?
+ Status.VALID : Status.INCOMPLETE
+ );
+};
+
+exports.CommandAssignment = CommandAssignment;
+
+
+/**
+ * Special assignment used when ignoring parameters that don't have a home
+ */
+function UnassignedAssignment(requisition, arg) {
+ var isIncompleteName = (arg.text.charAt(0) === '-');
+ this.param = new Parameter(requisition.system.types, {
+ name: '__unassigned',
+ description: l10n.lookup('cliOptions'),
+ type: {
+ name: 'param',
+ requisition: requisition,
+ isIncompleteName: isIncompleteName
+ }
+ });
+
+ // It would be nice to do 'conversion = parm.type.parse(arg, ...)' except
+ // that type.parse returns a promise (even though it's synchronous in this
+ // case)
+ if (isIncompleteName) {
+ var lookup = commandModule.getDisplayedParamLookup(requisition);
+ var predictions = selectionModule.findPredictions(arg, lookup);
+ this.conversion = selectionModule.convertPredictions(arg, predictions);
+ }
+ else {
+ var message = l10n.lookup('cliUnusedArg');
+ this.conversion = new Conversion(undefined, arg, Status.ERROR, message);
+ }
+
+ this.conversion.assignment = this;
+}
+
+UnassignedAssignment.prototype = Object.create(Assignment.prototype);
+
+UnassignedAssignment.prototype.getStatus = function(arg) {
+ return this.conversion.getStatus();
+};
+
+var logErrors = true;
+
+/**
+ * Allow tests that expect failures to avoid clogging up the console
+ */
+Object.defineProperty(exports, 'logErrors', {
+ get: function() {
+ return logErrors;
+ },
+ set: function(val) {
+ logErrors = val;
+ },
+ enumerable: true
+});
+
+/**
+ * A Requisition collects the information needed to execute a command.
+ *
+ * (For a definition of the term, see http://en.wikipedia.org/wiki/Requisition)
+ * This term is used because carries the notion of a work-flow, or process to
+ * getting the information to execute a command correct.
+ * There is little point in a requisition for parameter-less commands because
+ * there is no information to collect. A Requisition is a collection of
+ * assignments of values to parameters, each handled by an instance of
+ * Assignment.
+ *
+ * @param system Allows access to the various plug-in points in GCLI. At a
+ * minimum it must contain commands and types objects.
+ * @param options A set of options to customize how GCLI is used. Includes:
+ * - environment An optional opaque object passed to commands in the
+ * Execution Context.
+ * - document A DOM Document passed to commands using the Execution Context in
+ * order to allow creation of DOM nodes. If missing Requisition will use the
+ * global 'document', or leave undefined.
+ * - commandOutputManager A custom commandOutputManager to which output should
+ * be sent
+ * @constructor
+ */
+function Requisition(system, options) {
+ options = options || {};
+
+ this.environment = options.environment || {};
+ this.document = options.document;
+ if (this.document == null) {
+ try {
+ this.document = document;
+ }
+ catch (ex) {
+ // Ignore
+ }
+ }
+
+ this.commandOutputManager = options.commandOutputManager || new CommandOutputManager();
+ this.system = system;
+
+ this.shell = {
+ cwd: '/', // Where we store the current working directory
+ env: {} // Where we store the current environment
+ };
+
+ // The command that we are about to execute.
+ // @see setCommandConversion()
+ this.commandAssignment = new CommandAssignment(this);
+
+ // The object that stores of Assignment objects that we are filling out.
+ // The Assignment objects are stored under their param.name for named
+ // lookup. Note: We make use of the property of Javascript objects that
+ // they are not just hashmaps, but linked-list hashmaps which iterate in
+ // insertion order.
+ // _assignments excludes the commandAssignment.
+ this._assignments = {};
+
+ // The count of assignments. Excludes the commandAssignment
+ this.assignmentCount = 0;
+
+ // Used to store cli arguments in the order entered on the cli
+ this._args = [];
+
+ // Used to store cli arguments that were not assigned to parameters
+ this._unassigned = [];
+
+ // Changes can be asynchronous, when one update starts before another
+ // finishes we abandon the former change
+ this._nextUpdateId = 0;
+
+ // We can set a prefix to typed commands to make it easier to focus on
+ // Allowing us to type "add -a; commit" in place of "git add -a; git commit"
+ this.prefix = '';
+
+ addMapping(this);
+ this._setBlankAssignment(this.commandAssignment);
+
+ // If a command calls context.update then the UI needs some way to be
+ // informed of the change
+ this.onExternalUpdate = util.createEvent('Requisition.onExternalUpdate');
+}
+
+/**
+ * Avoid memory leaks
+ */
+Requisition.prototype.destroy = function() {
+ this.document = undefined;
+ this.environment = undefined;
+ removeMapping(this);
+};
+
+/**
+ * If we're about to make an asynchronous change when other async changes could
+ * overtake this one, then we want to be able to bail out if overtaken. The
+ * value passed back from beginChange should be passed to endChangeCheckOrder
+ * on completion of calculation, before the results are applied in order to
+ * check that the calculation has not been overtaken
+ */
+Requisition.prototype._beginChange = function() {
+ var updateId = this._nextUpdateId;
+ this._nextUpdateId++;
+ return updateId;
+};
+
+/**
+ * Check to see if another change has started since updateId started.
+ * This allows us to bail out of an update.
+ * It's hard to make updates atomic because until you've responded to a parse
+ * of the command argument, you don't know how to parse the arguments to that
+ * command.
+ */
+Requisition.prototype._isChangeCurrent = function(updateId) {
+ return updateId + 1 === this._nextUpdateId;
+};
+
+/**
+ * See notes on beginChange
+ */
+Requisition.prototype._endChangeCheckOrder = function(updateId) {
+ if (updateId + 1 !== this._nextUpdateId) {
+ // An update that started after we did has already finished, so our
+ // changes are out of date. Abandon further work.
+ return false;
+ }
+
+ return true;
+};
+
+var legacy = false;
+
+/**
+ * Functions and data related to the execution of a command
+ */
+Object.defineProperty(Requisition.prototype, 'executionContext', {
+ get: function() {
+ if (this._executionContext == null) {
+ this._executionContext = {
+ defer: defer,
+ typedData: function(type, data) {
+ return {
+ isTypedData: true,
+ data: data,
+ type: type
+ };
+ },
+ getArgsObject: this.getArgsObject.bind(this)
+ };
+
+ // Alias requisition so we're clear about what's what
+ var requisition = this;
+ Object.defineProperty(this._executionContext, 'prefix', {
+ get: function() { return requisition.prefix; },
+ enumerable: true
+ });
+ Object.defineProperty(this._executionContext, 'typed', {
+ get: function() { return requisition.toString(); },
+ enumerable: true
+ });
+ Object.defineProperty(this._executionContext, 'environment', {
+ get: function() { return requisition.environment; },
+ enumerable: true
+ });
+ Object.defineProperty(this._executionContext, 'shell', {
+ get: function() { return requisition.shell; },
+ enumerable: true
+ });
+ Object.defineProperty(this._executionContext, 'system', {
+ get: function() { return requisition.system; },
+ enumerable: true
+ });
+
+ this._executionContext.updateExec = this._contextUpdateExec.bind(this);
+
+ if (legacy) {
+ this._executionContext.createView = view.createView;
+ this._executionContext.exec = this.exec.bind(this);
+ this._executionContext.update = this._contextUpdate.bind(this);
+
+ Object.defineProperty(this._executionContext, 'document', {
+ get: function() { return requisition.document; },
+ enumerable: true
+ });
+ }
+ }
+
+ return this._executionContext;
+ },
+ enumerable: true
+});
+
+/**
+ * Functions and data related to the conversion of the output of a command
+ */
+Object.defineProperty(Requisition.prototype, 'conversionContext', {
+ get: function() {
+ if (this._conversionContext == null) {
+ this._conversionContext = {
+ defer: defer,
+
+ createView: view.createView,
+ exec: this.exec.bind(this),
+ update: this._contextUpdate.bind(this),
+ updateExec: this._contextUpdateExec.bind(this)
+ };
+
+ // Alias requisition so we're clear about what's what
+ var requisition = this;
+
+ Object.defineProperty(this._conversionContext, 'document', {
+ get: function() { return requisition.document; },
+ enumerable: true
+ });
+ Object.defineProperty(this._conversionContext, 'environment', {
+ get: function() { return requisition.environment; },
+ enumerable: true
+ });
+ Object.defineProperty(this._conversionContext, 'system', {
+ get: function() { return requisition.system; },
+ enumerable: true
+ });
+ }
+
+ return this._conversionContext;
+ },
+ enumerable: true
+});
+
+/**
+ * Assignments have an order, so we need to store them in an array.
+ * But we also need named access ...
+ * @return The found assignment, or undefined, if no match was found
+ */
+Requisition.prototype.getAssignment = function(nameOrNumber) {
+ var name = (typeof nameOrNumber === 'string') ?
+ nameOrNumber :
+ Object.keys(this._assignments)[nameOrNumber];
+ return this._assignments[name] || undefined;
+};
+
+/**
+ * Where parameter name == assignment names - they are the same
+ */
+Requisition.prototype.getParameterNames = function() {
+ return Object.keys(this._assignments);
+};
+
+/**
+ * The overall status is the most severe status.
+ * There is no such thing as an INCOMPLETE overall status because the
+ * definition of INCOMPLETE takes into account the cursor position to say 'this
+ * isn't quite ERROR because the user can fix it by typing', however overall,
+ * this is still an error status.
+ */
+Object.defineProperty(Requisition.prototype, 'status', {
+ get: function() {
+ var status = Status.VALID;
+ if (this._unassigned.length !== 0) {
+ var isAllIncomplete = true;
+ this._unassigned.forEach(function(assignment) {
+ if (!assignment.param.type.isIncompleteName) {
+ isAllIncomplete = false;
+ }
+ });
+ status = isAllIncomplete ? Status.INCOMPLETE : Status.ERROR;
+ }
+
+ this.getAssignments(true).forEach(function(assignment) {
+ var assignStatus = assignment.getStatus();
+ if (assignStatus > status) {
+ status = assignStatus;
+ }
+ }, this);
+ if (status === Status.INCOMPLETE) {
+ status = Status.ERROR;
+ }
+ return status;
+ },
+ enumerable : true
+});
+
+/**
+ * If ``requisition.status != VALID`` message then return a string which
+ * best describes what is wrong. Generally error messages are delivered by
+ * looking at the error associated with the argument at the cursor, but there
+ * are times when you just want to say 'tell me the worst'.
+ * If ``requisition.status != VALID`` then return ``null``.
+ */
+Requisition.prototype.getStatusMessage = function() {
+ if (this.commandAssignment.getStatus() !== Status.VALID) {
+ return l10n.lookupFormat('cliUnknownCommand2',
+ [ this.commandAssignment.arg.text ]);
+ }
+
+ var assignments = this.getAssignments();
+ for (var i = 0; i < assignments.length; i++) {
+ if (assignments[i].getStatus() !== Status.VALID) {
+ return assignments[i].message;
+ }
+ }
+
+ if (this._unassigned.length !== 0) {
+ return l10n.lookup('cliUnusedArg');
+ }
+
+ return null;
+};
+
+/**
+ * Extract the names and values of all the assignments, and return as
+ * an object.
+ */
+Requisition.prototype.getArgsObject = function() {
+ var args = {};
+ this.getAssignments().forEach(function(assignment) {
+ args[assignment.param.name] = assignment.conversion.isDataProvided() ?
+ assignment.value :
+ assignment.param.defaultValue;
+ }, this);
+ return args;
+};
+
+/**
+ * Access the arguments as an array.
+ * @param includeCommand By default only the parameter arguments are
+ * returned unless (includeCommand === true), in which case the list is
+ * prepended with commandAssignment.arg
+ */
+Requisition.prototype.getAssignments = function(includeCommand) {
+ var assignments = [];
+ if (includeCommand === true) {
+ assignments.push(this.commandAssignment);
+ }
+ Object.keys(this._assignments).forEach(function(name) {
+ assignments.push(this.getAssignment(name));
+ }, this);
+ return assignments;
+};
+
+/**
+ * There are a few places where we need to know what the 'next thing' is. What
+ * is the user going to be filling out next (assuming they don't enter a named
+ * argument). The next argument is the first in line that is both blank, and
+ * that can be filled in positionally.
+ * @return The next assignment to be used, or null if all the positional
+ * parameters have values.
+ */
+Requisition.prototype._getFirstBlankPositionalAssignment = function() {
+ var reply = null;
+ Object.keys(this._assignments).some(function(name) {
+ var assignment = this.getAssignment(name);
+ if (assignment.arg.type === 'BlankArgument' &&
+ assignment.param.isPositionalAllowed) {
+ reply = assignment;
+ return true; // i.e. break
+ }
+ return false;
+ }, this);
+ return reply;
+};
+
+/**
+ * The update process is asynchronous, so there is (unavoidably) a window
+ * where we've worked out the command but don't yet understand all the params.
+ * If we try to do things to a requisition in this window we may get
+ * inconsistent results. Asynchronous promises have made the window bigger.
+ * The only time we've seen this in practice is during focus events due to
+ * clicking on a shortcut. The focus want to check the cursor position while
+ * the shortcut is updating the command line.
+ * This function allows us to detect and back out of this problem.
+ * We should be able to remove this function when all the state in a
+ * requisition can be encapsulated and updated atomically.
+ */
+Requisition.prototype.isUpToDate = function() {
+ if (!this._args) {
+ return false;
+ }
+ for (var i = 0; i < this._args.length; i++) {
+ if (this._args[i].assignment == null) {
+ return false;
+ }
+ }
+ return true;
+};
+
+/**
+ * Look through the arguments attached to our assignments for the assignment
+ * at the given position.
+ * @param {number} cursor The cursor position to query
+ */
+Requisition.prototype.getAssignmentAt = function(cursor) {
+ // We short circuit this one because we may have no args, or no args with
+ // any size and the alg below only finds arguments with size.
+ if (cursor === 0) {
+ return this.commandAssignment;
+ }
+
+ var assignForPos = [];
+ var i, j;
+ for (i = 0; i < this._args.length; i++) {
+ var arg = this._args[i];
+ var assignment = arg.assignment;
+
+ // prefix and text are clearly part of the argument
+ for (j = 0; j < arg.prefix.length; j++) {
+ assignForPos.push(assignment);
+ }
+ for (j = 0; j < arg.text.length; j++) {
+ assignForPos.push(assignment);
+ }
+
+ // suffix is part of the argument only if this is a named parameter,
+ // otherwise it looks forwards
+ if (arg.assignment.arg.type === 'NamedArgument') {
+ // leave the argument as it is
+ }
+ else if (this._args.length > i + 1) {
+ // first to the next argument
+ assignment = this._args[i + 1].assignment;
+ }
+ else {
+ // then to the first blank positional parameter, leaving 'as is' if none
+ var nextAssignment = this._getFirstBlankPositionalAssignment();
+ if (nextAssignment != null) {
+ assignment = nextAssignment;
+ }
+ }
+
+ for (j = 0; j < arg.suffix.length; j++) {
+ assignForPos.push(assignment);
+ }
+ }
+
+ // Possible shortcut, we don't really need to go through all the args
+ // to work out the solution to this
+
+ return assignForPos[cursor - 1];
+};
+
+/**
+ * Extract a canonical version of the input
+ * @return a promise of a string which is the canonical version of what was
+ * typed
+ */
+Requisition.prototype.toCanonicalString = function() {
+ var cmd = this.commandAssignment.value ?
+ this.commandAssignment.value.name :
+ this.commandAssignment.arg.text;
+
+ // Canonically, if we've opened with a { then we should have a } to close
+ var lineSuffix = '';
+ if (cmd === '{') {
+ var scriptSuffix = this.getAssignment(0).arg.suffix;
+ lineSuffix = (scriptSuffix.indexOf('}') === -1) ? ' }' : '';
+ }
+
+ var ctx = this.executionContext;
+
+ // First stringify all the arguments
+ var argPromise = util.promiseEach(this.getAssignments(), function(assignment) {
+ // Bug 664377: This will cause problems if there is a non-default value
+ // after a default value. Also we need to decide when to use
+ // named parameters in place of positional params. Both can wait.
+ if (assignment.value === assignment.param.defaultValue) {
+ return '';
+ }
+
+ var val = assignment.param.type.stringify(assignment.value, ctx);
+ return Promise.resolve(val).then(function(str) {
+ return ' ' + str;
+ }.bind(this));
+ }.bind(this));
+
+ return argPromise.then(function(strings) {
+ return cmd + strings.join('') + lineSuffix;
+ }.bind(this));
+};
+
+/**
+ * Reconstitute the input from the args
+ */
+Requisition.prototype.toString = function() {
+ if (!this._args) {
+ throw new Error('toString requires a command line. See source.');
+ }
+
+ return this._args.map(function(arg) {
+ return arg.toString();
+ }).join('');
+};
+
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Requisition.prototype, '_summaryJson', {
+ get: function() {
+ var summary = {
+ $args: this._args.map(function(arg) {
+ return arg._summaryJson;
+ }),
+ _command: this.commandAssignment._summaryJson,
+ _unassigned: this._unassigned.forEach(function(assignment) {
+ return assignment._summaryJson;
+ })
+ };
+
+ Object.keys(this._assignments).forEach(function(name) {
+ summary[name] = this.getAssignment(name)._summaryJson;
+ }.bind(this));
+
+ return summary;
+ },
+ enumerable: true
+});
+
+/**
+ * When any assignment changes, we might need to update the _args array to
+ * match and inform people of changes to the typed input text.
+ */
+Requisition.prototype._setAssignmentInternal = function(assignment, conversion) {
+ var oldConversion = assignment.conversion;
+
+ assignment.conversion = conversion;
+ assignment.conversion.assignment = assignment;
+
+ // Do nothing if the conversion is unchanged
+ if (assignment.conversion.equals(oldConversion)) {
+ if (assignment === this.commandAssignment) {
+ this._setBlankArguments();
+ }
+ return;
+ }
+
+ // When the command changes, we need to keep a bunch of stuff in sync
+ if (assignment === this.commandAssignment) {
+ this._assignments = {};
+
+ var command = this.commandAssignment.value;
+ if (command) {
+ for (var i = 0; i < command.params.length; i++) {
+ var param = command.params[i];
+ var newAssignment = new Assignment(param);
+ this._setBlankAssignment(newAssignment);
+ this._assignments[param.name] = newAssignment;
+ }
+ }
+ this.assignmentCount = Object.keys(this._assignments).length;
+ }
+};
+
+/**
+ * Internal function to alter the given assignment using the given arg.
+ * @param assignment The assignment to alter
+ * @param arg The new value for the assignment. An instance of Argument, or an
+ * instance of Conversion, or null to set the blank value.
+ * @param options There are a number of ways to customize how the assignment
+ * is made, including:
+ * - internal: (default:false) External updates are required to do more work,
+ * including adjusting the args in this requisition to stay in sync.
+ * On the other hand non internal changes use beginChange to back out of
+ * changes when overtaken asynchronously.
+ * Setting internal:true effectively means this is being called as part of
+ * the update process.
+ * - matchPadding: (default:false) Alter the whitespace on the prefix and
+ * suffix of the new argument to match that of the old argument. This only
+ * makes sense with internal=false
+ * @return A promise that resolves to undefined when the assignment is complete
+ */
+Requisition.prototype.setAssignment = function(assignment, arg, options) {
+ options = options || {};
+ if (!options.internal) {
+ var originalArgs = assignment.arg.getArgs();
+
+ // Update the args array
+ var replacementArgs = arg.getArgs();
+ var maxLen = Math.max(originalArgs.length, replacementArgs.length);
+ for (var i = 0; i < maxLen; i++) {
+ // If there are no more original args, or if the original arg was blank
+ // (i.e. not typed by the user), we'll just need to add at the end
+ if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') {
+ this._args.push(replacementArgs[i]);
+ continue;
+ }
+
+ var index = this._args.indexOf(originalArgs[i]);
+ if (index === -1) {
+ console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args);
+ throw new Error('Couldn\'t find ' + originalArgs[i]);
+ }
+
+ // If there are no more replacement args, we just remove the original args
+ // Otherwise swap original args and replacements
+ if (i >= replacementArgs.length) {
+ this._args.splice(index, 1);
+ }
+ else {
+ if (options.matchPadding) {
+ if (replacementArgs[i].prefix.length === 0 &&
+ this._args[index].prefix.length !== 0) {
+ replacementArgs[i].prefix = this._args[index].prefix;
+ }
+ if (replacementArgs[i].suffix.length === 0 &&
+ this._args[index].suffix.length !== 0) {
+ replacementArgs[i].suffix = this._args[index].suffix;
+ }
+ }
+ this._args[index] = replacementArgs[i];
+ }
+ }
+ }
+
+ var updateId = options.internal ? null : this._beginChange();
+
+ var setAssignmentInternal = function(conversion) {
+ if (options.internal || this._isChangeCurrent(updateId)) {
+ this._setAssignmentInternal(assignment, conversion);
+ }
+
+ if (!options.internal) {
+ this._endChangeCheckOrder(updateId);
+ }
+
+ return Promise.resolve(undefined);
+ }.bind(this);
+
+ if (arg == null) {
+ var blank = assignment.param.type.getBlank(this.executionContext);
+ return setAssignmentInternal(blank);
+ }
+
+ if (typeof arg.getStatus === 'function') {
+ // It's not really an arg, it's a conversion already
+ return setAssignmentInternal(arg);
+ }
+
+ var parsed = assignment.param.type.parse(arg, this.executionContext);
+ return parsed.then(setAssignmentInternal);
+};
+
+/**
+ * Reset an assignment to its default value.
+ * For internal use only.
+ * Happens synchronously.
+ */
+Requisition.prototype._setBlankAssignment = function(assignment) {
+ var blank = assignment.param.type.getBlank(this.executionContext);
+ this._setAssignmentInternal(assignment, blank);
+};
+
+/**
+ * Reset all the assignments to their default values.
+ * For internal use only.
+ * Happens synchronously.
+ */
+Requisition.prototype._setBlankArguments = function() {
+ this.getAssignments().forEach(this._setBlankAssignment.bind(this));
+};
+
+/**
+ * Input trace gives us an array of Argument tracing objects, one for each
+ * character in the typed input, from which we can derive information about how
+ * to display this typed input. It's a bit like toString on steroids.
+ * <p>
+ * The returned object has the following members:<ul>
+ * <li>character: The character to which this arg trace refers.
+ * <li>arg: The Argument to which this character is assigned.
+ * <li>part: One of ['prefix'|'text'|suffix'] - how was this char understood
+ * </ul>
+ * <p>
+ * The Argument objects are as output from tokenize() rather than as applied
+ * to Assignments by _assign() (i.e. they are not instances of NamedArgument,
+ * ArrayArgument, etc).
+ * <p>
+ * To get at the arguments applied to the assignments simply call
+ * <tt>arg.assignment.arg</tt>. If <tt>arg.assignment.arg !== arg</tt> then
+ * the arg applied to the assignment will contain the original arg.
+ * See _assign() for details.
+ */
+Requisition.prototype.createInputArgTrace = function() {
+ if (!this._args) {
+ throw new Error('createInputMap requires a command line. See source.');
+ }
+
+ var args = [];
+ var i;
+ this._args.forEach(function(arg) {
+ for (i = 0; i < arg.prefix.length; i++) {
+ args.push({ arg: arg, character: arg.prefix[i], part: 'prefix' });
+ }
+ for (i = 0; i < arg.text.length; i++) {
+ args.push({ arg: arg, character: arg.text[i], part: 'text' });
+ }
+ for (i = 0; i < arg.suffix.length; i++) {
+ args.push({ arg: arg, character: arg.suffix[i], part: 'suffix' });
+ }
+ });
+
+ return args;
+};
+
+/**
+ * If the last character is whitespace then things that we suggest to add to
+ * the end don't need a space prefix.
+ * While this is quite a niche function, it has 2 benefits:
+ * - it's more correct because we can distinguish between final whitespace that
+ * is part of an unclosed string, and parameter separating whitespace.
+ * - also it's faster than toString() the whole thing and checking the end char
+ * @return true iff the last character is interpreted as parameter separating
+ * whitespace
+ */
+Requisition.prototype.typedEndsWithSeparator = function() {
+ if (!this._args) {
+ throw new Error('typedEndsWithSeparator requires a command line. See source.');
+ }
+
+ if (this._args.length === 0) {
+ return false;
+ }
+
+ // This is not as easy as doing (this.toString().slice(-1) === ' ')
+ // See the doc comments above; We're checking for separators, not spaces
+ var lastArg = this._args.slice(-1)[0];
+ if (lastArg.suffix.slice(-1) === ' ') {
+ return true;
+ }
+
+ return lastArg.text === '' && lastArg.suffix === ''
+ && lastArg.prefix.slice(-1) === ' ';
+};
+
+/**
+ * Return an array of Status scores so we can create a marked up
+ * version of the command line input.
+ * @param cursor We only take a status of INCOMPLETE to be INCOMPLETE when the
+ * cursor is actually in the argument. Otherwise it's an error.
+ * @return Array of objects each containing <tt>status</tt> property and a
+ * <tt>string</tt> property containing the characters to which the status
+ * applies. Concatenating the strings in order gives the original input.
+ */
+Requisition.prototype.getInputStatusMarkup = function(cursor) {
+ var argTraces = this.createInputArgTrace();
+ // Generally the 'argument at the cursor' is the argument before the cursor
+ // unless it is before the first char, in which case we take the first.
+ cursor = cursor === 0 ? 0 : cursor - 1;
+ var cTrace = argTraces[cursor];
+
+ var markup = [];
+ for (var i = 0; i < argTraces.length; i++) {
+ var argTrace = argTraces[i];
+ var arg = argTrace.arg;
+ var status = Status.VALID;
+ // When things get very async we can get here while something else is
+ // doing an update, in which case arg.assignment == null, so we check first
+ if (argTrace.part === 'text' && arg.assignment != null) {
+ status = arg.assignment.getStatus(arg);
+ // Promote INCOMPLETE to ERROR ...
+ if (status === Status.INCOMPLETE) {
+ // If the cursor is in the prefix or suffix of an argument then we
+ // don't consider it in the argument for the purposes of preventing
+ // the escalation to ERROR. However if this is a NamedArgument, then we
+ // allow the suffix (as space between 2 parts of the argument) to be in.
+ // We use arg.assignment.arg not arg because we're looking at the arg
+ // that got put into the assignment not as returned by tokenize()
+ var isNamed = (cTrace.arg.assignment.arg.type === 'NamedArgument');
+ var isInside = cTrace.part === 'text' ||
+ (isNamed && cTrace.part === 'suffix');
+ if (arg.assignment !== cTrace.arg.assignment || !isInside) {
+ // And if we're not in the command
+ if (!(arg.assignment instanceof CommandAssignment)) {
+ status = Status.ERROR;
+ }
+ }
+ }
+ }
+
+ markup.push({ status: status, string: argTrace.character });
+ }
+
+ // De-dupe: merge entries where 2 adjacent have same status
+ i = 0;
+ while (i < markup.length - 1) {
+ if (markup[i].status === markup[i + 1].status) {
+ markup[i].string += markup[i + 1].string;
+ markup.splice(i + 1, 1);
+ }
+ else {
+ i++;
+ }
+ }
+
+ return markup;
+};
+
+/**
+ * Describe the state of the current input in a way that allows display of
+ * predictions and completion hints
+ * @param start The location of the cursor
+ * @param rank The index of the chosen prediction
+ * @return A promise of an object containing the following properties:
+ * - statusMarkup: An array of Status scores so we can create a marked up
+ * version of the command line input. See getInputStatusMarkup() for details
+ * - unclosedJs: Is the entered command a JS command with no closing '}'?
+ * - directTabText: A promise of the text that we *add* to the command line
+ * when TAB is pressed, to be displayed directly after the cursor. See also
+ * arrowTabText.
+ * - emptyParameters: A promise of the text that describes the arguments that
+ * the user is yet to type.
+ * - arrowTabText: A promise of the text that *replaces* the current argument
+ * when TAB is pressed, generally displayed after a "|->" symbol. See also
+ * directTabText.
+ */
+Requisition.prototype.getStateData = function(start, rank) {
+ var typed = this.toString();
+ var current = this.getAssignmentAt(start);
+ var context = this.executionContext;
+ var predictionPromise = (typed.trim().length !== 0) ?
+ current.getPredictionRanked(context, rank) :
+ Promise.resolve(null);
+
+ return predictionPromise.then(function(prediction) {
+ // directTabText is for when the current input is a prefix of the completion
+ // arrowTabText is for when we need to use an -> to show what will be used
+ var directTabText = '';
+ var arrowTabText = '';
+ var emptyParameters = [];
+
+ if (typed.trim().length !== 0) {
+ var cArg = current.arg;
+
+ if (prediction) {
+ var tabText = prediction.name;
+ var existing = cArg.text;
+
+ // Normally the cursor being just before whitespace means that you are
+ // 'in' the previous argument, which means that the prediction is based
+ // on that argument, however NamedArguments break this by having 2 parts
+ // so we need to prepend the tabText with a space for NamedArguments,
+ // but only when there isn't already a space at the end of the prefix
+ // (i.e. ' --name' not ' --name ')
+ if (current.isInName()) {
+ tabText = ' ' + tabText;
+ }
+
+ if (existing !== tabText) {
+ // Decide to use directTabText or arrowTabText
+ // Strip any leading whitespace from the user inputted value because
+ // the tabText will never have leading whitespace.
+ var inputValue = existing.replace(/^\s*/, '');
+ var isStrictCompletion = tabText.indexOf(inputValue) === 0;
+ if (isStrictCompletion && start === typed.length) {
+ // Display the suffix of the prediction as the completion
+ var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
+
+ directTabText = tabText.slice(existing.length - numLeadingSpaces);
+ }
+ else {
+ // Display the '-> prediction' at the end of the completer element
+ // \u21E5 is the JS escape right arrow
+ arrowTabText = '\u21E5 ' + tabText;
+ }
+ }
+ }
+ else {
+ // There's no prediction, but if this is a named argument that needs a
+ // value (that is without any) then we need to show that one is needed
+ // For example 'git commit --message ', clearly needs some more text
+ if (cArg.type === 'NamedArgument' && cArg.valueArg == null) {
+ emptyParameters.push('<' + current.param.type.name + '>\u00a0');
+ }
+ }
+ }
+
+ // Add a space between the typed text (+ directTabText) and the hints,
+ // making sure we don't add 2 sets of padding
+ if (directTabText !== '') {
+ directTabText += '\u00a0'; // a.k.a &nbsp;
+ }
+ else if (!this.typedEndsWithSeparator()) {
+ emptyParameters.unshift('\u00a0');
+ }
+
+ // Calculate the list of parameters to be filled in
+ // We generate an array of emptyParameter markers for each positional
+ // parameter to the current command.
+ // Generally each emptyParameter marker begins with a space to separate it
+ // from whatever came before, unless what comes before ends in a space.
+
+ this.getAssignments().forEach(function(assignment) {
+ // Named arguments are handled with a group [options] marker
+ if (!assignment.param.isPositionalAllowed) {
+ return;
+ }
+
+ // No hints if we've got content for this parameter
+ if (assignment.arg.toString().trim() !== '') {
+ return;
+ }
+
+ // No hints if we have a prediction
+ if (directTabText !== '' && current === assignment) {
+ return;
+ }
+
+ var text = (assignment.param.isDataRequired) ?
+ '<' + assignment.param.name + '>\u00a0' :
+ '[' + assignment.param.name + ']\u00a0';
+
+ emptyParameters.push(text);
+ }.bind(this));
+
+ var command = this.commandAssignment.value;
+ var addOptionsMarker = false;
+
+ // We add an '[options]' marker when there are named parameters that are
+ // not filled in and not hidden, and we don't have any directTabText
+ if (command && command.hasNamedParameters) {
+ command.params.forEach(function(param) {
+ var arg = this.getAssignment(param.name).arg;
+ if (!param.isPositionalAllowed && !param.hidden
+ && arg.type === 'BlankArgument') {
+ addOptionsMarker = true;
+ }
+ }, this);
+ }
+
+ if (addOptionsMarker) {
+ // Add an nbsp if we don't have one at the end of the input or if
+ // this isn't the first param we've mentioned
+ emptyParameters.push('[options]\u00a0');
+ }
+
+ // Is the entered command a JS command with no closing '}'?
+ var unclosedJs = command && command.name === '{' &&
+ this.getAssignment(0).arg.suffix.indexOf('}') === -1;
+
+ return {
+ statusMarkup: this.getInputStatusMarkup(start),
+ unclosedJs: unclosedJs,
+ directTabText: directTabText,
+ arrowTabText: arrowTabText,
+ emptyParameters: emptyParameters
+ };
+ }.bind(this));
+};
+
+/**
+ * Pressing TAB sometimes requires that we add a space to denote that we're on
+ * to the 'next thing'.
+ * @param assignment The assignment to which to append the space
+ */
+Requisition.prototype._addSpace = function(assignment) {
+ var arg = assignment.arg.beget({ suffixSpace: true });
+ if (arg !== assignment.arg) {
+ return this.setAssignment(assignment, arg);
+ }
+ else {
+ return Promise.resolve(undefined);
+ }
+};
+
+/**
+ * Complete the argument at <tt>cursor</tt>.
+ * Basically the same as:
+ * assignment = getAssignmentAt(cursor);
+ * assignment.value = assignment.conversion.predictions[0];
+ * Except it's done safely, and with particular care to where we place the
+ * space, which is complex, and annoying if we get it wrong.
+ *
+ * WARNING: complete() can happen asynchronously.
+ *
+ * @param cursor The cursor configuration. Should have start and end properties
+ * which should be set to start and end of the selection.
+ * @param rank The index of the prediction that we should choose.
+ * This number is not bounded by the size of the prediction array, we take the
+ * modulus to get it within bounds
+ * @return A promise which completes (with undefined) when any outstanding
+ * completion tasks are done.
+ */
+Requisition.prototype.complete = function(cursor, rank) {
+ var assignment = this.getAssignmentAt(cursor.start);
+
+ var context = this.executionContext;
+ var predictionPromise = assignment.getPredictionRanked(context, rank);
+ return predictionPromise.then(function(prediction) {
+ var outstanding = [];
+
+ // Note: Since complete is asynchronous we should perhaps have a system to
+ // bail out of making changes if the command line has changed since TAB
+ // was pressed. It's not yet clear if this will be a problem.
+
+ if (prediction == null) {
+ // No predictions generally means we shouldn't change anything on TAB,
+ // but TAB has the connotation of 'next thing' and when we're at the end
+ // of a thing that implies that we should add a space. i.e.
+ // 'help<TAB>' -> 'help '
+ // But we should only do this if the thing that we're 'completing' is
+ // valid and doesn't already end in a space.
+ if (assignment.arg.suffix.slice(-1) !== ' ' &&
+ assignment.getStatus() === Status.VALID) {
+ outstanding.push(this._addSpace(assignment));
+ }
+
+ // Also add a space if we are in the name part of an assignment, however
+ // this time we don't want the 'push the space to the next assignment'
+ // logic, so we don't use addSpace
+ if (assignment.isInName()) {
+ var newArg = assignment.arg.beget({ prefixPostSpace: true });
+ outstanding.push(this.setAssignment(assignment, newArg));
+ }
+ }
+ else {
+ // Mutate this argument to hold the completion
+ var arg = assignment.arg.beget({
+ text: prediction.name,
+ dontQuote: (assignment === this.commandAssignment)
+ });
+ var assignPromise = this.setAssignment(assignment, arg);
+
+ if (!prediction.incomplete) {
+ assignPromise = assignPromise.then(function() {
+ // The prediction is complete, add a space to let the user move-on
+ return this._addSpace(assignment).then(function() {
+ // Bug 779443 - Remove or explain the re-parse
+ if (assignment instanceof UnassignedAssignment) {
+ return this.update(this.toString());
+ }
+ }.bind(this));
+ }.bind(this));
+ }
+
+ outstanding.push(assignPromise);
+ }
+
+ return Promise.all(outstanding).then(function() {
+ return true;
+ }.bind(this));
+ }.bind(this));
+};
+
+/**
+ * Replace the current value with the lower value if such a concept exists.
+ */
+Requisition.prototype.nudge = function(assignment, by) {
+ var ctx = this.executionContext;
+ var val = assignment.param.type.nudge(assignment.value, by, ctx);
+ return Promise.resolve(val).then(function(replacement) {
+ if (replacement != null) {
+ var val = assignment.param.type.stringify(replacement, ctx);
+ return Promise.resolve(val).then(function(str) {
+ var arg = assignment.arg.beget({ text: str });
+ return this.setAssignment(assignment, arg);
+ }.bind(this));
+ }
+ }.bind(this));
+};
+
+/**
+ * Helper to find the 'data-command' attribute, used by |update()|
+ */
+function getDataCommandAttribute(element) {
+ var command = element.getAttribute('data-command');
+ if (!command) {
+ command = element.querySelector('*[data-command]')
+ .getAttribute('data-command');
+ }
+ return command;
+}
+
+/**
+ * Designed to be called from context.update(). Acts just like update() except
+ * that it also calls onExternalUpdate() to inform the UI of an unexpected
+ * change to the current command.
+ */
+Requisition.prototype._contextUpdate = function(typed) {
+ return this.update(typed).then(function(reply) {
+ this.onExternalUpdate({ typed: typed });
+ return reply;
+ }.bind(this));
+};
+
+/**
+ * Called by the UI when ever the user interacts with a command line input
+ * @param typed The contents of the input field OR an HTML element (or an event
+ * that targets an HTML element) which has a data-command attribute or a child
+ * with the same that contains the command to update with
+ */
+Requisition.prototype.update = function(typed) {
+ // Should be "if (typed instanceof HTMLElement)" except Gecko
+ if (typeof typed.querySelector === 'function') {
+ typed = getDataCommandAttribute(typed);
+ }
+ // Should be "if (typed instanceof Event)" except Gecko
+ if (typeof typed.currentTarget === 'object') {
+ typed = getDataCommandAttribute(typed.currentTarget);
+ }
+
+ var updateId = this._beginChange();
+
+ this._args = exports.tokenize(typed);
+ var args = this._args.slice(0); // i.e. clone
+
+ this._split(args);
+
+ return this._assign(args).then(function() {
+ return this._endChangeCheckOrder(updateId);
+ }.bind(this));
+};
+
+/**
+ * Similar to update('') except that it's guaranteed to execute synchronously
+ */
+Requisition.prototype.clear = function() {
+ var arg = new Argument('', '', '');
+ this._args = [ arg ];
+
+ var conversion = commandModule.parse(this.executionContext, arg, false);
+ this.setAssignment(this.commandAssignment, conversion, { internal: true });
+};
+
+/**
+ * tokenize() is a state machine. These are the states.
+ */
+var In = {
+ /**
+ * The last character was ' '.
+ * Typing a ' ' character will not change the mode
+ * Typing one of '"{ will change mode to SINGLE_Q, DOUBLE_Q or SCRIPT.
+ * Anything else goes into SIMPLE mode.
+ */
+ WHITESPACE: 1,
+
+ /**
+ * The last character was part of a parameter.
+ * Typing ' ' returns to WHITESPACE mode. Any other character
+ * (including '"{} which are otherwise special) does not change the mode.
+ */
+ SIMPLE: 2,
+
+ /**
+ * We're inside single quotes: '
+ * Typing ' returns to WHITESPACE mode. Other characters do not change mode.
+ */
+ SINGLE_Q: 3,
+
+ /**
+ * We're inside double quotes: "
+ * Typing " returns to WHITESPACE mode. Other characters do not change mode.
+ */
+ DOUBLE_Q: 4,
+
+ /**
+ * We're inside { and }
+ * Typing } returns to WHITESPACE mode. Other characters do not change mode.
+ * SCRIPT mode is slightly different from other modes in that spaces between
+ * the {/} delimiters and the actual input are not considered significant.
+ * e.g: " x " is a 3 character string, delimited by double quotes, however
+ * { x } is a 1 character JavaScript surrounded by whitespace and {}
+ * delimiters.
+ * In the short term we assume that the JS routines can make sense of the
+ * extra whitespace, however at some stage we may need to move the space into
+ * the Argument prefix/suffix.
+ * Also we don't attempt to handle nested {}. See bug 678961
+ */
+ SCRIPT: 5
+};
+
+/**
+ * Split up the input taking into account ', " and {.
+ * We don't consider \t or other classical whitespace characters to split
+ * arguments apart. For one thing these characters are hard to type, but also
+ * if the user has gone to the trouble of pasting a TAB character into the
+ * input field (or whatever it takes), they probably mean it.
+ */
+exports.tokenize = function(typed) {
+ // For blank input, place a dummy empty argument into the list
+ if (typed == null || typed.length === 0) {
+ return [ new Argument('', '', '') ];
+ }
+
+ if (isSimple(typed)) {
+ return [ new Argument(typed, '', '') ];
+ }
+
+ var mode = In.WHITESPACE;
+
+ // First we swap out escaped characters that are special to the tokenizer.
+ // So a backslash followed by any of ['"{} ] is turned into a unicode private
+ // char so we can swap back later
+ typed = typed
+ .replace(/\\\\/g, '\uF000')
+ .replace(/\\ /g, '\uF001')
+ .replace(/\\'/g, '\uF002')
+ .replace(/\\"/g, '\uF003')
+ .replace(/\\{/g, '\uF004')
+ .replace(/\\}/g, '\uF005');
+
+ function unescape2(escaped) {
+ return escaped
+ .replace(/\uF000/g, '\\\\')
+ .replace(/\uF001/g, '\\ ')
+ .replace(/\uF002/g, '\\\'')
+ .replace(/\uF003/g, '\\\"')
+ .replace(/\uF004/g, '\\{')
+ .replace(/\uF005/g, '\\}');
+ }
+
+ var i = 0; // The index of the current character
+ var start = 0; // Where did this section start?
+ var prefix = ''; // Stuff that comes before the current argument
+ var args = []; // The array that we're creating
+ var blockDepth = 0; // For JS with nested {}
+
+ // This is just a state machine. We're going through the string char by char
+ // The 'mode' is one of the 'In' states. As we go, we're adding Arguments
+ // to the 'args' array.
+
+ while (true) {
+ var c = typed[i];
+ var str;
+ switch (mode) {
+ case In.WHITESPACE:
+ if (c === '\'') {
+ prefix = typed.substring(start, i + 1);
+ mode = In.SINGLE_Q;
+ start = i + 1;
+ }
+ else if (c === '"') {
+ prefix = typed.substring(start, i + 1);
+ mode = In.DOUBLE_Q;
+ start = i + 1;
+ }
+ else if (c === '{') {
+ prefix = typed.substring(start, i + 1);
+ mode = In.SCRIPT;
+ blockDepth++;
+ start = i + 1;
+ }
+ else if (/ /.test(c)) {
+ // Still whitespace, do nothing
+ }
+ else {
+ prefix = typed.substring(start, i);
+ mode = In.SIMPLE;
+ start = i;
+ }
+ break;
+
+ case In.SIMPLE:
+ // There is an edge case of xx'xx which we are assuming to
+ // be a single parameter (and same with ")
+ if (c === ' ') {
+ str = unescape2(typed.substring(start, i));
+ args.push(new Argument(str, prefix, ''));
+ mode = In.WHITESPACE;
+ start = i;
+ prefix = '';
+ }
+ break;
+
+ case In.SINGLE_Q:
+ if (c === '\'') {
+ str = unescape2(typed.substring(start, i));
+ args.push(new Argument(str, prefix, c));
+ mode = In.WHITESPACE;
+ start = i + 1;
+ prefix = '';
+ }
+ break;
+
+ case In.DOUBLE_Q:
+ if (c === '"') {
+ str = unescape2(typed.substring(start, i));
+ args.push(new Argument(str, prefix, c));
+ mode = In.WHITESPACE;
+ start = i + 1;
+ prefix = '';
+ }
+ break;
+
+ case In.SCRIPT:
+ if (c === '{') {
+ blockDepth++;
+ }
+ else if (c === '}') {
+ blockDepth--;
+ if (blockDepth === 0) {
+ str = unescape2(typed.substring(start, i));
+ args.push(new ScriptArgument(str, prefix, c));
+ mode = In.WHITESPACE;
+ start = i + 1;
+ prefix = '';
+ }
+ }
+ break;
+ }
+
+ i++;
+
+ if (i >= typed.length) {
+ // There is nothing else to read - tidy up
+ if (mode === In.WHITESPACE) {
+ if (i !== start) {
+ // There's whitespace at the end of the typed string. Add it to the
+ // last argument's suffix, creating an empty argument if needed.
+ var extra = typed.substring(start, i);
+ var lastArg = args[args.length - 1];
+ if (!lastArg) {
+ args.push(new Argument('', extra, ''));
+ }
+ else {
+ lastArg.suffix += extra;
+ }
+ }
+ }
+ else if (mode === In.SCRIPT) {
+ str = unescape2(typed.substring(start, i + 1));
+ args.push(new ScriptArgument(str, prefix, ''));
+ }
+ else {
+ str = unescape2(typed.substring(start, i + 1));
+ args.push(new Argument(str, prefix, ''));
+ }
+ break;
+ }
+ }
+
+ return args;
+};
+
+/**
+ * If the input has no spaces, quotes, braces or escapes,
+ * we can take the fast track.
+ */
+function isSimple(typed) {
+ for (var i = 0; i < typed.length; i++) {
+ var c = typed.charAt(i);
+ if (c === ' ' || c === '"' || c === '\'' ||
+ c === '{' || c === '}' || c === '\\') {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * Looks in the commands for a command extension that matches what has been
+ * typed at the command line.
+ */
+Requisition.prototype._split = function(args) {
+ // Handle the special case of the user typing { javascript(); }
+ // We use the hidden 'eval' command directly rather than shift()ing one of
+ // the parameters, and parse()ing it.
+ var conversion;
+ if (args[0].type === 'ScriptArgument') {
+ // Special case: if the user enters { console.log('foo'); } then we need to
+ // use the hidden 'eval' command
+ var command = this.system.commands.get(evalCmd.name);
+ conversion = new Conversion(command, new ScriptArgument());
+ this._setAssignmentInternal(this.commandAssignment, conversion);
+ return;
+ }
+
+ var argsUsed = 1;
+
+ while (argsUsed <= args.length) {
+ var arg = (argsUsed === 1) ?
+ args[0] :
+ new MergedArgument(args, 0, argsUsed);
+
+ if (this.prefix != null && this.prefix !== '') {
+ var prefixArg = new Argument(this.prefix, '', ' ');
+ var prefixedArg = new MergedArgument([ prefixArg, arg ]);
+
+ conversion = commandModule.parse(this.executionContext, prefixedArg, false);
+ if (conversion.value == null) {
+ conversion = commandModule.parse(this.executionContext, arg, false);
+ }
+ }
+ else {
+ conversion = commandModule.parse(this.executionContext, arg, false);
+ }
+
+ // We only want to carry on if this command is a parent command,
+ // which means that there is a commandAssignment, but not one with
+ // an exec function.
+ if (!conversion.value || conversion.value.exec) {
+ break;
+ }
+
+ // Previously we needed a way to hide commands depending context.
+ // We have not resurrected that feature yet, but if we do we should
+ // insert code here to ignore certain commands depending on the
+ // context/environment
+
+ argsUsed++;
+ }
+
+ // This could probably be re-written to consume args as we go
+ for (var i = 0; i < argsUsed; i++) {
+ args.shift();
+ }
+
+ this._setAssignmentInternal(this.commandAssignment, conversion);
+};
+
+/**
+ * Add all the passed args to the list of unassigned assignments.
+ */
+Requisition.prototype._addUnassignedArgs = function(args) {
+ args.forEach(function(arg) {
+ this._unassigned.push(new UnassignedAssignment(this, arg));
+ }.bind(this));
+
+ return RESOLVED;
+};
+
+/**
+ * Work out which arguments are applicable to which parameters.
+ */
+Requisition.prototype._assign = function(args) {
+ // See comment in _split. Avoid multiple updates
+ var noArgUp = { internal: true };
+
+ this._unassigned = [];
+
+ if (!this.commandAssignment.value) {
+ return this._addUnassignedArgs(args);
+ }
+
+ if (args.length === 0) {
+ this._setBlankArguments();
+ return RESOLVED;
+ }
+
+ // Create an error if the command does not take parameters, but we have
+ // been given them ...
+ if (this.assignmentCount === 0) {
+ return this._addUnassignedArgs(args);
+ }
+
+ // Special case: if there is only 1 parameter, and that's of type
+ // text, then we put all the params into the first param
+ if (this.assignmentCount === 1) {
+ var assignment = this.getAssignment(0);
+ if (assignment.param.type.name === 'string') {
+ var arg = (args.length === 1) ? args[0] : new MergedArgument(args);
+ return this.setAssignment(assignment, arg, noArgUp);
+ }
+ }
+
+ // Positional arguments can still be specified by name, but if they are
+ // then we need to ignore them when working them out positionally
+ var unassignedParams = this.getParameterNames();
+
+ // We collect the arguments used in arrays here before assigning
+ var arrayArgs = {};
+
+ // Extract all the named parameters
+ var assignments = this.getAssignments(false);
+ var namedDone = util.promiseEach(assignments, function(assignment) {
+ // Loop over the arguments
+ // Using while rather than loop because we remove args as we go
+ var i = 0;
+ while (i < args.length) {
+ if (!assignment.param.isKnownAs(args[i].text)) {
+ // Skip this parameter and handle as a positional parameter
+ i++;
+ continue;
+ }
+
+ var arg = args.splice(i, 1)[0];
+ /* jshint loopfunc:true */
+ unassignedParams = unassignedParams.filter(function(test) {
+ return test !== assignment.param.name;
+ });
+
+ // boolean parameters don't have values, default to false
+ if (assignment.param.type.name === 'boolean') {
+ arg = new TrueNamedArgument(arg);
+ }
+ else {
+ var valueArg = null;
+ if (i + 1 <= args.length) {
+ valueArg = args.splice(i, 1)[0];
+ }
+ arg = new NamedArgument(arg, valueArg);
+ }
+
+ if (assignment.param.type.name === 'array') {
+ var arrayArg = arrayArgs[assignment.param.name];
+ if (!arrayArg) {
+ arrayArg = new ArrayArgument();
+ arrayArgs[assignment.param.name] = arrayArg;
+ }
+ arrayArg.addArgument(arg);
+ return RESOLVED;
+ }
+ else {
+ if (assignment.arg.type === 'BlankArgument') {
+ return this.setAssignment(assignment, arg, noArgUp);
+ }
+ else {
+ return this._addUnassignedArgs(arg.getArgs());
+ }
+ }
+ }
+ }, this);
+
+ // What's left are positional parameters: assign in order
+ var positionalDone = namedDone.then(function() {
+ return util.promiseEach(unassignedParams, function(name) {
+ var assignment = this.getAssignment(name);
+
+ // If not set positionally, and we can't set it non-positionally,
+ // we have to default it to prevent previous values surviving
+ if (!assignment.param.isPositionalAllowed) {
+ this._setBlankAssignment(assignment);
+ return RESOLVED;
+ }
+
+ // If this is a positional array argument, then it swallows the
+ // rest of the arguments.
+ if (assignment.param.type.name === 'array') {
+ var arrayArg = arrayArgs[assignment.param.name];
+ if (!arrayArg) {
+ arrayArg = new ArrayArgument();
+ arrayArgs[assignment.param.name] = arrayArg;
+ }
+ arrayArg.addArguments(args);
+ args = [];
+ // The actual assignment to the array parameter is done below
+ return RESOLVED;
+ }
+
+ // Set assignment to defaults if there are no more arguments
+ if (args.length === 0) {
+ this._setBlankAssignment(assignment);
+ return RESOLVED;
+ }
+
+ var arg = args.splice(0, 1)[0];
+ // --foo and -f are named parameters, -4 is a number. So '-' is either
+ // the start of a named parameter or a number depending on the context
+ var isIncompleteName = assignment.param.type.name === 'number' ?
+ /-[-a-zA-Z_]/.test(arg.text) :
+ arg.text.charAt(0) === '-';
+
+ if (isIncompleteName) {
+ this._unassigned.push(new UnassignedAssignment(this, arg));
+ return RESOLVED;
+ }
+ else {
+ return this.setAssignment(assignment, arg, noArgUp);
+ }
+ }, this);
+ }.bind(this));
+
+ // Now we need to assign the array argument (if any)
+ var arrayDone = positionalDone.then(function() {
+ return util.promiseEach(Object.keys(arrayArgs), function(name) {
+ var assignment = this.getAssignment(name);
+ return this.setAssignment(assignment, arrayArgs[name], noArgUp);
+ }, this);
+ }.bind(this));
+
+ // What's left is can't be assigned, but we need to officially unassign them
+ return arrayDone.then(function() {
+ return this._addUnassignedArgs(args);
+ }.bind(this));
+};
+
+/**
+ * Entry point for keyboard accelerators or anything else that wants to execute
+ * a command.
+ * @param options Object describing how the execution should be handled.
+ * (optional). Contains some of the following properties:
+ * - hidden (boolean, default=false) Should the output be hidden from the
+ * commandOutputManager for this requisition
+ * - command/args A fast shortcut to executing a known command with a known
+ * set of parsed arguments.
+ */
+Requisition.prototype.exec = function(options) {
+ var command = null;
+ var args = null;
+ var hidden = false;
+
+ if (options) {
+ if (options.hidden) {
+ hidden = true;
+ }
+
+ if (options.command != null) {
+ // Fast track by looking up the command directly since passed args
+ // means there is no command line to parse.
+ command = this.system.commands.get(options.command);
+ if (!command) {
+ console.error('Command not found: ' + options.command);
+ }
+ args = options.args;
+ }
+ }
+
+ if (!command) {
+ command = this.commandAssignment.value;
+ args = this.getArgsObject();
+ }
+
+ // Display JavaScript input without the initial { or closing }
+ var typed = this.toString();
+ if (evalCmd.isCommandRegexp.test(typed)) {
+ typed = typed.replace(evalCmd.isCommandRegexp, '');
+ // Bug 717763: What if the JavaScript naturally ends with a }?
+ typed = typed.replace(/\s*}\s*$/, '');
+ }
+
+ var output = new Output({
+ command: command,
+ args: args,
+ typed: typed,
+ canonical: this.toCanonicalString(),
+ hidden: hidden
+ });
+
+ this.commandOutputManager.onOutput({ output: output });
+
+ var onDone = function(data) {
+ output.complete(data, false);
+ return output;
+ };
+
+ var onError = function(data, ex) {
+ if (logErrors) {
+ if (ex != null) {
+ util.errorHandler(ex);
+ }
+ else {
+ console.error(data);
+ }
+ }
+
+ if (data != null && typeof data === 'string') {
+ data = data.replace(/^Protocol error: /, ''); // Temp fix for bug 1035296
+ }
+
+ data = (data != null && data.isTypedData) ? data : {
+ isTypedData: true,
+ data: data,
+ type: 'error'
+ };
+ output.complete(data, true);
+ return output;
+ };
+
+ if (this.status !== Status.VALID) {
+ var ex = new Error(this.getStatusMessage());
+ // We only reject a call to exec if GCLI breaks. Errors with commands are
+ // exposed in the 'error' status of the Output object
+ return Promise.resolve(onError(ex)).then(function(output) {
+ this.clear();
+ return output;
+ }.bind(this));
+ }
+ else {
+ try {
+ return host.exec(function() {
+ return command.exec(args, this.executionContext);
+ }.bind(this)).then(onDone, onError);
+ }
+ catch (ex) {
+ var data = (typeof ex.message === 'string' && ex.stack != null) ?
+ ex.message : ex;
+ return Promise.resolve(onError(data, ex));
+ }
+ finally {
+ this.clear();
+ }
+ }
+};
+
+/**
+ * Designed to be called from context.updateExec(). Acts just like updateExec()
+ * except that it also calls onExternalUpdate() to inform the UI of an
+ * unexpected change to the current command.
+ */
+Requisition.prototype._contextUpdateExec = function(typed, options) {
+ var reqOpts = {
+ document: this.document,
+ environment: this.environment
+ };
+ var child = new Requisition(this.system, reqOpts);
+ return child.updateExec(typed, options).then(function(reply) {
+ child.destroy();
+ return reply;
+ }.bind(child));
+};
+
+/**
+ * A shortcut for calling update, resolving the promise and then exec.
+ * @param input The string to execute
+ * @param options Passed to exec
+ * @return A promise of an output object
+ */
+Requisition.prototype.updateExec = function(input, options) {
+ return this.update(input).then(function() {
+ return this.exec(options);
+ }.bind(this));
+};
+
+exports.Requisition = Requisition;
+
+/**
+ * A simple object to hold information about the output of a command
+ */
+function Output(options) {
+ options = options || {};
+ this.command = options.command || '';
+ this.args = options.args || {};
+ this.typed = options.typed || '';
+ this.canonical = options.canonical || '';
+ this.hidden = options.hidden === true ? true : false;
+
+ this.type = undefined;
+ this.data = undefined;
+ this.completed = false;
+ this.error = false;
+ this.start = new Date();
+
+ this.promise = new Promise(function(resolve, reject) {
+ this._resolve = resolve;
+ }.bind(this));
+}
+
+/**
+ * Called when there is data to display, and the command has finished executing
+ * See changed() for details on parameters.
+ */
+Output.prototype.complete = function(data, error) {
+ this.end = new Date();
+ this.completed = true;
+ this.error = error;
+
+ if (data != null && data.isTypedData) {
+ this.data = data.data;
+ this.type = data.type;
+ }
+ else {
+ this.data = data;
+ this.type = this.command.returnType;
+ if (this.type == null) {
+ this.type = (this.data == null) ? 'undefined' : typeof this.data;
+ }
+ }
+
+ if (this.type === 'object') {
+ throw new Error('No type from output of ' + this.typed);
+ }
+
+ this._resolve();
+};
+
+/**
+ * Call converters.convert using the data in this Output object
+ */
+Output.prototype.convert = function(type, conversionContext) {
+ var converters = conversionContext.system.converters;
+ return converters.convert(this.data, this.type, type, conversionContext);
+};
+
+Output.prototype.toJson = function() {
+ // Exceptions don't stringify, so we try a bit harder
+ var data = this.data;
+ if (this.error && JSON.stringify(this.data) === '{}') {
+ data = {
+ columnNumber: data.columnNumber,
+ fileName: data.fileName,
+ lineNumber: data.lineNumber,
+ message: data.message,
+ stack: data.stack
+ };
+ }
+
+ return {
+ typed: this.typed,
+ type: this.type,
+ data: data,
+ isError: this.error
+ };
+};
+
+exports.Output = Output;
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/clear.js b/devtools/shared/gcli/source/lib/gcli/commands/clear.js
new file mode 100644
index 000000000..8f9327021
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/clear.js
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+
+exports.items = [
+ {
+ // A command to clear the output area
+ item: 'command',
+ runAt: 'client',
+ name: 'clear',
+ description: l10n.lookup('clearDesc'),
+ returnType: 'clearoutput',
+ exec: function(args, context) { }
+ },
+ {
+ item: 'converter',
+ from: 'clearoutput',
+ to: 'view',
+ exec: function(ignore, conversionContext) {
+ return {
+ html: '<span onload="${onload}"></span>',
+ data: {
+ onload: function(ev) {
+ // element starts off being the span above, and we walk up the
+ // tree looking for the terminal
+ var element = ev.target;
+ while (element != null && element.terminal == null) {
+ element = element.parentElement;
+ }
+
+ if (element == null) {
+ // This is only an event handler on a completed command
+ // So we're relying on this showing up in the console
+ throw new Error('Failed to find clear');
+ }
+
+ element.terminal.clear();
+ }
+ }
+ };
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/commands.js b/devtools/shared/gcli/source/lib/gcli/commands/commands.js
new file mode 100644
index 000000000..67793b2dc
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/commands.js
@@ -0,0 +1,570 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var l10n = require('../util/l10n');
+
+/**
+ * Implement the localization algorithm for any documentation objects (i.e.
+ * description and manual) in a command.
+ * @param data The data assigned to a description or manual property
+ * @param onUndefined If data == null, should we return the data untouched or
+ * lookup a 'we don't know' key in it's place.
+ */
+function lookup(data, onUndefined) {
+ if (data == null) {
+ if (onUndefined) {
+ return l10n.lookup(onUndefined);
+ }
+
+ return data;
+ }
+
+ if (typeof data === 'string') {
+ return data;
+ }
+
+ if (typeof data === 'object') {
+ if (data.key) {
+ return l10n.lookup(data.key);
+ }
+
+ var locales = l10n.getPreferredLocales();
+ var translated;
+ locales.some(function(locale) {
+ translated = data[locale];
+ return translated != null;
+ });
+ if (translated != null) {
+ return translated;
+ }
+
+ console.error('Can\'t find locale in descriptions: ' +
+ 'locales=' + JSON.stringify(locales) + ', ' +
+ 'description=' + JSON.stringify(data));
+ return '(No description)';
+ }
+
+ return l10n.lookup(onUndefined);
+}
+
+
+/**
+ * The command object is mostly just setup around a commandSpec (as passed to
+ * Commands.add()).
+ */
+function Command(types, commandSpec) {
+ Object.keys(commandSpec).forEach(function(key) {
+ this[key] = commandSpec[key];
+ }, this);
+
+ if (!this.name) {
+ throw new Error('All registered commands must have a name');
+ }
+
+ if (this.params == null) {
+ this.params = [];
+ }
+ if (!Array.isArray(this.params)) {
+ throw new Error('command.params must be an array in ' + this.name);
+ }
+
+ this.hasNamedParameters = false;
+ this.description = 'description' in this ? this.description : undefined;
+ this.description = lookup(this.description, 'canonDescNone');
+ this.manual = 'manual' in this ? this.manual : undefined;
+ this.manual = lookup(this.manual);
+
+ // At this point this.params has nested param groups. We want to flatten it
+ // out and replace the param object literals with Parameter objects
+ var paramSpecs = this.params;
+ this.params = [];
+ this.paramGroups = {};
+ this._shortParams = {};
+
+ var addParam = function(param) {
+ var groupName = param.groupName || l10n.lookup('canonDefaultGroupName');
+ this.params.push(param);
+ if (!this.paramGroups.hasOwnProperty(groupName)) {
+ this.paramGroups[groupName] = [];
+ }
+ this.paramGroups[groupName].push(param);
+ }.bind(this);
+
+ // Track if the user is trying to mix default params and param groups.
+ // All the non-grouped parameters must come before all the param groups
+ // because non-grouped parameters can be assigned positionally, so their
+ // index is important. We don't want 'holes' in the order caused by
+ // parameter groups.
+ var usingGroups = false;
+
+ // In theory this could easily be made recursive, so param groups could
+ // contain nested param groups. Current thinking is that the added
+ // complexity for the UI probably isn't worth it, so this implementation
+ // prevents nesting.
+ paramSpecs.forEach(function(spec) {
+ if (!spec.group) {
+ var param = new Parameter(types, spec, this, null);
+ addParam(param);
+
+ if (!param.isPositionalAllowed) {
+ this.hasNamedParameters = true;
+ }
+
+ if (usingGroups && param.groupName == null) {
+ throw new Error('Parameters can\'t come after param groups.' +
+ ' Ignoring ' + this.name + '/' + spec.name);
+ }
+
+ if (param.groupName != null) {
+ usingGroups = true;
+ }
+ }
+ else {
+ spec.params.forEach(function(ispec) {
+ var param = new Parameter(types, ispec, this, spec.group);
+ addParam(param);
+
+ if (!param.isPositionalAllowed) {
+ this.hasNamedParameters = true;
+ }
+ }, this);
+
+ usingGroups = true;
+ }
+ }, this);
+
+ this.params.forEach(function(param) {
+ if (param.short != null) {
+ if (this._shortParams[param.short] != null) {
+ throw new Error('Multiple params using short name ' + param.short);
+ }
+ this._shortParams[param.short] = param;
+ }
+ }, this);
+}
+
+/**
+ * JSON serializer that avoids non-serializable data
+ * @param customProps Array of strings containing additional properties which,
+ * if specified in the command spec, will be included in the JSON. Normally we
+ * transfer only the properties required for GCLI to function.
+ */
+Command.prototype.toJson = function(customProps) {
+ var json = {
+ item: 'command',
+ name: this.name,
+ params: this.params.map(function(param) { return param.toJson(); }),
+ returnType: this.returnType,
+ isParent: (this.exec == null)
+ };
+
+ if (this.description !== l10n.lookup('canonDescNone')) {
+ json.description = this.description;
+ }
+ if (this.manual != null) {
+ json.manual = this.manual;
+ }
+ if (this.hidden != null) {
+ json.hidden = this.hidden;
+ }
+
+ if (Array.isArray(customProps)) {
+ customProps.forEach(function(prop) {
+ if (this[prop] != null) {
+ json[prop] = this[prop];
+ }
+ }.bind(this));
+ }
+
+ return json;
+};
+
+/**
+ * Easy way to lookup parameters by full name
+ */
+Command.prototype.getParameterByName = function(name) {
+ var reply;
+ this.params.forEach(function(param) {
+ if (param.name === name) {
+ reply = param;
+ }
+ });
+ return reply;
+};
+
+/**
+ * Easy way to lookup parameters by short name
+ */
+Command.prototype.getParameterByShortName = function(short) {
+ return this._shortParams[short];
+};
+
+exports.Command = Command;
+
+
+/**
+ * A wrapper for a paramSpec so we can sort out shortened versions names for
+ * option switches
+ */
+function Parameter(types, paramSpec, command, groupName) {
+ this.command = command || { name: 'unnamed' };
+ this.paramSpec = paramSpec;
+ this.name = this.paramSpec.name;
+ this.type = this.paramSpec.type;
+ this.short = this.paramSpec.short;
+
+ if (this.short != null && !/[0-9A-Za-z]/.test(this.short)) {
+ throw new Error('\'short\' value must be a single alphanumeric digit.');
+ }
+
+ this.groupName = groupName;
+ if (this.groupName != null) {
+ if (this.paramSpec.option != null) {
+ throw new Error('Can\'t have a "option" property in a nested parameter');
+ }
+ }
+ else {
+ if (this.paramSpec.option != null) {
+ this.groupName = (this.paramSpec.option === true) ?
+ l10n.lookup('canonDefaultGroupName') :
+ '' + this.paramSpec.option;
+ }
+ }
+
+ if (!this.name) {
+ throw new Error('In ' + this.command.name +
+ ': all params must have a name');
+ }
+
+ var typeSpec = this.type;
+ this.type = types.createType(typeSpec);
+ if (this.type == null) {
+ console.error('Known types: ' + types.getTypeNames().join(', '));
+ throw new Error('In ' + this.command.name + '/' + this.name +
+ ': can\'t find type for: ' + JSON.stringify(typeSpec));
+ }
+
+ // boolean parameters have an implicit defaultValue:false, which should
+ // not be changed. See the docs.
+ if (this.type.name === 'boolean' &&
+ this.paramSpec.defaultValue !== undefined) {
+ throw new Error('In ' + this.command.name + '/' + this.name +
+ ': boolean parameters can not have a defaultValue.' +
+ ' Ignoring');
+ }
+
+ // All parameters that can only be set via a named parameter must have a
+ // non-undefined default value
+ if (!this.isPositionalAllowed && this.paramSpec.defaultValue === undefined &&
+ this.type.getBlank == null && this.type.name !== 'boolean') {
+ throw new Error('In ' + this.command.name + '/' + this.name +
+ ': Missing defaultValue for optional parameter.');
+ }
+
+ if (this.paramSpec.defaultValue !== undefined) {
+ this.defaultValue = this.paramSpec.defaultValue;
+ }
+ else {
+ Object.defineProperty(this, 'defaultValue', {
+ get: function() {
+ return this.type.getBlank().value;
+ },
+ enumerable: true
+ });
+ }
+
+ // Resolve the documentation
+ this.manual = lookup(this.paramSpec.manual);
+ this.description = lookup(this.paramSpec.description, 'canonDescNone');
+
+ // Is the user required to enter data for this parameter? (i.e. has
+ // defaultValue been set to something other than undefined)
+ // TODO: When the defaultValue comes from type.getBlank().value (see above)
+ // then perhaps we should set using something like
+ // isDataRequired = (type.getBlank().status !== VALID)
+ this.isDataRequired = (this.defaultValue === undefined);
+
+ // Are we allowed to assign data to this parameter using positional
+ // parameters?
+ this.isPositionalAllowed = this.groupName == null;
+}
+
+/**
+ * Does the given name uniquely identify this param (among the other params
+ * in this command)
+ * @param name The name to check
+ */
+Parameter.prototype.isKnownAs = function(name) {
+ return (name === '--' + this.name) || (name === '-' + this.short);
+};
+
+/**
+ * Reflect the paramSpec 'hidden' property (dynamically so it can change)
+ */
+Object.defineProperty(Parameter.prototype, 'hidden', {
+ get: function() {
+ return this.paramSpec.hidden;
+ },
+ enumerable: true
+});
+
+/**
+ * JSON serializer that avoids non-serializable data
+ */
+Parameter.prototype.toJson = function() {
+ var json = {
+ name: this.name,
+ type: this.type.getSpec(this.command.name, this.name),
+ short: this.short
+ };
+
+ // Values do not need to be serializable, so we don't try. For the client
+ // side (which doesn't do any executing) we don't actually care what the
+ // default value is, just that it exists
+ if (this.paramSpec.defaultValue !== undefined) {
+ json.defaultValue = {};
+ }
+ if (this.paramSpec.description != null) {
+ json.description = this.paramSpec.description;
+ }
+ if (this.paramSpec.manual != null) {
+ json.manual = this.paramSpec.manual;
+ }
+ if (this.paramSpec.hidden != null) {
+ json.hidden = this.paramSpec.hidden;
+ }
+
+ // groupName can be set outside a paramSpec, (e.g. in grouped parameters)
+ // but it works like 'option' does so we use 'option' for groupNames
+ if (this.groupName != null || this.paramSpec.option != null) {
+ json.option = this.groupName || this.paramSpec.option;
+ }
+
+ return json;
+};
+
+exports.Parameter = Parameter;
+
+
+/**
+ * A store for a list of commands
+ * @param types Each command uses a set of Types to parse its parameters so the
+ * Commands container needs access to the list of available types.
+ * @param location String that, if set will force all commands to have a
+ * matching runAt property to be accepted
+ */
+function Commands(types, location) {
+ this.types = types;
+ this.location = location;
+
+ // A lookup hash of our registered commands
+ this._commands = {};
+ // A sorted list of command names, we regularly want them in order, so pre-sort
+ this._commandNames = [];
+ // A lookup of the original commandSpecs by command name
+ this._commandSpecs = {};
+
+ // Enable people to be notified of changes to the list of commands
+ this.onCommandsChange = util.createEvent('commands.onCommandsChange');
+}
+
+/**
+ * Add a command to the list of known commands.
+ * @param commandSpec The command and its metadata.
+ * @return The new command, or null if a location property has been set and the
+ * commandSpec doesn't have a matching runAt property.
+ */
+Commands.prototype.add = function(commandSpec) {
+ if (this.location != null && commandSpec.runAt != null &&
+ commandSpec.runAt !== this.location) {
+ return;
+ }
+
+ if (this._commands[commandSpec.name] != null) {
+ // Roughly commands.remove() without the event call, which we do later
+ delete this._commands[commandSpec.name];
+ this._commandNames = this._commandNames.filter(function(test) {
+ return test !== commandSpec.name;
+ });
+ }
+
+ var command = new Command(this.types, commandSpec);
+ this._commands[commandSpec.name] = command;
+ this._commandNames.push(commandSpec.name);
+ this._commandNames.sort();
+
+ this._commandSpecs[commandSpec.name] = commandSpec;
+
+ this.onCommandsChange();
+ return command;
+};
+
+/**
+ * Remove an individual command. The opposite of Commands.add().
+ * Removing a non-existent command is a no-op.
+ * @param commandOrName Either a command name or the command itself.
+ * @return true if a command was removed, false otherwise.
+ */
+Commands.prototype.remove = function(commandOrName) {
+ var name = typeof commandOrName === 'string' ?
+ commandOrName :
+ commandOrName.name;
+
+ if (!this._commands[name]) {
+ return false;
+ }
+
+ // See start of commands.add if changing this code
+ delete this._commands[name];
+ delete this._commandSpecs[name];
+ this._commandNames = this._commandNames.filter(function(test) {
+ return test !== name;
+ });
+
+ this.onCommandsChange();
+ return true;
+};
+
+/**
+ * Retrieve a command by name
+ * @param name The name of the command to retrieve
+ */
+Commands.prototype.get = function(name) {
+ // '|| undefined' is to silence 'reference to undefined property' warnings
+ return this._commands[name] || undefined;
+};
+
+/**
+ * Get an array of all the registered commands.
+ */
+Commands.prototype.getAll = function() {
+ return Object.keys(this._commands).map(function(name) {
+ return this._commands[name];
+ }, this);
+};
+
+/**
+ * Get access to the stored commandMetaDatas (i.e. before they were made into
+ * instances of Command/Parameters) so we can remote them.
+ * @param customProps Array of strings containing additional properties which,
+ * if specified in the command spec, will be included in the JSON. Normally we
+ * transfer only the properties required for GCLI to function.
+ */
+Commands.prototype.getCommandSpecs = function(customProps) {
+ var commandSpecs = [];
+
+ Object.keys(this._commands).forEach(function(name) {
+ var command = this._commands[name];
+ if (!command.noRemote) {
+ commandSpecs.push(command.toJson(customProps));
+ }
+ }.bind(this));
+
+ return commandSpecs;
+};
+
+/**
+ * Add a set of commands that are executed somewhere else, optionally with a
+ * command prefix to distinguish these commands from a local set of commands.
+ * @param commandSpecs Presumably as obtained from getCommandSpecs
+ * @param remoter Function to call on exec of a new remote command. This is
+ * defined just like an exec function (i.e. that takes args/context as params
+ * and returns a promise) with one extra feature, that the context includes a
+ * 'commandName' property that contains the original command name.
+ * @param prefix The name prefix that we assign to all command names
+ * @param to URL-like string that describes where the commands are executed.
+ * This is to complete the parent command description.
+ */
+Commands.prototype.addProxyCommands = function(commandSpecs, remoter, prefix, to) {
+ if (prefix != null) {
+ if (this._commands[prefix] != null) {
+ throw new Error(l10n.lookupFormat('canonProxyExists', [ prefix ]));
+ }
+
+ // We need to add the parent command so all the commands from the other
+ // system have a parent
+ this.add({
+ name: prefix,
+ isProxy: true,
+ description: l10n.lookupFormat('canonProxyDesc', [ to ]),
+ manual: l10n.lookupFormat('canonProxyManual', [ to ])
+ });
+ }
+
+ commandSpecs.forEach(function(commandSpec) {
+ var originalName = commandSpec.name;
+ if (!commandSpec.isParent) {
+ commandSpec.exec = function(args, context) {
+ context.commandName = originalName;
+ return remoter(args, context);
+ }.bind(this);
+ }
+
+ if (prefix != null) {
+ commandSpec.name = prefix + ' ' + commandSpec.name;
+ }
+ commandSpec.isProxy = true;
+ this.add(commandSpec);
+ }.bind(this));
+};
+
+/**
+ * Remove a set of commands added with addProxyCommands.
+ * @param prefix The name prefix that we assign to all command names
+ */
+Commands.prototype.removeProxyCommands = function(prefix) {
+ var toRemove = [];
+ Object.keys(this._commandSpecs).forEach(function(name) {
+ if (name.indexOf(prefix) === 0) {
+ toRemove.push(name);
+ }
+ }.bind(this));
+
+ var removed = [];
+ toRemove.forEach(function(name) {
+ var command = this.get(name);
+ if (command.isProxy) {
+ this.remove(name);
+ removed.push(name);
+ }
+ else {
+ console.error('Skipping removal of \'' + name +
+ '\' because it is not a proxy command.');
+ }
+ }.bind(this));
+
+ return removed;
+};
+
+exports.Commands = Commands;
+
+/**
+ * CommandOutputManager stores the output objects generated by executed
+ * commands.
+ *
+ * CommandOutputManager is exposed to the the outside world and could (but
+ * shouldn't) be used before gcli.startup() has been called.
+ * This could should be defensive to that where possible, and we should
+ * certainly document if the use of it or similar will fail if used too soon.
+ */
+function CommandOutputManager() {
+ this.onOutput = util.createEvent('CommandOutputManager.onOutput');
+}
+
+exports.CommandOutputManager = CommandOutputManager;
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/context.js b/devtools/shared/gcli/source/lib/gcli/commands/context.js
new file mode 100644
index 000000000..ad1f87ee8
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/context.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var cli = require('../cli');
+
+/**
+ * 'context' command
+ */
+var context = {
+ item: 'command',
+ name: 'context',
+ description: l10n.lookup('contextDesc'),
+ manual: l10n.lookup('contextManual'),
+ params: [
+ {
+ name: 'prefix',
+ type: 'command',
+ description: l10n.lookup('contextPrefixDesc'),
+ defaultValue: null
+ }
+ ],
+ returnType: 'string',
+ // The context command is client only because it's essentially sugar for
+ // typing commands. When there is a command prefix in action, it is the job
+ // of the remoter to add the prefix to the typed strings that are sent for
+ // remote execution
+ noRemote: true,
+ exec: function echo(args, context) {
+ var requisition = cli.getMapping(context).requisition;
+
+ if (args.prefix == null) {
+ requisition.prefix = null;
+ return l10n.lookup('contextEmptyReply');
+ }
+
+ if (args.prefix.exec != null) {
+ throw new Error(l10n.lookupFormat('contextNotParentError',
+ [ args.prefix.name ]));
+ }
+
+ requisition.prefix = args.prefix.name;
+ return l10n.lookupFormat('contextReply', [ args.prefix.name ]);
+ }
+};
+
+exports.items = [ context ];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/help.js b/devtools/shared/gcli/source/lib/gcli/commands/help.js
new file mode 100644
index 000000000..317f80240
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/help.js
@@ -0,0 +1,387 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var cli = require('../cli');
+
+/**
+ * Add an 'paramGroups' accessor to a command metadata object to sort the
+ * params into groups according to the option of the param.
+ */
+function addParamGroups(command) {
+ Object.defineProperty(command, 'paramGroups', {
+ get: function() {
+ var paramGroups = {};
+ this.params.forEach(function(param) {
+ var groupName = param.option || l10n.lookup('canonDefaultGroupName');
+ if (paramGroups[groupName] == null) {
+ paramGroups[groupName] = [];
+ }
+ paramGroups[groupName].push(param);
+ });
+ return paramGroups;
+ },
+ enumerable: true
+ });
+}
+
+/**
+ * Get a data block for the help_man.html/help_man.txt templates
+ */
+function getHelpManData(commandData, context) {
+ // Filter out hidden parameters
+ commandData.command.params = commandData.command.params.filter(
+ param => !param.hidden
+ );
+
+ addParamGroups(commandData.command);
+ commandData.subcommands.forEach(addParamGroups);
+
+ return {
+ l10n: l10n.propertyLookup,
+ onclick: context.update,
+ ondblclick: context.updateExec,
+ describe: function(item) {
+ return item.manual || item.description;
+ },
+ getTypeDescription: function(param) {
+ var input = '';
+ if (param.defaultValue === undefined) {
+ input = l10n.lookup('helpManRequired');
+ }
+ else if (param.defaultValue === null) {
+ input = l10n.lookup('helpManOptional');
+ }
+ else {
+ // We need defaultText to work the text version of defaultValue
+ input = l10n.lookupFormat('helpManOptional');
+ /*
+ var val = param.type.stringify(param.defaultValue);
+ input = Promise.resolve(val).then(function(defaultValue) {
+ return l10n.lookupFormat('helpManDefault', [ defaultValue ]);
+ }.bind(this));
+ */
+ }
+
+ return Promise.resolve(input).then(function(defaultDescr) {
+ return '(' + (param.type.name || param.type) + ', ' + defaultDescr + ')';
+ }.bind(this));
+ },
+ getSynopsis: function(param) {
+ var name = param.name + (param.short ? '|-' + param.short : '');
+ if (param.option == null) {
+ return param.defaultValue !== undefined ?
+ '[' + name + ']' :
+ '<' + name + '>';
+ }
+ else {
+ return param.type === 'boolean' || param.type.name === 'boolean' ?
+ '[--' + name + ']' :
+ '[--' + name + ' ...]';
+ }
+ },
+ command: commandData.command,
+ subcommands: commandData.subcommands
+ };
+}
+
+/**
+ * Get a data block for the help_list.html/help_list.txt templates
+ */
+function getHelpListData(commandsData, context) {
+ commandsData.commands.forEach(addParamGroups);
+
+ var heading;
+ if (commandsData.commands.length === 0) {
+ heading = l10n.lookupFormat('helpListNone', [ commandsData.prefix ]);
+ }
+ else if (commandsData.prefix == null) {
+ heading = l10n.lookup('helpListAll');
+ }
+ else {
+ heading = l10n.lookupFormat('helpListPrefix', [ commandsData.prefix ]);
+ }
+
+ return {
+ l10n: l10n.propertyLookup,
+ includeIntro: commandsData.prefix == null,
+ heading: heading,
+ onclick: context.update,
+ ondblclick: context.updateExec,
+ matchingCommands: commandsData.commands
+ };
+}
+
+/**
+ * Create a block of data suitable to be passed to the help_list.html template
+ */
+function getMatchingCommands(context, prefix) {
+ var commands = cli.getMapping(context).requisition.system.commands;
+ var reply = commands.getAll().filter(function(command) {
+ if (command.hidden) {
+ return false;
+ }
+
+ if (prefix && command.name.indexOf(prefix) !== 0) {
+ // Filtered out because they don't match the search
+ return false;
+ }
+ if (!prefix && command.name.indexOf(' ') != -1) {
+ // We don't show sub commands with plain 'help'
+ return false;
+ }
+ return true;
+ });
+
+ reply.sort(function(c1, c2) {
+ return c1.name.localeCompare(c2.name);
+ });
+
+ reply = reply.map(function(command) {
+ return command.toJson();
+ });
+
+ return reply;
+}
+
+/**
+ * Find all the sub commands of the given command
+ */
+function getSubCommands(context, command) {
+ var commands = cli.getMapping(context).requisition.system.commands;
+ var subcommands = commands.getAll().filter(function(subcommand) {
+ return subcommand.name.indexOf(command.name) === 0 &&
+ subcommand.name !== command.name &&
+ !subcommand.hidden;
+ });
+
+ subcommands.sort(function(c1, c2) {
+ return c1.name.localeCompare(c2.name);
+ });
+
+ subcommands = subcommands.map(function(subcommand) {
+ return subcommand.toJson();
+ });
+
+ return subcommands;
+}
+
+var helpCss = '' +
+ '.gcli-help-name {\n' +
+ ' text-align: end;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-help-arrow {\n' +
+ ' color: #AAA;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-help-description {\n' +
+ ' margin: 0 20px;\n' +
+ ' padding: 0;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-help-parameter {\n' +
+ ' margin: 0 30px;\n' +
+ ' padding: 0;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-help-header {\n' +
+ ' margin: 10px 0 6px;\n' +
+ '}\n';
+
+exports.items = [
+ {
+ // 'help' command
+ item: 'command',
+ name: 'help',
+ runAt: 'client',
+ description: l10n.lookup('helpDesc'),
+ manual: l10n.lookup('helpManual'),
+ params: [
+ {
+ name: 'search',
+ type: 'string',
+ description: l10n.lookup('helpSearchDesc'),
+ manual: l10n.lookup('helpSearchManual3'),
+ defaultValue: null
+ }
+ ],
+
+ exec: function(args, context) {
+ var commands = cli.getMapping(context).requisition.system.commands;
+ var command = commands.get(args.search);
+ if (command) {
+ return context.typedData('commandData', {
+ command: command.toJson(),
+ subcommands: getSubCommands(context, command)
+ });
+ }
+
+ return context.typedData('commandsData', {
+ prefix: args.search,
+ commands: getMatchingCommands(context, args.search)
+ });
+ }
+ },
+ {
+ // Convert a command into an HTML man page
+ item: 'converter',
+ from: 'commandData',
+ to: 'view',
+ exec: function(commandData, context) {
+ return {
+ html:
+ '<div>\n' +
+ ' <p class="gcli-help-header">\n' +
+ ' ${l10n.helpManSynopsis}:\n' +
+ ' <span class="gcli-out-shortcut" data-command="${command.name}"\n' +
+ ' onclick="${onclick}" ondblclick="${ondblclick}">\n' +
+ ' ${command.name}\n' +
+ ' <span foreach="param in ${command.params}">${getSynopsis(param)} </span>\n' +
+ ' </span>\n' +
+ ' </p>\n' +
+ '\n' +
+ ' <p class="gcli-help-description">${describe(command)}</p>\n' +
+ '\n' +
+ ' <div if="${!command.isParent}">\n' +
+ ' <div foreach="groupName in ${command.paramGroups}">\n' +
+ ' <p class="gcli-help-header">${groupName}:</p>\n' +
+ ' <ul class="gcli-help-parameter">\n' +
+ ' <li if="${command.params.length === 0}">${l10n.helpManNone}</li>\n' +
+ ' <li foreach="param in ${command.paramGroups[groupName]}">\n' +
+ ' <code>${getSynopsis(param)}</code> <em>${getTypeDescription(param)}</em>\n' +
+ ' <br/>\n' +
+ ' ${describe(param)}\n' +
+ ' </li>\n' +
+ ' </ul>\n' +
+ ' </div>\n' +
+ ' </div>\n' +
+ '\n' +
+ ' <div if="${command.isParent}">\n' +
+ ' <p class="gcli-help-header">${l10n.subCommands}:</p>\n' +
+ ' <ul class="gcli-help-${subcommands}">\n' +
+ ' <li if="${subcommands.length === 0}">${l10n.subcommandsNone}</li>\n' +
+ ' <li foreach="subcommand in ${subcommands}">\n' +
+ ' ${subcommand.name}: ${subcommand.description}\n' +
+ ' <span class="gcli-out-shortcut" data-command="help ${subcommand.name}"\n' +
+ ' onclick="${onclick}" ondblclick="${ondblclick}">\n' +
+ ' help ${subcommand.name}\n' +
+ ' </span>\n' +
+ ' </li>\n' +
+ ' </ul>\n' +
+ ' </div>\n' +
+ '\n' +
+ '</div>\n',
+ options: { allowEval: true, stack: 'commandData->view' },
+ data: getHelpManData(commandData, context),
+ css: helpCss,
+ cssId: 'gcli-help'
+ };
+ }
+ },
+ {
+ // Convert a command into a string based man page
+ item: 'converter',
+ from: 'commandData',
+ to: 'stringView',
+ exec: function(commandData, context) {
+ return {
+ html:
+ '<div>## ${command.name}\n' +
+ '\n' +
+ '# ${l10n.helpManSynopsis}: ${command.name} <loop foreach="param in ${command.params}">${getSynopsis(param)} </loop>\n' +
+ '\n' +
+ '# ${l10n.helpManDescription}:\n' +
+ '\n' +
+ '${command.manual || command.description}\n' +
+ '\n' +
+ '<loop foreach="groupName in ${command.paramGroups}">\n' +
+ '<span if="${!command.isParent}"># ${groupName}:\n' +
+ '\n' +
+ '<span if="${command.params.length === 0}">${l10n.helpManNone}</span><loop foreach="param in ${command.paramGroups[groupName]}">* ${param.name}: ${getTypeDescription(param)}\n' +
+ ' ${param.manual || param.description}\n' +
+ '</loop>\n' +
+ '</span>\n' +
+ '</loop>\n' +
+ '\n' +
+ '<span if="${command.isParent}"># ${l10n.subCommands}:</span>\n' +
+ '\n' +
+ '<span if="${subcommands.length === 0}">${l10n.subcommandsNone}</span>\n' +
+ '<loop foreach="subcommand in ${subcommands}">* ${subcommand.name}: ${subcommand.description}\n' +
+ '</loop>\n' +
+ '</div>\n',
+ options: { allowEval: true, stack: 'commandData->stringView' },
+ data: getHelpManData(commandData, context)
+ };
+ }
+ },
+ {
+ // Convert a list of commands into a formatted list
+ item: 'converter',
+ from: 'commandsData',
+ to: 'view',
+ exec: function(commandsData, context) {
+ return {
+ html:
+ '<div>\n' +
+ ' <div if="${includeIntro}">\n' +
+ ' <p>${l10n.helpIntro}</p>\n' +
+ ' </div>\n' +
+ '\n' +
+ ' <p>${heading}</p>\n' +
+ '\n' +
+ ' <table>\n' +
+ ' <tr foreach="command in ${matchingCommands}">\n' +
+ ' <td class="gcli-help-name">${command.name}</td>\n' +
+ ' <td class="gcli-help-arrow">-</td>\n' +
+ ' <td>\n' +
+ ' ${command.description}\n' +
+ ' <span class="gcli-out-shortcut"\n' +
+ ' onclick="${onclick}" ondblclick="${ondblclick}"\n' +
+ ' data-command="help ${command.name}">help ${command.name}</span>\n' +
+ ' </td>\n' +
+ ' </tr>\n' +
+ ' </table>\n' +
+ '</div>\n',
+ options: { allowEval: true, stack: 'commandsData->view' },
+ data: getHelpListData(commandsData, context),
+ css: helpCss,
+ cssId: 'gcli-help'
+ };
+ }
+ },
+ {
+ // Convert a list of commands into a formatted list
+ item: 'converter',
+ from: 'commandsData',
+ to: 'stringView',
+ exec: function(commandsData, context) {
+ return {
+ html:
+ '<pre><span if="${includeIntro}">## ${l10n.helpIntro}</span>\n' +
+ '\n' +
+ '# ${heading}\n' +
+ '\n' +
+ '<loop foreach="command in ${matchingCommands}">${command.name} &#x2192; ${command.description}\n' +
+ '</loop></pre>',
+ options: { allowEval: true, stack: 'commandsData->stringView' },
+ data: getHelpListData(commandsData, context)
+ };
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/mocks.js b/devtools/shared/gcli/source/lib/gcli/commands/mocks.js
new file mode 100644
index 000000000..12b2ade86
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/mocks.js
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var cli = require('../cli');
+var mockCommands = require('../test/mockCommands');
+var mockFileCommands = require('../test/mockFileCommands');
+var mockSettings = require('../test/mockSettings');
+
+var isNode = (typeof(process) !== 'undefined' &&
+ process.title.indexOf('node') != -1);
+
+exports.items = [
+ {
+ item: 'command',
+ name: 'mocks',
+ description: 'Add/remove mock commands',
+ params: [
+ {
+ name: 'included',
+ type: {
+ name: 'selection',
+ data: [ 'on', 'off' ]
+ },
+ description: 'Turn mock commands on or off',
+ }
+ ],
+ returnType: 'string',
+
+ exec: function(args, context) {
+ var requisition = cli.getMapping(context).requisition;
+ this[args.included](requisition);
+ return 'Mock commands are now ' + args.included;
+ },
+
+ on: function(requisition) {
+ mockCommands.setup(requisition);
+ mockSettings.setup(requisition.system);
+
+ if (isNode) {
+ mockFileCommands.setup(requisition);
+ }
+ },
+
+ off: function(requisition) {
+ mockCommands.shutdown(requisition);
+ mockSettings.shutdown(requisition.system);
+
+ if (isNode) {
+ mockFileCommands.shutdown(requisition);
+ }
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/moz.build b/devtools/shared/gcli/source/lib/gcli/commands/moz.build
new file mode 100644
index 000000000..8cf5f0e96
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'clear.js',
+ 'commands.js',
+ 'context.js',
+ 'help.js',
+ 'mocks.js',
+ 'pref.js',
+ 'preflist.js',
+ 'test.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/pref.js b/devtools/shared/gcli/source/lib/gcli/commands/pref.js
new file mode 100644
index 000000000..387b1f8e4
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/pref.js
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+
+exports.items = [
+ {
+ // 'pref' command
+ item: 'command',
+ name: 'pref',
+ description: l10n.lookup('prefDesc'),
+ manual: l10n.lookup('prefManual')
+ },
+ {
+ // 'pref show' command
+ item: 'command',
+ name: 'pref show',
+ runAt: 'client',
+ description: l10n.lookup('prefShowDesc'),
+ manual: l10n.lookup('prefShowManual'),
+ params: [
+ {
+ name: 'setting',
+ type: 'setting',
+ description: l10n.lookup('prefShowSettingDesc'),
+ manual: l10n.lookup('prefShowSettingManual')
+ }
+ ],
+ exec: function(args, context) {
+ return l10n.lookupFormat('prefShowSettingValue',
+ [ args.setting.name, args.setting.value ]);
+ }
+ },
+ {
+ // 'pref set' command
+ item: 'command',
+ name: 'pref set',
+ runAt: 'client',
+ description: l10n.lookup('prefSetDesc'),
+ manual: l10n.lookup('prefSetManual'),
+ params: [
+ {
+ name: 'setting',
+ type: 'setting',
+ description: l10n.lookup('prefSetSettingDesc'),
+ manual: l10n.lookup('prefSetSettingManual')
+ },
+ {
+ name: 'value',
+ type: 'settingValue',
+ description: l10n.lookup('prefSetValueDesc'),
+ manual: l10n.lookup('prefSetValueManual')
+ }
+ ],
+ exec: function(args, context) {
+ args.setting.value = args.value;
+ }
+ },
+ {
+ // 'pref reset' command
+ item: 'command',
+ name: 'pref reset',
+ runAt: 'client',
+ description: l10n.lookup('prefResetDesc'),
+ manual: l10n.lookup('prefResetManual'),
+ params: [
+ {
+ name: 'setting',
+ type: 'setting',
+ description: l10n.lookup('prefResetSettingDesc'),
+ manual: l10n.lookup('prefResetSettingManual')
+ }
+ ],
+ exec: function(args, context) {
+ args.setting.setDefault();
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/preflist.js b/devtools/shared/gcli/source/lib/gcli/commands/preflist.js
new file mode 100644
index 000000000..b6ca04a0b
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/preflist.js
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+
+/**
+ * Format a list of settings for display
+ */
+var prefsViewConverter = {
+ item: 'converter',
+ from: 'prefsData',
+ to: 'view',
+ exec: function(prefsData, conversionContext) {
+ var prefList = new PrefList(prefsData, conversionContext);
+ return {
+ html:
+ '<div ignore="${onLoad(__element)}">\n' +
+ ' <!-- This is broken, and unimportant. Comment out for now\n' +
+ ' <div class="gcli-pref-list-filter">\n' +
+ ' ${l10n.prefOutputFilter}:\n' +
+ ' <input onKeyUp="${onFilterChange}" value="${search}"/>\n' +
+ ' </div>\n' +
+ ' -->\n' +
+ ' <table class="gcli-pref-list-table">\n' +
+ ' <colgroup>\n' +
+ ' <col class="gcli-pref-list-name"/>\n' +
+ ' <col class="gcli-pref-list-value"/>\n' +
+ ' </colgroup>\n' +
+ ' <tr>\n' +
+ ' <th>${l10n.prefOutputName}</th>\n' +
+ ' <th>${l10n.prefOutputValue}</th>\n' +
+ ' </tr>\n' +
+ ' </table>\n' +
+ ' <div class="gcli-pref-list-scroller">\n' +
+ ' <table class="gcli-pref-list-table" save="${table}">\n' +
+ ' </table>\n' +
+ ' </div>\n' +
+ '</div>\n',
+ data: prefList,
+ options: {
+ blankNullUndefined: true,
+ allowEval: true,
+ stack: 'prefsData->view'
+ },
+ css:
+ '.gcli-pref-list-scroller {\n' +
+ ' max-height: 200px;\n' +
+ ' overflow-y: auto;\n' +
+ ' overflow-x: hidden;\n' +
+ ' display: inline-block;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-table {\n' +
+ ' width: 500px;\n' +
+ ' table-layout: fixed;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-table tr > th {\n' +
+ ' text-align: left;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-table tr > td {\n' +
+ ' text-overflow: elipsis;\n' +
+ ' word-wrap: break-word;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-name {\n' +
+ ' width: 70%;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-command {\n' +
+ ' display: none;\n' +
+ '}\n' +
+ '\n' +
+ '.gcli-pref-list-row:hover .gcli-pref-list-command {\n' +
+ ' /* \'pref list\' is a bit broken and unimportant. Band-aid follows */\n' +
+ ' /* display: inline-block; */\n' +
+ '}\n',
+ cssId: 'gcli-pref-list'
+ };
+ }
+};
+
+/**
+ * Format a list of settings for display
+ */
+var prefsStringConverter = {
+ item: 'converter',
+ from: 'prefsData',
+ to: 'string',
+ exec: function(prefsData, conversionContext) {
+ var reply = '';
+ prefsData.settings.forEach(function(setting) {
+ reply += setting.name + ' -> ' + setting.value + '\n';
+ });
+ return reply;
+ }
+};
+
+/**
+ * 'pref list' command
+ */
+var prefList = {
+ item: 'command',
+ name: 'pref list',
+ description: l10n.lookup('prefListDesc'),
+ manual: l10n.lookup('prefListManual'),
+ params: [
+ {
+ name: 'search',
+ type: 'string',
+ defaultValue: null,
+ description: l10n.lookup('prefListSearchDesc'),
+ manual: l10n.lookup('prefListSearchManual')
+ }
+ ],
+ returnType: 'prefsData',
+ exec: function(args, context) {
+ return new Promise(function(resolve, reject) {
+ // This can be slow, get out of the way of the main thread
+ setTimeout(function() {
+ var prefsData = {
+ settings: context.system.settings.getAll(args.search),
+ search: args.search
+ };
+ resolve(prefsData);
+ }.bind(this), 10);
+ });
+ }
+};
+
+/**
+ * A manager for our version of about:config
+ */
+function PrefList(prefsData, conversionContext) {
+ this.search = prefsData.search;
+ this.settings = prefsData.settings;
+ this.conversionContext = conversionContext;
+
+ this.onLoad = this.onLoad.bind(this);
+}
+
+/**
+ * A load event handler registered by the template engine so we can load the
+ * inner document
+ */
+PrefList.prototype.onLoad = function(element) {
+ var table = element.querySelector('.gcli-pref-list-table');
+ this.updateTable(table);
+ return '';
+};
+
+/**
+ * Forward localization lookups
+ */
+PrefList.prototype.l10n = l10n.propertyLookup;
+
+/**
+ * Called from the template onkeyup for the filter element
+ */
+PrefList.prototype.updateTable = function(table) {
+ var view = this.conversionContext.createView({
+ html:
+ '<table>\n' +
+ ' <colgroup>\n' +
+ ' <col class="gcli-pref-list-name"/>\n' +
+ ' <col class="gcli-pref-list-value"/>\n' +
+ ' </colgroup>\n' +
+ ' <tr class="gcli-pref-list-row" foreach="setting in ${settings}">\n' +
+ ' <td>${setting.name}</td>\n' +
+ ' <td onclick="${onSetClick}" data-command="pref set ${setting.name} ">\n' +
+ ' ${setting.value}\n' +
+ ' [Edit]\n' +
+ ' </td>\n' +
+ ' </tr>\n' +
+ '</table>\n',
+ options: { blankNullUndefined: true, stack: 'prefsData#inner' },
+ data: this
+ });
+
+ view.appendTo(table, true);
+};
+
+PrefList.prototype.onFilterChange = function(ev) {
+ if (ev.target.value !== this.search) {
+ this.search = ev.target.value;
+
+ var root = ev.target.parentNode.parentNode;
+ var table = root.querySelector('.gcli-pref-list-table');
+ this.updateTable(table);
+ }
+};
+
+PrefList.prototype.onSetClick = function(ev) {
+ var typed = ev.currentTarget.getAttribute('data-command');
+ this.conversionContext.update(typed);
+};
+
+exports.items = [ prefsViewConverter, prefsStringConverter, prefList ];
diff --git a/devtools/shared/gcli/source/lib/gcli/commands/test.js b/devtools/shared/gcli/source/lib/gcli/commands/test.js
new file mode 100644
index 000000000..90f56c361
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/commands/test.js
@@ -0,0 +1,215 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var examiner = require('../testharness/examiner');
+var stati = require('../testharness/status').stati;
+var helpers = require('../test/helpers');
+var suite = require('../test/suite');
+var cli = require('../cli');
+var Requisition = require('../cli').Requisition;
+var createRequisitionAutomator = require('../test/automators/requisition').createRequisitionAutomator;
+
+var isNode = (typeof(process) !== 'undefined' &&
+ process.title.indexOf('node') != -1);
+
+suite.init(isNode);
+
+exports.optionsContainer = [];
+
+exports.items = [
+ {
+ item: 'type',
+ name: 'suite',
+ parent: 'selection',
+ cacheable: true,
+ lookup: function() {
+ return Object.keys(examiner.suites).map(function(name) {
+ return { name: name, value: examiner.suites[name] };
+ });
+ }
+ },
+ {
+ item: 'command',
+ name: 'test',
+ description: 'Run GCLI unit tests',
+ params: [
+ {
+ name: 'suite',
+ type: 'suite',
+ description: 'Test suite to run.',
+ defaultValue: examiner
+ },
+ {
+ name: 'usehost',
+ type: 'boolean',
+ description: 'Run the unit tests in the host window',
+ option: true
+ }
+ ],
+ returnType: 'examiner-output',
+ noRemote: true,
+ exec: function(args, context) {
+ if (args.usehost && exports.optionsContainer.length === 0) {
+ throw new Error('Can\'t use --usehost without injected options');
+ }
+
+ var options;
+ if (args.usehost) {
+ options = exports.optionsContainer[0];
+ }
+ else {
+ var env = {
+ document: document,
+ window: window
+ };
+ options = {
+ isNode: isNode,
+ isFirefox: false,
+ isPhantomjs: false,
+ requisition: new Requisition(context.system, { environment: env })
+ };
+ options.automator = createRequisitionAutomator(options.requisition);
+ }
+
+ var requisition = options.requisition;
+ requisition.system.commands.get('mocks').on(requisition);
+ helpers.resetResponseTimes();
+ examiner.reset();
+
+ return args.suite.run(options).then(function() {
+ requisition.system.commands.get('mocks').off(requisition);
+ var output = context.typedData('examiner-output', examiner.toRemote());
+
+ if (output.data.summary.status === stati.pass) {
+ return output;
+ }
+ else {
+ cli.logErrors = false;
+ throw output;
+ }
+ });
+ }
+ },
+ {
+ item: 'converter',
+ from: 'examiner-output',
+ to: 'string',
+ exec: function(output, conversionContext) {
+ return '\n' + examiner.detailedResultLog('NodeJS/NoDom') +
+ '\n' + helpers.timingSummary;
+ }
+ },
+ {
+ item: 'converter',
+ from: 'examiner-output',
+ to: 'view',
+ exec: function(output, conversionContext) {
+ return {
+ html:
+ '<div>\n' +
+ ' <table class="gcliTestResults">\n' +
+ ' <thead>\n' +
+ ' <tr>\n' +
+ ' <th class="gcliTestSuite">Suite</th>\n' +
+ ' <th>Test</th>\n' +
+ ' <th>Results</th>\n' +
+ ' <th>Checks</th>\n' +
+ ' <th>Notes</th>\n' +
+ ' </tr>\n' +
+ ' </thead>\n' +
+ ' <tbody foreach="suite in ${suites}">\n' +
+ ' <tr foreach="test in ${suite.tests}" title="${suite.name}.${test.name}()">\n' +
+ ' <td class="gcliTestSuite">${suite.name}</td>\n' +
+ ' <td class="gcliTestTitle">${test.title}</td>\n' +
+ ' <td class="gcliTest${test.status.name}">${test.status.name}</td>\n' +
+ ' <td class="gcliTestChecks">${test.checks}</td>\n' +
+ ' <td class="gcliTestMessages">\n' +
+ ' <div foreach="failure in ${test.failures}">\n' +
+ ' ${failure.message}\n' +
+ ' <ul if="${failure.params}">\n' +
+ ' <li>P1: ${failure.p1}</li>\n' +
+ ' <li>P2: ${failure.p2}</li>\n' +
+ ' </ul>\n' +
+ ' </div>\n' +
+ ' </td>\n' +
+ ' </tr>\n' +
+ ' </tbody>\n' +
+ ' <tfoot>\n' +
+ ' <tr>\n' +
+ ' <th></th>\n' +
+ ' <th>Total</th>\n' +
+ ' <th>${summary.status.name}</th>\n' +
+ ' <th class="gcliTestChecks">${summary.checks}</th>\n' +
+ ' <th></th>\n' +
+ ' </tr>\n' +
+ ' </tfoot>\n' +
+ ' </table>\n' +
+ '</div>',
+ css:
+ '.gcliTestSkipped {\n' +
+ ' background-color: #EEE;\n' +
+ ' color: #000;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestExecuting {\n' +
+ ' background-color: #888;\n' +
+ ' color: #FFF;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestWaiting {\n' +
+ ' background-color: #FFA;\n' +
+ ' color: #000;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestPass {\n' +
+ ' background-color: #8F8;\n' +
+ ' color: #000;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestFail {\n' +
+ ' background-color: #F00;\n' +
+ ' color: #FFF;\n' +
+ '}\n' +
+ '\n' +
+ 'td.gcliTestSuite {\n' +
+ ' font-family: monospace;\n' +
+ ' font-size: 90%;\n' +
+ ' text-align: right;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestResults th.gcliTestSuite,\n' +
+ '.gcliTestResults .gcliTestChecks {\n' +
+ ' text-align: right;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestResults th {\n' +
+ ' text-align: left;\n' +
+ '}\n' +
+ '\n' +
+ '.gcliTestMessages ul {\n' +
+ ' margin: 0 0 10px;\n' +
+ ' padding-left: 20px;\n' +
+ ' list-style-type: square;\n' +
+ '}\n',
+ cssId: 'gcli-test',
+ data: output,
+ options: { allowEval: true, stack: 'test.html' }
+ };
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/connectors/connectors.js b/devtools/shared/gcli/source/lib/gcli/connectors/connectors.js
new file mode 100644
index 000000000..f1a6fe339
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/connectors/connectors.js
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/**
+ * This is how to implement a connector
+ * var baseConnector = {
+ * item: 'connector',
+ * name: 'foo',
+ *
+ * connect: function(url) {
+ * return Promise.resolve(new FooConnection(url));
+ * }
+ * };
+ */
+
+/**
+ * A prototype base for Connectors
+ */
+function Connection() {
+}
+
+/**
+ * Add an event listener
+ */
+Connection.prototype.on = function(event, action) {
+ if (!this._listeners) {
+ this._listeners = {};
+ }
+ if (!this._listeners[event]) {
+ this._listeners[event] = [];
+ }
+ this._listeners[event].push(action);
+};
+
+/**
+ * Remove an event listener
+ */
+Connection.prototype.off = function(event, action) {
+ if (!this._listeners) {
+ return;
+ }
+ var actions = this._listeners[event];
+ if (actions) {
+ this._listeners[event] = actions.filter(function(li) {
+ return li !== action;
+ }.bind(this));
+ }
+};
+
+/**
+ * Emit an event. For internal use only
+ */
+Connection.prototype._emit = function(event, data) {
+ if (this._listeners == null || this._listeners[event] == null) {
+ return;
+ }
+
+ var listeners = this._listeners[event];
+ listeners.forEach(function(listener) {
+ // Fail fast if we mutate the list of listeners while emitting
+ if (listeners !== this._listeners[event]) {
+ throw new Error('Listener list changed while emitting');
+ }
+
+ try {
+ listener.call(null, data);
+ }
+ catch (ex) {
+ console.log('Error calling listeners to ' + event);
+ console.error(ex);
+ }
+ }.bind(this));
+};
+
+/**
+ * Send a message to the other side of the connection
+ */
+Connection.prototype.call = function(feature, data) {
+ throw new Error('Not implemented');
+};
+
+/**
+ * Disconnecting a Connection destroys the resources it holds. There is no
+ * common route back to being connected once this has been called
+ */
+Connection.prototype.disconnect = function() {
+ return Promise.resolve();
+};
+
+exports.Connection = Connection;
+
+/**
+ * A manager for the registered Connectors
+ */
+function Connectors() {
+ // This is where we cache the connectors that we know about
+ this._registered = {};
+}
+
+/**
+ * Add a new connector to the cache
+ */
+Connectors.prototype.add = function(connector) {
+ this._registered[connector.name] = connector;
+};
+
+/**
+ * Remove an existing connector from the cache
+ */
+Connectors.prototype.remove = function(connector) {
+ var name = typeof connector === 'string' ? connector : connector.name;
+ delete this._registered[name];
+};
+
+/**
+ * Get access to the list of known connectors
+ */
+Connectors.prototype.getAll = function() {
+ return Object.keys(this._registered).map(function(name) {
+ return this._registered[name];
+ }.bind(this));
+};
+
+var defaultConnectorName;
+
+/**
+ * Get access to a connector by name. If name is undefined then first try to
+ * use the same connector that we used last time, and if there was no last
+ * time, then just use the first registered connector as a default.
+ */
+Connectors.prototype.get = function(name) {
+ if (name == null) {
+ name = (defaultConnectorName == null) ?
+ Object.keys(this._registered)[0] :
+ defaultConnectorName;
+ }
+
+ defaultConnectorName = name;
+ return this._registered[name];
+};
+
+exports.Connectors = Connectors;
diff --git a/devtools/shared/gcli/source/lib/gcli/connectors/moz.build b/devtools/shared/gcli/source/lib/gcli/connectors/moz.build
new file mode 100644
index 000000000..33fda8fbc
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/connectors/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'connectors.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/converters/basic.js b/devtools/shared/gcli/source/lib/gcli/converters/basic.js
new file mode 100644
index 000000000..3cb448e91
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/converters/basic.js
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+/**
+ * Several converters are just data.toString inside a 'p' element
+ */
+function nodeFromDataToString(data, conversionContext) {
+ var node = util.createElement(conversionContext.document, 'p');
+ node.textContent = data.toString();
+ return node;
+}
+
+exports.items = [
+ {
+ item: 'converter',
+ from: 'string',
+ to: 'dom',
+ exec: nodeFromDataToString
+ },
+ {
+ item: 'converter',
+ from: 'number',
+ to: 'dom',
+ exec: nodeFromDataToString
+ },
+ {
+ item: 'converter',
+ from: 'boolean',
+ to: 'dom',
+ exec: nodeFromDataToString
+ },
+ {
+ item: 'converter',
+ from: 'undefined',
+ to: 'dom',
+ exec: function(data, conversionContext) {
+ return util.createElement(conversionContext.document, 'span');
+ }
+ },
+ {
+ item: 'converter',
+ from: 'json',
+ to: 'view',
+ exec: function(json, context) {
+ var html = JSON.stringify(json, null, '&#160;').replace(/\n/g, '<br/>');
+ return {
+ html: '<pre>' + html + '</pre>'
+ };
+ }
+ },
+ {
+ item: 'converter',
+ from: 'number',
+ to: 'string',
+ exec: function(data) { return '' + data; }
+ },
+ {
+ item: 'converter',
+ from: 'boolean',
+ to: 'string',
+ exec: function(data) { return '' + data; }
+ },
+ {
+ item: 'converter',
+ from: 'undefined',
+ to: 'string',
+ exec: function(data) { return ''; }
+ },
+ {
+ item: 'converter',
+ from: 'json',
+ to: 'string',
+ exec: function(json, conversionContext) {
+ return JSON.stringify(json, null, ' ');
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/converters/converters.js b/devtools/shared/gcli/source/lib/gcli/converters/converters.js
new file mode 100644
index 000000000..c054871d6
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/converters/converters.js
@@ -0,0 +1,280 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var host = require('../util/host');
+
+// It's probably easiest to read this bottom to top
+
+/**
+ * Best guess at creating a DOM element from random data
+ */
+var fallbackDomConverter = {
+ from: '*',
+ to: 'dom',
+ exec: function(data, conversionContext) {
+ return conversionContext.document.createTextNode(data || '');
+ }
+};
+
+/**
+ * Best guess at creating a string from random data
+ */
+var fallbackStringConverter = {
+ from: '*',
+ to: 'string',
+ exec: function(data, conversionContext) {
+ return data == null ? '' : data.toString();
+ }
+};
+
+/**
+ * Convert a view object to a DOM element
+ */
+var viewDomConverter = {
+ item: 'converter',
+ from: 'view',
+ to: 'dom',
+ exec: function(view, conversionContext) {
+ if (!view.isView) {
+ view = conversionContext.createView(view);
+ }
+ return view.toDom(conversionContext.document);
+ }
+};
+
+/**
+ * Convert a view object to a string
+ */
+var viewStringConverter = {
+ item: 'converter',
+ from: 'view',
+ to: 'string',
+ exec: function(view, conversionContext) {
+ if (!view.isView) {
+ view = conversionContext.createView(view);
+ }
+ return view.toDom(conversionContext.document).textContent;
+ }
+};
+
+/**
+ * Convert a view object to a string
+ */
+var stringViewStringConverter = {
+ item: 'converter',
+ from: 'stringView',
+ to: 'string',
+ exec: function(view, conversionContext) {
+ if (!view.isView) {
+ view = conversionContext.createView(view);
+ }
+ return view.toDom(conversionContext.document).textContent;
+ }
+};
+
+/**
+ * Convert an exception to a DOM element
+ */
+var errorDomConverter = {
+ item: 'converter',
+ from: 'error',
+ to: 'dom',
+ exec: function(ex, conversionContext) {
+ var node = util.createElement(conversionContext.document, 'p');
+ node.className = 'gcli-error';
+ node.textContent = errorStringConverter.exec(ex, conversionContext);
+ return node;
+ }
+};
+
+/**
+ * Convert an exception to a string
+ */
+var errorStringConverter = {
+ item: 'converter',
+ from: 'error',
+ to: 'string',
+ exec: function(ex, conversionContext) {
+ if (typeof ex === 'string') {
+ return ex;
+ }
+ if (ex instanceof Error) {
+ return '' + ex;
+ }
+ if (typeof ex.message === 'string') {
+ return ex.message;
+ }
+ return '' + ex;
+ }
+};
+
+/**
+ * Create a new converter by using 2 converters, one after the other
+ */
+function getChainConverter(first, second) {
+ if (first.to !== second.from) {
+ throw new Error('Chain convert impossible: ' + first.to + '!=' + second.from);
+ }
+ return {
+ from: first.from,
+ to: second.to,
+ exec: function(data, conversionContext) {
+ var intermediate = first.exec(data, conversionContext);
+ return second.exec(intermediate, conversionContext);
+ }
+ };
+}
+
+/**
+ * A manager for the registered Converters
+ */
+function Converters() {
+ // This is where we cache the converters that we know about
+ this._registered = {
+ from: {}
+ };
+}
+
+/**
+ * Add a new converter to the cache
+ */
+Converters.prototype.add = function(converter) {
+ var fromMatch = this._registered.from[converter.from];
+ if (fromMatch == null) {
+ fromMatch = {};
+ this._registered.from[converter.from] = fromMatch;
+ }
+
+ fromMatch[converter.to] = converter;
+};
+
+/**
+ * Remove an existing converter from the cache
+ */
+Converters.prototype.remove = function(converter) {
+ var fromMatch = this._registered.from[converter.from];
+ if (fromMatch == null) {
+ return;
+ }
+
+ if (fromMatch[converter.to] === converter) {
+ fromMatch[converter.to] = null;
+ }
+};
+
+/**
+ * Work out the best converter that we've got, for a given conversion.
+ */
+Converters.prototype.get = function(from, to) {
+ var fromMatch = this._registered.from[from];
+ if (fromMatch == null) {
+ return this._getFallbackConverter(from, to);
+ }
+
+ var converter = fromMatch[to];
+ if (converter == null) {
+ // Someone is going to love writing a graph search algorithm to work out
+ // the smallest number of conversions, or perhaps the least 'lossy'
+ // conversion but for now the only 2 step conversions which we are going to
+ // special case are foo->view->dom and foo->stringView->string.
+ if (to === 'dom') {
+ converter = fromMatch.view;
+ if (converter != null) {
+ return getChainConverter(converter, viewDomConverter);
+ }
+ }
+
+ if (to === 'string') {
+ converter = fromMatch.stringView;
+ if (converter != null) {
+ return getChainConverter(converter, stringViewStringConverter);
+ }
+ converter = fromMatch.view;
+ if (converter != null) {
+ return getChainConverter(converter, viewStringConverter);
+ }
+ }
+
+ return this._getFallbackConverter(from, to);
+ }
+ return converter;
+};
+
+/**
+ * Get all the registered converters. Most for debugging
+ */
+Converters.prototype.getAll = function() {
+ return Object.keys(this._registered.from).map(function(name) {
+ return this._registered.from[name];
+ }.bind(this));
+};
+
+/**
+ * Helper for get to pick the best fallback converter
+ */
+Converters.prototype._getFallbackConverter = function(from, to) {
+ console.error('No converter from ' + from + ' to ' + to + '. Using fallback');
+
+ if (to === 'dom') {
+ return fallbackDomConverter;
+ }
+
+ if (to === 'string') {
+ return fallbackStringConverter;
+ }
+
+ throw new Error('No conversion possible from ' + from + ' to ' + to + '.');
+};
+
+/**
+ * Convert some data from one type to another
+ * @param data The object to convert
+ * @param from The type of the data right now
+ * @param to The type that we would like the data in
+ * @param conversionContext An execution context (i.e. simplified requisition)
+ * which is often required for access to a document, or createView function
+ */
+Converters.prototype.convert = function(data, from, to, conversionContext) {
+ try {
+ if (from === to) {
+ return Promise.resolve(data);
+ }
+
+ var converter = this.get(from, to);
+ return host.exec(function() {
+ return converter.exec(data, conversionContext);
+ }.bind(this));
+ }
+ catch (ex) {
+ var converter = this.get('error', to);
+ return host.exec(function() {
+ return converter.exec(ex, conversionContext);
+ }.bind(this));
+ }
+};
+
+exports.Converters = Converters;
+
+/**
+ * Items for export
+ */
+exports.items = [
+ viewDomConverter, viewStringConverter, stringViewStringConverter,
+ errorDomConverter, errorStringConverter
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/converters/html.js b/devtools/shared/gcli/source/lib/gcli/converters/html.js
new file mode 100644
index 000000000..2dea0eb82
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/converters/html.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+/**
+ * 'html' means a string containing HTML markup. We use innerHTML to inject
+ * this into a DOM which has security implications, so this module will not
+ * be used in all implementations.
+ */
+exports.items = [
+ {
+ item: 'converter',
+ from: 'html',
+ to: 'dom',
+ exec: function(html, conversionContext) {
+ var div = util.createElement(conversionContext.document, 'div');
+ div.innerHTML = html;
+ return div;
+ }
+ },
+ {
+ item: 'converter',
+ from: 'html',
+ to: 'string',
+ exec: function(html, conversionContext) {
+ var div = util.createElement(conversionContext.document, 'div');
+ div.innerHTML = html;
+ return div.textContent;
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/converters/moz.build b/devtools/shared/gcli/source/lib/gcli/converters/moz.build
new file mode 100644
index 000000000..d3a649197
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/converters/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'basic.js',
+ 'converters.js',
+ 'html.js',
+ 'terminal.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/converters/terminal.js b/devtools/shared/gcli/source/lib/gcli/converters/terminal.js
new file mode 100644
index 000000000..a2406c689
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/converters/terminal.js
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+/**
+ * A 'terminal' object is a string or an array of strings, which are typically
+ * the output from a shell command
+ */
+exports.items = [
+ {
+ item: 'converter',
+ from: 'terminal',
+ to: 'dom',
+ createTextArea: function(text, conversionContext) {
+ var node = util.createElement(conversionContext.document, 'textarea');
+ node.classList.add('gcli-row-subterminal');
+ node.readOnly = true;
+ node.textContent = text;
+ return node;
+ },
+ exec: function(data, conversionContext) {
+ if (Array.isArray(data)) {
+ var node = util.createElement(conversionContext.document, 'div');
+ data.forEach(function(member) {
+ node.appendChild(this.createTextArea(member, conversionContext));
+ });
+ return node;
+ }
+ return this.createTextArea(data);
+ }
+ },
+ {
+ item: 'converter',
+ from: 'terminal',
+ to: 'string',
+ exec: function(data, conversionContext) {
+ return Array.isArray(data) ? data.join('') : '' + data;
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/fields/delegate.js b/devtools/shared/gcli/source/lib/gcli/fields/delegate.js
new file mode 100644
index 000000000..a2fa508f0
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/fields/delegate.js
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var Field = require('./fields').Field;
+
+/**
+ * A field that works with delegate types by delaying resolution until that
+ * last possible time
+ */
+function DelegateField(type, options) {
+ Field.call(this, type, options);
+ this.options = options;
+
+ this.element = util.createElement(this.document, 'div');
+ this.update();
+
+ this.onFieldChange = util.createEvent('DelegateField.onFieldChange');
+}
+
+DelegateField.prototype = Object.create(Field.prototype);
+
+DelegateField.prototype.update = function() {
+ var subtype = this.type.getType(this.options.requisition.executionContext);
+ if (typeof subtype.parse !== 'function') {
+ subtype = this.options.requisition.system.types.createType(subtype);
+ }
+
+ // It's not clear that we can compare subtypes in this way.
+ // Perhaps we need a type.equals(...) function
+ if (subtype === this.subtype) {
+ return;
+ }
+
+ if (this.field) {
+ this.field.destroy();
+ }
+
+ this.subtype = subtype;
+ var fields = this.options.requisition.system.fields;
+ this.field = fields.get(subtype, this.options);
+
+ util.clearElement(this.element);
+ this.element.appendChild(this.field.element);
+};
+
+DelegateField.claim = function(type, context) {
+ return type.isDelegate ? Field.MATCH : Field.NO_MATCH;
+};
+
+DelegateField.prototype.destroy = function() {
+ this.element = undefined;
+ this.options = undefined;
+ if (this.field) {
+ this.field.destroy();
+ }
+ this.subtype = undefined;
+ Field.prototype.destroy.call(this);
+};
+
+DelegateField.prototype.setConversion = function(conversion) {
+ this.field.setConversion(conversion);
+};
+
+DelegateField.prototype.getConversion = function() {
+ return this.field.getConversion();
+};
+
+Object.defineProperty(DelegateField.prototype, 'isImportant', {
+ get: function() {
+ return this.field.isImportant;
+ },
+ enumerable: true
+});
+
+/**
+ * Exported items
+ */
+exports.items = [
+ DelegateField
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/fields/fields.js b/devtools/shared/gcli/source/lib/gcli/fields/fields.js
new file mode 100644
index 000000000..c97184731
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/fields/fields.js
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+/**
+ * A Field is a way to get input for a single parameter.
+ * This class is designed to be inherited from. It's important that all
+ * subclasses have a similar constructor signature because they are created
+ * via Fields.get(...)
+ * @param type The type to use in conversions
+ * @param options A set of properties to help fields configure themselves:
+ * - document: The document we use in calling createElement
+ * - requisition: The requisition that we're attached to
+ */
+function Field(type, options) {
+ this.type = type;
+ this.document = options.document;
+ this.requisition = options.requisition;
+}
+
+/**
+ * Enable registration of fields using addItems
+ */
+Field.prototype.item = 'field';
+
+/**
+ * Subclasses should assign their element with the DOM node that gets added
+ * to the 'form'. It doesn't have to be an input node, just something that
+ * contains it.
+ */
+Field.prototype.element = undefined;
+
+/**
+ * Called from the outside to indicate that the command line has changed and
+ * the field should update itself
+ */
+Field.prototype.update = function() {
+};
+
+/**
+ * Indicates that this field should drop any resources that it has created
+ */
+Field.prototype.destroy = function() {
+ this.messageElement = undefined;
+ this.document = undefined;
+ this.requisition = undefined;
+};
+
+// Note: We could/should probably change Fields from working with Conversions
+// to working with Arguments (Tokens), which makes for less calls to parse()
+
+/**
+ * Update this field display with the value from this conversion.
+ * Subclasses should provide an implementation of this function.
+ */
+Field.prototype.setConversion = function(conversion) {
+ throw new Error('Field should not be used directly');
+};
+
+/**
+ * Extract a conversion from the values in this field.
+ * Subclasses should provide an implementation of this function.
+ */
+Field.prototype.getConversion = function() {
+ throw new Error('Field should not be used directly');
+};
+
+/**
+ * Set the element where messages and validation errors will be displayed
+ * @see setMessage()
+ */
+Field.prototype.setMessageElement = function(element) {
+ this.messageElement = element;
+};
+
+/**
+ * Display a validation message in the UI
+ */
+Field.prototype.setMessage = function(message) {
+ if (this.messageElement) {
+ util.setTextContent(this.messageElement, message || '');
+ }
+};
+
+/**
+ * Some fields contain information that is more important to the user, for
+ * example error messages and completion menus.
+ */
+Field.prototype.isImportant = false;
+
+/**
+ * 'static/abstract' method to allow implementations of Field to lay a claim
+ * to a type. This allows claims of various strength to be weighted up.
+ * See the Field.*MATCH values.
+ */
+Field.claim = function(type, context) {
+ throw new Error('Field should not be used directly');
+};
+
+/**
+ * How good a match is a field for a given type
+ */
+Field.MATCH = 3; // Good match
+Field.DEFAULT = 2; // A default match
+Field.BASIC = 1; // OK in an emergency. i.e. assume Strings
+Field.NO_MATCH = 0; // This field can't help with the given type
+
+exports.Field = Field;
+
+
+/**
+ * A manager for the registered Fields
+ */
+function Fields() {
+ // Internal array of known fields
+ this._fieldCtors = [];
+}
+
+/**
+ * Add a field definition by field constructor
+ * @param fieldCtor Constructor function of new Field
+ */
+Fields.prototype.add = function(fieldCtor) {
+ if (typeof fieldCtor !== 'function') {
+ console.error('fields.add erroring on ', fieldCtor);
+ throw new Error('fields.add requires a Field constructor');
+ }
+ this._fieldCtors.push(fieldCtor);
+};
+
+/**
+ * Remove a Field definition
+ * @param field A previously registered field, specified either with a field
+ * name or from the field name
+ */
+Fields.prototype.remove = function(field) {
+ if (typeof field !== 'string') {
+ this._fieldCtors = this._fieldCtors.filter(function(test) {
+ return test !== field;
+ });
+ }
+ else if (field instanceof Field) {
+ this.remove(field.name);
+ }
+ else {
+ console.error('fields.remove erroring on ', field);
+ throw new Error('fields.remove requires an instance of Field');
+ }
+};
+
+/**
+ * Find the best possible matching field from the specification of the type
+ * of field required.
+ * @param type An instance of Type that we will represent
+ * @param options A set of properties that we should attempt to match, and use
+ * in the construction of the new field object:
+ * - document: The document to use in creating new elements
+ * - requisition: The requisition we're monitoring,
+ * @return A newly constructed field that best matches the input options
+ */
+Fields.prototype.get = function(type, options) {
+ var FieldConstructor;
+ var highestClaim = -1;
+ this._fieldCtors.forEach(function(fieldCtor) {
+ var context = (options.requisition == null) ?
+ null : options.requisition.executionContext;
+ var claim = fieldCtor.claim(type, context);
+ if (claim > highestClaim) {
+ highestClaim = claim;
+ FieldConstructor = fieldCtor;
+ }
+ });
+
+ if (!FieldConstructor) {
+ console.error('Unknown field type ', type, ' in ', this._fieldCtors);
+ throw new Error('Can\'t find field for ' + type);
+ }
+
+ if (highestClaim < Field.DEFAULT) {
+ return new BlankField(type, options);
+ }
+
+ return new FieldConstructor(type, options);
+};
+
+/**
+ * Get all the registered fields. Most for debugging
+ */
+Fields.prototype.getAll = function() {
+ return this._fieldCtors.slice();
+};
+
+exports.Fields = Fields;
+
+/**
+ * For use with delegate types that do not yet have anything to resolve to.
+ * BlankFields are not for general use.
+ */
+function BlankField(type, options) {
+ Field.call(this, type, options);
+
+ this.element = util.createElement(this.document, 'div');
+
+ this.onFieldChange = util.createEvent('BlankField.onFieldChange');
+}
+
+BlankField.prototype = Object.create(Field.prototype);
+
+BlankField.claim = function(type, context) {
+ return type.name === 'blank' ? Field.MATCH : Field.NO_MATCH;
+};
+
+BlankField.prototype.destroy = function() {
+ this.element = undefined;
+ Field.prototype.destroy.call(this);
+};
+
+BlankField.prototype.setConversion = function(conversion) {
+ this.setMessage(conversion.message);
+};
+
+BlankField.prototype.getConversion = function() {
+ return this.type.parseString('', this.requisition.executionContext);
+};
+
+/**
+ * Items for export
+ */
+exports.items = [ BlankField ];
diff --git a/devtools/shared/gcli/source/lib/gcli/fields/moz.build b/devtools/shared/gcli/source/lib/gcli/fields/moz.build
new file mode 100644
index 000000000..74fa1cc95
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/fields/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'delegate.js',
+ 'fields.js',
+ 'selection.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/fields/selection.js b/devtools/shared/gcli/source/lib/gcli/fields/selection.js
new file mode 100644
index 000000000..4f5885777
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/fields/selection.js
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var Menu = require('../ui/menu').Menu;
+
+var Argument = require('../types/types').Argument;
+var Conversion = require('../types/types').Conversion;
+var Field = require('./fields').Field;
+
+/**
+ * A field that allows selection of one of a number of options
+ */
+function SelectionField(type, options) {
+ Field.call(this, type, options);
+
+ this.arg = new Argument();
+
+ this.menu = new Menu({
+ document: this.document,
+ maxPredictions: Conversion.maxPredictions
+ });
+ this.element = this.menu.element;
+
+ this.onFieldChange = util.createEvent('SelectionField.onFieldChange');
+
+ // i.e. Register this.onItemClick as the default action for a menu click
+ this.menu.onItemClick.add(this.itemClicked, this);
+}
+
+SelectionField.prototype = Object.create(Field.prototype);
+
+SelectionField.claim = function(type, context) {
+ if (context == null) {
+ return Field.NO_MATCH;
+ }
+ return type.getType(context).hasPredictions ? Field.DEFAULT : Field.NO_MATCH;
+};
+
+SelectionField.prototype.destroy = function() {
+ this.menu.onItemClick.remove(this.itemClicked, this);
+ this.menu.destroy();
+ this.menu = undefined;
+ this.element = undefined;
+ Field.prototype.destroy.call(this);
+};
+
+SelectionField.prototype.setConversion = function(conversion) {
+ this.arg = conversion.arg;
+ this.setMessage(conversion.message);
+
+ var context = this.requisition.executionContext;
+ conversion.getPredictions(context).then(function(predictions) {
+ var items = predictions.map(function(prediction) {
+ // If the prediction value is an 'item' (that is an object with a name and
+ // description) then use that, otherwise use the prediction itself, because
+ // at least that has a name.
+ return prediction.value && prediction.value.description ?
+ prediction.value :
+ prediction;
+ }, this);
+ if (this.menu != null) {
+ this.menu.show(items, conversion.arg.text);
+ }
+ }.bind(this)).catch(util.errorHandler);
+};
+
+SelectionField.prototype.itemClicked = function(ev) {
+ var arg = new Argument(ev.name, '', ' ');
+ var context = this.requisition.executionContext;
+
+ this.type.parse(arg, context).then(function(conversion) {
+ this.onFieldChange({ conversion: conversion });
+ this.setMessage(conversion.message);
+ }.bind(this)).catch(util.errorHandler);
+};
+
+SelectionField.prototype.getConversion = function() {
+ // This tweaks the prefix/suffix of the argument to fit
+ this.arg = this.arg.beget({ text: this.input.value });
+ return this.type.parse(this.arg, this.requisition.executionContext);
+};
+
+/**
+ * Allow the terminal to use RETURN to chose the current menu item when
+ * it can't execute the command line
+ * @return true if an item was 'clicked', false otherwise
+ */
+SelectionField.prototype.selectChoice = function() {
+ var selected = this.menu.selected;
+ if (selected == null) {
+ return false;
+ }
+
+ this.itemClicked({ name: selected });
+ return true;
+};
+
+Object.defineProperty(SelectionField.prototype, 'isImportant', {
+ get: function() {
+ return this.type.name !== 'command';
+ },
+ enumerable: true
+});
+
+/**
+ * Allow registration and de-registration.
+ */
+exports.items = [ SelectionField ];
diff --git a/devtools/shared/gcli/source/lib/gcli/index.js b/devtools/shared/gcli/source/lib/gcli/index.js
new file mode 100644
index 000000000..0b889b63d
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/index.js
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Cc = require('chrome').Cc;
+var Ci = require('chrome').Ci;
+
+
+var prefSvc = Cc['@mozilla.org/preferences-service;1']
+ .getService(Ci.nsIPrefService);
+var prefBranch = prefSvc.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+
+exports.hiddenByChromePref = function() {
+ return !prefBranch.prefHasUserValue('devtools.chrome.enabled');
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/l10n.js b/devtools/shared/gcli/source/lib/gcli/l10n.js
new file mode 100644
index 000000000..4d3f36595
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/l10n.js
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+var Cc = require("chrome").Cc;
+var Ci = require("chrome").Ci;
+
+var prefSvc = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefService);
+var prefBranch = prefSvc.getBranch(null).QueryInterface(Ci.nsIPrefBranch);
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/shared/locales/gclicommands.properties");
+
+/**
+ * Lookup a string in the GCLI string bundle
+ */
+exports.lookup = function (name) {
+ try {
+ return L10N.getStr(name);
+ } catch (ex) {
+ throw new Error("Failure in lookup('" + name + "')");
+ }
+};
+
+/**
+ * An alternative to lookup().
+ * <code>l10n.lookup("BLAH") === l10n.propertyLookup.BLAH</code>
+ * This is particularly nice for templates because you can pass
+ * <code>l10n:l10n.propertyLookup</code> in the template data and use it
+ * like <code>${l10n.BLAH}</code>
+ */
+exports.propertyLookup = new Proxy({}, {
+ get: function (rcvr, name) {
+ return exports.lookup(name);
+ }
+});
+
+/**
+ * Lookup a string in the GCLI string bundle
+ */
+exports.lookupFormat = function (name, swaps) {
+ try {
+ return L10N.getFormatStr(name, ...swaps);
+ } catch (ex) {
+ throw new Error("Failure in lookupFormat('" + name + "')");
+ }
+};
+
+/**
+ * Allow GCLI users to be hidden by the "devtools.chrome.enabled" pref.
+ * Use it in commands like this:
+ * <pre>
+ * name: "somecommand",
+ * hidden: l10n.hiddenByChromePref(),
+ * exec: function (args, context) { ... }
+ * </pre>
+ */
+exports.hiddenByChromePref = function () {
+ return !prefBranch.getBoolPref("devtools.chrome.enabled");
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/languages/command.html b/devtools/shared/gcli/source/lib/gcli/languages/command.html
new file mode 100644
index 000000000..45b367332
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/languages/command.html
@@ -0,0 +1,14 @@
+
+<div>
+ <div class="gcli-row-in" save="${rowinEle}" aria-live="assertive"
+ onclick="${onclick}" ondblclick="${ondblclick}"
+ data-command="${output.canonical}">
+ <span
+ save="${promptEle}"
+ class="gcli-row-prompt ${promptClass}">:</span><span
+ class="gcli-row-in-typed">${output.typed}</span>
+ <div class="gcli-row-throbber" save="${throbEle}"></div>
+ </div>
+ <div class="gcli-row-out" aria-live="assertive" save="${rowoutEle}">
+ </div>
+</div>
diff --git a/devtools/shared/gcli/source/lib/gcli/languages/command.js b/devtools/shared/gcli/source/lib/gcli/languages/command.js
new file mode 100644
index 000000000..58357ce2b
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/languages/command.js
@@ -0,0 +1,563 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var domtemplate = require('../util/domtemplate');
+var host = require('../util/host');
+
+var Status = require('../types/types').Status;
+var cli = require('../cli');
+var Requisition = require('../cli').Requisition;
+var CommandAssignment = require('../cli').CommandAssignment;
+var intro = require('../ui/intro');
+
+var RESOLVED = Promise.resolve(true);
+
+/**
+ * Various ways in which we need to manipulate the caret/selection position.
+ * A value of null means we're not expecting a change
+ */
+var Caret = exports.Caret = {
+ /**
+ * We are expecting changes, but we don't need to move the cursor
+ */
+ NO_CHANGE: 0,
+
+ /**
+ * We want the entire input area to be selected
+ */
+ SELECT_ALL: 1,
+
+ /**
+ * The whole input has changed - push the cursor to the end
+ */
+ TO_END: 2,
+
+ /**
+ * A part of the input has changed - push the cursor to the end of the
+ * changed section
+ */
+ TO_ARG_END: 3
+};
+
+/**
+ * Shared promise for loading command.html
+ */
+var commandHtmlPromise;
+
+var commandLanguage = exports.commandLanguage = {
+ // Language implementation for GCLI commands
+ item: 'language',
+ name: 'commands',
+ prompt: ':',
+ proportionalFonts: true,
+
+ constructor: function(terminal) {
+ this.terminal = terminal;
+ this.document = terminal.document;
+ this.focusManager = terminal.focusManager;
+
+ var options = this.terminal.options;
+ this.requisition = options.requisition;
+ if (this.requisition == null) {
+ if (options.environment == null) {
+ options.environment = {};
+ options.environment.document = options.document || this.document;
+ options.environment.window = options.environment.document.defaultView;
+ }
+
+ this.requisition = new Requisition(terminal.system, options);
+ }
+
+ // We also keep track of the last known arg text for the current assignment
+ this.lastText = undefined;
+
+ // Used to effect caret changes. See _processCaretChange()
+ this._caretChange = null;
+
+ // We keep track of which assignment the cursor is in
+ this.assignment = this.requisition.getAssignmentAt(0);
+
+ if (commandHtmlPromise == null) {
+ commandHtmlPromise = host.staticRequire(module, './command.html');
+ }
+
+ return commandHtmlPromise.then(function(commandHtml) {
+ this.commandDom = host.toDom(this.document, commandHtml);
+
+ this.requisition.commandOutputManager.onOutput.add(this.outputted, this);
+ var mapping = cli.getMapping(this.requisition.executionContext);
+ mapping.terminal = this.terminal;
+
+ this.requisition.onExternalUpdate.add(this.textChanged, this);
+
+ return this;
+ }.bind(this));
+ },
+
+ destroy: function() {
+ var mapping = cli.getMapping(this.requisition.executionContext);
+ delete mapping.terminal;
+
+ this.requisition.commandOutputManager.onOutput.remove(this.outputted, this);
+ this.requisition.onExternalUpdate.remove(this.textChanged, this);
+
+ this.terminal = undefined;
+ this.requisition = undefined;
+ this.commandDom = undefined;
+ },
+
+ textChanged: function() {
+ if (this.terminal == null) {
+ return; // This can happen post-destroy()
+ }
+
+ if (this.terminal._caretChange == null) {
+ // We weren't expecting a change so this was requested by the hint system
+ // we should move the cursor to the end of the 'changed section', and the
+ // best we can do for that right now is the end of the current argument.
+ this.terminal._caretChange = Caret.TO_ARG_END;
+ }
+
+ var newStr = this.requisition.toString();
+ var input = this.terminal.getInputState();
+
+ input.typed = newStr;
+ this._processCaretChange(input);
+
+ // We don't update terminal._previousValue. Should we?
+ // Shouldn't this really be a function of terminal?
+ if (this.terminal.inputElement.value !== newStr) {
+ this.terminal.inputElement.value = newStr;
+ }
+ this.terminal.onInputChange({ inputState: input });
+
+ // We get here for minor things like whitespace change in arg prefix,
+ // so we ignore anything but an actual value change.
+ if (this.assignment.arg.text === this.lastText) {
+ return;
+ }
+
+ this.lastText = this.assignment.arg.text;
+
+ this.terminal.field.update();
+ this.terminal.field.setConversion(this.assignment.conversion);
+ util.setTextContent(this.terminal.descriptionEle, this.description);
+ },
+
+ // Called internally whenever we think that the current assignment might
+ // have changed, typically on mouse-clicks or key presses.
+ caretMoved: function(start) {
+ if (!this.requisition.isUpToDate()) {
+ return;
+ }
+ var newAssignment = this.requisition.getAssignmentAt(start);
+ if (newAssignment == null) {
+ return;
+ }
+
+ if (this.assignment !== newAssignment) {
+ if (this.assignment.param.type.onLeave) {
+ this.assignment.param.type.onLeave(this.assignment);
+ }
+
+ // This can be kicked off either by requisition doing an assign or by
+ // terminal noticing a cursor movement out of a command, so we should
+ // check that this really is a new assignment
+ var isNew = (this.assignment !== newAssignment);
+
+ this.assignment = newAssignment;
+ this.terminal.updateCompletion().catch(util.errorHandler);
+
+ if (isNew) {
+ this.updateHints();
+ }
+
+ if (this.assignment.param.type.onEnter) {
+ this.assignment.param.type.onEnter(this.assignment);
+ }
+ }
+ else {
+ if (this.assignment && this.assignment.param.type.onChange) {
+ this.assignment.param.type.onChange(this.assignment);
+ }
+ }
+
+ // Warning: compare the logic here with the logic in fieldChanged, which
+ // is slightly different. They should probably be the same
+ var error = (this.assignment.status === Status.ERROR);
+ this.focusManager.setError(error);
+ },
+
+ // Called whenever the assignment that we're providing help with changes
+ updateHints: function() {
+ this.lastText = this.assignment.arg.text;
+
+ var field = this.terminal.field;
+ if (field) {
+ field.onFieldChange.remove(this.terminal.fieldChanged, this.terminal);
+ field.destroy();
+ }
+
+ var fields = this.terminal.system.fields;
+ field = this.terminal.field = fields.get(this.assignment.param.type, {
+ document: this.terminal.document,
+ requisition: this.requisition
+ });
+
+ this.focusManager.setImportantFieldFlag(field.isImportant);
+
+ field.onFieldChange.add(this.terminal.fieldChanged, this.terminal);
+ field.setConversion(this.assignment.conversion);
+
+ // Filled in by the template process
+ this.terminal.errorEle = undefined;
+ this.terminal.descriptionEle = undefined;
+
+ var contents = this.terminal.tooltipTemplate.cloneNode(true);
+ domtemplate.template(contents, this.terminal, {
+ blankNullUndefined: true,
+ stack: 'terminal.html#tooltip'
+ });
+
+ util.clearElement(this.terminal.tooltipElement);
+ this.terminal.tooltipElement.appendChild(contents);
+ this.terminal.tooltipElement.style.display = 'block';
+
+ field.setMessageElement(this.terminal.errorEle);
+ },
+
+ /**
+ * See also handleDownArrow for some symmetry
+ */
+ handleUpArrow: function() {
+ // If the user is on a valid value, then we increment the value, but if
+ // they've typed something that's not right we page through predictions
+ if (this.assignment.getStatus() === Status.VALID) {
+ return this.requisition.nudge(this.assignment, 1).then(function() {
+ this.textChanged();
+ this.focusManager.onInputChange();
+ return true;
+ }.bind(this));
+ }
+
+ return Promise.resolve(false);
+ },
+
+ /**
+ * See also handleUpArrow for some symmetry
+ */
+ handleDownArrow: function() {
+ if (this.assignment.getStatus() === Status.VALID) {
+ return this.requisition.nudge(this.assignment, -1).then(function() {
+ this.textChanged();
+ this.focusManager.onInputChange();
+ return true;
+ }.bind(this));
+ }
+
+ return Promise.resolve(false);
+ },
+
+ /**
+ * RETURN checks status and might exec
+ */
+ handleReturn: function(input) {
+ // Deny RETURN unless the command might work
+ if (this.requisition.status !== Status.VALID) {
+ return Promise.resolve(false);
+ }
+
+ this.terminal.history.add(input);
+ this.terminal.unsetChoice().catch(util.errorHandler);
+
+ this.terminal._previousValue = this.terminal.inputElement.value;
+ this.terminal.inputElement.value = '';
+
+ return this.requisition.exec().then(function() {
+ this.textChanged();
+ return true;
+ }.bind(this));
+ },
+
+ /**
+ * Warning: We get TAB events for more than just the user pressing TAB in our
+ * input element.
+ */
+ handleTab: function() {
+ // It's possible for TAB to not change the input, in which case the
+ // textChanged event will not fire, and the caret move will not be
+ // processed. So we check that this is done first
+ this.terminal._caretChange = Caret.TO_ARG_END;
+ var inputState = this.terminal.getInputState();
+ this._processCaretChange(inputState);
+
+ this.terminal._previousValue = this.terminal.inputElement.value;
+
+ // The changes made by complete may happen asynchronously, so after the
+ // the call to complete() we should avoid making changes before the end
+ // of the event loop
+ var index = this.terminal.getChoiceIndex();
+ return this.requisition.complete(inputState.cursor, index).then(function(updated) {
+ // Abort UI changes if this UI update has been overtaken
+ if (!updated) {
+ return RESOLVED;
+ }
+ this.textChanged();
+ return this.terminal.unsetChoice();
+ }.bind(this));
+ },
+
+ /**
+ * The input text has changed in some way.
+ */
+ handleInput: function(value) {
+ this.terminal._caretChange = Caret.NO_CHANGE;
+
+ return this.requisition.update(value).then(function(updated) {
+ // Abort UI changes if this UI update has been overtaken
+ if (!updated) {
+ return RESOLVED;
+ }
+ this.textChanged();
+ return this.terminal.unsetChoice();
+ }.bind(this));
+ },
+
+ /**
+ * Counterpart to |setInput| for moving the cursor.
+ * @param cursor A JS object shaped like { start: x, end: y }
+ */
+ setCursor: function(cursor) {
+ this._caretChange = Caret.NO_CHANGE;
+ this._processCaretChange({
+ typed: this.terminal.inputElement.value,
+ cursor: cursor
+ });
+ },
+
+ /**
+ * If this._caretChange === Caret.TO_ARG_END, we alter the input object to move
+ * the selection start to the end of the current argument.
+ * @param input An object shaped like { typed:'', cursor: { start:0, end:0 }}
+ */
+ _processCaretChange: function(input) {
+ var start, end;
+ switch (this._caretChange) {
+ case Caret.SELECT_ALL:
+ start = 0;
+ end = input.typed.length;
+ break;
+
+ case Caret.TO_END:
+ start = input.typed.length;
+ end = input.typed.length;
+ break;
+
+ case Caret.TO_ARG_END:
+ // There could be a fancy way to do this involving assignment/arg math
+ // but it doesn't seem easy, so we cheat a move the cursor to just before
+ // the next space, or the end of the input
+ start = input.cursor.start;
+ do {
+ start++;
+ }
+ while (start < input.typed.length && input.typed[start - 1] !== ' ');
+
+ end = start;
+ break;
+
+ default:
+ start = input.cursor.start;
+ end = input.cursor.end;
+ break;
+ }
+
+ start = (start > input.typed.length) ? input.typed.length : start;
+ end = (end > input.typed.length) ? input.typed.length : end;
+
+ var newInput = {
+ typed: input.typed,
+ cursor: { start: start, end: end }
+ };
+
+ if (this.terminal.inputElement.selectionStart !== start) {
+ this.terminal.inputElement.selectionStart = start;
+ }
+ if (this.terminal.inputElement.selectionEnd !== end) {
+ this.terminal.inputElement.selectionEnd = end;
+ }
+
+ this.caretMoved(start);
+
+ this._caretChange = null;
+ return newInput;
+ },
+
+ /**
+ * Calculate the properties required by the template process for completer.html
+ */
+ getCompleterTemplateData: function() {
+ var input = this.terminal.getInputState();
+ var start = input.cursor.start;
+ var index = this.terminal.getChoiceIndex();
+
+ return this.requisition.getStateData(start, index).then(function(data) {
+ // Calculate the statusMarkup required to show wavy lines underneath the
+ // input text (like that of an inline spell-checker) which used by the
+ // template process for completer.html
+ // i.e. s/space/&nbsp/g in the string (for HTML display) and status to an
+ // appropriate class name (i.e. lower cased, prefixed with gcli-in-)
+ data.statusMarkup.forEach(function(member) {
+ member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
+ member.className = 'gcli-in-' + member.status.toString().toLowerCase();
+ }, this);
+
+ return data;
+ });
+ },
+
+ /**
+ * Called by the onFieldChange event (via the terminal) on the current Field
+ */
+ fieldChanged: function(ev) {
+ this.requisition.setAssignment(this.assignment, ev.conversion.arg,
+ { matchPadding: true }).then(function() {
+ this.textChanged();
+ }.bind(this));
+
+ var isError = ev.conversion.message != null && ev.conversion.message !== '';
+ this.focusManager.setError(isError);
+ },
+
+ /**
+ * Monitor for new command executions
+ */
+ outputted: function(ev) {
+ if (ev.output.hidden) {
+ return;
+ }
+
+ var template = this.commandDom.cloneNode(true);
+ var templateOptions = { stack: 'terminal.html#outputView' };
+
+ var context = this.requisition.conversionContext;
+ var data = {
+ onclick: context.update,
+ ondblclick: context.updateExec,
+ language: this,
+ output: ev.output,
+ promptClass: (ev.output.error ? 'gcli-row-error' : '') +
+ (ev.output.completed ? ' gcli-row-complete' : ''),
+ // Elements attached to this by template().
+ rowinEle: null,
+ rowoutEle: null,
+ throbEle: null,
+ promptEle: null
+ };
+
+ domtemplate.template(template, data, templateOptions);
+
+ ev.output.promise.then(function() {
+ var document = data.rowoutEle.ownerDocument;
+
+ if (ev.output.completed) {
+ data.promptEle.classList.add('gcli-row-complete');
+ }
+ if (ev.output.error) {
+ data.promptEle.classList.add('gcli-row-error');
+ }
+
+ util.clearElement(data.rowoutEle);
+
+ return ev.output.convert('dom', context).then(function(node) {
+ this.terminal.scrollToBottom();
+ data.throbEle.style.display = ev.output.completed ? 'none' : 'block';
+
+ if (node == null) {
+ data.promptEle.classList.add('gcli-row-error');
+ // TODO: show some error to the user
+ }
+
+ this._linksToNewTab(node);
+ data.rowoutEle.appendChild(node);
+
+ var event = document.createEvent('Event');
+ event.initEvent('load', true, true);
+ event.addedElement = node;
+ node.dispatchEvent(event);
+ }.bind(this));
+ }.bind(this)).catch(console.error);
+
+ this.terminal.addElement(data.rowinEle);
+ this.terminal.addElement(data.rowoutEle);
+ this.terminal.scrollToBottom();
+
+ this.focusManager.outputted();
+ },
+
+ /**
+ * Find elements with href attributes and add a target=_blank so opened links
+ * will open in a new window
+ */
+ _linksToNewTab: function(element) {
+ var links = element.querySelectorAll('*[href]');
+ for (var i = 0; i < links.length; i++) {
+ links[i].setAttribute('target', '_blank');
+ }
+ return element;
+ },
+
+ /**
+ * Show a short introduction to this language
+ */
+ showIntro: function() {
+ intro.maybeShowIntro(this.requisition.commandOutputManager,
+ this.requisition.conversionContext);
+ },
+};
+
+/**
+ * The description (displayed at the top of the hint area) should be blank if
+ * we're entering the CommandAssignment (because it's obvious) otherwise it's
+ * the parameter description.
+ */
+Object.defineProperty(commandLanguage, 'description', {
+ get: function() {
+ if (this.assignment == null || (
+ this.assignment instanceof CommandAssignment &&
+ this.assignment.value == null)) {
+ return '';
+ }
+
+ return this.assignment.param.manual || this.assignment.param.description;
+ },
+ enumerable: true
+});
+
+/**
+ * Present an error message to the hint popup
+ */
+Object.defineProperty(commandLanguage, 'message', {
+ get: function() {
+ return this.assignment.conversion.message;
+ },
+ enumerable: true
+});
+
+exports.items = [ commandLanguage ];
diff --git a/devtools/shared/gcli/source/lib/gcli/languages/javascript.js b/devtools/shared/gcli/source/lib/gcli/languages/javascript.js
new file mode 100644
index 000000000..229cdd4ff
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/languages/javascript.js
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var host = require('../util/host');
+var prism = require('../util/prism').Prism;
+
+function isMultiline(text) {
+ return typeof text === 'string' && text.indexOf('\n') > -1;
+}
+
+exports.items = [
+ {
+ // Language implementation for Javascript
+ item: 'language',
+ name: 'javascript',
+ prompt: '>',
+
+ constructor: function(terminal) {
+ this.document = terminal.document;
+ this.focusManager = terminal.focusManager;
+
+ this.updateHints();
+ },
+
+ destroy: function() {
+ this.document = undefined;
+ },
+
+ exec: function(input) {
+ return this.evaluate(input).then(function(response) {
+ var output = (response.exception != null) ?
+ response.exception.class :
+ response.output;
+
+ var isSameString = typeof output === 'string' &&
+ input.substr(1, input.length - 2) === output;
+ var isSameOther = typeof output !== 'string' &&
+ input === '' + output;
+
+ // Place strings in quotes
+ if (typeof output === 'string' && response.exception == null) {
+ if (output.indexOf('\'') === -1) {
+ output = '\'' + output + '\'';
+ }
+ else {
+ output = output.replace(/\\/, '\\').replace(/"/, '"').replace(/'/, '\'');
+ output = '"' + output + '"';
+ }
+ }
+
+ var line;
+ if (isSameString || isSameOther || output === undefined) {
+ line = input;
+ }
+ else if (isMultiline(output)) {
+ line = input + '\n/*\n' + output + '\n*/';
+ }
+ else {
+ line = input + ' // ' + output;
+ }
+
+ var grammar = prism.languages[this.name];
+ return prism.highlight(line, grammar, this.name);
+ }.bind(this));
+ },
+
+ evaluate: function(input) {
+ return host.script.evaluate(input);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/languages/languages.js b/devtools/shared/gcli/source/lib/gcli/languages/languages.js
new file mode 100644
index 000000000..3444c9a8f
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/languages/languages.js
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+var RESOLVED = Promise.resolve(true);
+
+/**
+ * This is the base implementation for all languages
+ */
+var baseLanguage = {
+ item: 'language',
+ name: undefined,
+
+ constructor: function(terminal) {
+ },
+
+ destroy: function() {
+ },
+
+ updateHints: function() {
+ util.clearElement(this.terminal.tooltipElement);
+ },
+
+ description: '',
+ message: '',
+ caretMoved: function() {},
+
+ handleUpArrow: function() {
+ return Promise.resolve(false);
+ },
+
+ handleDownArrow: function() {
+ return Promise.resolve(false);
+ },
+
+ handleTab: function() {
+ return this.terminal.unsetChoice().then(function() {
+ return RESOLVED;
+ }, util.errorHandler);
+ },
+
+ handleInput: function(input) {
+ if (input === ':') {
+ return this.terminal.setInput('').then(function() {
+ return this.terminal.pushLanguage('commands');
+ }.bind(this));
+ }
+
+ return this.terminal.unsetChoice().then(function() {
+ return RESOLVED;
+ }, util.errorHandler);
+ },
+
+ handleReturn: function(input) {
+ var rowoutEle = this.document.createElement('pre');
+ rowoutEle.classList.add('gcli-row-out');
+ rowoutEle.classList.add('gcli-row-script');
+ rowoutEle.setAttribute('aria-live', 'assertive');
+
+ return this.exec(input).then(function(line) {
+ rowoutEle.innerHTML = line;
+
+ this.terminal.addElement(rowoutEle);
+ this.terminal.scrollToBottom();
+
+ this.focusManager.outputted();
+
+ this.terminal.unsetChoice().catch(util.errorHandler);
+ this.terminal.inputElement.value = '';
+ }.bind(this));
+ },
+
+ setCursor: function(cursor) {
+ this.terminal.inputElement.selectionStart = cursor.start;
+ this.terminal.inputElement.selectionEnd = cursor.end;
+ },
+
+ getCompleterTemplateData: function() {
+ return Promise.resolve({
+ statusMarkup: [
+ {
+ string: this.terminal.inputElement.value.replace(/ /g, '\u00a0'), // i.e. &nbsp;
+ className: 'gcli-in-valid'
+ }
+ ],
+ unclosedJs: false,
+ directTabText: '',
+ arrowTabText: '',
+ emptyParameters: ''
+ });
+ },
+
+ showIntro: function() {
+ },
+
+ exec: function(input) {
+ throw new Error('Missing implementation of handleReturn() or exec() ' + this.name);
+ }
+};
+
+/**
+ * A manager for the registered Languages
+ */
+function Languages() {
+ // This is where we cache the languages that we know about
+ this._registered = {};
+}
+
+/**
+ * Add a new language to the cache
+ */
+Languages.prototype.add = function(language) {
+ this._registered[language.name] = language;
+};
+
+/**
+ * Remove an existing language from the cache
+ */
+Languages.prototype.remove = function(language) {
+ var name = typeof language === 'string' ? language : language.name;
+ delete this._registered[name];
+};
+
+/**
+ * Get access to the list of known languages
+ */
+Languages.prototype.getAll = function() {
+ return Object.keys(this._registered).map(function(name) {
+ return this._registered[name];
+ }.bind(this));
+};
+
+/**
+ * Find a previously registered language
+ */
+Languages.prototype.createLanguage = function(name, terminal) {
+ if (name == null) {
+ name = Object.keys(this._registered)[0];
+ }
+
+ var language = (typeof name === 'string') ? this._registered[name] : name;
+ if (!language) {
+ console.error('Known languages: ' + Object.keys(this._registered).join(', '));
+ throw new Error('Unknown language: \'' + name + '\'');
+ }
+
+ // clone 'type'
+ var newInstance = {};
+ util.copyProperties(baseLanguage, newInstance);
+ util.copyProperties(language, newInstance);
+
+ if (typeof newInstance.constructor === 'function') {
+ var reply = newInstance.constructor(terminal);
+ return Promise.resolve(reply).then(function() {
+ return newInstance;
+ });
+ }
+ else {
+ return Promise.resolve(newInstance);
+ }
+};
+
+exports.Languages = Languages;
diff --git a/devtools/shared/gcli/source/lib/gcli/languages/moz.build b/devtools/shared/gcli/source/lib/gcli/languages/moz.build
new file mode 100644
index 000000000..e1828a51f
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/languages/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'command.html',
+ 'command.js',
+ 'javascript.js',
+ 'languages.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/moz.build b/devtools/shared/gcli/source/lib/gcli/moz.build
new file mode 100644
index 000000000..7b1e6dd2a
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'cli.js',
+ 'index.js',
+ 'l10n.js',
+ 'settings.js',
+ 'system.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/mozui/completer.js b/devtools/shared/gcli/source/lib/gcli/mozui/completer.js
new file mode 100644
index 000000000..fd9a74732
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/mozui/completer.js
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var host = require('../util/host');
+var domtemplate = require('../util/domtemplate');
+
+var completerHtml =
+ '<description\n' +
+ ' xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">\n' +
+ ' <loop foreach="member in ${statusMarkup}">\n' +
+ ' <label class="${member.className}" value="${member.string}"></label>\n' +
+ ' </loop>\n' +
+ ' <label class="gcli-in-ontab" value="${directTabText}"/>\n' +
+ ' <label class="gcli-in-todo" foreach="param in ${emptyParameters}" value="${param}"/>\n' +
+ ' <label class="gcli-in-ontab" value="${arrowTabText}"/>\n' +
+ ' <label class="gcli-in-closebrace" if="${unclosedJs}" value="}"/>\n' +
+ '</description>\n';
+
+/**
+ * Completer is an 'input-like' element that sits an input element annotating
+ * it with visual goodness.
+ * @param components Object that links to other UI components. GCLI provided:
+ * - requisition: A GCLI Requisition object whose state is monitored
+ * - element: Element to use as root
+ * - autoResize: (default=false): Should we attempt to sync the dimensions of
+ * the complete element with the input element.
+ */
+function Completer(components) {
+ this.requisition = components.requisition;
+ this.input = { typed: '', cursor: { start: 0, end: 0 } };
+ this.choice = 0;
+
+ this.element = components.element;
+ this.element.classList.add('gcli-in-complete');
+ this.element.setAttribute('tabindex', '-1');
+ this.element.setAttribute('aria-live', 'polite');
+
+ this.document = this.element.ownerDocument;
+
+ this.inputter = components.inputter;
+
+ this.inputter.onInputChange.add(this.update, this);
+ this.inputter.onAssignmentChange.add(this.update, this);
+ this.inputter.onChoiceChange.add(this.update, this);
+
+ this.autoResize = components.autoResize;
+ if (this.autoResize) {
+ this.inputter.onResize.add(this.resized, this);
+
+ var dimensions = this.inputter.getDimensions();
+ if (dimensions) {
+ this.resized(dimensions);
+ }
+ }
+
+ this.template = host.toDom(this.document, completerHtml);
+ // We want the spans to line up without the spaces in the template
+ util.removeWhitespace(this.template, true);
+
+ this.update();
+}
+
+/**
+ * Avoid memory leaks
+ */
+Completer.prototype.destroy = function() {
+ this.inputter.onInputChange.remove(this.update, this);
+ this.inputter.onAssignmentChange.remove(this.update, this);
+ this.inputter.onChoiceChange.remove(this.update, this);
+
+ if (this.autoResize) {
+ this.inputter.onResize.remove(this.resized, this);
+ }
+
+ this.document = undefined;
+ this.element = undefined;
+ this.template = undefined;
+ this.inputter = undefined;
+};
+
+/**
+ * Ensure that the completion element is the same size and the inputter element
+ */
+Completer.prototype.resized = function(ev) {
+ this.element.style.top = ev.top + 'px';
+ this.element.style.height = ev.height + 'px';
+ this.element.style.lineHeight = ev.height + 'px';
+ this.element.style.left = ev.left + 'px';
+ this.element.style.width = ev.width + 'px';
+};
+
+/**
+ * Bring the completion element up to date with what the requisition says
+ */
+Completer.prototype.update = function(ev) {
+ this.choice = (ev && ev.choice != null) ? ev.choice : 0;
+
+ this._getCompleterTemplateData().then(function(data) {
+ if (this.template == null) {
+ return; // destroy() has been called
+ }
+
+ var template = this.template.cloneNode(true);
+ domtemplate.template(template, data, { stack: 'completer.html' });
+
+ util.clearElement(this.element);
+ while (template.hasChildNodes()) {
+ this.element.appendChild(template.firstChild);
+ }
+ }.bind(this));
+};
+
+/**
+ * Calculate the properties required by the template process for completer.html
+ */
+Completer.prototype._getCompleterTemplateData = function() {
+ var input = this.inputter.getInputState();
+ var start = input.cursor.start;
+
+ return this.requisition.getStateData(start, this.choice).then(function(data) {
+ // Calculate the statusMarkup required to show wavy lines underneath the
+ // input text (like that of an inline spell-checker) which used by the
+ // template process for completer.html
+ // i.e. s/space/&nbsp/g in the string (for HTML display) and status to an
+ // appropriate class name (i.e. lower cased, prefixed with gcli-in-)
+ data.statusMarkup.forEach(function(member) {
+ member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
+ member.className = 'gcli-in-' + member.status.toString().toLowerCase();
+ }, this);
+
+ return data;
+ });
+};
+
+exports.Completer = Completer;
diff --git a/devtools/shared/gcli/source/lib/gcli/mozui/inputter.js b/devtools/shared/gcli/source/lib/gcli/mozui/inputter.js
new file mode 100644
index 000000000..3810c2e8c
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/mozui/inputter.js
@@ -0,0 +1,657 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var KeyEvent = require('../util/util').KeyEvent;
+
+var Status = require('../types/types').Status;
+var History = require('../ui/history').History;
+
+var RESOLVED = Promise.resolve(true);
+
+/**
+ * A wrapper to take care of the functions concerning an input element
+ * @param components Object that links to other UI components. GCLI provided:
+ * - requisition
+ * - focusManager
+ * - element
+ */
+function Inputter(components) {
+ this.requisition = components.requisition;
+ this.focusManager = components.focusManager;
+
+ this.element = components.element;
+ this.element.classList.add('gcli-in-input');
+ this.element.spellcheck = false;
+
+ this.document = this.element.ownerDocument;
+
+ // Used to distinguish focus from TAB in CLI. See onKeyUp()
+ this.lastTabDownAt = 0;
+
+ // Used to effect caret changes. See _processCaretChange()
+ this._caretChange = null;
+
+ // Ensure that TAB/UP/DOWN isn't handled by the browser
+ this.onKeyDown = this.onKeyDown.bind(this);
+ this.onKeyUp = this.onKeyUp.bind(this);
+ this.element.addEventListener('keydown', this.onKeyDown, false);
+ this.element.addEventListener('keyup', this.onKeyUp, false);
+
+ // Setup History
+ this.history = new History();
+ this._scrollingThroughHistory = false;
+
+ // Used when we're selecting which prediction to complete with
+ this._choice = null;
+ this.onChoiceChange = util.createEvent('Inputter.onChoiceChange');
+
+ // Cursor position affects hint severity
+ this.onMouseUp = this.onMouseUp.bind(this);
+ this.element.addEventListener('mouseup', this.onMouseUp, false);
+
+ if (this.focusManager) {
+ this.focusManager.addMonitoredElement(this.element, 'input');
+ }
+
+ // Initially an asynchronous completion isn't in-progress
+ this._completed = RESOLVED;
+
+ this.textChanged = this.textChanged.bind(this);
+
+ this.outputted = this.outputted.bind(this);
+ this.requisition.commandOutputManager.onOutput.add(this.outputted, this);
+
+ this.assignment = this.requisition.getAssignmentAt(0);
+ this.onAssignmentChange = util.createEvent('Inputter.onAssignmentChange');
+ this.onInputChange = util.createEvent('Inputter.onInputChange');
+
+ this.onResize = util.createEvent('Inputter.onResize');
+ this.onWindowResize = this.onWindowResize.bind(this);
+ this.document.defaultView.addEventListener('resize', this.onWindowResize, false);
+ this.requisition.onExternalUpdate.add(this.textChanged, this);
+
+ this._previousValue = undefined;
+ this.requisition.update(this.element.value || '');
+}
+
+/**
+ * Avoid memory leaks
+ */
+Inputter.prototype.destroy = function() {
+ this.document.defaultView.removeEventListener('resize', this.onWindowResize, false);
+
+ this.requisition.commandOutputManager.onOutput.remove(this.outputted, this);
+ this.requisition.onExternalUpdate.remove(this.textChanged, this);
+ if (this.focusManager) {
+ this.focusManager.removeMonitoredElement(this.element, 'input');
+ }
+
+ this.element.removeEventListener('mouseup', this.onMouseUp, false);
+ this.element.removeEventListener('keydown', this.onKeyDown, false);
+ this.element.removeEventListener('keyup', this.onKeyUp, false);
+
+ this.history.destroy();
+
+ if (this.style) {
+ this.style.parentNode.removeChild(this.style);
+ this.style = undefined;
+ }
+
+ this.textChanged = undefined;
+ this.outputted = undefined;
+ this.onMouseUp = undefined;
+ this.onKeyDown = undefined;
+ this.onKeyUp = undefined;
+ this.onWindowResize = undefined;
+ this.tooltip = undefined;
+ this.document = undefined;
+ this.element = undefined;
+};
+
+/**
+ * Make ourselves visually similar to the input element, and make the input
+ * element transparent so our background shines through
+ */
+Inputter.prototype.onWindowResize = function() {
+ // Mochitest sometimes causes resize after shutdown. See Bug 743190
+ if (!this.element) {
+ return;
+ }
+
+ this.onResize(this.getDimensions());
+};
+
+/**
+ * Make ourselves visually similar to the input element, and make the input
+ * element transparent so our background shines through
+ */
+Inputter.prototype.getDimensions = function() {
+ var fixedLoc = {};
+ var currentElement = this.element.parentNode;
+ while (currentElement && currentElement.nodeName !== '#document') {
+ var style = this.document.defaultView.getComputedStyle(currentElement, '');
+ if (style) {
+ var position = style.getPropertyValue('position');
+ if (position === 'absolute' || position === 'fixed') {
+ var bounds = currentElement.getBoundingClientRect();
+ fixedLoc.top = bounds.top;
+ fixedLoc.left = bounds.left;
+ break;
+ }
+ }
+ currentElement = currentElement.parentNode;
+ }
+
+ var rect = this.element.getBoundingClientRect();
+ return {
+ top: rect.top - (fixedLoc.top || 0) + 1,
+ height: rect.bottom - rect.top - 1,
+ left: rect.left - (fixedLoc.left || 0) + 2,
+ width: rect.right - rect.left
+ };
+};
+
+/**
+ * Pass 'outputted' events on to the focus manager
+ */
+Inputter.prototype.outputted = function() {
+ if (this.focusManager) {
+ this.focusManager.outputted();
+ }
+};
+
+/**
+ * Handler for the input-element.onMouseUp event
+ */
+Inputter.prototype.onMouseUp = function(ev) {
+ this._checkAssignment();
+};
+
+/**
+ * Function called when we think the text might have changed
+ */
+Inputter.prototype.textChanged = function() {
+ if (!this.document) {
+ return; // This can happen post-destroy()
+ }
+
+ if (this._caretChange == null) {
+ // We weren't expecting a change so this was requested by the hint system
+ // we should move the cursor to the end of the 'changed section', and the
+ // best we can do for that right now is the end of the current argument.
+ this._caretChange = Caret.TO_ARG_END;
+ }
+
+ var newStr = this.requisition.toString();
+ var input = this.getInputState();
+
+ input.typed = newStr;
+ this._processCaretChange(input);
+
+ if (this.element.value !== newStr) {
+ this.element.value = newStr;
+ }
+ this.onInputChange({ inputState: input });
+
+ this.tooltip.textChanged();
+};
+
+/**
+ * Various ways in which we need to manipulate the caret/selection position.
+ * A value of null means we're not expecting a change
+ */
+var Caret = {
+ /**
+ * We are expecting changes, but we don't need to move the cursor
+ */
+ NO_CHANGE: 0,
+
+ /**
+ * We want the entire input area to be selected
+ */
+ SELECT_ALL: 1,
+
+ /**
+ * The whole input has changed - push the cursor to the end
+ */
+ TO_END: 2,
+
+ /**
+ * A part of the input has changed - push the cursor to the end of the
+ * changed section
+ */
+ TO_ARG_END: 3
+};
+
+/**
+ * If this._caretChange === Caret.TO_ARG_END, we alter the input object to move
+ * the selection start to the end of the current argument.
+ * @param input An object shaped like { typed:'', cursor: { start:0, end:0 }}
+ */
+Inputter.prototype._processCaretChange = function(input) {
+ var start, end;
+ switch (this._caretChange) {
+ case Caret.SELECT_ALL:
+ start = 0;
+ end = input.typed.length;
+ break;
+
+ case Caret.TO_END:
+ start = input.typed.length;
+ end = input.typed.length;
+ break;
+
+ case Caret.TO_ARG_END:
+ // There could be a fancy way to do this involving assignment/arg math
+ // but it doesn't seem easy, so we cheat a move the cursor to just before
+ // the next space, or the end of the input
+ start = input.cursor.start;
+ do {
+ start++;
+ }
+ while (start < input.typed.length && input.typed[start - 1] !== ' ');
+
+ end = start;
+ break;
+
+ default:
+ start = input.cursor.start;
+ end = input.cursor.end;
+ break;
+ }
+
+ start = (start > input.typed.length) ? input.typed.length : start;
+ end = (end > input.typed.length) ? input.typed.length : end;
+
+ var newInput = {
+ typed: input.typed,
+ cursor: { start: start, end: end }
+ };
+
+ if (this.element.selectionStart !== start) {
+ this.element.selectionStart = start;
+ }
+ if (this.element.selectionEnd !== end) {
+ this.element.selectionEnd = end;
+ }
+
+ this._checkAssignment(start);
+
+ this._caretChange = null;
+ return newInput;
+};
+
+/**
+ * To be called internally whenever we think that the current assignment might
+ * have changed, typically on mouse-clicks or key presses.
+ * @param start Optional - if specified, the cursor position to use in working
+ * out the current assignment. This is needed because setting the element
+ * selection start is only recognised when the event loop has finished
+ */
+Inputter.prototype._checkAssignment = function(start) {
+ if (start == null) {
+ start = this.element.selectionStart;
+ }
+ if (!this.requisition.isUpToDate()) {
+ return;
+ }
+ var newAssignment = this.requisition.getAssignmentAt(start);
+ if (newAssignment == null) {
+ return;
+ }
+ if (this.assignment !== newAssignment) {
+ if (this.assignment.param.type.onLeave) {
+ this.assignment.param.type.onLeave(this.assignment);
+ }
+
+ this.assignment = newAssignment;
+ this.onAssignmentChange({ assignment: this.assignment });
+
+ if (this.assignment.param.type.onEnter) {
+ this.assignment.param.type.onEnter(this.assignment);
+ }
+ }
+ else {
+ if (this.assignment && this.assignment.param.type.onChange) {
+ this.assignment.param.type.onChange(this.assignment);
+ }
+ }
+
+ // This is slightly nasty - the focusManager generally relies on people
+ // telling it what it needs to know (which makes sense because the event
+ // system to do it with events would be unnecessarily complex). However
+ // requisition doesn't know about the focusManager either. So either one
+ // needs to know about the other, or a third-party needs to break the
+ // deadlock. These 2 lines are all we're quibbling about, so for now we hack
+ if (this.focusManager) {
+ var error = (this.assignment.status === Status.ERROR);
+ this.focusManager.setError(error);
+ }
+};
+
+/**
+ * Set the input field to a value, for external use.
+ * This function updates the data model. It sets the caret to the end of the
+ * input. It does not make any similarity checks so calling this function with
+ * it's current value resets the cursor position.
+ * It does not execute the input or affect the history.
+ * This function should not be called internally, by Inputter and never as a
+ * result of a keyboard event on this.element or bug 676520 could be triggered.
+ */
+Inputter.prototype.setInput = function(str) {
+ this._caretChange = Caret.TO_END;
+ return this.requisition.update(str).then(function(updated) {
+ this.textChanged();
+ return updated;
+ }.bind(this));
+};
+
+/**
+ * Counterpart to |setInput| for moving the cursor.
+ * @param cursor An object shaped like { start: x, end: y }
+ */
+Inputter.prototype.setCursor = function(cursor) {
+ this._caretChange = Caret.NO_CHANGE;
+ this._processCaretChange({ typed: this.element.value, cursor: cursor });
+ return RESOLVED;
+};
+
+/**
+ * Focus the input element
+ */
+Inputter.prototype.focus = function() {
+ this.element.focus();
+ this._checkAssignment();
+};
+
+/**
+ * Ensure certain keys (arrows, tab, etc) that we would like to handle
+ * are not handled by the browser
+ */
+Inputter.prototype.onKeyDown = function(ev) {
+ if (ev.keyCode === KeyEvent.DOM_VK_UP || ev.keyCode === KeyEvent.DOM_VK_DOWN) {
+ ev.preventDefault();
+ return;
+ }
+
+ // The following keys do not affect the state of the command line so we avoid
+ // informing the focusManager about keyboard events that involve these keys
+ if (ev.keyCode === KeyEvent.DOM_VK_F1 ||
+ ev.keyCode === KeyEvent.DOM_VK_ESCAPE ||
+ ev.keyCode === KeyEvent.DOM_VK_UP ||
+ ev.keyCode === KeyEvent.DOM_VK_DOWN) {
+ return;
+ }
+
+ if (this.focusManager) {
+ this.focusManager.onInputChange();
+ }
+
+ if (ev.keyCode === KeyEvent.DOM_VK_TAB) {
+ this.lastTabDownAt = 0;
+ if (!ev.shiftKey) {
+ ev.preventDefault();
+ // Record the timestamp of this TAB down so onKeyUp can distinguish
+ // focus from TAB in the CLI.
+ this.lastTabDownAt = ev.timeStamp;
+ }
+ if (ev.metaKey || ev.altKey || ev.crtlKey) {
+ if (this.document.commandDispatcher) {
+ this.document.commandDispatcher.advanceFocus();
+ }
+ else {
+ this.element.blur();
+ }
+ }
+ }
+};
+
+/**
+ * Handler for use with DOM events, which just calls the promise enabled
+ * handleKeyUp function but checks the exit state of the promise so we know
+ * if something went wrong.
+ */
+Inputter.prototype.onKeyUp = function(ev) {
+ this.handleKeyUp(ev).catch(util.errorHandler);
+};
+
+/**
+ * The main keyboard processing loop
+ * @return A promise that resolves (to undefined) when the actions kicked off
+ * by this handler are completed.
+ */
+Inputter.prototype.handleKeyUp = function(ev) {
+ if (this.focusManager && ev.keyCode === KeyEvent.DOM_VK_F1) {
+ this.focusManager.helpRequest();
+ return RESOLVED;
+ }
+
+ if (this.focusManager && ev.keyCode === KeyEvent.DOM_VK_ESCAPE) {
+ this.focusManager.removeHelp();
+ return RESOLVED;
+ }
+
+ if (ev.keyCode === KeyEvent.DOM_VK_UP) {
+ return this._handleUpArrow();
+ }
+
+ if (ev.keyCode === KeyEvent.DOM_VK_DOWN) {
+ return this._handleDownArrow();
+ }
+
+ if (ev.keyCode === KeyEvent.DOM_VK_RETURN) {
+ return this._handleReturn();
+ }
+
+ if (ev.keyCode === KeyEvent.DOM_VK_TAB && !ev.shiftKey) {
+ return this._handleTab(ev);
+ }
+
+ if (this._previousValue === this.element.value) {
+ return RESOLVED;
+ }
+
+ this._scrollingThroughHistory = false;
+ this._caretChange = Caret.NO_CHANGE;
+
+ this._completed = this.requisition.update(this.element.value);
+ this._previousValue = this.element.value;
+
+ return this._completed.then(function() {
+ // Abort UI changes if this UI update has been overtaken
+ if (this._previousValue === this.element.value) {
+ this._choice = null;
+ this.textChanged();
+ this.onChoiceChange({ choice: this._choice });
+ }
+ }.bind(this));
+};
+
+/**
+ * See also _handleDownArrow for some symmetry
+ */
+Inputter.prototype._handleUpArrow = function() {
+ if (this.tooltip && this.tooltip.isMenuShowing) {
+ this.changeChoice(-1);
+ return RESOLVED;
+ }
+
+ if (this.element.value === '' || this._scrollingThroughHistory) {
+ this._scrollingThroughHistory = true;
+ return this.requisition.update(this.history.backward()).then(function(updated) {
+ this.textChanged();
+ return updated;
+ }.bind(this));
+ }
+
+ // If the user is on a valid value, then we increment the value, but if
+ // they've typed something that's not right we page through predictions
+ if (this.assignment.getStatus() === Status.VALID) {
+ return this.requisition.nudge(this.assignment, 1).then(function() {
+ // See notes on focusManager.onInputChange in onKeyDown
+ this.textChanged();
+ if (this.focusManager) {
+ this.focusManager.onInputChange();
+ }
+ }.bind(this));
+ }
+
+ this.changeChoice(-1);
+ return RESOLVED;
+};
+
+/**
+ * See also _handleUpArrow for some symmetry
+ */
+Inputter.prototype._handleDownArrow = function() {
+ if (this.tooltip && this.tooltip.isMenuShowing) {
+ this.changeChoice(+1);
+ return RESOLVED;
+ }
+
+ if (this.element.value === '' || this._scrollingThroughHistory) {
+ this._scrollingThroughHistory = true;
+ return this.requisition.update(this.history.forward()).then(function(updated) {
+ this.textChanged();
+ return updated;
+ }.bind(this));
+ }
+
+ // See notes above for the UP key
+ if (this.assignment.getStatus() === Status.VALID) {
+ return this.requisition.nudge(this.assignment, -1).then(function() {
+ // See notes on focusManager.onInputChange in onKeyDown
+ this.textChanged();
+ if (this.focusManager) {
+ this.focusManager.onInputChange();
+ }
+ }.bind(this));
+ }
+
+ this.changeChoice(+1);
+ return RESOLVED;
+};
+
+/**
+ * RETURN checks status and might exec
+ */
+Inputter.prototype._handleReturn = function() {
+ // Deny RETURN unless the command might work
+ if (this.requisition.status === Status.VALID) {
+ this._scrollingThroughHistory = false;
+ this.history.add(this.element.value);
+
+ return this.requisition.exec().then(function() {
+ this.textChanged();
+ }.bind(this));
+ }
+
+ // If we can't execute the command, but there is a menu choice to use
+ // then use it.
+ if (!this.tooltip.selectChoice()) {
+ this.focusManager.setError(true);
+ }
+
+ this._choice = null;
+ return RESOLVED;
+};
+
+/**
+ * Warning: We get TAB events for more than just the user pressing TAB in our
+ * input element.
+ */
+Inputter.prototype._handleTab = function(ev) {
+ // Being able to complete 'nothing' is OK if there is some context, but
+ // when there is nothing on the command line it just looks bizarre.
+ var hasContents = (this.element.value.length > 0);
+
+ // If the TAB keypress took the cursor from another field to this one,
+ // then they get the keydown/keypress, and we get the keyup. In this
+ // case we don't want to do any completion.
+ // If the time of the keydown/keypress of TAB was close (i.e. within
+ // 1 second) to the time of the keyup then we assume that we got them
+ // both, and do the completion.
+ if (hasContents && this.lastTabDownAt + 1000 > ev.timeStamp) {
+ // It's possible for TAB to not change the input, in which case the caret
+ // move will not be processed. So we check that this is done first
+ this._caretChange = Caret.TO_ARG_END;
+ var inputState = this.getInputState();
+ this._processCaretChange(inputState);
+
+ if (this._choice == null) {
+ this._choice = 0;
+ }
+
+ // The changes made by complete may happen asynchronously, so after the
+ // the call to complete() we should avoid making changes before the end
+ // of the event loop
+ this._completed = this.requisition.complete(inputState.cursor,
+ this._choice);
+ this._previousValue = this.element.value;
+ }
+ this.lastTabDownAt = 0;
+ this._scrollingThroughHistory = false;
+
+ return this._completed.then(function(updated) {
+ // Abort UI changes if this UI update has been overtaken
+ if (updated) {
+ this.textChanged();
+ this._choice = null;
+ this.onChoiceChange({ choice: this._choice });
+ }
+ }.bind(this));
+};
+
+/**
+ * Used by onKeyUp for UP/DOWN to change the current choice from an options
+ * menu.
+ */
+Inputter.prototype.changeChoice = function(amount) {
+ if (this._choice == null) {
+ this._choice = 0;
+ }
+ // There's an annoying up is down thing here, the menu is presented
+ // with the zeroth index at the top working down, so the UP arrow needs
+ // pick the choice below because we're working down
+ this._choice += amount;
+ this.onChoiceChange({ choice: this._choice });
+};
+
+/**
+ * Pull together an input object, which may include XUL hacks
+ */
+Inputter.prototype.getInputState = function() {
+ var input = {
+ typed: this.element.value,
+ cursor: {
+ start: this.element.selectionStart,
+ end: this.element.selectionEnd
+ }
+ };
+
+ // Workaround for potential XUL bug 676520 where textbox gives incorrect
+ // values for its content
+ if (input.typed == null) {
+ input = { typed: '', cursor: { start: 0, end: 0 } };
+ }
+
+ return input;
+};
+
+exports.Inputter = Inputter;
diff --git a/devtools/shared/gcli/source/lib/gcli/mozui/moz.build b/devtools/shared/gcli/source/lib/gcli/mozui/moz.build
new file mode 100644
index 000000000..af76e0d99
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/mozui/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'completer.js',
+ 'inputter.js',
+ 'tooltip.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/mozui/tooltip.js b/devtools/shared/gcli/source/lib/gcli/mozui/tooltip.js
new file mode 100644
index 000000000..f72900a80
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/mozui/tooltip.js
@@ -0,0 +1,298 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var host = require('../util/host');
+var domtemplate = require('../util/domtemplate');
+
+var CommandAssignment = require('../cli').CommandAssignment;
+
+var tooltipHtml =
+ '<div class="gcli-tt" aria-live="polite">\n' +
+ ' <div class="gcli-tt-description" save="${descriptionEle}">${description}</div>\n' +
+ ' ${field.element}\n' +
+ ' <div class="gcli-tt-error" save="${errorEle}">${assignment.conversion.message}</div>\n' +
+ ' <div class="gcli-tt-highlight" save="${highlightEle}"></div>\n' +
+ '</div>';
+
+/**
+ * A widget to display an inline dialog which allows the user to fill out
+ * the arguments to a command.
+ * @param components Object that links to other UI components. GCLI provided:
+ * - requisition: The Requisition to fill out
+ * - inputter: An instance of Inputter
+ * - focusManager: Component to manage hiding/showing this element
+ * - panelElement (optional): The element to show/hide on visibility events
+ * - element: The root element to populate
+ */
+function Tooltip(components) {
+ this.inputter = components.inputter;
+ this.requisition = components.requisition;
+ this.focusManager = components.focusManager;
+
+ this.element = components.element;
+ this.element.classList.add('gcliterm-tooltip');
+ this.document = this.element.ownerDocument;
+
+ this.panelElement = components.panelElement;
+ if (this.panelElement) {
+ this.panelElement.classList.add('gcli-panel-hide');
+ this.focusManager.onVisibilityChange.add(this.visibilityChanged, this);
+ }
+ this.focusManager.addMonitoredElement(this.element, 'tooltip');
+
+ // We cache the fields we create so we can destroy them later
+ this.fields = [];
+
+ this.template = host.toDom(this.document, tooltipHtml);
+ this.templateOptions = { blankNullUndefined: true, stack: 'tooltip.html' };
+
+ this.inputter.onChoiceChange.add(this.choiceChanged, this);
+ this.inputter.onAssignmentChange.add(this.assignmentChanged, this);
+
+ // We keep a track of which assignment the cursor is in
+ this.assignment = undefined;
+ this.assignmentChanged({ assignment: this.inputter.assignment });
+
+ // We also keep track of the last known arg text for the current assignment
+ this.lastText = undefined;
+}
+
+/**
+ * Avoid memory leaks
+ */
+Tooltip.prototype.destroy = function() {
+ this.inputter.onAssignmentChange.remove(this.assignmentChanged, this);
+ this.inputter.onChoiceChange.remove(this.choiceChanged, this);
+
+ if (this.panelElement) {
+ this.focusManager.onVisibilityChange.remove(this.visibilityChanged, this);
+ }
+ this.focusManager.removeMonitoredElement(this.element, 'tooltip');
+
+ if (this.style) {
+ this.style.parentNode.removeChild(this.style);
+ this.style = undefined;
+ }
+
+ this.field.onFieldChange.remove(this.fieldChanged, this);
+ this.field.destroy();
+
+ this.lastText = undefined;
+ this.assignment = undefined;
+
+ this.errorEle = undefined;
+ this.descriptionEle = undefined;
+ this.highlightEle = undefined;
+
+ this.document = undefined;
+ this.element = undefined;
+ this.panelElement = undefined;
+ this.template = undefined;
+};
+
+/**
+ * The inputter acts on UP/DOWN if there is a menu showing
+ */
+Object.defineProperty(Tooltip.prototype, 'isMenuShowing', {
+ get: function() {
+ return this.focusManager.isTooltipVisible &&
+ this.field != null &&
+ this.field.menu != null;
+ },
+ enumerable: true
+});
+
+/**
+ * Called whenever the assignment that we're providing help with changes
+ */
+Tooltip.prototype.assignmentChanged = function(ev) {
+ // This can be kicked off either by requisition doing an assign or by
+ // inputter noticing a cursor movement out of a command, so we should check
+ // that this really is a new assignment
+ if (this.assignment === ev.assignment) {
+ return;
+ }
+
+ this.assignment = ev.assignment;
+ this.lastText = this.assignment.arg.text;
+
+ if (this.field) {
+ this.field.onFieldChange.remove(this.fieldChanged, this);
+ this.field.destroy();
+ }
+
+ this.field = this.requisition.system.fields.get(this.assignment.param.type, {
+ document: this.document,
+ requisition: this.requisition
+ });
+
+ this.focusManager.setImportantFieldFlag(this.field.isImportant);
+
+ this.field.onFieldChange.add(this.fieldChanged, this);
+ this.field.setConversion(this.assignment.conversion);
+
+ // Filled in by the template process
+ this.errorEle = undefined;
+ this.descriptionEle = undefined;
+ this.highlightEle = undefined;
+
+ var contents = this.template.cloneNode(true);
+ domtemplate.template(contents, this, this.templateOptions);
+ util.clearElement(this.element);
+ this.element.appendChild(contents);
+ this.element.style.display = 'block';
+
+ this.field.setMessageElement(this.errorEle);
+
+ this._updatePosition();
+};
+
+/**
+ * Forward the event to the current field
+ */
+Tooltip.prototype.choiceChanged = function(ev) {
+ if (this.field && this.field.menu) {
+ var conversion = this.assignment.conversion;
+ var context = this.requisition.executionContext;
+ conversion.constrainPredictionIndex(context, ev.choice).then(function(choice) {
+ this.field.menu._choice = choice;
+ this.field.menu._updateHighlight();
+ }.bind(this)).catch(util.errorHandler);
+ }
+};
+
+/**
+ * Allow the inputter to use RETURN to chose the current menu item when
+ * it can't execute the command line
+ * @return true if there was a selection to use, false otherwise
+ */
+Tooltip.prototype.selectChoice = function(ev) {
+ if (this.field && this.field.selectChoice) {
+ return this.field.selectChoice();
+ }
+ return false;
+};
+
+/**
+ * Called by the onFieldChange event on the current Field
+ */
+Tooltip.prototype.fieldChanged = function(ev) {
+ this.requisition.setAssignment(this.assignment, ev.conversion.arg,
+ { matchPadding: true });
+
+ var isError = ev.conversion.message != null && ev.conversion.message !== '';
+ this.focusManager.setError(isError);
+
+ // Nasty hack, the inputter won't know about the text change yet, so it will
+ // get it's calculations wrong. We need to wait until the current set of
+ // changes has had a chance to propagate
+ this.document.defaultView.setTimeout(function() {
+ this.inputter.focus();
+ }.bind(this), 10);
+};
+
+/**
+ * Called by the Inputter when the text changes
+ */
+Tooltip.prototype.textChanged = function() {
+ // We get here for minor things like whitespace change in arg prefix,
+ // so we ignore anything but an actual value change.
+ if (this.assignment.arg.text === this.lastText) {
+ return;
+ }
+
+ this.lastText = this.assignment.arg.text;
+
+ this.field.setConversion(this.assignment.conversion);
+ util.setTextContent(this.descriptionEle, this.description);
+
+ this._updatePosition();
+};
+
+/**
+ * Called to move the tooltip to the correct horizontal position
+ */
+Tooltip.prototype._updatePosition = function() {
+ var dimensions = this.getDimensionsOfAssignment();
+
+ // 10 is roughly the width of a char
+ if (this.panelElement) {
+ this.panelElement.style.left = (dimensions.start * 10) + 'px';
+ }
+
+ this.focusManager.updatePosition(dimensions);
+};
+
+/**
+ * Returns a object containing 'start' and 'end' properties which identify the
+ * number of pixels from the left hand edge of the input element that represent
+ * the text portion of the current assignment.
+ */
+Tooltip.prototype.getDimensionsOfAssignment = function() {
+ var before = '';
+ var assignments = this.requisition.getAssignments(true);
+ for (var i = 0; i < assignments.length; i++) {
+ if (assignments[i] === this.assignment) {
+ break;
+ }
+ before += assignments[i].toString();
+ }
+ before += this.assignment.arg.prefix;
+
+ var startChar = before.length;
+ before += this.assignment.arg.text;
+ var endChar = before.length;
+
+ return { start: startChar, end: endChar };
+};
+
+/**
+ * The description (displayed at the top of the hint area) should be blank if
+ * we're entering the CommandAssignment (because it's obvious) otherwise it's
+ * the parameter description.
+ */
+Object.defineProperty(Tooltip.prototype, 'description', {
+ get: function() {
+ if (this.assignment instanceof CommandAssignment &&
+ this.assignment.value == null) {
+ return '';
+ }
+
+ return this.assignment.param.manual || this.assignment.param.description;
+ },
+ enumerable: true
+});
+
+/**
+ * Tweak CSS to show/hide the output
+ */
+Tooltip.prototype.visibilityChanged = function(ev) {
+ if (!this.panelElement) {
+ return;
+ }
+
+ if (ev.tooltipVisible) {
+ this.panelElement.classList.remove('gcli-panel-hide');
+ }
+ else {
+ this.panelElement.classList.add('gcli-panel-hide');
+ }
+};
+
+exports.Tooltip = Tooltip;
diff --git a/devtools/shared/gcli/source/lib/gcli/settings.js b/devtools/shared/gcli/source/lib/gcli/settings.js
new file mode 100644
index 000000000..29e608cbd
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/settings.js
@@ -0,0 +1,284 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var imports = {};
+
+var Cc = require('chrome').Cc;
+var Ci = require('chrome').Ci;
+var Cu = require('chrome').Cu;
+
+var XPCOMUtils = Cu.import('resource://gre/modules/XPCOMUtils.jsm', {}).XPCOMUtils;
+var Services = require("Services");
+
+XPCOMUtils.defineLazyGetter(imports, 'prefBranch', function() {
+ var prefService = Cc['@mozilla.org/preferences-service;1']
+ .getService(Ci.nsIPrefService);
+ return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+});
+
+XPCOMUtils.defineLazyGetter(imports, 'supportsString', function() {
+ return Cc['@mozilla.org/supports-string;1']
+ .createInstance(Ci.nsISupportsString);
+});
+
+var util = require('./util/util');
+
+/**
+ * All local settings have this prefix when used in Firefox
+ */
+var DEVTOOLS_PREFIX = 'devtools.gcli.';
+
+/**
+ * A manager for the registered Settings
+ */
+function Settings(types, settingValues) {
+ this._types = types;
+
+ if (settingValues != null) {
+ throw new Error('settingValues is not supported when writing to prefs');
+ }
+
+ // Collection of preferences for sorted access
+ this._settingsAll = [];
+
+ // Collection of preferences for fast indexed access
+ this._settingsMap = new Map();
+
+ // Flag so we know if we've read the system preferences
+ this._hasReadSystem = false;
+
+ // Event for use to detect when the list of settings changes
+ this.onChange = util.createEvent('Settings.onChange');
+}
+
+/**
+ * Load system prefs if they've not been loaded already
+ * @return true
+ */
+Settings.prototype._readSystem = function() {
+ if (this._hasReadSystem) {
+ return;
+ }
+
+ imports.prefBranch.getChildList('').forEach(function(name) {
+ var setting = new Setting(this, name);
+ this._settingsAll.push(setting);
+ this._settingsMap.set(name, setting);
+ }.bind(this));
+
+ this._settingsAll.sort(function(s1, s2) {
+ return s1.name.localeCompare(s2.name);
+ }.bind(this));
+
+ this._hasReadSystem = true;
+};
+
+/**
+ * Get an array containing all known Settings filtered to match the given
+ * filter (string) at any point in the name of the setting
+ */
+Settings.prototype.getAll = function(filter) {
+ this._readSystem();
+
+ if (filter == null) {
+ return this._settingsAll;
+ }
+
+ return this._settingsAll.filter(function(setting) {
+ return setting.name.indexOf(filter) !== -1;
+ }.bind(this));
+};
+
+/**
+ * Add a new setting
+ */
+Settings.prototype.add = function(prefSpec) {
+ var setting = new Setting(this, prefSpec);
+
+ if (this._settingsMap.has(setting.name)) {
+ // Once exists already, we're going to need to replace it in the array
+ for (var i = 0; i < this._settingsAll.length; i++) {
+ if (this._settingsAll[i].name === setting.name) {
+ this._settingsAll[i] = setting;
+ }
+ }
+ }
+
+ this._settingsMap.set(setting.name, setting);
+ this.onChange({ added: setting.name });
+
+ return setting;
+};
+
+/**
+ * Getter for an existing setting. Generally use of this function should be
+ * avoided. Systems that define a setting should export it if they wish it to
+ * be available to the outside, or not otherwise. Use of this function breaks
+ * that boundary and also hides dependencies. Acceptable uses include testing
+ * and embedded uses of GCLI that pre-define all settings (e.g. Firefox)
+ * @param name The name of the setting to fetch
+ * @return The found Setting object, or undefined if the setting was not found
+ */
+Settings.prototype.get = function(name) {
+ // We might be able to give the answer without needing to read all system
+ // settings if this is an internal setting
+ var found = this._settingsMap.get(name);
+ if (!found) {
+ found = this._settingsMap.get(DEVTOOLS_PREFIX + name);
+ }
+
+ if (found) {
+ return found;
+ }
+
+ if (this._hasReadSystem) {
+ return undefined;
+ }
+ else {
+ this._readSystem();
+ found = this._settingsMap.get(name);
+ if (!found) {
+ found = this._settingsMap.get(DEVTOOLS_PREFIX + name);
+ }
+ return found;
+ }
+};
+
+/**
+ * Remove a setting. A no-op in this case
+ */
+Settings.prototype.remove = function() {
+};
+
+exports.Settings = Settings;
+
+/**
+ * A class to wrap up the properties of a Setting.
+ * @see toolkit/components/viewconfig/content/config.js
+ */
+function Setting(settings, prefSpec) {
+ this._settings = settings;
+ if (typeof prefSpec === 'string') {
+ // We're coming from getAll() i.e. a full listing of prefs
+ this.name = prefSpec;
+ this.description = '';
+ }
+ else {
+ // A specific addition by GCLI
+ this.name = DEVTOOLS_PREFIX + prefSpec.name;
+
+ if (prefSpec.ignoreTypeDifference !== true && prefSpec.type) {
+ if (this.type.name !== prefSpec.type) {
+ throw new Error('Locally declared type (' + prefSpec.type + ') != ' +
+ 'Mozilla declared type (' + this.type.name + ') for ' + this.name);
+ }
+ }
+
+ this.description = prefSpec.description;
+ }
+
+ this.onChange = util.createEvent('Setting.onChange');
+}
+
+/**
+ * Reset this setting to it's initial default value
+ */
+Setting.prototype.setDefault = function() {
+ imports.prefBranch.clearUserPref(this.name);
+ Services.prefs.savePrefFile(null);
+};
+
+/**
+ * What type is this property: boolean/integer/string?
+ */
+Object.defineProperty(Setting.prototype, 'type', {
+ get: function() {
+ switch (imports.prefBranch.getPrefType(this.name)) {
+ case imports.prefBranch.PREF_BOOL:
+ return this._settings._types.createType('boolean');
+
+ case imports.prefBranch.PREF_INT:
+ return this._settings._types.createType('number');
+
+ case imports.prefBranch.PREF_STRING:
+ return this._settings._types.createType('string');
+
+ default:
+ throw new Error('Unknown type for ' + this.name);
+ }
+ },
+ enumerable: true
+});
+
+/**
+ * What type is this property: boolean/integer/string?
+ */
+Object.defineProperty(Setting.prototype, 'value', {
+ get: function() {
+ switch (imports.prefBranch.getPrefType(this.name)) {
+ case imports.prefBranch.PREF_BOOL:
+ return imports.prefBranch.getBoolPref(this.name);
+
+ case imports.prefBranch.PREF_INT:
+ return imports.prefBranch.getIntPref(this.name);
+
+ case imports.prefBranch.PREF_STRING:
+ var value = imports.prefBranch.getComplexValue(this.name,
+ Ci.nsISupportsString).data;
+ // In case of a localized string
+ if (/^chrome:\/\/.+\/locale\/.+\.properties/.test(value)) {
+ value = imports.prefBranch.getComplexValue(this.name,
+ Ci.nsIPrefLocalizedString).data;
+ }
+ return value;
+
+ default:
+ throw new Error('Invalid value for ' + this.name);
+ }
+ },
+
+ set: function(value) {
+ if (imports.prefBranch.prefIsLocked(this.name)) {
+ throw new Error('Locked preference ' + this.name);
+ }
+
+ switch (imports.prefBranch.getPrefType(this.name)) {
+ case imports.prefBranch.PREF_BOOL:
+ imports.prefBranch.setBoolPref(this.name, value);
+ break;
+
+ case imports.prefBranch.PREF_INT:
+ imports.prefBranch.setIntPref(this.name, value);
+ break;
+
+ case imports.prefBranch.PREF_STRING:
+ imports.supportsString.data = value;
+ imports.prefBranch.setComplexValue(this.name,
+ Ci.nsISupportsString,
+ imports.supportsString);
+ break;
+
+ default:
+ throw new Error('Invalid value for ' + this.name);
+ }
+
+ Services.prefs.savePrefFile(null);
+ },
+
+ enumerable: true
+});
diff --git a/devtools/shared/gcli/source/lib/gcli/system.js b/devtools/shared/gcli/source/lib/gcli/system.js
new file mode 100644
index 000000000..5a4719b8d
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/system.js
@@ -0,0 +1,370 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('./util/util');
+var Commands = require('./commands/commands').Commands;
+var Connectors = require('./connectors/connectors').Connectors;
+var Converters = require('./converters/converters').Converters;
+var Fields = require('./fields/fields').Fields;
+var Languages = require('./languages/languages').Languages;
+var Settings = require('./settings').Settings;
+var Types = require('./types/types').Types;
+
+/**
+ * This is the heart of the API that we expose to the outside.
+ * @param options Object that customizes how the system acts. Valid properties:
+ * - commands, connectors, converters, fields, languages, settings, types:
+ * Custom configured manager objects for these item types
+ * - location: a system with a location will ignore commands that don't have a
+ * matching runAt property. This is principly for client/server setups where
+ * we import commands from the server to the client, so a system with
+ * `{ location: 'client' }` will silently ignore commands with
+ * `{ runAt: 'server' }`. Any system without a location will accept commands
+ * with any runAt property (including none).
+ */
+exports.createSystem = function(options) {
+ options = options || {};
+ var location = options.location;
+
+ // The plural/singular thing may make you want to scream, but it allows us
+ // to say components[getItemType(item)], so a lookup here (and below) saves
+ // multiple lookups in the middle of the code
+ var components = {
+ connector: options.connectors || new Connectors(),
+ converter: options.converters || new Converters(),
+ field: options.fields || new Fields(),
+ language: options.languages || new Languages(),
+ type: options.types || new Types()
+ };
+ components.setting = new Settings(components.type);
+ components.command = new Commands(components.type, location);
+
+ var getItemType = function(item) {
+ if (item.item) {
+ return item.item;
+ }
+ // Some items are registered using the constructor so we need to check
+ // the prototype for the the type of the item
+ return (item.prototype && item.prototype.item) ?
+ item.prototype.item : 'command';
+ };
+
+ var addItem = function(item) {
+ try {
+ components[getItemType(item)].add(item);
+ }
+ catch (ex) {
+ if (item != null) {
+ console.error('While adding: ' + item.name);
+ }
+ throw ex;
+ }
+ };
+
+ var removeItem = function(item) {
+ components[getItemType(item)].remove(item);
+ };
+
+ /**
+ * loadableModules is a lookup of names to module loader functions (like
+ * the venerable 'require') to which we can pass a name and get back a
+ * JS object (or a promise of a JS object). This allows us to have custom
+ * loaders to get stuff from the filesystem etc.
+ */
+ var loadableModules = {};
+
+ /**
+ * loadedModules is a lookup by name of the things returned by the functions
+ * in loadableModules so we can track what we need to unload / reload.
+ */
+ var loadedModules = {};
+
+ var unloadModule = function(name) {
+ var existingModule = loadedModules[name];
+ if (existingModule != null) {
+ existingModule.items.forEach(removeItem);
+ }
+ delete loadedModules[name];
+ };
+
+ var loadModule = function(name) {
+ var existingModule = loadedModules[name];
+ unloadModule(name);
+
+ // And load the new items
+ try {
+ var loader = loadableModules[name];
+ return Promise.resolve(loader(name)).then(function(newModule) {
+ if (existingModule === newModule) {
+ return;
+ }
+
+ if (newModule == null) {
+ throw 'Module \'' + name + '\' not found';
+ }
+
+ if (newModule.items == null || typeof newModule.items.forEach !== 'function') {
+ console.log('Exported properties: ' + Object.keys(newModule).join(', '));
+ throw 'Module \'' + name + '\' has no \'items\' array export';
+ }
+
+ newModule.items.forEach(addItem);
+
+ loadedModules[name] = newModule;
+ });
+ }
+ catch (ex) {
+ console.error('Failed to load module ' + name + ': ' + ex);
+ console.error(ex.stack);
+
+ return Promise.resolve();
+ }
+ };
+
+ var pendingChanges = false;
+
+ var system = {
+ addItems: function(items) {
+ items.forEach(addItem);
+ },
+
+ removeItems: function(items) {
+ items.forEach(removeItem);
+ },
+
+ addItemsByModule: function(names, options) {
+ var promises = [];
+
+ options = options || {};
+ if (!options.delayedLoad) {
+ // We could be about to add many commands, just report the change once
+ this.commands.onCommandsChange.holdFire();
+ }
+
+ if (typeof names === 'string') {
+ names = [ names ];
+ }
+ names.forEach(function(name) {
+ if (options.loader == null) {
+ options.loader = function(name) {
+ return require(name);
+ };
+ }
+ loadableModules[name] = options.loader;
+
+ if (options.delayedLoad) {
+ pendingChanges = true;
+ }
+ else {
+ promises.push(loadModule(name).catch(console.error));
+ }
+ });
+
+ if (options.delayedLoad) {
+ return Promise.resolve();
+ }
+ else {
+ return Promise.all(promises).then(function() {
+ this.commands.onCommandsChange.resumeFire();
+ }.bind(this));
+ }
+ },
+
+ removeItemsByModule: function(name) {
+ this.commands.onCommandsChange.holdFire();
+
+ delete loadableModules[name];
+ unloadModule(name);
+
+ this.commands.onCommandsChange.resumeFire();
+ },
+
+ load: function() {
+ if (!pendingChanges) {
+ return Promise.resolve();
+ }
+ this.commands.onCommandsChange.holdFire();
+
+ // clone loadedModules, so we can remove what is left at the end
+ var modules = Object.keys(loadedModules).map(function(name) {
+ return loadedModules[name];
+ });
+
+ var promises = Object.keys(loadableModules).map(function(name) {
+ delete modules[name];
+ return loadModule(name).catch(console.error);
+ });
+
+ Object.keys(modules).forEach(unloadModule);
+ pendingChanges = false;
+
+ return Promise.all(promises).then(function() {
+ this.commands.onCommandsChange.resumeFire();
+ }.bind(this));
+ },
+
+ destroy: function() {
+ this.commands.onCommandsChange.holdFire();
+
+ Object.keys(loadedModules).forEach(function(name) {
+ unloadModule(name);
+ });
+
+ this.commands.onCommandsChange.resumeFire();
+ },
+
+ toString: function() {
+ return 'System [' +
+ 'commands:' + components.command.getAll().length + ', ' +
+ 'connectors:' + components.connector.getAll().length + ', ' +
+ 'converters:' + components.converter.getAll().length + ', ' +
+ 'fields:' + components.field.getAll().length + ', ' +
+ 'settings:' + components.setting.getAll().length + ', ' +
+ 'types:' + components.type.getTypeNames().length + ']';
+ }
+ };
+
+ Object.defineProperty(system, 'commands', {
+ get: function() { return components.command; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'connectors', {
+ get: function() { return components.connector; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'converters', {
+ get: function() { return components.converter; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'fields', {
+ get: function() { return components.field; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'languages', {
+ get: function() { return components.language; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'settings', {
+ get: function() { return components.setting; },
+ enumerable: true
+ });
+
+ Object.defineProperty(system, 'types', {
+ get: function() { return components.type; },
+ enumerable: true
+ });
+
+ return system;
+};
+
+/**
+ * Connect a local system with another at the other end of a connector
+ * @param system System to which we're adding commands
+ * @param front Front which allows access to the remote system from which we
+ * import commands
+ * @param customProps Array of strings specifying additional properties defined
+ * on remote commands that should be considered part of the metadata for the
+ * commands imported into the local system
+ */
+exports.connectFront = function(system, front, customProps) {
+ system._handleCommandsChanged = function() {
+ syncItems(system, front, customProps).catch(util.errorHandler);
+ };
+ front.on('commands-changed', system._handleCommandsChanged);
+
+ return syncItems(system, front, customProps);
+};
+
+/**
+ * Undo the effect of #connectFront
+ */
+exports.disconnectFront = function(system, front) {
+ front.off('commands-changed', system._handleCommandsChanged);
+ system._handleCommandsChanged = undefined;
+ removeItemsFromFront(system, front);
+};
+
+/**
+ * Remove the items in this system that came from a previous sync action, and
+ * re-add them. See connectFront() for explanation of properties
+ */
+function syncItems(system, front, customProps) {
+ return front.specs(customProps).then(function(specs) {
+ removeItemsFromFront(system, front);
+
+ var remoteItems = addLocalFunctions(specs, front);
+ system.addItems(remoteItems);
+
+ return system;
+ });
+};
+
+/**
+ * Take the data from the 'specs' command (or the 'commands-changed' event) and
+ * add function to proxy the execution back over the front
+ */
+function addLocalFunctions(specs, front) {
+ // Inject an 'exec' function into the commands, and the front into
+ // all the remote types
+ specs.forEach(function(commandSpec) {
+ // HACK: Tack the front to the command so we know how to remove it
+ // in removeItemsFromFront() below
+ commandSpec.front = front;
+
+ // Tell the type instances for a command how to contact their counterparts
+ // Don't confuse this with setting the front on the commandSpec which is
+ // about associating a proxied command with it's source for later removal.
+ // This is actually going to be used by the type
+ commandSpec.params.forEach(function(param) {
+ if (typeof param.type !== 'string') {
+ param.type.front = front;
+ }
+ });
+
+ if (!commandSpec.isParent) {
+ commandSpec.exec = function(args, context) {
+ var typed = (context.prefix ? context.prefix + ' ' : '') + context.typed;
+ return front.execute(typed).then(function(reply) {
+ var typedData = context.typedData(reply.type, reply.data);
+ return reply.isError ? Promise.reject(typedData) : typedData;
+ });
+ };
+ }
+
+ commandSpec.isProxy = true;
+ });
+
+ return specs;
+}
+
+/**
+ * Go through all the commands removing any that are associated with the
+ * given front. The method of association is the hack in addLocalFunctions.
+ */
+function removeItemsFromFront(system, front) {
+ system.commands.getAll().forEach(function(command) {
+ if (command.front === front) {
+ system.commands.remove(command);
+ }
+ });
+}
diff --git a/devtools/shared/gcli/source/lib/gcli/types/array.js b/devtools/shared/gcli/source/lib/gcli/types/array.js
new file mode 100644
index 000000000..381bd0b80
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/array.js
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var ArrayConversion = require('./types').ArrayConversion;
+var ArrayArgument = require('./types').ArrayArgument;
+
+exports.items = [
+ {
+ // A set of objects of the same type
+ item: 'type',
+ name: 'array',
+ subtype: undefined,
+
+ constructor: function() {
+ if (!this.subtype) {
+ console.error('Array.typeSpec is missing subtype. Assuming string.' +
+ this.name);
+ this.subtype = 'string';
+ }
+ this.subtype = this.types.createType(this.subtype);
+ },
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'array',
+ subtype: this.subtype.getSpec(commandName, paramName),
+ };
+ },
+
+ stringify: function(values, context) {
+ if (values == null) {
+ return '';
+ }
+ // BUG 664204: Check for strings with spaces and add quotes
+ return values.join(' ');
+ },
+
+ parse: function(arg, context) {
+ if (arg.type !== 'ArrayArgument') {
+ console.error('non ArrayArgument to ArrayType.parse', arg);
+ throw new Error('non ArrayArgument to ArrayType.parse');
+ }
+
+ // Parse an argument to a conversion
+ // Hack alert. ArrayConversion needs to be able to answer questions about
+ // the status of individual conversions in addition to the overall state.
+ // |subArg.conversion| allows us to do that easily.
+ var subArgParse = function(subArg) {
+ return this.subtype.parse(subArg, context).then(function(conversion) {
+ subArg.conversion = conversion;
+ return conversion;
+ }.bind(this));
+ }.bind(this);
+
+ var conversionPromises = arg.getArguments().map(subArgParse);
+ return Promise.all(conversionPromises).then(function(conversions) {
+ return new ArrayConversion(conversions, arg);
+ });
+ },
+
+ getBlank: function(context) {
+ return new ArrayConversion([], new ArrayArgument());
+ }
+ },
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/boolean.js b/devtools/shared/gcli/source/lib/gcli/types/boolean.js
new file mode 100644
index 000000000..01f5f5022
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/boolean.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+var BlankArgument = require('./types').BlankArgument;
+var SelectionType = require('./selection').SelectionType;
+
+exports.items = [
+ {
+ // 'boolean' type
+ item: 'type',
+ name: 'boolean',
+ parent: 'selection',
+
+ getSpec: function() {
+ return 'boolean';
+ },
+
+ lookup: [
+ { name: 'false', value: false },
+ { name: 'true', value: true }
+ ],
+
+ parse: function(arg, context) {
+ if (arg.type === 'TrueNamedArgument') {
+ return Promise.resolve(new Conversion(true, arg));
+ }
+ if (arg.type === 'FalseNamedArgument') {
+ return Promise.resolve(new Conversion(false, arg));
+ }
+ return SelectionType.prototype.parse.call(this, arg, context);
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return '' + value;
+ },
+
+ getBlank: function(context) {
+ return new Conversion(false, new BlankArgument(), Status.VALID, '',
+ Promise.resolve(this.lookup));
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/command.js b/devtools/shared/gcli/source/lib/gcli/types/command.js
new file mode 100644
index 000000000..779aa77ab
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/command.js
@@ -0,0 +1,255 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var spell = require('../util/spell');
+var SelectionType = require('./selection').SelectionType;
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+var cli = require('../cli');
+
+exports.items = [
+ {
+ // Select from the available parameters to a command
+ item: 'type',
+ name: 'param',
+ parent: 'selection',
+ stringifyProperty: 'name',
+ requisition: undefined,
+ isIncompleteName: undefined,
+
+ getSpec: function() {
+ throw new Error('param type is not remotable');
+ },
+
+ lookup: function() {
+ return exports.getDisplayedParamLookup(this.requisition);
+ },
+
+ parse: function(arg, context) {
+ if (this.isIncompleteName) {
+ return SelectionType.prototype.parse.call(this, arg, context);
+ }
+ else {
+ var message = l10n.lookup('cliUnusedArg');
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, message));
+ }
+ }
+ },
+ {
+ // Select from the available commands
+ // This is very similar to a SelectionType, however the level of hackery in
+ // SelectionType to make it handle Commands correctly was to high, so we
+ // simplified.
+ // If you are making changes to this code, you should check there too.
+ item: 'type',
+ name: 'command',
+ parent: 'selection',
+ stringifyProperty: 'name',
+ allowNonExec: true,
+
+ getSpec: function() {
+ return {
+ name: 'command',
+ allowNonExec: this.allowNonExec
+ };
+ },
+
+ lookup: function(context) {
+ var commands = cli.getMapping(context).requisition.system.commands;
+ return exports.getCommandLookup(commands);
+ },
+
+ parse: function(arg, context) {
+ var conversion = exports.parse(context, arg, this.allowNonExec);
+ return Promise.resolve(conversion);
+ }
+ }
+];
+
+exports.getDisplayedParamLookup = function(requisition) {
+ var displayedParams = [];
+ var command = requisition.commandAssignment.value;
+ if (command != null) {
+ command.params.forEach(function(param) {
+ var arg = requisition.getAssignment(param.name).arg;
+ if (!param.isPositionalAllowed && arg.type === 'BlankArgument') {
+ displayedParams.push({ name: '--' + param.name, value: param });
+ }
+ });
+ }
+ return displayedParams;
+};
+
+exports.parse = function(context, arg, allowNonExec) {
+ var commands = cli.getMapping(context).requisition.system.commands;
+ var lookup = exports.getCommandLookup(commands);
+ var predictions = exports.findPredictions(arg, lookup);
+ return exports.convertPredictions(commands, arg, allowNonExec, predictions);
+};
+
+exports.getCommandLookup = function(commands) {
+ var sorted = commands.getAll().sort(function(c1, c2) {
+ return c1.name.localeCompare(c2.name);
+ });
+ return sorted.map(function(command) {
+ return { name: command.name, value: command };
+ });
+};
+
+exports.findPredictions = function(arg, lookup) {
+ var predictions = [];
+ var i, option;
+ var maxPredictions = Conversion.maxPredictions;
+ var match = arg.text.toLowerCase();
+
+ // Add an option to our list of predicted options
+ var addToPredictions = function(option) {
+ if (arg.text.length === 0) {
+ // If someone hasn't typed anything, we only show top level commands in
+ // the menu. i.e. sub-commands (those with a space in their name) are
+ // excluded. We do this to keep the list at an overview level.
+ if (option.name.indexOf(' ') === -1) {
+ predictions.push(option);
+ }
+ }
+ else {
+ // If someone has typed something, then we exclude parent commands
+ // (those without an exec). We do this because the user is drilling
+ // down and doesn't need the summary level.
+ if (option.value.exec != null) {
+ predictions.push(option);
+ }
+ }
+ };
+
+ // If the arg has a suffix then we're kind of 'done'. Only an exact
+ // match will do.
+ if (arg.suffix.match(/ +/)) {
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option.name === arg.text ||
+ option.name.indexOf(arg.text + ' ') === 0) {
+ addToPredictions(option);
+ }
+ }
+
+ return predictions;
+ }
+
+ // Cache lower case versions of all the option names
+ for (i = 0; i < lookup.length; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName == null) {
+ option._gcliLowerName = option.name.toLowerCase();
+ }
+ }
+
+ // Exact hidden matches. If 'hidden: true' then we only allow exact matches
+ // All the tests after here check that !option.value.hidden
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option.name === arg.text) {
+ addToPredictions(option);
+ }
+ }
+
+ // Start with prefix matching
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName.indexOf(match) === 0 && !option.value.hidden) {
+ if (predictions.indexOf(option) === -1) {
+ addToPredictions(option);
+ }
+ }
+ }
+
+ // Try infix matching if we get less half max matched
+ if (predictions.length < (maxPredictions / 2)) {
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName.indexOf(match) !== -1 && !option.value.hidden) {
+ if (predictions.indexOf(option) === -1) {
+ addToPredictions(option);
+ }
+ }
+ }
+ }
+
+ // Try fuzzy matching if we don't get a prefix match
+ if (predictions.length === 0) {
+ var names = [];
+ lookup.forEach(function(opt) {
+ if (!opt.value.hidden) {
+ names.push(opt.name);
+ }
+ });
+ var corrected = spell.correct(match, names);
+ if (corrected) {
+ lookup.forEach(function(opt) {
+ if (opt.name === corrected) {
+ predictions.push(opt);
+ }
+ });
+ }
+ }
+
+ return predictions;
+};
+
+exports.convertPredictions = function(commands, arg, allowNonExec, predictions) {
+ var command = commands.get(arg.text);
+ // Helper function - Commands like 'context' work best with parent
+ // commands which are not executable. However obviously to execute a
+ // command, it needs an exec function.
+ var execWhereNeeded = (allowNonExec ||
+ (command != null && typeof command.exec === 'function'));
+
+ var isExact = command && command.name === arg.text &&
+ execWhereNeeded && predictions.length === 1;
+ var alternatives = isExact ? [] : predictions;
+
+ if (command) {
+ var status = execWhereNeeded ? Status.VALID : Status.INCOMPLETE;
+ return new Conversion(command, arg, status, '', alternatives);
+ }
+
+ if (predictions.length === 0) {
+ var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
+ return new Conversion(undefined, arg, Status.ERROR, msg, alternatives);
+ }
+
+ command = predictions[0].value;
+
+ if (predictions.length === 1) {
+ // Is it an exact match of an executable command,
+ // or just the only possibility?
+ if (command.name === arg.text && execWhereNeeded) {
+ return new Conversion(command, arg, Status.VALID, '');
+ }
+
+ return new Conversion(undefined, arg, Status.INCOMPLETE, '', alternatives);
+ }
+
+ // It's valid if the text matches, even if there are several options
+ if (predictions[0].name === arg.text) {
+ return new Conversion(command, arg, Status.VALID, '', alternatives);
+ }
+
+ return new Conversion(undefined, arg, Status.INCOMPLETE, '', alternatives);
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/types/date.js b/devtools/shared/gcli/source/lib/gcli/types/date.js
new file mode 100644
index 000000000..f05569724
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/date.js
@@ -0,0 +1,248 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+
+/**
+ * Helper for stringify() to left pad a single digit number with a single '0'
+ * so 1 -> '01', 42 -> '42', etc.
+ */
+function pad(number) {
+ var r = String(number);
+ return r.length === 1 ? '0' + r : r;
+}
+
+/**
+ * Utility to convert a string to a date, throwing if the date can't be
+ * parsed rather than having an invalid date
+ */
+function toDate(str) {
+ var millis = Date.parse(str);
+ if (isNaN(millis)) {
+ throw new Error(l10n.lookupFormat('typesDateNan', [ str ]));
+ }
+ return new Date(millis);
+}
+
+/**
+ * Is |thing| a valid date?
+ * @see http://stackoverflow.com/questions/1353684/detecting-an-invalid-date-date-instance-in-javascript
+ */
+function isDate(thing) {
+ return Object.prototype.toString.call(thing) === '[object Date]'
+ && !isNaN(thing.getTime());
+}
+
+exports.items = [
+ {
+ // ECMA 5.1 §15.9.1.1
+ // @see http://stackoverflow.com/questions/11526504/minimum-and-maximum-date
+ item: 'type',
+ name: 'date',
+ step: 1,
+ min: new Date(-8640000000000000),
+ max: new Date(8640000000000000),
+
+ constructor: function() {
+ this._origMin = this.min;
+ if (this.min != null) {
+ if (typeof this.min === 'string') {
+ this.min = toDate(this.min);
+ }
+ else if (isDate(this.min) || typeof this.min === 'function') {
+ this.min = this.min;
+ }
+ else {
+ throw new Error('date min value must be one of string/date/function');
+ }
+ }
+
+ this._origMax = this.max;
+ if (this.max != null) {
+ if (typeof this.max === 'string') {
+ this.max = toDate(this.max);
+ }
+ else if (isDate(this.max) || typeof this.max === 'function') {
+ this.max = this.max;
+ }
+ else {
+ throw new Error('date max value must be one of string/date/function');
+ }
+ }
+ },
+
+ getSpec: function() {
+ var spec = {
+ name: 'date'
+ };
+ if (this.step !== 1) {
+ spec.step = this.step;
+ }
+ if (this._origMax != null) {
+ spec.max = this._origMax;
+ }
+ if (this._origMin != null) {
+ spec.min = this._origMin;
+ }
+ return spec;
+ },
+
+ stringify: function(value, context) {
+ if (!isDate(value)) {
+ return '';
+ }
+
+ var str = pad(value.getFullYear()) + '-' +
+ pad(value.getMonth() + 1) + '-' +
+ pad(value.getDate());
+
+ // Only add in the time if it's not midnight
+ if (value.getHours() !== 0 || value.getMinutes() !== 0 ||
+ value.getSeconds() !== 0 || value.getMilliseconds() !== 0) {
+
+ // What string should we use to separate the date from the time?
+ // There are 3 options:
+ // 'T': This is the standard from ISO8601. i.e. 2013-05-20T11:05
+ // The good news - it's a standard. The bad news - it's weird and
+ // alien to many if not most users
+ // ' ': This looks nicest, but needs escaping (which GCLI will do
+ // automatically) so it would look like: '2013-05-20 11:05'
+ // Good news: looks best, bad news: on completion we place the
+ // cursor after the final ', breaking repeated increment/decrement
+ // '\ ': It's possible that we could find a way to use a \ to escape
+ // the space, so the output would look like: 2013-05-20\ 11:05
+ // This would involve changes to a number of parts, and is
+ // probably too complex a solution for this problem for now
+ // In the short term I'm going for ' ', and raising the priority of
+ // cursor positioning on actions like increment/decrement/tab.
+
+ str += ' ' + pad(value.getHours());
+ str += ':' + pad(value.getMinutes());
+
+ // Only add in seconds/milliseconds if there is anything to report
+ if (value.getSeconds() !== 0 || value.getMilliseconds() !== 0) {
+ str += ':' + pad(value.getSeconds());
+ if (value.getMilliseconds() !== 0) {
+ var milliVal = (value.getUTCMilliseconds() / 1000).toFixed(3);
+ str += '.' + String(milliVal).slice(2, 5);
+ }
+ }
+ }
+
+ return str;
+ },
+
+ getMax: function(context) {
+ if (typeof this.max === 'function') {
+ return this._max(context);
+ }
+ if (isDate(this.max)) {
+ return this.max;
+ }
+ return undefined;
+ },
+
+ getMin: function(context) {
+ if (typeof this.min === 'function') {
+ return this._min(context);
+ }
+ if (isDate(this.min)) {
+ return this.min;
+ }
+ return undefined;
+ },
+
+ parse: function(arg, context) {
+ var value;
+
+ if (arg.text.replace(/\s/g, '').length === 0) {
+ return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
+ }
+
+ // Lots of room for improvement here: 1h ago, in two days, etc.
+ // Should "1h ago" dynamically update the step?
+ if (arg.text.toLowerCase() === 'now' ||
+ arg.text.toLowerCase() === 'today') {
+ value = new Date();
+ }
+ else if (arg.text.toLowerCase() === 'yesterday') {
+ value = new Date();
+ value.setDate(value.getDate() - 1);
+ }
+ else if (arg.text.toLowerCase() === 'tomorrow') {
+ value = new Date();
+ value.setDate(value.getDate() + 1);
+ }
+ else {
+ // So now actual date parsing.
+ // Javascript dates are a mess. Like the default date libraries in most
+ // common languages, but with added browser weirdness.
+ // There is an argument for saying that the user will expect dates to
+ // be formatted as JavaScript dates, except that JS dates are of
+ // themselves very unexpected.
+ // See http://blog.dygraphs.com/2012/03/javascript-and-dates-what-mess.html
+
+ // The timezone used by Date.parse depends on whether or not the string
+ // can be interpreted as ISO-8601, so "2000-01-01" is not the same as
+ // "2000/01/01" (unless your TZ aligns with UTC) because the first is
+ // ISO-8601 and therefore assumed to be UTC, where the latter is
+ // assumed to be in the local timezone.
+
+ // First, if the user explicitly includes a 'Z' timezone marker, then
+ // we assume they know what they are doing with timezones. ISO-8601
+ // uses 'Z' as a marker for 'Zulu time', zero hours offset i.e. UTC
+ if (arg.text.indexOf('Z') !== -1) {
+ value = new Date(arg.text);
+ }
+ else {
+ // Now we don't want the browser to assume ISO-8601 and therefore use
+ // UTC so we replace the '-' with '/'
+ value = new Date(arg.text.replace(/-/g, '/'));
+ }
+
+ if (isNaN(value.getTime())) {
+ var msg = l10n.lookupFormat('typesDateNan', [ arg.text ]);
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+ }
+ }
+
+ return Promise.resolve(new Conversion(value, arg));
+ },
+
+ nudge: function(value, by, context) {
+ if (!isDate(value)) {
+ return new Date();
+ }
+
+ var newValue = new Date(value);
+ newValue.setDate(value.getDate() + (by * this.step));
+
+ if (newValue < this.getMin(context)) {
+ return this.getMin(context);
+ }
+ else if (newValue > this.getMax(context)) {
+ return this.getMax();
+ }
+ else {
+ return newValue;
+ }
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/delegate.js b/devtools/shared/gcli/source/lib/gcli/types/delegate.js
new file mode 100644
index 000000000..978718231
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/delegate.js
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Conversion = require('./types').Conversion;
+var Status = require('./types').Status;
+var BlankArgument = require('./types').BlankArgument;
+
+/**
+ * The types we expose for registration
+ */
+exports.items = [
+ // A type for "we don't know right now, but hope to soon"
+ {
+ item: 'type',
+ name: 'delegate',
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'delegate',
+ param: paramName
+ };
+ },
+
+ // Child types should implement this method to return an instance of the type
+ // that should be used. If no type is available, or some sort of temporary
+ // placeholder is required, BlankType can be used.
+ delegateType: undefined,
+
+ stringify: function(value, context) {
+ return this.getType(context).then(function(delegated) {
+ return delegated.stringify(value, context);
+ }.bind(this));
+ },
+
+ parse: function(arg, context) {
+ return this.getType(context).then(function(delegated) {
+ return delegated.parse(arg, context);
+ }.bind(this));
+ },
+
+ nudge: function(value, by, context) {
+ return this.getType(context).then(function(delegated) {
+ return delegated.nudge ?
+ delegated.nudge(value, by, context) :
+ undefined;
+ }.bind(this));
+ },
+
+ getType: function(context) {
+ if (this.delegateType === undefined) {
+ return Promise.resolve(this.types.createType('blank'));
+ }
+
+ var type = this.delegateType(context);
+ if (typeof type.parse !== 'function') {
+ type = this.types.createType(type);
+ }
+ return Promise.resolve(type);
+ },
+
+ // DelegateType is designed to be inherited from, so DelegateField needs a
+ // way to check if something works like a delegate without using 'name'
+ isDelegate: true,
+
+ // Technically we perhaps should proxy this, except that properties are
+ // inherently synchronous, so we can't. It doesn't seem important enough to
+ // change the function definition to accommodate this right now
+ isImportant: false
+ },
+ {
+ item: 'type',
+ name: 'remote',
+ paramName: undefined,
+ blankIsValid: false,
+ hasPredictions: true,
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'remote',
+ commandName: commandName,
+ paramName: paramName,
+ blankIsValid: this.blankIsValid
+ };
+ },
+
+ getBlank: function(context) {
+ if (this.blankIsValid) {
+ return new Conversion({ stringified: '' },
+ new BlankArgument(), Status.VALID);
+ }
+ else {
+ return new Conversion(undefined, new BlankArgument(),
+ Status.INCOMPLETE, '');
+ }
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ // remote types are client only, and we don't attempt to transfer value
+ // objects to the client (we can't be sure the are jsonable) so it is a
+ // bit strange to be asked to stringify a value object, however since
+ // parse creates a Conversion with a (fake) value object we might be
+ // asked to stringify that. We can stringify fake value objects.
+ if (typeof value.stringified === 'string') {
+ return value.stringified;
+ }
+ throw new Error('Can\'t stringify that value');
+ },
+
+ parse: function(arg, context) {
+ return this.front.parseType(context.typed, this.paramName).then(function(json) {
+ var status = Status.fromString(json.status);
+ return new Conversion(undefined, arg, status, json.message, json.predictions);
+ }.bind(this));
+ },
+
+ nudge: function(value, by, context) {
+ return this.front.nudgeType(context.typed, by, this.paramName).then(function(json) {
+ return { stringified: json.arg };
+ }.bind(this));
+ }
+ },
+ // 'blank' is a type for use with DelegateType when we don't know yet.
+ // It should not be used anywhere else.
+ {
+ item: 'type',
+ name: 'blank',
+
+ getSpec: function(commandName, paramName) {
+ return 'blank';
+ },
+
+ stringify: function(value, context) {
+ return '';
+ },
+
+ parse: function(arg, context) {
+ return Promise.resolve(new Conversion(undefined, arg));
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/file.js b/devtools/shared/gcli/source/lib/gcli/types/file.js
new file mode 100644
index 000000000..004f0108c
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/file.js
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/*
+ * The file type is a bit of a spiders-web, but there isn't a nice solution
+ * yet. The core of the problem is that the modules used by Firefox and NodeJS
+ * intersect with the modules used by the web, but not each other. Except here.
+ * So we have to do something fancy to get the sharing but not mess up the web.
+ *
+ * This file requires 'gcli/types/fileparser', and there are 4 implementations
+ * of this:
+ * - '/lib/gcli/types/fileparser.js', the default web version that uses XHR to
+ * talk to the node server
+ * - '/lib/server/gcli/types/fileparser.js', an NodeJS stub, and ...
+ * - '/mozilla/gcli/types/fileparser.js', the Firefox implementation both of
+ * these are shims which import
+ * - 'gcli/util/fileparser', does the real work, except the actual file access
+ *
+ * The file access comes from the 'gcli/util/filesystem' module, and there are
+ * 2 implementations of this:
+ * - '/lib/server/gcli/util/filesystem.js', which uses NodeJS APIs
+ * - '/mozilla/gcli/util/filesystem.js', which uses OS.File APIs
+ */
+
+var fileparser = require('./fileparser');
+var Conversion = require('./types').Conversion;
+
+exports.items = [
+ {
+ item: 'type',
+ name: 'file',
+
+ filetype: 'any', // One of 'file', 'directory', 'any'
+ existing: 'maybe', // Should be one of 'yes', 'no', 'maybe'
+ matches: undefined, // RegExp to match the file part of the path
+
+ hasPredictions: true,
+
+ constructor: function() {
+ if (this.filetype !== 'any' && this.filetype !== 'file' &&
+ this.filetype !== 'directory') {
+ throw new Error('filetype must be one of [any|file|directory]');
+ }
+
+ if (this.existing !== 'yes' && this.existing !== 'no' &&
+ this.existing !== 'maybe') {
+ throw new Error('existing must be one of [yes|no|maybe]');
+ }
+ },
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'remote',
+ commandName: commandName,
+ paramName: paramName
+ };
+ },
+
+ stringify: function(file) {
+ if (file == null) {
+ return '';
+ }
+
+ return file.toString();
+ },
+
+ parse: function(arg, context) {
+ var options = {
+ filetype: this.filetype,
+ existing: this.existing,
+ matches: this.matches
+ };
+ var promise = fileparser.parse(context, arg.text, options);
+
+ return promise.then(function(reply) {
+ return new Conversion(reply.value, arg, reply.status,
+ reply.message, reply.predictor);
+ });
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/fileparser.js b/devtools/shared/gcli/source/lib/gcli/types/fileparser.js
new file mode 100644
index 000000000..5db86dc66
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/fileparser.js
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+exports.parse = require('../util/fileparser').parse;
diff --git a/devtools/shared/gcli/source/lib/gcli/types/javascript.js b/devtools/shared/gcli/source/lib/gcli/types/javascript.js
new file mode 100644
index 000000000..71324ef2a
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/javascript.js
@@ -0,0 +1,522 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+
+var Conversion = require('./types').Conversion;
+var Type = require('./types').Type;
+var Status = require('./types').Status;
+
+/**
+ * 'javascript' handles scripted input
+ */
+function JavascriptType(typeSpec) {
+}
+
+JavascriptType.prototype = Object.create(Type.prototype);
+
+JavascriptType.prototype.getSpec = function(commandName, paramName) {
+ return {
+ name: 'remote',
+ paramName: paramName
+ };
+};
+
+JavascriptType.prototype.stringify = function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return value;
+};
+
+/**
+ * When sorting out completions, there is no point in displaying millions of
+ * matches - this the number of matches that we aim for
+ */
+JavascriptType.MAX_COMPLETION_MATCHES = 10;
+
+JavascriptType.prototype.parse = function(arg, context) {
+ var typed = arg.text;
+ var scope = (context.environment.window == null) ?
+ null : context.environment.window;
+
+ // No input is undefined
+ if (typed === '') {
+ return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE));
+ }
+ // Just accept numbers
+ if (!isNaN(parseFloat(typed)) && isFinite(typed)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+ // Just accept constants like true/false/null/etc
+ if (typed.trim().match(/(null|undefined|NaN|Infinity|true|false)/)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ // Analyze the input text and find the beginning of the last part that
+ // should be completed.
+ var beginning = this._findCompletionBeginning(typed);
+
+ // There was an error analyzing the string.
+ if (beginning.err) {
+ return Promise.resolve(new Conversion(typed, arg, Status.ERROR, beginning.err));
+ }
+
+ // If the current state is ParseState.COMPLEX, then we can't do completion.
+ // so bail out now
+ if (beginning.state === ParseState.COMPLEX) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ // If the current state is not ParseState.NORMAL, then we are inside of a
+ // string which means that no completion is possible.
+ if (beginning.state !== ParseState.NORMAL) {
+ return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
+ }
+
+ var completionPart = typed.substring(beginning.startPos);
+ var properties = completionPart.split('.');
+ var matchProp;
+ var prop;
+
+ if (properties.length > 1) {
+ matchProp = properties.pop().trimLeft();
+ for (var i = 0; i < properties.length; i++) {
+ prop = properties[i].trim();
+
+ // We can't complete on null.foo, so bail out
+ if (scope == null) {
+ return Promise.resolve(new Conversion(typed, arg, Status.ERROR,
+ l10n.lookup('jstypeParseScope')));
+ }
+
+ if (prop === '') {
+ return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
+ }
+
+ // Check if prop is a getter function on 'scope'. Functions can change
+ // other stuff so we can't execute them to get the next object. Stop here.
+ if (this._isSafeProperty(scope, prop)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ try {
+ scope = scope[prop];
+ }
+ catch (ex) {
+ // It would be nice to be able to report this error in some way but
+ // as it can happen just when someone types '{sessionStorage.', it
+ // almost doesn't really count as an error, so we ignore it
+ return Promise.resolve(new Conversion(typed, arg, Status.VALID, ''));
+ }
+ }
+ }
+ else {
+ matchProp = properties[0].trimLeft();
+ }
+
+ // If the reason we just stopped adjusting the scope was a non-simple string,
+ // then we're not sure if the input is valid or invalid, so accept it
+ if (prop && !prop.match(/^[0-9A-Za-z]*$/)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ // However if the prop was a simple string, it is an error
+ if (scope == null) {
+ var msg = l10n.lookupFormat('jstypeParseMissing', [ prop ]);
+ return Promise.resolve(new Conversion(typed, arg, Status.ERROR, msg));
+ }
+
+ // If the thing we're looking for isn't a simple string, then we're not going
+ // to find it, but we're not sure if it's valid or invalid, so accept it
+ if (!matchProp.match(/^[0-9A-Za-z]*$/)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ // Skip Iterators and Generators.
+ if (this._isIteratorOrGenerator(scope)) {
+ return Promise.resolve(new Conversion(typed, arg));
+ }
+
+ var matchLen = matchProp.length;
+ var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen);
+ var status = Status.INCOMPLETE;
+ var message = '';
+
+ // We really want an array of matches (for sorting) but it's easier to
+ // detect existing members if we're using a map initially
+ var matches = {};
+
+ // We only display a maximum of MAX_COMPLETION_MATCHES, so there is no point
+ // in digging up the prototype chain for matches that we're never going to
+ // use. Initially look for matches directly on the object itself and then
+ // look up the chain to find more
+ var distUpPrototypeChain = 0;
+ var root = scope;
+ try {
+ while (root != null &&
+ Object.keys(matches).length < JavascriptType.MAX_COMPLETION_MATCHES) {
+
+ /* jshint loopfunc:true */
+ Object.keys(root).forEach(function(property) {
+ // Only add matching properties. Also, as we're walking up the
+ // prototype chain, properties on 'higher' prototypes don't override
+ // similarly named properties lower down
+ if (property.indexOf(matchProp) === 0 && !(property in matches)) {
+ matches[property] = {
+ prop: property,
+ distUpPrototypeChain: distUpPrototypeChain
+ };
+ }
+ });
+
+ distUpPrototypeChain++;
+ root = Object.getPrototypeOf(root);
+ }
+ }
+ catch (ex) {
+ return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
+ }
+
+ // Convert to an array for sorting, and while we're at it, note if we got
+ // an exact match so we know that this input is valid
+ matches = Object.keys(matches).map(function(property) {
+ if (property === matchProp) {
+ status = Status.VALID;
+ }
+ return matches[property];
+ });
+
+ // The sort keys are:
+ // - Being on the object itself, not in the prototype chain
+ // - The lack of existence of a vendor prefix
+ // - The name
+ matches.sort(function(m1, m2) {
+ if (m1.distUpPrototypeChain !== m2.distUpPrototypeChain) {
+ return m1.distUpPrototypeChain - m2.distUpPrototypeChain;
+ }
+ // Push all vendor prefixes to the bottom of the list
+ return isVendorPrefixed(m1.prop) ?
+ (isVendorPrefixed(m2.prop) ? m1.prop.localeCompare(m2.prop) : 1) :
+ (isVendorPrefixed(m2.prop) ? -1 : m1.prop.localeCompare(m2.prop));
+ });
+
+ // Trim to size. There is a bug for doing a better job of finding matches
+ // (bug 682694), but in the mean time there is a performance problem
+ // associated with creating a large number of DOM nodes that few people will
+ // ever read, so trim ...
+ if (matches.length > JavascriptType.MAX_COMPLETION_MATCHES) {
+ matches = matches.slice(0, JavascriptType.MAX_COMPLETION_MATCHES - 1);
+ }
+
+ // Decorate the matches with:
+ // - a description
+ // - a value (for the menu) and,
+ // - an incomplete flag which reports if we should assume that the user isn't
+ // going to carry on the JS expression with this input so far
+ var predictions = matches.map(function(match) {
+ var description;
+ var incomplete = true;
+
+ if (this._isSafeProperty(scope, match.prop)) {
+ description = '(property getter)';
+ }
+ else {
+ try {
+ var value = scope[match.prop];
+
+ if (typeof value === 'function') {
+ description = '(function)';
+ }
+ else if (typeof value === 'boolean' || typeof value === 'number') {
+ description = '= ' + value;
+ incomplete = false;
+ }
+ else if (typeof value === 'string') {
+ if (value.length > 40) {
+ value = value.substring(0, 37) + '…';
+ }
+ description = '= \'' + value + '\'';
+ incomplete = false;
+ }
+ else {
+ description = '(' + typeof value + ')';
+ }
+ }
+ catch (ex) {
+ description = '(' + l10n.lookup('jstypeParseError') + ')';
+ }
+ }
+
+ return {
+ name: prefix + match.prop,
+ value: {
+ name: prefix + match.prop,
+ description: description
+ },
+ description: description,
+ incomplete: incomplete
+ };
+ }, this);
+
+ if (predictions.length === 0) {
+ status = Status.ERROR;
+ message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]);
+ }
+
+ // If the match is the only one possible, and its VALID, predict nothing
+ if (predictions.length === 1 && status === Status.VALID) {
+ predictions = [];
+ }
+
+ return Promise.resolve(new Conversion(typed, arg, status, message,
+ Promise.resolve(predictions)));
+};
+
+/**
+ * Does the given property have a prefix that indicates that it is vendor
+ * specific?
+ */
+function isVendorPrefixed(name) {
+ return name.indexOf('moz') === 0 ||
+ name.indexOf('webkit') === 0 ||
+ name.indexOf('ms') === 0;
+}
+
+/**
+ * Constants used in return value of _findCompletionBeginning()
+ */
+var ParseState = {
+ /**
+ * We have simple input like window.foo, without any punctuation that makes
+ * completion prediction be confusing or wrong
+ */
+ NORMAL: 0,
+
+ /**
+ * The cursor is in some Javascript that makes completion hard to predict,
+ * like console.log(
+ */
+ COMPLEX: 1,
+
+ /**
+ * The cursor is inside single quotes (')
+ */
+ QUOTE: 2,
+
+ /**
+ * The cursor is inside single quotes (")
+ */
+ DQUOTE: 3
+};
+
+var OPEN_BODY = '{[('.split('');
+var CLOSE_BODY = '}])'.split('');
+var OPEN_CLOSE_BODY = {
+ '{': '}',
+ '[': ']',
+ '(': ')'
+};
+
+/**
+ * How we distinguish between simple and complex JS input. We attempt
+ * completion against simple JS.
+ */
+var simpleChars = /[a-zA-Z0-9.]/;
+
+/**
+ * Analyzes a given string to find the last statement that is interesting for
+ * later completion.
+ * @param text A string to analyze
+ * @return If there was an error in the string detected, then a object like
+ * { err: 'ErrorMesssage' }
+ * is returned, otherwise a object like
+ * {
+ * state: ParseState.NORMAL|ParseState.QUOTE|ParseState.DQUOTE,
+ * startPos: index of where the last statement begins
+ * }
+ */
+JavascriptType.prototype._findCompletionBeginning = function(text) {
+ var bodyStack = [];
+
+ var state = ParseState.NORMAL;
+ var start = 0;
+ var c;
+ var complex = false;
+
+ for (var i = 0; i < text.length; i++) {
+ c = text[i];
+ if (!simpleChars.test(c)) {
+ complex = true;
+ }
+
+ switch (state) {
+ // Normal JS state.
+ case ParseState.NORMAL:
+ if (c === '"') {
+ state = ParseState.DQUOTE;
+ }
+ else if (c === '\'') {
+ state = ParseState.QUOTE;
+ }
+ else if (c === ';') {
+ start = i + 1;
+ }
+ else if (c === ' ') {
+ start = i + 1;
+ }
+ else if (OPEN_BODY.indexOf(c) != -1) {
+ bodyStack.push({
+ token: c,
+ start: start
+ });
+ start = i + 1;
+ }
+ else if (CLOSE_BODY.indexOf(c) != -1) {
+ var last = bodyStack.pop();
+ if (!last || OPEN_CLOSE_BODY[last.token] != c) {
+ return { err: l10n.lookup('jstypeBeginSyntax') };
+ }
+ if (c === '}') {
+ start = i + 1;
+ }
+ else {
+ start = last.start;
+ }
+ }
+ break;
+
+ // Double quote state > " <
+ case ParseState.DQUOTE:
+ if (c === '\\') {
+ i ++;
+ }
+ else if (c === '\n') {
+ return { err: l10n.lookup('jstypeBeginUnterm') };
+ }
+ else if (c === '"') {
+ state = ParseState.NORMAL;
+ }
+ break;
+
+ // Single quote state > ' <
+ case ParseState.QUOTE:
+ if (c === '\\') {
+ i ++;
+ }
+ else if (c === '\n') {
+ return { err: l10n.lookup('jstypeBeginUnterm') };
+ }
+ else if (c === '\'') {
+ state = ParseState.NORMAL;
+ }
+ break;
+ }
+ }
+
+ if (state === ParseState.NORMAL && complex) {
+ state = ParseState.COMPLEX;
+ }
+
+ return {
+ state: state,
+ startPos: start
+ };
+};
+
+/**
+ * Return true if the passed object is either an iterator or a generator, and
+ * false otherwise
+ * @param obj The object to check
+ */
+JavascriptType.prototype._isIteratorOrGenerator = function(obj) {
+ if (obj === null) {
+ return false;
+ }
+
+ if (typeof aObject === 'object') {
+ if (typeof obj.__iterator__ === 'function' ||
+ obj.constructor && obj.constructor.name === 'Iterator') {
+ return true;
+ }
+
+ try {
+ var str = obj.toString();
+ if (typeof obj.next === 'function' &&
+ str.indexOf('[object Generator') === 0) {
+ return true;
+ }
+ }
+ catch (ex) {
+ // window.history.next throws in the typeof check above.
+ return false;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Would calling 'scope[prop]' cause the invocation of a non-native (i.e. user
+ * defined) function property?
+ * Since calling functions can have side effects, it's only safe to do that if
+ * explicitly requested, rather than because we're trying things out for the
+ * purposes of completion.
+ */
+JavascriptType.prototype._isSafeProperty = function(scope, prop) {
+ if (typeof scope !== 'object') {
+ return false;
+ }
+
+ // Walk up the prototype chain of 'scope' looking for a property descriptor
+ // for 'prop'
+ var propDesc;
+ while (scope) {
+ try {
+ propDesc = Object.getOwnPropertyDescriptor(scope, prop);
+ if (propDesc) {
+ break;
+ }
+ }
+ catch (ex) {
+ // Native getters throw here. See bug 520882.
+ if (ex.name === 'NS_ERROR_XPC_BAD_CONVERT_JS' ||
+ ex.name === 'NS_ERROR_XPC_BAD_OP_ON_WN_PROTO') {
+ return false;
+ }
+ return true;
+ }
+ scope = Object.getPrototypeOf(scope);
+ }
+
+ if (!propDesc) {
+ return false;
+ }
+
+ if (!propDesc.get) {
+ return false;
+ }
+
+ // The property is safe if 'get' isn't a function or if the function has a
+ // prototype (in which case it's native)
+ return typeof propDesc.get !== 'function' || 'prototype' in propDesc.get;
+};
+
+JavascriptType.prototype.name = 'javascript';
+
+exports.items = [ JavascriptType ];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/moz.build b/devtools/shared/gcli/source/lib/gcli/types/moz.build
new file mode 100644
index 000000000..dc3063594
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/moz.build
@@ -0,0 +1,25 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'array.js',
+ 'boolean.js',
+ 'command.js',
+ 'date.js',
+ 'delegate.js',
+ 'file.js',
+ 'fileparser.js',
+ 'javascript.js',
+ 'node.js',
+ 'number.js',
+ 'resource.js',
+ 'selection.js',
+ 'setting.js',
+ 'string.js',
+ 'types.js',
+ 'union.js',
+ 'url.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/types/node.js b/devtools/shared/gcli/source/lib/gcli/types/node.js
new file mode 100644
index 000000000..2f71704e3
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/node.js
@@ -0,0 +1,201 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Highlighter = require('../util/host').Highlighter;
+var l10n = require('../util/l10n');
+var util = require('../util/util');
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+var BlankArgument = require('./types').BlankArgument;
+
+/**
+ * Helper functions to be attached to the prototypes of NodeType and
+ * NodeListType to allow terminal to tell us which nodes should be highlighted
+ */
+function onEnter(assignment) {
+ // TODO: GCLI doesn't support passing a context to notifications of cursor
+ // position, so onEnter/onLeave/onChange are disabled below until we fix this
+ assignment.highlighter = new Highlighter(context.environment.window.document);
+ assignment.highlighter.nodelist = assignment.conversion.matches;
+}
+
+/** @see #onEnter() */
+function onLeave(assignment) {
+ if (!assignment.highlighter) {
+ return;
+ }
+
+ assignment.highlighter.destroy();
+ delete assignment.highlighter;
+}
+/** @see #onEnter() */
+function onChange(assignment) {
+ if (assignment.conversion.matches == null) {
+ return;
+ }
+ if (!assignment.highlighter) {
+ return;
+ }
+
+ assignment.highlighter.nodelist = assignment.conversion.matches;
+}
+
+/**
+ * The exported 'node' and 'nodelist' types
+ */
+exports.items = [
+ {
+ // The 'node' type is a CSS expression that refers to a single node
+ item: 'type',
+ name: 'node',
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'remote',
+ commandName: commandName,
+ paramName: paramName
+ };
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return value.__gcliQuery || 'Error';
+ },
+
+ parse: function(arg, context) {
+ var reply;
+
+ if (arg.text === '') {
+ reply = new Conversion(undefined, arg, Status.INCOMPLETE);
+ }
+ else {
+ var nodes;
+ try {
+ nodes = context.environment.window.document.querySelectorAll(arg.text);
+ if (nodes.length === 0) {
+ reply = new Conversion(undefined, arg, Status.INCOMPLETE,
+ l10n.lookup('nodeParseNone'));
+ }
+ else if (nodes.length === 1) {
+ var node = nodes.item(0);
+ node.__gcliQuery = arg.text;
+
+ reply = new Conversion(node, arg, Status.VALID, '');
+ }
+ else {
+ var msg = l10n.lookupFormat('nodeParseMultiple', [ nodes.length ]);
+ reply = new Conversion(undefined, arg, Status.ERROR, msg);
+ }
+
+ reply.matches = nodes;
+ }
+ catch (ex) {
+ reply = new Conversion(undefined, arg, Status.ERROR,
+ l10n.lookup('nodeParseSyntax'));
+ }
+ }
+
+ return Promise.resolve(reply);
+ },
+
+ // onEnter: onEnter,
+ // onLeave: onLeave,
+ // onChange: onChange
+ },
+ {
+ // The 'nodelist' type is a CSS expression that refers to a node list
+ item: 'type',
+ name: 'nodelist',
+
+ // The 'allowEmpty' option ensures that we do not complain if the entered
+ // CSS selector is valid, but does not match any nodes. There is some
+ // overlap between this option and 'defaultValue'. What the user wants, in
+ // most cases, would be to use 'defaultText' (i.e. what is typed rather than
+ // the value that it represents). However this isn't a concept that exists
+ // yet and should probably be a part of GCLI if/when it does.
+ // All NodeListTypes have an automatic defaultValue of an empty NodeList so
+ // they can easily be used in named parameters.
+ allowEmpty: false,
+
+ constructor: function() {
+ if (typeof this.allowEmpty !== 'boolean') {
+ throw new Error('Legal values for allowEmpty are [true|false]');
+ }
+ },
+
+ getSpec: function(commandName, paramName) {
+ return {
+ name: 'remote',
+ commandName: commandName,
+ paramName: paramName,
+ blankIsValid: true
+ };
+ },
+
+ getBlank: function(context) {
+ var emptyNodeList = [];
+ if (context != null && context.environment.window != null) {
+ var doc = context.environment.window.document;
+ emptyNodeList = util.createEmptyNodeList(doc);
+ }
+ return new Conversion(emptyNodeList, new BlankArgument(), Status.VALID);
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return value.__gcliQuery || 'Error';
+ },
+
+ parse: function(arg, context) {
+ var reply;
+ try {
+ if (arg.text === '') {
+ reply = new Conversion(undefined, arg, Status.INCOMPLETE);
+ }
+ else {
+ var nodes = context.environment.window.document.querySelectorAll(arg.text);
+
+ if (nodes.length === 0 && !this.allowEmpty) {
+ reply = new Conversion(undefined, arg, Status.INCOMPLETE,
+ l10n.lookup('nodeParseNone'));
+ }
+ else {
+ nodes.__gcliQuery = arg.text;
+ reply = new Conversion(nodes, arg, Status.VALID, '');
+ }
+
+ reply.matches = nodes;
+ }
+ }
+ catch (ex) {
+ reply = new Conversion(undefined, arg, Status.ERROR,
+ l10n.lookup('nodeParseSyntax'));
+ }
+
+ return Promise.resolve(reply);
+ },
+
+ // onEnter: onEnter,
+ // onLeave: onLeave,
+ // onChange: onChange
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/number.js b/devtools/shared/gcli/source/lib/gcli/types/number.js
new file mode 100644
index 000000000..4c67e5807
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/number.js
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+
+exports.items = [
+ {
+ // 'number' type
+ // Has custom max / min / step values to control increment and decrement
+ // and a boolean allowFloat property to clamp values to integers
+ item: 'type',
+ name: 'number',
+
+ allowFloat: false,
+ max: undefined,
+ min: undefined,
+ step: 1,
+
+ constructor: function() {
+ if (!this.allowFloat &&
+ (this._isFloat(this.min) ||
+ this._isFloat(this.max) ||
+ this._isFloat(this.step))) {
+ throw new Error('allowFloat is false, but non-integer values given in type spec');
+ }
+ },
+
+ getSpec: function() {
+ var spec = {
+ name: 'number'
+ };
+ if (this.step !== 1) {
+ spec.step = this.step;
+ }
+ if (this.max != null) {
+ spec.max = this.max;
+ }
+ if (this.min != null) {
+ spec.min = this.min;
+ }
+ if (this.allowFloat) {
+ spec.allowFloat = true;
+ }
+ return (Object.keys(spec).length === 1) ? 'number' : spec;
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return '' + value;
+ },
+
+ getMin: function(context) {
+ if (this.min != null) {
+ if (typeof this.min === 'function') {
+ return this.min(context);
+ }
+ if (typeof this.min === 'number') {
+ return this.min;
+ }
+ }
+ return undefined;
+ },
+
+ getMax: function(context) {
+ if (this.max != null) {
+ if (typeof this.max === 'function') {
+ return this.max(context);
+ }
+ if (typeof this.max === 'number') {
+ return this.max;
+ }
+ }
+ return undefined;
+ },
+
+ parse: function(arg, context) {
+ var msg;
+ if (arg.text.replace(/^\s*-?/, '').length === 0) {
+ return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
+ }
+
+ if (!this.allowFloat && (arg.text.indexOf('.') !== -1)) {
+ msg = l10n.lookupFormat('typesNumberNotInt2', [ arg.text ]);
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+ }
+
+ var value;
+ if (this.allowFloat) {
+ value = parseFloat(arg.text);
+ }
+ else {
+ value = parseInt(arg.text, 10);
+ }
+
+ if (isNaN(value)) {
+ msg = l10n.lookupFormat('typesNumberNan', [ arg.text ]);
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+ }
+
+ var max = this.getMax(context);
+ if (max != null && value > max) {
+ msg = l10n.lookupFormat('typesNumberMax', [ value, max ]);
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+ }
+
+ var min = this.getMin(context);
+ if (min != null && value < min) {
+ msg = l10n.lookupFormat('typesNumberMin', [ value, min ]);
+ return Promise.resolve(new Conversion(undefined, arg, Status.ERROR, msg));
+ }
+
+ return Promise.resolve(new Conversion(value, arg));
+ },
+
+ nudge: function(value, by, context) {
+ if (typeof value !== 'number' || isNaN(value)) {
+ if (by < 0) {
+ return this.getMax(context) || 1;
+ }
+ else {
+ var min = this.getMin(context);
+ return min != null ? min : 0;
+ }
+ }
+
+ var newValue = value + (by * this.step);
+
+ // Snap to the nearest incremental of the step
+ if (by < 0) {
+ newValue = Math.ceil(newValue / this.step) * this.step;
+ }
+ else {
+ newValue = Math.floor(newValue / this.step) * this.step;
+ if (this.getMax(context) == null) {
+ return newValue;
+ }
+ }
+ return this._boundsCheck(newValue, context);
+ },
+
+ // Return the input value so long as it is within the max/min bounds.
+ // If it is lower than the minimum, return the minimum. If it is bigger
+ // than the maximum then return the maximum.
+ _boundsCheck: function(value, context) {
+ var min = this.getMin(context);
+ if (min != null && value < min) {
+ return min;
+ }
+ var max = this.getMax(context);
+ if (max != null && value > max) {
+ return max;
+ }
+ return value;
+ },
+
+ // Return true if the given value is a finite number and not an integer,
+ // else return false.
+ _isFloat: function(value) {
+ return ((typeof value === 'number') && isFinite(value) && (value % 1 !== 0));
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/resource.js b/devtools/shared/gcli/source/lib/gcli/types/resource.js
new file mode 100644
index 000000000..cd1984824
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/resource.js
@@ -0,0 +1,270 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+exports.clearResourceCache = function() {
+ ResourceCache.clear();
+};
+
+/**
+ * Resources are bits of CSS and JavaScript that the page either includes
+ * directly or as a result of reading some remote resource.
+ * Resource should not be used directly, but instead through a sub-class like
+ * CssResource or ScriptResource.
+ */
+function Resource(name, type, inline, element) {
+ this.name = name;
+ this.type = type;
+ this.inline = inline;
+ this.element = element;
+}
+
+/**
+ * Get the contents of the given resource as a string.
+ * The base Resource leaves this unimplemented.
+ */
+Resource.prototype.loadContents = function() {
+ throw new Error('not implemented');
+};
+
+Resource.TYPE_SCRIPT = 'text/javascript';
+Resource.TYPE_CSS = 'text/css';
+
+/**
+ * A CssResource provides an implementation of Resource that works for both
+ * [style] elements and [link type='text/css'] elements in the [head].
+ */
+function CssResource(domSheet) {
+ this.name = domSheet.href;
+ if (!this.name) {
+ this.name = domSheet.ownerNode && domSheet.ownerNode.id ?
+ 'css#' + domSheet.ownerNode.id :
+ 'inline-css';
+ }
+
+ this.inline = (domSheet.href == null);
+ this.type = Resource.TYPE_CSS;
+ this.element = domSheet;
+}
+
+CssResource.prototype = Object.create(Resource.prototype);
+
+CssResource.prototype.loadContents = function() {
+ return new Promise(function(resolve, reject) {
+ resolve(this.element.ownerNode.innerHTML);
+ }.bind(this));
+};
+
+CssResource._getAllStyles = function(context) {
+ var resources = [];
+ if (context.environment.window == null) {
+ return resources;
+ }
+
+ var doc = context.environment.window.document;
+ Array.prototype.forEach.call(doc.styleSheets, function(domSheet) {
+ CssResource._getStyle(domSheet, resources);
+ });
+
+ dedupe(resources, function(clones) {
+ for (var i = 0; i < clones.length; i++) {
+ clones[i].name = clones[i].name + '-' + i;
+ }
+ });
+
+ return resources;
+};
+
+CssResource._getStyle = function(domSheet, resources) {
+ var resource = ResourceCache.get(domSheet);
+ if (!resource) {
+ resource = new CssResource(domSheet);
+ ResourceCache.add(domSheet, resource);
+ }
+ resources.push(resource);
+
+ // Look for imported stylesheets
+ try {
+ Array.prototype.forEach.call(domSheet.cssRules, function(domRule) {
+ if (domRule.type == CSSRule.IMPORT_RULE && domRule.styleSheet) {
+ CssResource._getStyle(domRule.styleSheet, resources);
+ }
+ }, this);
+ }
+ catch (ex) {
+ // For system stylesheets
+ }
+};
+
+/**
+ * A ScriptResource provides an implementation of Resource that works for
+ * [script] elements (both with a src attribute, and used directly).
+ */
+function ScriptResource(scriptNode) {
+ this.name = scriptNode.src;
+ if (!this.name) {
+ this.name = scriptNode.id ?
+ 'script#' + scriptNode.id :
+ 'inline-script';
+ }
+
+ this.inline = (scriptNode.src === '' || scriptNode.src == null);
+ this.type = Resource.TYPE_SCRIPT;
+ this.element = scriptNode;
+}
+
+ScriptResource.prototype = Object.create(Resource.prototype);
+
+ScriptResource.prototype.loadContents = function() {
+ return new Promise(function(resolve, reject) {
+ if (this.inline) {
+ resolve(this.element.innerHTML);
+ }
+ else {
+ // It would be good if there was a better way to get the script source
+ var xhr = new XMLHttpRequest();
+ xhr.onreadystatechange = function() {
+ if (xhr.readyState !== xhr.DONE) {
+ return;
+ }
+ resolve(xhr.responseText);
+ };
+ xhr.open('GET', this.element.src, true);
+ xhr.send();
+ }
+ }.bind(this));
+};
+
+ScriptResource._getAllScripts = function(context) {
+ if (context.environment.window == null) {
+ return [];
+ }
+
+ var doc = context.environment.window.document;
+ var scriptNodes = doc.querySelectorAll('script');
+ var resources = Array.prototype.map.call(scriptNodes, function(scriptNode) {
+ var resource = ResourceCache.get(scriptNode);
+ if (!resource) {
+ resource = new ScriptResource(scriptNode);
+ ResourceCache.add(scriptNode, resource);
+ }
+ return resource;
+ });
+
+ dedupe(resources, function(clones) {
+ for (var i = 0; i < clones.length; i++) {
+ clones[i].name = clones[i].name + '-' + i;
+ }
+ });
+
+ return resources;
+};
+
+/**
+ * Find resources with the same name, and call onDupe to change the names
+ */
+function dedupe(resources, onDupe) {
+ // first create a map of name->[array of resources with same name]
+ var names = {};
+ resources.forEach(function(scriptResource) {
+ if (names[scriptResource.name] == null) {
+ names[scriptResource.name] = [];
+ }
+ names[scriptResource.name].push(scriptResource);
+ });
+
+ // Call the de-dupe function for each set of dupes
+ Object.keys(names).forEach(function(name) {
+ var clones = names[name];
+ if (clones.length > 1) {
+ onDupe(clones);
+ }
+ });
+}
+
+/**
+ * A quick cache of resources against nodes
+ * TODO: Potential memory leak when the target document has css or script
+ * resources repeatedly added and removed. Solution might be to use a weak
+ * hash map or some such.
+ */
+var ResourceCache = {
+ _cached: [],
+
+ /**
+ * Do we already have a resource that was created for the given node
+ */
+ get: function(node) {
+ for (var i = 0; i < ResourceCache._cached.length; i++) {
+ if (ResourceCache._cached[i].node === node) {
+ return ResourceCache._cached[i].resource;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Add a resource for a given node
+ */
+ add: function(node, resource) {
+ ResourceCache._cached.push({ node: node, resource: resource });
+ },
+
+ /**
+ * Drop all cache entries. Helpful to prevent memory leaks
+ */
+ clear: function() {
+ ResourceCache._cached = [];
+ }
+};
+
+/**
+ * The resource type itself
+ */
+exports.items = [
+ {
+ item: 'type',
+ name: 'resource',
+ parent: 'selection',
+ cacheable: false,
+ include: null,
+
+ constructor: function() {
+ if (this.include !== Resource.TYPE_SCRIPT &&
+ this.include !== Resource.TYPE_CSS &&
+ this.include != null) {
+ throw new Error('invalid include property: ' + this.include);
+ }
+ },
+
+ lookup: function(context) {
+ var resources = [];
+ if (this.include !== Resource.TYPE_SCRIPT) {
+ Array.prototype.push.apply(resources,
+ CssResource._getAllStyles(context));
+ }
+ if (this.include !== Resource.TYPE_CSS) {
+ Array.prototype.push.apply(resources,
+ ScriptResource._getAllScripts(context));
+ }
+
+ return Promise.resolve(resources.map(function(resource) {
+ return { name: resource.name, value: resource };
+ }));
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/selection.js b/devtools/shared/gcli/source/lib/gcli/types/selection.js
new file mode 100644
index 000000000..0e64c8fa2
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/selection.js
@@ -0,0 +1,389 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var spell = require('../util/spell');
+var Type = require('./types').Type;
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+var BlankArgument = require('./types').BlankArgument;
+
+/**
+ * A selection allows the user to pick a value from known set of options.
+ * An option is made up of a name (which is what the user types) and a value
+ * (which is passed to exec)
+ * @param typeSpec Object containing properties that describe how this
+ * selection functions. Properties include:
+ * - lookup: An array of objects, one for each option, which contain name and
+ * value properties. lookup can be a function which returns this array
+ * - data: An array of strings - alternative to 'lookup' where the valid values
+ * are strings. i.e. there is no mapping between what is typed and the value
+ * that is used by the program
+ * - stringifyProperty: Conversion from value to string is generally a process
+ * of looking through all the valid options for a matching value, and using
+ * the associated name. However the name maybe available directly from the
+ * value using a property lookup. Setting 'stringifyProperty' allows
+ * SelectionType to take this shortcut.
+ * - cacheable: If lookup is a function, then we normally assume that
+ * the values fetched can change. Setting 'cacheable:true' enables internal
+ * caching.
+ */
+function SelectionType(typeSpec) {
+ if (typeSpec) {
+ Object.keys(typeSpec).forEach(function(key) {
+ this[key] = typeSpec[key];
+ }, this);
+ }
+
+ if (this.name !== 'selection' &&
+ this.lookup == null && this.data == null) {
+ throw new Error(this.name + ' has no lookup or data');
+ }
+
+ this._dataToLookup = this._dataToLookup.bind(this);
+}
+
+SelectionType.prototype = Object.create(Type.prototype);
+
+SelectionType.prototype.getSpec = function(commandName, paramName) {
+ var spec = { name: 'selection' };
+ if (this.lookup != null && typeof this.lookup !== 'function') {
+ spec.lookup = this.lookup;
+ }
+ if (this.data != null && typeof this.data !== 'function') {
+ spec.data = this.data;
+ }
+ if (this.stringifyProperty != null) {
+ spec.stringifyProperty = this.stringifyProperty;
+ }
+ if (this.cacheable) {
+ spec.cacheable = true;
+ }
+ if (typeof this.lookup === 'function' || typeof this.data === 'function') {
+ spec.commandName = commandName;
+ spec.paramName = paramName;
+ spec.remoteLookup = true;
+ }
+ return spec;
+};
+
+SelectionType.prototype.stringify = function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ if (this.stringifyProperty != null) {
+ return value[this.stringifyProperty];
+ }
+
+ return this.getLookup(context).then(function(lookup) {
+ var name = null;
+ lookup.some(function(item) {
+ if (item.value === value) {
+ name = item.name;
+ return true;
+ }
+ return false;
+ }, this);
+ return name;
+ }.bind(this));
+};
+
+/**
+ * If typeSpec contained cacheable:true then calls to parse() work on cached
+ * data. clearCache() enables the cache to be cleared.
+ */
+SelectionType.prototype.clearCache = function() {
+ this._cachedLookup = undefined;
+};
+
+/**
+ * There are several ways to get selection data. This unifies them into one
+ * single function.
+ * @return An array of objects with name and value properties.
+ */
+SelectionType.prototype.getLookup = function(context) {
+ if (this._cachedLookup != null) {
+ return this._cachedLookup;
+ }
+
+ var reply;
+
+ if (this.remoteLookup) {
+ reply = this.front.getSelectionLookup(this.commandName, this.paramName);
+ reply = resolve(reply, context);
+ }
+ else if (typeof this.lookup === 'function') {
+ reply = resolve(this.lookup.bind(this), context);
+ }
+ else if (this.lookup != null) {
+ reply = resolve(this.lookup, context);
+ }
+ else if (this.data != null) {
+ reply = resolve(this.data, context).then(this._dataToLookup);
+ }
+ else {
+ throw new Error(this.name + ' has no lookup or data');
+ }
+
+ if (this.cacheable) {
+ this._cachedLookup = reply;
+ }
+
+ if (reply == null) {
+ console.error(arguments);
+ }
+ return reply;
+};
+
+/**
+ * Both 'lookup' and 'data' properties (see docs on SelectionType constructor)
+ * in addition to being real data can be a function or a promise, or even a
+ * function which returns a promise of real data, etc. This takes a thing and
+ * returns a promise of actual values.
+ */
+function resolve(thing, context) {
+ return Promise.resolve(thing).then(function(resolved) {
+ if (typeof resolved === 'function') {
+ return resolve(resolved(context), context);
+ }
+ return resolved;
+ });
+}
+
+/**
+ * Selection can be provided with either a lookup object (in the 'lookup'
+ * property) or an array of strings (in the 'data' property). Internally we
+ * always use lookup, so we need a way to convert a 'data' array to a lookup.
+ */
+SelectionType.prototype._dataToLookup = function(data) {
+ if (!Array.isArray(data)) {
+ throw new Error('data for ' + this.name + ' resolved to non-array');
+ }
+
+ return data.map(function(option) {
+ return { name: option, value: option };
+ });
+};
+
+/**
+ * Return a list of possible completions for the given arg.
+ * @param arg The initial input to match
+ * @return A trimmed array of string:value pairs
+ */
+exports.findPredictions = function(arg, lookup) {
+ var predictions = [];
+ var i, option;
+ var maxPredictions = Conversion.maxPredictions;
+ var match = arg.text.toLowerCase();
+
+ // If the arg has a suffix then we're kind of 'done'. Only an exact match
+ // will do.
+ if (arg.suffix.length > 0) {
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option.name === arg.text) {
+ predictions.push(option);
+ }
+ }
+
+ return predictions;
+ }
+
+ // Cache lower case versions of all the option names
+ for (i = 0; i < lookup.length; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName == null) {
+ option._gcliLowerName = option.name.toLowerCase();
+ }
+ }
+
+ // Exact hidden matches. If 'hidden: true' then we only allow exact matches
+ // All the tests after here check that !isHidden(option)
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option.name === arg.text) {
+ predictions.push(option);
+ }
+ }
+
+ // Start with prefix matching
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName.indexOf(match) === 0 && !isHidden(option)) {
+ if (predictions.indexOf(option) === -1) {
+ predictions.push(option);
+ }
+ }
+ }
+
+ // Try infix matching if we get less half max matched
+ if (predictions.length < (maxPredictions / 2)) {
+ for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+ option = lookup[i];
+ if (option._gcliLowerName.indexOf(match) !== -1 && !isHidden(option)) {
+ if (predictions.indexOf(option) === -1) {
+ predictions.push(option);
+ }
+ }
+ }
+ }
+
+ // Try fuzzy matching if we don't get a prefix match
+ if (predictions.length === 0) {
+ var names = [];
+ lookup.forEach(function(opt) {
+ if (!isHidden(opt)) {
+ names.push(opt.name);
+ }
+ });
+ var corrected = spell.correct(match, names);
+ if (corrected) {
+ lookup.forEach(function(opt) {
+ if (opt.name === corrected) {
+ predictions.push(opt);
+ }
+ }, this);
+ }
+ }
+
+ return predictions;
+};
+
+SelectionType.prototype.parse = function(arg, context) {
+ return Promise.resolve(this.getLookup(context)).then(function(lookup) {
+ var predictions = exports.findPredictions(arg, lookup);
+ return exports.convertPredictions(arg, predictions);
+ }.bind(this));
+};
+
+/**
+ * Decide what sort of conversion to return based on the available predictions
+ * and how they match the passed arg
+ */
+exports.convertPredictions = function(arg, predictions) {
+ if (predictions.length === 0) {
+ var msg = l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
+ return new Conversion(undefined, arg, Status.ERROR, msg,
+ Promise.resolve(predictions));
+ }
+
+ if (predictions[0].name === arg.text) {
+ var value = predictions[0].value;
+ return new Conversion(value, arg, Status.VALID, '',
+ Promise.resolve(predictions));
+ }
+
+ return new Conversion(undefined, arg, Status.INCOMPLETE, '',
+ Promise.resolve(predictions));
+};
+
+/**
+ * Checking that an option is hidden involves messing in properties on the
+ * value right now (which isn't a good idea really) we really should be marking
+ * that on the option, so this encapsulates the problem
+ */
+function isHidden(option) {
+ return option.hidden === true ||
+ (option.value != null && option.value.hidden);
+}
+
+SelectionType.prototype.getBlank = function(context) {
+ var predictFunc = function(context2) {
+ return Promise.resolve(this.getLookup(context2)).then(function(lookup) {
+ return lookup.filter(function(option) {
+ return !isHidden(option);
+ }).slice(0, Conversion.maxPredictions - 1);
+ });
+ }.bind(this);
+
+ return new Conversion(undefined, new BlankArgument(), Status.INCOMPLETE, '',
+ predictFunc);
+};
+
+/**
+ * Increment and decrement are confusing for selections. +1 is -1 and -1 is +1.
+ * Given an array e.g. [ 'a', 'b', 'c' ] with the current selection on 'b',
+ * displayed to the user in the natural way, i.e.:
+ *
+ * 'a'
+ * 'b' <- highlighted as current value
+ * 'c'
+ *
+ * Pressing the UP arrow should take us to 'a', which decrements this index
+ * (compare pressing UP on a number which would increment the number)
+ *
+ * So for selections, we treat +1 as -1 and -1 as +1.
+ */
+SelectionType.prototype.nudge = function(value, by, context) {
+ return this.getLookup(context).then(function(lookup) {
+ var index = this._findValue(lookup, value);
+ if (index === -1) {
+ if (by < 0) {
+ // We're supposed to be doing a decrement (which means +1), but the
+ // value isn't found, so we reset the index to the top of the list
+ // which is index 0
+ index = 0;
+ }
+ else {
+ // For an increment operation when there is nothing to start from, we
+ // want to start from the top, i.e. index 0, so the value before we
+ // 'increment' (see note above) must be 1.
+ index = 1;
+ }
+ }
+
+ // This is where we invert the sense of up/down (see doc comment)
+ index -= by;
+
+ if (index >= lookup.length) {
+ index = 0;
+ }
+ return lookup[index].value;
+ }.bind(this));
+};
+
+/**
+ * Walk through an array of { name:.., value:... } objects looking for a
+ * matching value (using strict equality), returning the matched index (or -1
+ * if not found).
+ * @param lookup Array of objects with name/value properties to search through
+ * @param value The value to search for
+ * @return The index at which the match was found, or -1 if no match was found
+ */
+SelectionType.prototype._findValue = function(lookup, value) {
+ var index = -1;
+ for (var i = 0; i < lookup.length; i++) {
+ var pair = lookup[i];
+ if (pair.value === value) {
+ index = i;
+ break;
+ }
+ }
+ return index;
+};
+
+/**
+ * This is how we indicate to SelectionField that we have predictions that
+ * might work in a menu.
+ */
+SelectionType.prototype.hasPredictions = true;
+
+SelectionType.prototype.name = 'selection';
+
+exports.SelectionType = SelectionType;
+exports.items = [ SelectionType ];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/setting.js b/devtools/shared/gcli/source/lib/gcli/types/setting.js
new file mode 100644
index 000000000..26c6f4063
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/setting.js
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+exports.items = [
+ {
+ // A type for selecting a known setting
+ item: 'type',
+ name: 'setting',
+ parent: 'selection',
+ cacheable: true,
+ lookup: function(context) {
+ var settings = context.system.settings;
+
+ // Lazily add a settings.onChange listener to clear the cache
+ if (!this._registeredListener) {
+ settings.onChange.add(function(ev) {
+ this.clearCache();
+ }, this);
+ this._registeredListener = true;
+ }
+
+ return settings.getAll().map(function(setting) {
+ return { name: setting.name, value: setting };
+ });
+ }
+ },
+ {
+ // A type for entering the value of a known setting
+ // Customizations:
+ // - settingParamName The name of the setting parameter so we can customize
+ // the type that we are expecting to read
+ item: 'type',
+ name: 'settingValue',
+ parent: 'delegate',
+ settingParamName: 'setting',
+ delegateType: function(context) {
+ if (context != null) {
+ var setting = context.getArgsObject()[this.settingParamName];
+ if (setting != null) {
+ return setting.type;
+ }
+ }
+
+ return 'blank';
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/string.js b/devtools/shared/gcli/source/lib/gcli/types/string.js
new file mode 100644
index 000000000..a3aebacad
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/string.js
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+
+exports.items = [
+ {
+ // 'string' the most basic string type where all we need to do is to take
+ // care of converting escaped characters like \t, \n, etc.
+ // For the full list see
+ // https://developer.mozilla.org/en-US/docs/JavaScript/Guide/Values,_variables,_and_literals
+ // The exception is that we ignore \b because replacing '\b' characters in
+ // stringify() with their escaped version injects '\\b' all over the place
+ // and the need to support \b seems low)
+ // Customizations:
+ // allowBlank: Allow a blank string to be counted as valid
+ item: 'type',
+ name: 'string',
+ allowBlank: false,
+
+ getSpec: function() {
+ return this.allowBlank ?
+ { name: 'string', allowBlank: true } :
+ 'string';
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+
+ return value
+ .replace(/\\/g, '\\\\')
+ .replace(/\f/g, '\\f')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/\t/g, '\\t')
+ .replace(/\v/g, '\\v')
+ .replace(/\n/g, '\\n')
+ .replace(/\r/g, '\\r')
+ .replace(/ /g, '\\ ')
+ .replace(/'/g, '\\\'')
+ .replace(/"/g, '\\"')
+ .replace(/{/g, '\\{')
+ .replace(/}/g, '\\}');
+ },
+
+ parse: function(arg, context) {
+ if (!this.allowBlank && (arg.text == null || arg.text === '')) {
+ return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE, ''));
+ }
+
+ // The string '\\' (i.e. an escaped \ (represented here as '\\\\' because it
+ // is double escaped)) is first converted to a private unicode character and
+ // then at the end from \uF000 to a single '\' to avoid the string \\n being
+ // converted first to \n and then to a <LF>
+ var value = arg.text
+ .replace(/\\\\/g, '\uF000')
+ .replace(/\\f/g, '\f')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\t/g, '\t')
+ .replace(/\\v/g, '\v')
+ .replace(/\\n/g, '\n')
+ .replace(/\\r/g, '\r')
+ .replace(/\\ /g, ' ')
+ .replace(/\\'/g, '\'')
+ .replace(/\\"/g, '"')
+ .replace(/\\{/g, '{')
+ .replace(/\\}/g, '}')
+ .replace(/\uF000/g, '\\');
+
+ return Promise.resolve(new Conversion(value, arg));
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/types.js b/devtools/shared/gcli/source/lib/gcli/types/types.js
new file mode 100644
index 000000000..ed5a93d54
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/types.js
@@ -0,0 +1,1146 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+
+/**
+ * We record where in the input string an argument comes so we can report
+ * errors against those string positions.
+ * @param text The string (trimmed) that contains the argument
+ * @param prefix Knowledge of quotation marks and whitespace used prior to the
+ * text in the input string allows us to re-generate the original input from
+ * the arguments.
+ * @param suffix Any quotation marks and whitespace used after the text.
+ * Whitespace is normally placed in the prefix to the succeeding argument, but
+ * can be used here when this is the last argument.
+ * @constructor
+ */
+function Argument(text, prefix, suffix) {
+ if (text === undefined) {
+ this.text = '';
+ this.prefix = '';
+ this.suffix = '';
+ }
+ else {
+ this.text = text;
+ this.prefix = prefix !== undefined ? prefix : '';
+ this.suffix = suffix !== undefined ? suffix : '';
+ }
+}
+
+Argument.prototype.type = 'Argument';
+
+/**
+ * Return the result of merging these arguments.
+ * case and some of the arguments are in quotation marks?
+ */
+Argument.prototype.merge = function(following) {
+ // Is it possible that this gets called when we're merging arguments
+ // for the single string?
+ return new Argument(
+ this.text + this.suffix + following.prefix + following.text,
+ this.prefix, following.suffix);
+};
+
+/**
+ * Returns a new Argument like this one but with various items changed.
+ * @param options Values to use in creating a new Argument.
+ * Warning: some implementations of beget make additions to the options
+ * argument. You should be aware of this in the unlikely event that you want to
+ * reuse 'options' arguments.
+ * Properties:
+ * - text: The new text value
+ * - prefixSpace: Should the prefix be altered to begin with a space?
+ * - prefixPostSpace: Should the prefix be altered to end with a space?
+ * - suffixSpace: Should the suffix be altered to end with a space?
+ * - type: Constructor to use in creating new instances. Default: Argument
+ * - dontQuote: Should we avoid adding prefix/suffix quotes when the text value
+ * has a space? Needed when we're completing a sub-command.
+ */
+Argument.prototype.beget = function(options) {
+ var text = this.text;
+ var prefix = this.prefix;
+ var suffix = this.suffix;
+
+ if (options.text != null) {
+ text = options.text;
+
+ // We need to add quotes when the replacement string has spaces or is empty
+ if (!options.dontQuote) {
+ var needsQuote = text.indexOf(' ') >= 0 || text.length === 0;
+ var hasQuote = /['"]$/.test(prefix);
+ if (needsQuote && !hasQuote) {
+ prefix = prefix + '\'';
+ suffix = '\'' + suffix;
+ }
+ }
+ }
+
+ if (options.prefixSpace && prefix.charAt(0) !== ' ') {
+ prefix = ' ' + prefix;
+ }
+
+ if (options.prefixPostSpace && prefix.charAt(prefix.length - 1) !== ' ') {
+ prefix = prefix + ' ';
+ }
+
+ if (options.suffixSpace && suffix.charAt(suffix.length - 1) !== ' ') {
+ suffix = suffix + ' ';
+ }
+
+ if (text === this.text && suffix === this.suffix && prefix === this.prefix) {
+ return this;
+ }
+
+ var ArgumentType = options.type || Argument;
+ return new ArgumentType(text, prefix, suffix);
+};
+
+/**
+ * We need to keep track of which assignment we've been assigned to
+ */
+Object.defineProperty(Argument.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) { this._assignment = assignment; },
+ enumerable: true
+});
+
+/**
+ * Sub-classes of Argument are collections of arguments, getArgs() gets access
+ * to the members of the collection in order to do things like re-create input
+ * command lines. For the simple Argument case it's just an array containing
+ * only this.
+ */
+Argument.prototype.getArgs = function() {
+ return [ this ];
+};
+
+/**
+ * We define equals to mean all arg properties are strict equals.
+ * Used by Conversion.argEquals and Conversion.equals and ultimately
+ * Assignment.equals to avoid reporting a change event when a new conversion
+ * is assigned.
+ */
+Argument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null || !(that instanceof Argument)) {
+ return false;
+ }
+
+ return this.text === that.text &&
+ this.prefix === that.prefix && this.suffix === that.suffix;
+};
+
+/**
+ * Helper when we're putting arguments back together
+ */
+Argument.prototype.toString = function() {
+ // BUG 664207: We should re-escape escaped characters
+ // But can we do that reliably?
+ return this.prefix + this.text + this.suffix;
+};
+
+/**
+ * Merge an array of arguments into a single argument.
+ * All Arguments in the array are expected to have the same emitter
+ */
+Argument.merge = function(argArray, start, end) {
+ start = (start === undefined) ? 0 : start;
+ end = (end === undefined) ? argArray.length : end;
+
+ var joined;
+ for (var i = start; i < end; i++) {
+ var arg = argArray[i];
+ if (!joined) {
+ joined = arg;
+ }
+ else {
+ joined = joined.merge(arg);
+ }
+ }
+ return joined;
+};
+
+/**
+ * For test/debug use only. The output from this function is subject to wanton
+ * random change without notice, and should not be relied upon to even exist
+ * at some later date.
+ */
+Object.defineProperty(Argument.prototype, '_summaryJson', {
+ get: function() {
+ var assignStatus = this.assignment == null ?
+ 'null' :
+ this.assignment.param.name;
+ return '<' + this.prefix + ':' + this.text + ':' + this.suffix + '>' +
+ ' (a=' + assignStatus + ',' + ' t=' + this.type + ')';
+ },
+ enumerable: true
+});
+
+exports.Argument = Argument;
+
+
+/**
+ * BlankArgument is a marker that the argument wasn't typed but is there to
+ * fill a slot. Assignments begin with their arg set to a BlankArgument.
+ */
+function BlankArgument() {
+ this.text = '';
+ this.prefix = '';
+ this.suffix = '';
+}
+
+BlankArgument.prototype = Object.create(Argument.prototype);
+
+BlankArgument.prototype.type = 'BlankArgument';
+
+exports.BlankArgument = BlankArgument;
+
+
+/**
+ * ScriptArgument is a marker that the argument is designed to be JavaScript.
+ * It also implements the special rules that spaces after the { or before the
+ * } are part of the pre/suffix rather than the content, and that they are
+ * never 'blank' so they can be used by Requisition._split() and not raise an
+ * ERROR status due to being blank.
+ */
+function ScriptArgument(text, prefix, suffix) {
+ this.text = text !== undefined ? text : '';
+ this.prefix = prefix !== undefined ? prefix : '';
+ this.suffix = suffix !== undefined ? suffix : '';
+
+ ScriptArgument._moveSpaces(this);
+}
+
+ScriptArgument.prototype = Object.create(Argument.prototype);
+
+ScriptArgument.prototype.type = 'ScriptArgument';
+
+/**
+ * Private/Dangerous: Alters a ScriptArgument to move the spaces at the start
+ * or end of the 'text' into the prefix/suffix. With a string, " a " is 3 chars
+ * long, but with a ScriptArgument, { a } is only one char long.
+ * Arguments are generally supposed to be immutable, so this method should only
+ * be called on a ScriptArgument that isn't exposed to the outside world yet.
+ */
+ScriptArgument._moveSpaces = function(arg) {
+ while (arg.text.charAt(0) === ' ') {
+ arg.prefix = arg.prefix + ' ';
+ arg.text = arg.text.substring(1);
+ }
+
+ while (arg.text.charAt(arg.text.length - 1) === ' ') {
+ arg.suffix = ' ' + arg.suffix;
+ arg.text = arg.text.slice(0, -1);
+ }
+};
+
+/**
+ * As Argument.beget that implements the space rule documented in the ctor.
+ */
+ScriptArgument.prototype.beget = function(options) {
+ options.type = ScriptArgument;
+ var begotten = Argument.prototype.beget.call(this, options);
+ ScriptArgument._moveSpaces(begotten);
+ return begotten;
+};
+
+exports.ScriptArgument = ScriptArgument;
+
+
+/**
+ * Commands like 'echo' with a single string argument, and used with the
+ * special format like: 'echo a b c' effectively have a number of arguments
+ * merged together.
+ */
+function MergedArgument(args, start, end) {
+ if (!Array.isArray(args)) {
+ throw new Error('args is not an array of Arguments');
+ }
+
+ if (start === undefined) {
+ this.args = args;
+ }
+ else {
+ this.args = args.slice(start, end);
+ }
+
+ var arg = Argument.merge(this.args);
+ this.text = arg.text;
+ this.prefix = arg.prefix;
+ this.suffix = arg.suffix;
+}
+
+MergedArgument.prototype = Object.create(Argument.prototype);
+
+MergedArgument.prototype.type = 'MergedArgument';
+
+/**
+ * Keep track of which assignment we've been assigned to, and allow the
+ * original args to do the same.
+ */
+Object.defineProperty(MergedArgument.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) {
+ this._assignment = assignment;
+
+ this.args.forEach(function(arg) {
+ arg.assignment = assignment;
+ }, this);
+ },
+ enumerable: true
+});
+
+MergedArgument.prototype.getArgs = function() {
+ return this.args;
+};
+
+MergedArgument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null || !(that instanceof MergedArgument)) {
+ return false;
+ }
+
+ // We might need to add a check that args is the same here
+
+ return this.text === that.text &&
+ this.prefix === that.prefix && this.suffix === that.suffix;
+};
+
+exports.MergedArgument = MergedArgument;
+
+
+/**
+ * TrueNamedArguments are for when we have an argument like --verbose which
+ * has a boolean value, and thus the opposite of '--verbose' is ''.
+ */
+function TrueNamedArgument(arg) {
+ this.arg = arg;
+ this.text = arg.text;
+ this.prefix = arg.prefix;
+ this.suffix = arg.suffix;
+}
+
+TrueNamedArgument.prototype = Object.create(Argument.prototype);
+
+TrueNamedArgument.prototype.type = 'TrueNamedArgument';
+
+Object.defineProperty(TrueNamedArgument.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) {
+ this._assignment = assignment;
+
+ if (this.arg) {
+ this.arg.assignment = assignment;
+ }
+ },
+ enumerable: true
+});
+
+TrueNamedArgument.prototype.getArgs = function() {
+ return [ this.arg ];
+};
+
+TrueNamedArgument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null || !(that instanceof TrueNamedArgument)) {
+ return false;
+ }
+
+ return this.text === that.text &&
+ this.prefix === that.prefix && this.suffix === that.suffix;
+};
+
+/**
+ * As Argument.beget that rebuilds nameArg and valueArg
+ */
+TrueNamedArgument.prototype.beget = function(options) {
+ if (options.text) {
+ console.error('Can\'t change text of a TrueNamedArgument', this, options);
+ }
+
+ options.type = TrueNamedArgument;
+ var begotten = Argument.prototype.beget.call(this, options);
+ begotten.arg = new Argument(begotten.text, begotten.prefix, begotten.suffix);
+ return begotten;
+};
+
+exports.TrueNamedArgument = TrueNamedArgument;
+
+
+/**
+ * FalseNamedArguments are for when we don't have an argument like --verbose
+ * which has a boolean value, and thus the opposite of '' is '--verbose'.
+ */
+function FalseNamedArgument() {
+ this.text = '';
+ this.prefix = '';
+ this.suffix = '';
+}
+
+FalseNamedArgument.prototype = Object.create(Argument.prototype);
+
+FalseNamedArgument.prototype.type = 'FalseNamedArgument';
+
+FalseNamedArgument.prototype.getArgs = function() {
+ return [ ];
+};
+
+FalseNamedArgument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null || !(that instanceof FalseNamedArgument)) {
+ return false;
+ }
+
+ return this.text === that.text &&
+ this.prefix === that.prefix && this.suffix === that.suffix;
+};
+
+exports.FalseNamedArgument = FalseNamedArgument;
+
+
+/**
+ * A named argument is for cases where we have input in one of the following
+ * formats:
+ * <ul>
+ * <li>--param value
+ * <li>-p value
+ * </ul>
+ * We model this as a normal argument but with a long prefix.
+ *
+ * There are 2 ways to construct a NamedArgument. One using 2 Arguments which
+ * are taken to be the argument for the name (e.g. '--param') and one for the
+ * value to assign to that parameter.
+ * Alternatively, you can pass in the text/prefix/suffix values in the same
+ * way as an Argument is constructed. If you do this then you are expected to
+ * assign to nameArg and valueArg before exposing the new NamedArgument.
+ */
+function NamedArgument() {
+ if (typeof arguments[0] === 'string') {
+ this.nameArg = null;
+ this.valueArg = null;
+ this.text = arguments[0];
+ this.prefix = arguments[1];
+ this.suffix = arguments[2];
+ }
+ else if (arguments[1] == null) {
+ this.nameArg = arguments[0];
+ this.valueArg = null;
+ this.text = '';
+ this.prefix = this.nameArg.toString();
+ this.suffix = '';
+ }
+ else {
+ this.nameArg = arguments[0];
+ this.valueArg = arguments[1];
+ this.text = this.valueArg.text;
+ this.prefix = this.nameArg.toString() + this.valueArg.prefix;
+ this.suffix = this.valueArg.suffix;
+ }
+}
+
+NamedArgument.prototype = Object.create(Argument.prototype);
+
+NamedArgument.prototype.type = 'NamedArgument';
+
+Object.defineProperty(NamedArgument.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) {
+ this._assignment = assignment;
+
+ this.nameArg.assignment = assignment;
+ if (this.valueArg != null) {
+ this.valueArg.assignment = assignment;
+ }
+ },
+ enumerable: true
+});
+
+NamedArgument.prototype.getArgs = function() {
+ return this.valueArg ? [ this.nameArg, this.valueArg ] : [ this.nameArg ];
+};
+
+NamedArgument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null) {
+ return false;
+ }
+
+ if (!(that instanceof NamedArgument)) {
+ return false;
+ }
+
+ // We might need to add a check that nameArg and valueArg are the same
+
+ return this.text === that.text &&
+ this.prefix === that.prefix && this.suffix === that.suffix;
+};
+
+/**
+ * As Argument.beget that rebuilds nameArg and valueArg
+ */
+NamedArgument.prototype.beget = function(options) {
+ options.type = NamedArgument;
+ var begotten = Argument.prototype.beget.call(this, options);
+
+ // Cut the prefix into |whitespace|non-whitespace|whitespace+quote so we can
+ // rebuild nameArg and valueArg from the parts
+ var matches = /^([\s]*)([^\s]*)([\s]*['"]?)$/.exec(begotten.prefix);
+
+ if (this.valueArg == null && begotten.text === '') {
+ begotten.nameArg = new Argument(matches[2], matches[1], matches[3]);
+ begotten.valueArg = null;
+ }
+ else {
+ begotten.nameArg = new Argument(matches[2], matches[1], '');
+ begotten.valueArg = new Argument(begotten.text, matches[3], begotten.suffix);
+ }
+
+ return begotten;
+};
+
+exports.NamedArgument = NamedArgument;
+
+
+/**
+ * An argument the groups together a number of plain arguments together so they
+ * can be jointly assigned to a single array parameter
+ */
+function ArrayArgument() {
+ this.args = [];
+}
+
+ArrayArgument.prototype = Object.create(Argument.prototype);
+
+ArrayArgument.prototype.type = 'ArrayArgument';
+
+ArrayArgument.prototype.addArgument = function(arg) {
+ this.args.push(arg);
+};
+
+ArrayArgument.prototype.addArguments = function(args) {
+ Array.prototype.push.apply(this.args, args);
+};
+
+ArrayArgument.prototype.getArguments = function() {
+ return this.args;
+};
+
+Object.defineProperty(ArrayArgument.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) {
+ this._assignment = assignment;
+
+ this.args.forEach(function(arg) {
+ arg.assignment = assignment;
+ }, this);
+ },
+ enumerable: true
+});
+
+ArrayArgument.prototype.getArgs = function() {
+ return this.args;
+};
+
+ArrayArgument.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null) {
+ return false;
+ }
+
+ if (that.type !== 'ArrayArgument') {
+ return false;
+ }
+
+ if (this.args.length !== that.args.length) {
+ return false;
+ }
+
+ for (var i = 0; i < this.args.length; i++) {
+ if (!this.args[i].equals(that.args[i])) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Helper when we're putting arguments back together
+ */
+ArrayArgument.prototype.toString = function() {
+ return '{' + this.args.map(function(arg) {
+ return arg.toString();
+ }, this).join(',') + '}';
+};
+
+exports.ArrayArgument = ArrayArgument;
+
+/**
+ * Some types can detect validity, that is to say they can distinguish between
+ * valid and invalid values.
+ * We might want to change these constants to be numbers for better performance
+ */
+var Status = {
+ /**
+ * The conversion process worked without any problem, and the value is
+ * valid. There are a number of failure states, so the best way to check
+ * for failure is (x !== Status.VALID)
+ */
+ VALID: {
+ toString: function() { return 'VALID'; },
+ valueOf: function() { return 0; }
+ },
+
+ /**
+ * A conversion process failed, however it was noted that the string
+ * provided to 'parse()' could be VALID by the addition of more characters,
+ * so the typing may not be actually incorrect yet, just unfinished.
+ * @see Status.ERROR
+ */
+ INCOMPLETE: {
+ toString: function() { return 'INCOMPLETE'; },
+ valueOf: function() { return 1; }
+ },
+
+ /**
+ * The conversion process did not work, the value should be null and a
+ * reason for failure should have been provided. In addition some
+ * completion values may be available.
+ * @see Status.INCOMPLETE
+ */
+ ERROR: {
+ toString: function() { return 'ERROR'; },
+ valueOf: function() { return 2; }
+ },
+
+ /**
+ * A combined status is the worser of the provided statuses. The statuses
+ * can be provided either as a set of arguments or a single array
+ */
+ combine: function() {
+ var combined = Status.VALID;
+ for (var i = 0; i < arguments.length; i++) {
+ var status = arguments[i];
+ if (Array.isArray(status)) {
+ status = Status.combine.apply(null, status);
+ }
+ if (status > combined) {
+ combined = status;
+ }
+ }
+ return combined;
+ },
+
+ fromString: function(str) {
+ switch (str) {
+ case Status.VALID.toString():
+ return Status.VALID;
+ case Status.INCOMPLETE.toString():
+ return Status.INCOMPLETE;
+ case Status.ERROR.toString():
+ return Status.ERROR;
+ default:
+ throw new Error('\'' + str + '\' is not a status');
+ }
+ }
+};
+
+exports.Status = Status;
+
+
+/**
+ * The type.parse() method converts an Argument into a value, Conversion is
+ * a wrapper to that value.
+ * Conversion is needed to collect a number of properties related to that
+ * conversion in one place, i.e. to handle errors and provide traceability.
+ * @param value The result of the conversion. null if status == VALID
+ * @param arg The data from which the conversion was made
+ * @param status See the Status values [VALID|INCOMPLETE|ERROR] defined above.
+ * The default status is Status.VALID.
+ * @param message If status=ERROR, there should be a message to describe the
+ * error. A message is not needed unless for other statuses, but could be
+ * present for any status including VALID (in the case where we want to note a
+ * warning, for example).
+ * See BUG 664676: GCLI conversion error messages should be localized
+ * @param predictions If status=INCOMPLETE, there could be predictions as to
+ * the options available to complete the input.
+ * We generally expect there to be about 7 predictions (to match human list
+ * comprehension ability) however it is valid to provide up to about 20,
+ * or less. It is the job of the predictor to decide a smart cut-off.
+ * For example if there are 4 very good matches and 4 very poor ones,
+ * probably only the 4 very good matches should be presented.
+ * The predictions are presented either as an array of prediction objects or as
+ * a function which returns this array when called with no parameters.
+ * Each prediction object has the following shape:
+ * {
+ * name: '...', // textual completion. i.e. what the cli uses
+ * value: { ... }, // value behind the textual completion
+ * incomplete: true // this completion is only partial (optional)
+ * }
+ * The 'incomplete' property could be used to denote a valid completion which
+ * could have sub-values (e.g. for tree navigation).
+ */
+function Conversion(value, arg, status, message, predictions) {
+ if (arg == null) {
+ throw new Error('Missing arg');
+ }
+
+ if (predictions != null && typeof predictions !== 'function' &&
+ !Array.isArray(predictions) && typeof predictions.then !== 'function') {
+ throw new Error('predictions exists but is not a promise, function or array');
+ }
+
+ if (status === Status.ERROR && !message) {
+ throw new Error('Conversion has status=ERROR but no message');
+ }
+
+ this.value = value;
+ this.arg = arg;
+ this._status = status || Status.VALID;
+ this.message = message;
+ this.predictions = predictions;
+}
+
+/**
+ * Ensure that all arguments that are part of this conversion know what they
+ * are assigned to.
+ * @param assignment The Assignment (param/conversion link) to inform the
+ * argument about.
+ */
+Object.defineProperty(Conversion.prototype, 'assignment', {
+ get: function() { return this.arg.assignment; },
+ set: function(assignment) { this.arg.assignment = assignment; },
+ enumerable: true
+});
+
+/**
+ * Work out if there is information provided in the contained argument.
+ */
+Conversion.prototype.isDataProvided = function() {
+ return this.arg.type !== 'BlankArgument';
+};
+
+/**
+ * 2 conversions are equal if and only if their args are equal (argEquals) and
+ * their values are equal (valueEquals).
+ * @param that The conversion object to compare against.
+ */
+Conversion.prototype.equals = function(that) {
+ if (this === that) {
+ return true;
+ }
+ if (that == null) {
+ return false;
+ }
+ return this.valueEquals(that) && this.argEquals(that);
+};
+
+/**
+ * Check that the value in this conversion is strict equal to the value in the
+ * provided conversion.
+ * @param that The conversion to compare values with
+ */
+Conversion.prototype.valueEquals = function(that) {
+ return that != null && this.value === that.value;
+};
+
+/**
+ * Check that the argument in this conversion is equal to the value in the
+ * provided conversion as defined by the argument (i.e. arg.equals).
+ * @param that The conversion to compare arguments with
+ */
+Conversion.prototype.argEquals = function(that) {
+ return that == null ? false : this.arg.equals(that.arg);
+};
+
+/**
+ * Accessor for the status of this conversion
+ */
+Conversion.prototype.getStatus = function(arg) {
+ return this._status;
+};
+
+/**
+ * Defined by the toString() value provided by the argument
+ */
+Conversion.prototype.toString = function() {
+ return this.arg.toString();
+};
+
+/**
+ * If status === INCOMPLETE, then we may be able to provide predictions as to
+ * how the argument can be completed.
+ * @return An array of items, or a promise of an array of items, where each
+ * item is an object with the following properties:
+ * - name (mandatory): Displayed to the user, and typed in. No whitespace
+ * - description (optional): Short string for display in a tool-tip
+ * - manual (optional): Longer description which details usage
+ * - incomplete (optional): Indicates that the prediction if used should not
+ * be considered necessarily sufficient, which typically will mean that the
+ * UI should not append a space to the completion
+ * - value (optional): If a value property is present, this will be used as the
+ * value of the conversion, otherwise the item itself will be used.
+ */
+Conversion.prototype.getPredictions = function(context) {
+ if (typeof this.predictions === 'function') {
+ return this.predictions(context);
+ }
+ return Promise.resolve(this.predictions || []);
+};
+
+/**
+ * Return a promise of an index constrained by the available predictions.
+ * i.e. (index % predicitons.length)
+ * This code can probably be removed when the Firefox developer toolbar isn't
+ * needed any more.
+ */
+Conversion.prototype.constrainPredictionIndex = function(context, index) {
+ if (index == null) {
+ return Promise.resolve();
+ }
+
+ return this.getPredictions(context).then(function(value) {
+ if (value.length === 0) {
+ return undefined;
+ }
+
+ index = index % value.length;
+ if (index < 0) {
+ index = value.length + index;
+ }
+ return index;
+ }.bind(this));
+};
+
+/**
+ * Constant to allow everyone to agree on the maximum number of predictions
+ * that should be provided. We actually display 1 less than this number.
+ */
+Conversion.maxPredictions = 9;
+
+exports.Conversion = Conversion;
+
+
+/**
+ * ArrayConversion is a special Conversion, needed because arrays are converted
+ * member by member rather then as a whole, which means we can track the
+ * conversion if individual array elements. So an ArrayConversion acts like a
+ * normal Conversion (which is needed as Assignment requires a Conversion) but
+ * it can also be devolved into a set of Conversions for each array member.
+ */
+function ArrayConversion(conversions, arg) {
+ this.arg = arg;
+ this.conversions = conversions;
+ this.value = conversions.map(function(conversion) {
+ return conversion.value;
+ }, this);
+
+ this._status = Status.combine(conversions.map(function(conversion) {
+ return conversion.getStatus();
+ }));
+
+ // This message is just for reporting errors like "not enough values"
+ // rather that for problems with individual values.
+ this.message = '';
+
+ // Predictions are generally provided by individual values
+ this.predictions = [];
+}
+
+ArrayConversion.prototype = Object.create(Conversion.prototype);
+
+Object.defineProperty(ArrayConversion.prototype, 'assignment', {
+ get: function() { return this._assignment; },
+ set: function(assignment) {
+ this._assignment = assignment;
+
+ this.conversions.forEach(function(conversion) {
+ conversion.assignment = assignment;
+ }, this);
+ },
+ enumerable: true
+});
+
+ArrayConversion.prototype.getStatus = function(arg) {
+ if (arg && arg.conversion) {
+ return arg.conversion.getStatus();
+ }
+ return this._status;
+};
+
+ArrayConversion.prototype.isDataProvided = function() {
+ return this.conversions.length > 0;
+};
+
+ArrayConversion.prototype.valueEquals = function(that) {
+ if (that == null) {
+ return false;
+ }
+
+ if (!(that instanceof ArrayConversion)) {
+ throw new Error('Can\'t compare values with non ArrayConversion');
+ }
+
+ if (this.value === that.value) {
+ return true;
+ }
+
+ if (this.value.length !== that.value.length) {
+ return false;
+ }
+
+ for (var i = 0; i < this.conversions.length; i++) {
+ if (!this.conversions[i].valueEquals(that.conversions[i])) {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+ArrayConversion.prototype.toString = function() {
+ return '[ ' + this.conversions.map(function(conversion) {
+ return conversion.toString();
+ }, this).join(', ') + ' ]';
+};
+
+exports.ArrayConversion = ArrayConversion;
+
+
+/**
+ * Most of our types are 'static' e.g. there is only one type of 'string',
+ * however some types like 'selection' and 'delegate' are customizable.
+ * The basic Type type isn't useful, but does provide documentation about what
+ * types do.
+ */
+function Type() {
+}
+
+/**
+ * Get a JSONable data structure that entirely describes this type.
+ * commandName and paramName are the names of the command and parameter which
+ * we are remoting to help the server get back to the remoted action.
+ */
+Type.prototype.getSpec = function(commandName, paramName) {
+ throw new Error('Not implemented');
+};
+
+/**
+ * Convert the given <tt>value</tt> to a string representation.
+ * Where possible, there should be round-tripping between values and their
+ * string representations.
+ * @param value The object to convert into a string
+ * @param context An ExecutionContext to allow basic Requisition access
+ */
+Type.prototype.stringify = function(value, context) {
+ throw new Error('Not implemented');
+};
+
+/**
+ * Convert the given <tt>arg</tt> to an instance of this type.
+ * Where possible, there should be round-tripping between values and their
+ * string representations.
+ * @param arg An instance of <tt>Argument</tt> to convert.
+ * @param context An ExecutionContext to allow basic Requisition access
+ * @return Conversion
+ */
+Type.prototype.parse = function(arg, context) {
+ throw new Error('Not implemented');
+};
+
+/**
+ * A convenience method for times when you don't have an argument to parse
+ * but instead have a string.
+ * @see #parse(arg)
+ */
+Type.prototype.parseString = function(str, context) {
+ return this.parse(new Argument(str), context);
+};
+
+/**
+ * The plug-in system, and other things need to know what this type is
+ * called. The name alone is not enough to fully specify a type. Types like
+ * 'selection' and 'delegate' need extra data, however this function returns
+ * only the name, not the extra data.
+ */
+Type.prototype.name = undefined;
+
+/**
+ * If there is some concept of a lower or higher value, return it,
+ * otherwise return undefined.
+ * @param by number indicating how much to nudge by, usually +1 or -1 which is
+ * caused by the user pressing the UP/DOWN keys with the cursor in this type
+ */
+Type.prototype.nudge = function(value, by, context) {
+ return undefined;
+};
+
+/**
+ * The 'blank value' of most types is 'undefined', but there are exceptions;
+ * This allows types to specify a better conversion from empty string than
+ * 'undefined'.
+ * 2 known examples of this are boolean -> false and array -> []
+ */
+Type.prototype.getBlank = function(context) {
+ return new Conversion(undefined, new BlankArgument(), Status.INCOMPLETE, '');
+};
+
+/**
+ * This is something of a hack for the benefit of DelegateType which needs to
+ * be able to lie about it's type for fields to accept it as one of their own.
+ * Sub-types can ignore this unless they're DelegateType.
+ * @param context An ExecutionContext to allow basic Requisition access
+ */
+Type.prototype.getType = function(context) {
+ return this;
+};
+
+/**
+ * addItems allows registrations of a number of things. This allows it to know
+ * what type of item, and how it should be registered.
+ */
+Type.prototype.item = 'type';
+
+exports.Type = Type;
+
+/**
+ * 'Types' represents a registry of types
+ */
+function Types() {
+ // Invariant: types[name] = type.name
+ this._registered = {};
+}
+
+exports.Types = Types;
+
+/**
+ * Get an array of the names of registered types
+ */
+Types.prototype.getTypeNames = function() {
+ return Object.keys(this._registered);
+};
+
+/**
+ * Add a new type to the list available to the system.
+ * You can pass 2 things to this function - either an instance of Type, in
+ * which case we return this instance when #getType() is called with a 'name'
+ * that matches type.name.
+ * Also you can pass in a constructor (i.e. function) in which case when
+ * #getType() is called with a 'name' that matches Type.prototype.name we will
+ * pass the typeSpec into this constructor.
+ */
+Types.prototype.add = function(type) {
+ if (typeof type === 'object') {
+ if (!type.name) {
+ throw new Error('All registered types must have a name');
+ }
+
+ if (type instanceof Type) {
+ this._registered[type.name] = type;
+ }
+ else {
+ var name = type.name;
+ var parent = type.parent;
+ type.name = parent;
+ delete type.parent;
+
+ this._registered[name] = this.createType(type);
+
+ type.name = name;
+ type.parent = parent;
+ }
+ }
+ else if (typeof type === 'function') {
+ if (!type.prototype.name) {
+ throw new Error('All registered types must have a name');
+ }
+ this._registered[type.prototype.name] = type;
+ }
+ else {
+ throw new Error('Unknown type: ' + type);
+ }
+};
+
+/**
+ * Remove a type from the list available to the system
+ */
+Types.prototype.remove = function(type) {
+ delete this._registered[type.name];
+};
+
+/**
+ * Find a previously registered type
+ */
+Types.prototype.createType = function(typeSpec) {
+ if (typeof typeSpec === 'string') {
+ typeSpec = { name: typeSpec };
+ }
+
+ if (typeof typeSpec !== 'object') {
+ throw new Error('Can\'t extract type from ' + typeSpec);
+ }
+
+ var NewTypeCtor, newType;
+ if (typeSpec.name == null || typeSpec.name == 'type') {
+ NewTypeCtor = Type;
+ }
+ else {
+ NewTypeCtor = this._registered[typeSpec.name];
+ }
+
+ if (!NewTypeCtor) {
+ console.error('Known types: ' + Object.keys(this._registered).join(', '));
+ throw new Error('Unknown type: \'' + typeSpec.name + '\'');
+ }
+
+ if (typeof NewTypeCtor === 'function') {
+ newType = new NewTypeCtor(typeSpec);
+ }
+ else {
+ // clone 'type'
+ newType = {};
+ util.copyProperties(NewTypeCtor, newType);
+ }
+
+ // Copy the properties of typeSpec onto the new type
+ util.copyProperties(typeSpec, newType);
+
+ // Several types need special powers to create child types
+ newType.types = this;
+
+ if (typeof NewTypeCtor !== 'function') {
+ if (typeof newType.constructor === 'function') {
+ newType.constructor();
+ }
+ }
+
+ return newType;
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/types/union.js b/devtools/shared/gcli/source/lib/gcli/types/union.js
new file mode 100644
index 000000000..c98d3411b
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/union.js
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var Conversion = require('./types').Conversion;
+var Status = require('./types').Status;
+
+exports.items = [
+ {
+ // The union type allows for a combination of different parameter types.
+ item: 'type',
+ name: 'union',
+ hasPredictions: true,
+
+ constructor: function() {
+ // Get the properties of the type. Later types in the list should always
+ // be more general, so 'catch all' types like string must be last
+ this.alternatives = this.alternatives.map(function(typeData) {
+ return this.types.createType(typeData);
+ }.bind(this));
+ },
+
+ getSpec: function(command, param) {
+ var spec = { name: 'union', alternatives: [] };
+ this.alternatives.forEach(function(type) {
+ spec.alternatives.push(type.getSpec(command, param));
+ }.bind(this));
+ return spec;
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+
+ var type = this.alternatives.find(function(typeData) {
+ return typeData.name === value.type;
+ });
+
+ return type.stringify(value[value.type], context);
+ },
+
+ parse: function(arg, context) {
+ var conversionPromises = this.alternatives.map(function(type) {
+ return type.parse(arg, context);
+ }.bind(this));
+
+ return Promise.all(conversionPromises).then(function(conversions) {
+ // Find a list of the predictions made by any conversion
+ var predictionPromises = conversions.map(function(conversion) {
+ return conversion.getPredictions(context);
+ }.bind(this));
+
+ return Promise.all(predictionPromises).then(function(allPredictions) {
+ // Take one prediction from each set of predictions, ignoring
+ // duplicates, until we've got up to Conversion.maxPredictions
+ var maxIndex = allPredictions.reduce(function(prev, prediction) {
+ return Math.max(prev, prediction.length);
+ }.bind(this), 0);
+ var predictions = [];
+
+ indexLoop:
+ for (var index = 0; index < maxIndex; index++) {
+ for (var p = 0; p <= allPredictions.length; p++) {
+ if (predictions.length >= Conversion.maxPredictions) {
+ break indexLoop;
+ }
+
+ if (allPredictions[p] != null) {
+ var prediction = allPredictions[p][index];
+ if (prediction != null && predictions.indexOf(prediction) === -1) {
+ predictions.push(prediction);
+ }
+ }
+ }
+ }
+
+ var bestStatus = Status.ERROR;
+ var value;
+ for (var i = 0; i < conversions.length; i++) {
+ var conversion = conversions[i];
+ var thisStatus = conversion.getStatus(arg);
+ if (thisStatus < bestStatus) {
+ bestStatus = thisStatus;
+ }
+ if (bestStatus === Status.VALID) {
+ var type = this.alternatives[i].name;
+ value = { type: type };
+ value[type] = conversion.value;
+ break;
+ }
+ }
+
+ var msg = (bestStatus === Status.VALID) ?
+ '' :
+ l10n.lookupFormat('typesSelectionNomatch', [ arg.text ]);
+ return new Conversion(value, arg, bestStatus, msg, predictions);
+ }.bind(this));
+ }.bind(this));
+ },
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/types/url.js b/devtools/shared/gcli/source/lib/gcli/types/url.js
new file mode 100644
index 000000000..73895d66b
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/types/url.js
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var host = require('../util/host');
+var Status = require('./types').Status;
+var Conversion = require('./types').Conversion;
+
+exports.items = [
+ {
+ item: 'type',
+ name: 'url',
+
+ getSpec: function() {
+ return 'url';
+ },
+
+ stringify: function(value, context) {
+ if (value == null) {
+ return '';
+ }
+ return value.href;
+ },
+
+ parse: function(arg, context) {
+ var conversion;
+
+ try {
+ var url = host.createUrl(arg.text);
+ conversion = new Conversion(url, arg);
+ }
+ catch (ex) {
+ var predictions = [];
+ var status = Status.ERROR;
+
+ // Maybe the URL was missing a scheme?
+ if (arg.text.indexOf('://') === -1) {
+ [ 'http', 'https' ].forEach(function(scheme) {
+ try {
+ var http = host.createUrl(scheme + '://' + arg.text);
+ predictions.push({ name: http.href, value: http });
+ }
+ catch (ex) {
+ // Ignore
+ }
+ }.bind(this));
+
+ // Try to create a URL with the current page as a base ref
+ if ('window' in context.environment) {
+ try {
+ var base = context.environment.window.location.href;
+ var localized = host.createUrl(arg.text, base);
+ predictions.push({ name: localized.href, value: localized });
+ }
+ catch (ex) {
+ // Ignore
+ }
+ }
+ }
+
+ if (predictions.length > 0) {
+ status = Status.INCOMPLETE;
+ }
+
+ conversion = new Conversion(undefined, arg, status,
+ ex.message, predictions);
+ }
+
+ return Promise.resolve(conversion);
+ }
+ }
+];
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/focus.js b/devtools/shared/gcli/source/lib/gcli/ui/focus.js
new file mode 100644
index 000000000..6d3761cca
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/focus.js
@@ -0,0 +1,403 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var l10n = require('../util/l10n');
+
+/**
+ * Record how much help the user wants from the tooltip
+ */
+var Eagerness = {
+ NEVER: 1,
+ SOMETIMES: 2,
+ ALWAYS: 3
+};
+
+/**
+ * Export the eagerHelper setting
+ */
+exports.items = [
+ {
+ item: 'setting',
+ name: 'eagerHelper',
+ type: {
+ name: 'selection',
+ lookup: [
+ { name: 'never', value: Eagerness.NEVER },
+ { name: 'sometimes', value: Eagerness.SOMETIMES },
+ { name: 'always', value: Eagerness.ALWAYS }
+ ]
+ },
+ defaultValue: Eagerness.SOMETIMES,
+ description: l10n.lookup('eagerHelperDesc'),
+ ignoreTypeDifference: true
+ }
+];
+
+/**
+ * FocusManager solves the problem of tracking focus among a set of nodes.
+ * The specific problem we are solving is when the hint element must be visible
+ * if either the command line or any of the inputs in the hint element has the
+ * focus, and invisible at other times, without hiding and showing the hint
+ * element even briefly as the focus changes between them.
+ * It does this simply by postponing the hide events by 250ms to see if
+ * something else takes focus.
+ */
+function FocusManager(document, settings) {
+ if (document == null) {
+ throw new Error('document == null');
+ }
+
+ this.document = document;
+ this.settings = settings;
+ this.debug = false;
+ this.blurDelay = 150;
+ this.window = this.document.defaultView;
+
+ this._blurDelayTimeout = null; // Result of setTimeout in delaying a blur
+ this._monitoredElements = []; // See addMonitoredElement()
+
+ this._isError = false;
+ this._hasFocus = false;
+ this._helpRequested = false;
+ this._recentOutput = false;
+
+ this.onVisibilityChange = util.createEvent('FocusManager.onVisibilityChange');
+
+ this._focused = this._focused.bind(this);
+ if (this.document.addEventListener) {
+ this.document.addEventListener('focus', this._focused, true);
+ }
+
+ var eagerHelper = this.settings.get('eagerHelper');
+ eagerHelper.onChange.add(this._eagerHelperChanged, this);
+
+ this.isTooltipVisible = undefined;
+ this.isOutputVisible = undefined;
+ this._checkShow();
+}
+
+/**
+ * Avoid memory leaks
+ */
+FocusManager.prototype.destroy = function() {
+ var eagerHelper = this.settings.get('eagerHelper');
+ eagerHelper.onChange.remove(this._eagerHelperChanged, this);
+
+ this.document.removeEventListener('focus', this._focused, true);
+
+ for (var i = 0; i < this._monitoredElements.length; i++) {
+ var monitor = this._monitoredElements[i];
+ console.error('Hanging monitored element: ', monitor.element);
+
+ monitor.element.removeEventListener('focus', monitor.onFocus, true);
+ monitor.element.removeEventListener('blur', monitor.onBlur, true);
+ }
+
+ if (this._blurDelayTimeout) {
+ this.window.clearTimeout(this._blurDelayTimeout);
+ this._blurDelayTimeout = null;
+ }
+
+ this._focused = undefined;
+ this.document = undefined;
+ this.settings = undefined;
+ this.window = undefined;
+};
+
+/**
+ * The easy way to include an element in the set of things that are part of the
+ * aggregate focus. Using [add|remove]MonitoredElement() is a simpler way of
+ * option than calling report[Focus|Blur]()
+ * @param element The element on which to track focus|blur events
+ * @param where Optional source string for debugging only
+ */
+FocusManager.prototype.addMonitoredElement = function(element, where) {
+ if (this.debug) {
+ console.log('FocusManager.addMonitoredElement(' + (where || 'unknown') + ')');
+ }
+
+ var monitor = {
+ element: element,
+ where: where,
+ onFocus: function() { this._reportFocus(where); }.bind(this),
+ onBlur: function() { this._reportBlur(where); }.bind(this)
+ };
+
+ element.addEventListener('focus', monitor.onFocus, true);
+ element.addEventListener('blur', monitor.onBlur, true);
+
+ if (this.document.activeElement === element) {
+ this._reportFocus(where);
+ }
+
+ this._monitoredElements.push(monitor);
+};
+
+/**
+ * Undo the effects of addMonitoredElement()
+ * @param element The element to stop tracking
+ * @param where Optional source string for debugging only
+ */
+FocusManager.prototype.removeMonitoredElement = function(element, where) {
+ if (this.debug) {
+ console.log('FocusManager.removeMonitoredElement(' + (where || 'unknown') + ')');
+ }
+
+ this._monitoredElements = this._monitoredElements.filter(function(monitor) {
+ if (monitor.element === element) {
+ element.removeEventListener('focus', monitor.onFocus, true);
+ element.removeEventListener('blur', monitor.onBlur, true);
+ return false;
+ }
+ return true;
+ });
+};
+
+/**
+ * Monitor for new command executions
+ */
+FocusManager.prototype.updatePosition = function(dimensions) {
+ var ev = {
+ tooltipVisible: this.isTooltipVisible,
+ outputVisible: this.isOutputVisible,
+ dimensions: dimensions
+ };
+ this.onVisibilityChange(ev);
+};
+
+/**
+ * Monitor for new command executions
+ */
+FocusManager.prototype.outputted = function() {
+ this._recentOutput = true;
+ this._helpRequested = false;
+ this._checkShow();
+};
+
+/**
+ * We take a focus event anywhere to be an indication that we might be about
+ * to lose focus
+ */
+FocusManager.prototype._focused = function() {
+ this._reportBlur('document');
+};
+
+/**
+ * Some component has received a 'focus' event. This sets the internal status
+ * straight away and informs the listeners
+ * @param where Optional source string for debugging only
+ */
+FocusManager.prototype._reportFocus = function(where) {
+ if (this.debug) {
+ console.log('FocusManager._reportFocus(' + (where || 'unknown') + ')');
+ }
+
+ if (this._blurDelayTimeout) {
+ if (this.debug) {
+ console.log('FocusManager.cancelBlur');
+ }
+ this.window.clearTimeout(this._blurDelayTimeout);
+ this._blurDelayTimeout = null;
+ }
+
+ if (!this._hasFocus) {
+ this._hasFocus = true;
+ }
+ this._checkShow();
+};
+
+/**
+ * Some component has received a 'blur' event. This waits for a while to see if
+ * we are going to get any subsequent 'focus' events and then sets the internal
+ * status and informs the listeners
+ * @param where Optional source string for debugging only
+ */
+FocusManager.prototype._reportBlur = function(where) {
+ if (this.debug) {
+ console.log('FocusManager._reportBlur(' + where + ')');
+ }
+
+ if (this._hasFocus) {
+ if (this._blurDelayTimeout) {
+ if (this.debug) {
+ console.log('FocusManager.blurPending');
+ }
+ return;
+ }
+
+ this._blurDelayTimeout = this.window.setTimeout(function() {
+ if (this.debug) {
+ console.log('FocusManager.blur');
+ }
+ this._hasFocus = false;
+ this._checkShow();
+ this._blurDelayTimeout = null;
+ }.bind(this), this.blurDelay);
+ }
+};
+
+/**
+ * The setting has changed
+ */
+FocusManager.prototype._eagerHelperChanged = function() {
+ this._checkShow();
+};
+
+/**
+ * The terminal tells us about keyboard events so we can decide to delay
+ * showing the tooltip element
+ */
+FocusManager.prototype.onInputChange = function() {
+ this._recentOutput = false;
+ this._checkShow();
+};
+
+/**
+ * Generally called for something like a F1 key press, when the user explicitly
+ * wants help
+ */
+FocusManager.prototype.helpRequest = function() {
+ if (this.debug) {
+ console.log('FocusManager.helpRequest');
+ }
+
+ this._helpRequested = true;
+ this._recentOutput = false;
+ this._checkShow();
+};
+
+/**
+ * Generally called for something like a ESC key press, when the user explicitly
+ * wants to get rid of the help
+ */
+FocusManager.prototype.removeHelp = function() {
+ if (this.debug) {
+ console.log('FocusManager.removeHelp');
+ }
+
+ this._importantFieldFlag = false;
+ this._isError = false;
+ this._helpRequested = false;
+ this._recentOutput = false;
+ this._checkShow();
+};
+
+/**
+ * Set to true whenever a field thinks it's output is important
+ */
+FocusManager.prototype.setImportantFieldFlag = function(flag) {
+ if (this.debug) {
+ console.log('FocusManager.setImportantFieldFlag', flag);
+ }
+ this._importantFieldFlag = flag;
+ this._checkShow();
+};
+
+/**
+ * Set to true whenever a field thinks it's output is important
+ */
+FocusManager.prototype.setError = function(isError) {
+ if (this.debug) {
+ console.log('FocusManager._isError', isError);
+ }
+ this._isError = isError;
+ this._checkShow();
+};
+
+/**
+ * Helper to compare the current showing state with the value calculated by
+ * _shouldShow() and take appropriate action
+ */
+FocusManager.prototype._checkShow = function() {
+ var fire = false;
+ var ev = {
+ tooltipVisible: this.isTooltipVisible,
+ outputVisible: this.isOutputVisible
+ };
+
+ var showTooltip = this._shouldShowTooltip();
+ if (this.isTooltipVisible !== showTooltip.visible) {
+ ev.tooltipVisible = this.isTooltipVisible = showTooltip.visible;
+ fire = true;
+ }
+
+ var showOutput = this._shouldShowOutput();
+ if (this.isOutputVisible !== showOutput.visible) {
+ ev.outputVisible = this.isOutputVisible = showOutput.visible;
+ fire = true;
+ }
+
+ if (fire) {
+ if (this.debug) {
+ console.log('FocusManager.onVisibilityChange', ev);
+ }
+ this.onVisibilityChange(ev);
+ }
+};
+
+/**
+ * Calculate if we should be showing or hidden taking into account all the
+ * available inputs
+ */
+FocusManager.prototype._shouldShowTooltip = function() {
+ var eagerHelper = this.settings.get('eagerHelper');
+ if (eagerHelper.value === Eagerness.NEVER) {
+ return { visible: false, reason: 'eagerHelperNever' };
+ }
+
+ if (eagerHelper.value === Eagerness.ALWAYS) {
+ return { visible: true, reason: 'eagerHelperAlways' };
+ }
+
+ if (!this._hasFocus) {
+ return { visible: false, reason: 'notHasFocus' };
+ }
+
+ if (this._isError) {
+ return { visible: true, reason: 'isError' };
+ }
+
+ if (this._helpRequested) {
+ return { visible: true, reason: 'helpRequested' };
+ }
+
+ if (this._importantFieldFlag) {
+ return { visible: true, reason: 'importantFieldFlag' };
+ }
+
+ return { visible: false, reason: 'default' };
+};
+
+/**
+ * Calculate if we should be showing or hidden taking into account all the
+ * available inputs
+ */
+FocusManager.prototype._shouldShowOutput = function() {
+ if (!this._hasFocus) {
+ return { visible: false, reason: 'notHasFocus' };
+ }
+
+ if (this._recentOutput) {
+ return { visible: true, reason: 'recentOutput' };
+ }
+
+ return { visible: false, reason: 'default' };
+};
+
+exports.FocusManager = FocusManager;
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/history.js b/devtools/shared/gcli/source/lib/gcli/ui/history.js
new file mode 100644
index 000000000..a9d4b868c
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/history.js
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/**
+ * A History object remembers commands that have been entered in the past and
+ * provides an API for accessing them again.
+ * See Bug 681340: Search through history (like C-r in bash)?
+ */
+function History() {
+ // This is the actual buffer where previous commands are kept.
+ // 'this._buffer[0]' should always be equal the empty string. This is so
+ // that when you try to go in to the "future", you will just get an empty
+ // command.
+ this._buffer = [''];
+
+ // This is an index in to the history buffer which points to where we
+ // currently are in the history.
+ this._current = 0;
+}
+
+/**
+ * Avoid memory leaks
+ */
+History.prototype.destroy = function() {
+ this._buffer = undefined;
+};
+
+/**
+ * Record and save a new command in the history.
+ */
+History.prototype.add = function(command) {
+ this._buffer.splice(1, 0, command);
+ this._current = 0;
+};
+
+/**
+ * Get the next (newer) command from history.
+ */
+History.prototype.forward = function() {
+ if (this._current > 0 ) {
+ this._current--;
+ }
+ return this._buffer[this._current];
+};
+
+/**
+ * Get the previous (older) item from history.
+ */
+History.prototype.backward = function() {
+ if (this._current < this._buffer.length - 1) {
+ this._current++;
+ }
+ return this._buffer[this._current];
+};
+
+exports.History = History;
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/intro.js b/devtools/shared/gcli/source/lib/gcli/ui/intro.js
new file mode 100644
index 000000000..9abf51db6
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/intro.js
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var l10n = require('../util/l10n');
+var Output = require('../cli').Output;
+var view = require('./view');
+
+/**
+ * Record if the user has clicked on 'Got It!'
+ */
+exports.items = [
+ {
+ item: 'setting',
+ name: 'hideIntro',
+ type: 'boolean',
+ description: l10n.lookup('hideIntroDesc'),
+ defaultValue: false
+ }
+];
+
+/**
+ * Called when the UI is ready to add a welcome message to the output
+ */
+exports.maybeShowIntro = function (commandOutputManager, conversionContext,
+ outputPanel) {
+ var hideIntro = conversionContext.system.settings.get('hideIntro');
+ if (hideIntro.value) {
+ return;
+ }
+
+ var output = new Output(conversionContext);
+ output.type = 'view';
+ commandOutputManager.onOutput({ output: output });
+
+ var viewData = this.createView(null, conversionContext, true, outputPanel);
+
+ output.complete({ isTypedData: true, type: 'view', data: viewData });
+};
+
+/**
+ * Called when the UI is ready to add a welcome message to the output
+ */
+exports.createView = function (ignoreArgs, conversionContext, showHideButton,
+ outputPanel) {
+ return view.createView({
+ html:
+ '<div save="${mainDiv}">\n' +
+ ' <p>${l10n.introTextOpening3}</p>\n' +
+ '\n' +
+ ' <p>\n' +
+ ' ${l10n.introTextCommands}\n' +
+ ' <span class="gcli-out-shortcut" onclick="${onclick}"\n' +
+ ' ondblclick="${ondblclick}"\n' +
+ ' data-command="help">help</span>${l10n.introTextKeys2}\n' +
+ ' <code>${l10n.introTextF1Escape}</code>.\n' +
+ ' </p>\n' +
+ '\n' +
+ ' <button onclick="${onGotIt}"\n' +
+ ' if="${showHideButton}">${l10n.introTextGo}</button>\n' +
+ '</div>',
+ options: { stack: 'intro.html' },
+ data: {
+ l10n: l10n.propertyLookup,
+ onclick: conversionContext.update,
+ ondblclick: conversionContext.updateExec,
+ showHideButton: showHideButton,
+ onGotIt: function(ev) {
+ var settings = conversionContext.system.settings;
+ var hideIntro = settings.get('hideIntro');
+ hideIntro.value = true;
+ outputPanel.remove();
+ }
+ }
+ });
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/menu.css b/devtools/shared/gcli/source/lib/gcli/ui/menu.css
new file mode 100644
index 000000000..913ee1eec
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/menu.css
@@ -0,0 +1,69 @@
+
+.gcli-menu {
+ overflow: hidden;
+ font-size: 90%;
+}
+
+.gcli-menu:not(:first-of-type) {
+ padding-top: 5px;
+}
+
+.gcli-menu-vert {
+ white-space: nowrap;
+ max-width: 22em;
+ display: inline-flex;
+ padding-inline-end: 20px;
+ -webkit-padding-end: 20px;
+}
+
+.gcli-menu-names {
+ white-space: nowrap;
+ flex-grow: 0;
+ flex-shrink: 0;
+}
+
+.gcli-menu-descs {
+ flex-grow: 1;
+ flex-shrink: 1;
+}
+
+.gcli-menu-name,
+.gcli-menu-desc {
+ white-space: nowrap;
+}
+
+.gcli-menu-name {
+ padding-inline-start: 2px;
+ -webkit-padding-start: 2px;
+ padding-inline-end: 8px;
+ -webkit-padding-end: 8px;
+}
+
+.gcli-menu-desc {
+ padding-inline-end: 2px;
+ -webkit-padding-end: 2px;
+ color: #777;
+ text-overflow: ellipsis;
+ overflow: hidden;
+}
+
+.gcli-menu-name:hover,
+.gcli-menu-desc:hover {
+ background-color: rgba(0, 0, 0, 0.05);
+}
+
+.gcli-menu-highlight,
+.gcli-menu-highlight.gcli-menu-option:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.gcli-menu-typed {
+ color: #FF6600;
+}
+
+.gcli-menu-more {
+ font-size: 80%;
+ width: 8em;
+ display: inline-flex;
+ vertical-align: bottom;
+}
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/menu.html b/devtools/shared/gcli/source/lib/gcli/ui/menu.html
new file mode 100644
index 000000000..ab6a690f4
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/menu.html
@@ -0,0 +1,20 @@
+
+<div>
+ <div class="gcli-menu-template" aria-live="polite">
+ <div class="gcli-menu-names">
+ <div class="gcli-menu-name"
+ foreach="item in ${items}"
+ data-name="${item.name}"
+ onclick="${onItemClickInternal}"
+ title="${item.manual}">${item.highlight}</div>
+ </div>
+ <div class="gcli-menu-descs">
+ <div class="gcli-menu-desc"
+ foreach="item in ${items}"
+ data-name="${item.name}"
+ onclick="${onItemClickInternal}"
+ title="${item.manual}">${item.description}</div>
+ </div>
+ </div>
+ <div class="gcli-menu-more" if="${hasMore}">${l10n.fieldMenuMore}</div>
+</div>
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/menu.js b/devtools/shared/gcli/source/lib/gcli/ui/menu.js
new file mode 100644
index 000000000..52b415384
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/menu.js
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var l10n = require('../util/l10n');
+var domtemplate = require('../util/domtemplate');
+var host = require('../util/host');
+
+/**
+ * Shared promises for loading resource files
+ */
+var menuCssPromise;
+var menuHtmlPromise;
+
+/**
+ * Menu is a display of the commands that are possible given the state of a
+ * requisition.
+ * @param options A way to customize the menu display.
+ * - document: The document to use in creating widgets
+ * - maxPredictions (default=8): The maximum predictions to show at one time
+ * If more are requested, a message will be displayed asking the user to
+ * continue typing to narrow the list of options
+ */
+function Menu(options) {
+ options = options || {};
+ this.document = options.document || document;
+ this.maxPredictions = options.maxPredictions || 8;
+
+ // Keep track of any highlighted items
+ this._choice = null;
+
+ // FF can be really hard to debug if doc is null, so we check early on
+ if (!this.document) {
+ throw new Error('No document');
+ }
+
+ this.element = util.createElement(this.document, 'div');
+ this.element.classList.add('gcli-menu');
+
+ if (menuCssPromise == null) {
+ menuCssPromise = host.staticRequire(module, './menu.css');
+ }
+ menuCssPromise.then(function(menuCss) {
+ // Pull the HTML into the DOM, but don't add it to the document
+ if (menuCss != null) {
+ util.importCss(menuCss, this.document, 'gcli-menu');
+ }
+ }.bind(this), console.error);
+
+ this.templateOptions = { blankNullUndefined: true, stack: 'menu.html' };
+ if (menuHtmlPromise == null) {
+ menuHtmlPromise = host.staticRequire(module, './menu.html');
+ }
+ menuHtmlPromise.then(function(menuHtml) {
+ if (this.document == null) {
+ return; // destroy() has been called
+ }
+
+ this.template = host.toDom(this.document, menuHtml);
+ }.bind(this), console.error);
+
+ // Contains the items that should be displayed
+ this.items = [];
+
+ this.onItemClick = util.createEvent('Menu.onItemClick');
+}
+
+/**
+ * Allow the template engine to get at localization strings
+ */
+Menu.prototype.l10n = l10n.propertyLookup;
+
+/**
+ * Avoid memory leaks
+ */
+Menu.prototype.destroy = function() {
+ this.element = undefined;
+ this.template = undefined;
+ this.document = undefined;
+ this.items = undefined;
+};
+
+/**
+ * The default is to do nothing when someone clicks on the menu.
+ * This is called from template.html
+ * @param ev The click event from the browser
+ */
+Menu.prototype.onItemClickInternal = function(ev) {
+ var name = ev.currentTarget.getAttribute('data-name');
+ if (!name) {
+ var named = ev.currentTarget.querySelector('[data-name]');
+ name = named.getAttribute('data-name');
+ }
+ this.onItemClick({ name: name });
+};
+
+/**
+ * Act as though someone clicked on the selected item
+ */
+Menu.prototype.clickSelected = function() {
+ this.onItemClick({ name: this.selected });
+};
+
+/**
+ * What is the currently selected item?
+ */
+Object.defineProperty(Menu.prototype, 'isSelected', {
+ get: function() {
+ return this.selected != null;
+ },
+ enumerable: true
+});
+
+/**
+ * What is the currently selected item?
+ */
+Object.defineProperty(Menu.prototype, 'selected', {
+ get: function() {
+ var item = this.element.querySelector('.gcli-menu-name.gcli-menu-highlight');
+ if (!item) {
+ return null;
+ }
+ return item.textContent;
+ },
+ enumerable: true
+});
+
+/**
+ * Display a number of items in the menu (or hide the menu if there is nothing
+ * to display)
+ * @param items The items to show in the menu
+ * @param match Matching text to highlight in the output
+ */
+Menu.prototype.show = function(items, match) {
+ // If the HTML hasn't loaded yet then just don't show a menu
+ if (this.template == null) {
+ return;
+ }
+
+ this.items = items.filter(function(item) {
+ return item.hidden === undefined || item.hidden !== true;
+ }.bind(this));
+
+ this.items = this.items.map(function(item) {
+ return getHighlightingProxy(item, match, this.template.ownerDocument);
+ }.bind(this));
+
+ if (this.items.length === 0) {
+ this.element.style.display = 'none';
+ return;
+ }
+
+ if (this.items.length >= this.maxPredictions) {
+ this.items.splice(-1);
+ this.hasMore = true;
+ }
+ else {
+ this.hasMore = false;
+ }
+
+ var options = this.template.cloneNode(true);
+ domtemplate.template(options, this, this.templateOptions);
+
+ util.clearElement(this.element);
+ this.element.appendChild(options);
+
+ this.element.style.display = 'block';
+};
+
+var MAX_ITEMS = 3;
+
+/**
+ * Takes an array of items and cuts it into an array of arrays to help us
+ * to place the items into columns.
+ * The inner arrays will have at most MAX_ITEMS in them, with the number of
+ * outer arrays expanding to accommodate.
+ */
+Object.defineProperty(Menu.prototype, 'itemsSubdivided', {
+ get: function() {
+ var reply = [];
+
+ var taken = 0;
+ while (taken < this.items.length) {
+ reply.push(this.items.slice(taken, taken + MAX_ITEMS));
+ taken += MAX_ITEMS;
+ }
+
+ return reply;
+ },
+ enumerable: true
+});
+
+/**
+ * Create a proxy around an item that highlights matching text
+ */
+function getHighlightingProxy(item, match, document) {
+ var proxy = {};
+ Object.defineProperties(proxy, {
+ highlight: {
+ get: function() {
+ if (!match) {
+ return item.name;
+ }
+
+ var value = item.name;
+ var startMatch = value.indexOf(match);
+ if (startMatch === -1) {
+ return value;
+ }
+
+ var before = value.substr(0, startMatch);
+ var after = value.substr(startMatch + match.length);
+ var parent = util.createElement(document, 'span');
+ parent.appendChild(document.createTextNode(before));
+ var highlight = util.createElement(document, 'span');
+ highlight.classList.add('gcli-menu-typed');
+ highlight.appendChild(document.createTextNode(match));
+ parent.appendChild(highlight);
+ parent.appendChild(document.createTextNode(after));
+ return parent;
+ },
+ enumerable: true
+ },
+
+ name: {
+ value: item.name,
+ enumerable: true
+ },
+
+ manual: {
+ value: item.manual,
+ enumerable: true
+ },
+
+ description: {
+ value: item.description,
+ enumerable: true
+ }
+ });
+ return proxy;
+}
+
+/**
+ * @return {int} current choice index
+ */
+Menu.prototype.getChoiceIndex = function() {
+ return this._choice == null ? 0 : this._choice;
+};
+
+/**
+ * Highlight the next (for by=1) or previous (for by=-1) option
+ */
+Menu.prototype.nudgeChoice = function(by) {
+ if (this._choice == null) {
+ this._choice = 0;
+ }
+
+ // There's an annoying up is down thing here, the menu is presented
+ // with the zeroth index at the top working down, so the UP arrow needs
+ // pick the choice below because we're working down
+ this._choice -= by;
+ this._updateHighlight();
+};
+
+/**
+ * Highlight nothing
+ */
+Menu.prototype.unsetChoice = function() {
+ this._choice = null;
+ this._updateHighlight();
+};
+
+/**
+ * Internal option to update the currently highlighted option
+ */
+Menu.prototype._updateHighlight = function() {
+ var names = this.element.querySelectorAll('.gcli-menu-name');
+ var descs = this.element.querySelectorAll('.gcli-menu-desc');
+ for (var i = 0; i < names.length; i++) {
+ names[i].classList.remove('gcli-menu-highlight');
+ }
+ for (i = 0; i < descs.length; i++) {
+ descs[i].classList.remove('gcli-menu-highlight');
+ }
+
+ if (this._choice == null || names.length === 0) {
+ return;
+ }
+
+ var index = this._choice % names.length;
+ if (index < 0) {
+ index = names.length + index;
+ }
+
+ names.item(index).classList.add('gcli-menu-highlight');
+ descs.item(index).classList.add('gcli-menu-highlight');
+};
+
+/**
+ * Hide the menu
+ */
+Menu.prototype.hide = function() {
+ this.element.style.display = 'none';
+};
+
+/**
+ * Change how much vertical space this menu can take up
+ */
+Menu.prototype.setMaxHeight = function(height) {
+ this.element.style.maxHeight = height + 'px';
+};
+
+exports.Menu = Menu;
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/moz.build b/devtools/shared/gcli/source/lib/gcli/ui/moz.build
new file mode 100644
index 000000000..70ac666f0
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'focus.js',
+ 'history.js',
+ 'intro.js',
+ 'menu.css',
+ 'menu.html',
+ 'menu.js',
+ 'view.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/ui/view.js b/devtools/shared/gcli/source/lib/gcli/ui/view.js
new file mode 100644
index 000000000..193fb2d96
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/ui/view.js
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('../util/util');
+var host = require('../util/host');
+var domtemplate = require('../util/domtemplate');
+
+
+/**
+ * We want to avoid commands having to create DOM structures because that's
+ * messy and because we're going to need to have command output displayed in
+ * different documents. A View is a way to wrap an HTML template (for
+ * domtemplate) in with the data and options to render the template, so anyone
+ * can later run the template in the context of any document.
+ * View also cuts out a chunk of boiler place code.
+ * @param options The information needed to create the DOM from HTML. Includes:
+ * - html (required): The HTML source, probably from a call to require
+ * - options (default={}): The domtemplate options. See domtemplate for details
+ * - data (default={}): The data to domtemplate. See domtemplate for details.
+ * - css (default=none): Some CSS to be added to the final document. If 'css'
+ * is used, use of cssId is strongly recommended.
+ * - cssId (default=none): An ID to prevent multiple CSS additions. See
+ * util.importCss for more details.
+ * @return An object containing a single function 'appendTo()' which runs the
+ * template adding the result to the specified element. Takes 2 parameters:
+ * - element (required): the element to add to
+ * - clear (default=false): if clear===true then remove all pre-existing
+ * children of 'element' before appending the results of this template.
+ */
+exports.createView = function(options) {
+ if (options.html == null) {
+ throw new Error('options.html is missing');
+ }
+
+ return {
+ /**
+ * RTTI. Yeah.
+ */
+ isView: true,
+
+ /**
+ * Run the template against the document to which element belongs.
+ * @param element The element to append the result to
+ * @param clear Set clear===true to remove all children of element
+ */
+ appendTo: function(element, clear) {
+ // Strict check on the off-chance that we later think of other options
+ // and want to replace 'clear' with an 'options' parameter, but want to
+ // support backwards compat.
+ if (clear === true) {
+ util.clearElement(element);
+ }
+
+ element.appendChild(this.toDom(element.ownerDocument));
+ },
+
+ /**
+ * Actually convert the view data into a DOM suitable to be appended to
+ * an element
+ * @param document to use in realizing the template
+ */
+ toDom: function(document) {
+ if (options.css) {
+ util.importCss(options.css, document, options.cssId);
+ }
+
+ var child = host.toDom(document, options.html);
+ domtemplate.template(child, options.data || {}, options.options || {});
+ return child;
+ }
+ };
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/domtemplate.js b/devtools/shared/gcli/source/lib/gcli/util/domtemplate.js
new file mode 100644
index 000000000..d8979db3b
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/domtemplate.js
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var {template} = require("devtools/shared/gcli/templater");
+exports.template = template;
diff --git a/devtools/shared/gcli/source/lib/gcli/util/fileparser.js b/devtools/shared/gcli/source/lib/gcli/util/fileparser.js
new file mode 100644
index 000000000..4c470e638
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/fileparser.js
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var util = require('./util');
+var l10n = require('./l10n');
+var spell = require('./spell');
+var filesystem = require('./filesystem');
+var Status = require('../types/types').Status;
+
+/*
+ * An implementation of the functions that call the filesystem, designed to
+ * support the file type.
+ */
+
+/**
+ * Helper for the parse() function from the file type.
+ * See gcli/util/filesystem.js for details
+ */
+exports.parse = function(context, typed, options) {
+ return filesystem.stat(typed).then(function(stats) {
+ // The 'save-as' case - the path should not exist but does
+ if (options.existing === 'no' && stats.exists) {
+ return {
+ value: undefined,
+ status: Status.INCOMPLETE,
+ message: l10n.lookupFormat('fileErrExists', [ typed ]),
+ predictor: undefined // No predictions that we can give here
+ };
+ }
+
+ if (stats.exists) {
+ // The path exists - check it's the correct file type ...
+ if (options.filetype === 'file' && !stats.isFile) {
+ return {
+ value: undefined,
+ status: Status.INCOMPLETE,
+ message: l10n.lookupFormat('fileErrIsNotFile', [ typed ]),
+ predictor: getPredictor(typed, options)
+ };
+ }
+
+ if (options.filetype === 'directory' && !stats.isDir) {
+ return {
+ value: undefined,
+ status: Status.INCOMPLETE,
+ message: l10n.lookupFormat('fileErrIsNotDirectory', [ typed ]),
+ predictor: getPredictor(typed, options)
+ };
+ }
+
+ // ... and that it matches any 'match' RegExp
+ if (options.matches != null && !options.matches.test(typed)) {
+ return {
+ value: undefined,
+ status: Status.INCOMPLETE,
+ message: l10n.lookupFormat('fileErrDoesntMatch',
+ [ typed, options.source ]),
+ predictor: getPredictor(typed, options)
+ };
+ }
+ }
+ else {
+ if (options.existing === 'yes') {
+ // We wanted something that exists, but it doesn't. But we don't know
+ // if the path so far is an ERROR or just INCOMPLETE
+ var parentName = filesystem.dirname(typed);
+ return filesystem.stat(parentName).then(function(stats) {
+ return {
+ value: undefined,
+ status: stats.isDir ? Status.INCOMPLETE : Status.ERROR,
+ message: l10n.lookupFormat('fileErrNotExists', [ typed ]),
+ predictor: getPredictor(typed, options)
+ };
+ });
+ }
+ }
+
+ // We found no problems
+ return {
+ value: typed,
+ status: Status.VALID,
+ message: undefined,
+ predictor: getPredictor(typed, options)
+ };
+ });
+};
+
+var RANK_OPTIONS = { noSort: true, prefixZero: true };
+
+/**
+ * We want to be able to turn predictions off in Firefox
+ */
+exports.supportsPredictions = false;
+
+/**
+ * Get a function which creates predictions of files that match the given
+ * path
+ */
+function getPredictor(typed, options) {
+ if (!exports.supportsPredictions) {
+ return undefined;
+ }
+
+ return function() {
+ var allowFile = (options.filetype !== 'directory');
+ var parts = filesystem.split(typed);
+
+ var absolute = (typed.indexOf('/') === 0);
+ var roots;
+ if (absolute) {
+ roots = [ { name: '/', dist: 0, original: '/' } ];
+ }
+ else {
+ roots = dirHistory.getCommonDirectories().map(function(root) {
+ return { name: root, dist: 0, original: root };
+ });
+ }
+
+ // Add each part of the typed pathname onto each of the roots in turn,
+ // Finding options from each of those paths, and using these options as
+ // our roots for the next part
+ var partsAdded = util.promiseEach(parts, function(part, index) {
+
+ var partsSoFar = filesystem.join.apply(filesystem, parts.slice(0, index + 1));
+
+ // We allow this file matches in this pass if we're allowed files at all
+ // (i.e this isn't 'cd') and if this is the last part of the path
+ var allowFileForPart = (allowFile && index >= parts.length - 1);
+
+ var rootsPromise = util.promiseEach(roots, function(root) {
+
+ // Extend each roots to a list of all the files in each of the roots
+ var matchFile = allowFileForPart ? options.matches : null;
+ var promise = filesystem.ls(root.name, matchFile);
+
+ var onSuccess = function(entries) {
+ // Unless this is the final part filter out the non-directories
+ if (!allowFileForPart) {
+ entries = entries.filter(function(entry) {
+ return entry.isDir;
+ });
+ }
+ var entryMap = {};
+ entries.forEach(function(entry) {
+ entryMap[entry.pathname] = entry;
+ });
+ return entryMap;
+ };
+
+ var onError = function(err) {
+ // We expect errors due to the path not being a directory, not being
+ // accessible, or removed since the call to 'readdir'
+ return {};
+ };
+
+ promise = promise.then(onSuccess, onError);
+
+ // We want to compare all the directory entries with the original root
+ // plus the partsSoFar
+ var compare = filesystem.join(root.original, partsSoFar);
+
+ return promise.then(function(entryMap) {
+
+ var ranks = spell.rank(compare, Object.keys(entryMap), RANK_OPTIONS);
+ // penalize each path by the distance of it's parent
+ ranks.forEach(function(rank) {
+ rank.original = root.original;
+ rank.stats = entryMap[rank.name];
+ });
+ return ranks;
+ });
+ });
+
+ return rootsPromise.then(function(data) {
+ // data is an array of arrays of ranking objects. Squash down.
+ data = data.reduce(function(prev, curr) {
+ return prev.concat(curr);
+ }, []);
+
+ data.sort(function(r1, r2) {
+ return r1.dist - r2.dist;
+ });
+
+ // Trim, but by how many?
+ // If this is the last run through, we want to present the user with
+ // a sensible set of predictions. Otherwise we want to trim the tree
+ // to a reasonable set of matches, so we're happy with 1
+ // We look through x +/- 3 roots, and find the one with the biggest
+ // distance delta, and cut below that
+ // x=5 for the last time through, and x=8 otherwise
+ var isLast = index >= parts.length - 1;
+ var start = isLast ? 1 : 5;
+ var end = isLast ? 7 : 10;
+
+ var maxDeltaAt = start;
+ var maxDelta = data[start].dist - data[start - 1].dist;
+
+ for (var i = start + 1; i < end; i++) {
+ var delta = data[i].dist - data[i - 1].dist;
+ if (delta >= maxDelta) {
+ maxDelta = delta;
+ maxDeltaAt = i;
+ }
+ }
+
+ // Update the list of roots for the next time round
+ roots = data.slice(0, maxDeltaAt);
+ });
+ });
+
+ return partsAdded.then(function() {
+ var predictions = roots.map(function(root) {
+ var isFile = root.stats && root.stats.isFile;
+ var isDir = root.stats && root.stats.isDir;
+
+ var name = root.name;
+ if (isDir && name.charAt(name.length) !== filesystem.sep) {
+ name += filesystem.sep;
+ }
+
+ return {
+ name: name,
+ incomplete: !(allowFile && isFile),
+ isFile: isFile, // Added for describe, below
+ dist: root.dist, // TODO: Remove - added for debug in describe
+ };
+ });
+
+ return util.promiseEach(predictions, function(prediction) {
+ if (!prediction.isFile) {
+ prediction.description = '(' + prediction.dist + ')';
+ prediction.dist = undefined;
+ prediction.isFile = undefined;
+ return prediction;
+ }
+
+ return filesystem.describe(prediction.name).then(function(description) {
+ prediction.description = description;
+ prediction.dist = undefined;
+ prediction.isFile = undefined;
+ return prediction;
+ });
+ });
+ });
+ };
+}
+
+// =============================================================================
+
+/*
+ * The idea is that we maintain a list of 'directories that the user is
+ * interested in'. We store directories in a most-frequently-used cache
+ * of some description.
+ * But for now we're just using / and ~/
+ */
+var dirHistory = {
+ getCommonDirectories: function() {
+ return [
+ filesystem.sep, // i.e. the root directory
+ filesystem.home // i.e. the users home directory
+ ];
+ },
+ addCommonDirectory: function(ignore) {
+ // Not implemented yet
+ }
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/filesystem.js b/devtools/shared/gcli/source/lib/gcli/util/filesystem.js
new file mode 100644
index 000000000..a7b22a8f7
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/filesystem.js
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Cu = require('chrome').Cu;
+var Cc = require('chrome').Cc;
+var Ci = require('chrome').Ci;
+
+var OS = Cu.import('resource://gre/modules/osfile.jsm', {}).OS;
+
+/**
+ * A set of functions that don't really belong in 'fs' (because they're not
+ * really universal in scope) but also kind of do (because they're not specific
+ * to GCLI
+ */
+
+exports.join = OS.Path.join;
+exports.sep = OS.Path.sep;
+exports.dirname = OS.Path.dirname;
+
+// On B2G, there is no home folder
+var home = null;
+try {
+ var dirService = Cc['@mozilla.org/file/directory_service;1']
+ .getService(Ci.nsIProperties);
+ home = dirService.get('Home', Ci.nsIFile).path;
+} catch(e) {}
+exports.home = home;
+
+if ('winGetDrive' in OS.Path) {
+ exports.sep = '\\';
+}
+else {
+ exports.sep = '/';
+}
+
+/**
+ * Split a path into its components.
+ * @param pathname (string) The part to cut up
+ * @return An array of path components
+ */
+exports.split = function(pathname) {
+ return OS.Path.split(pathname).components;
+};
+
+/**
+ * @param pathname string, path of an existing directory
+ * @param matches optional regular expression - filter output to include only
+ * the files that match the regular expression. The regexp is applied to the
+ * filename only not to the full path
+ * @return A promise of an array of stat objects for each member of the
+ * directory pointed to by ``pathname``, each containing 2 extra properties:
+ * - pathname: The full pathname of the file
+ * - filename: The final filename part of the pathname
+ */
+exports.ls = function(pathname, matches) {
+ var iterator = new OS.File.DirectoryIterator(pathname);
+ var entries = [];
+
+ var iteratePromise = iterator.forEach(function(entry) {
+ entries.push({
+ exists: true,
+ isDir: entry.isDir,
+ isFile: !entry.isFile,
+ filename: entry.name,
+ pathname: entry.path
+ });
+ });
+
+ return iteratePromise.then(function onSuccess() {
+ iterator.close();
+ return entries;
+ },
+ function onFailure(reason) {
+ iterator.close();
+ throw reason;
+ }
+ );
+};
+
+/**
+ * stat() is annoying because it considers stat('/doesnt/exist') to be an
+ * error, when the point of stat() is to *find* *out*. So this wrapper just
+ * converts 'ENOENT' i.e. doesn't exist to { exists:false } and adds
+ * exists:true to stat blocks from existing paths
+ */
+exports.stat = function(pathname) {
+ var onResolve = function(stats) {
+ return {
+ exists: true,
+ isDir: stats.isDir,
+ isFile: !stats.isFile
+ };
+ };
+
+ var onReject = function(err) {
+ if (err instanceof OS.File.Error && err.becauseNoSuchFile) {
+ return {
+ exists: false,
+ isDir: false,
+ isFile: false
+ };
+ }
+ throw err;
+ };
+
+ return OS.File.stat(pathname).then(onResolve, onReject);
+};
+
+/**
+ * We may read the first line of a file to describe it?
+ * Right now, however, we do nothing.
+ */
+exports.describe = function(pathname) {
+ return Promise.resolve('');
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/host.js b/devtools/shared/gcli/source/lib/gcli/util/host.js
new file mode 100644
index 000000000..00fefa4f6
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/host.js
@@ -0,0 +1,230 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+var Cc = require('chrome').Cc;
+var Ci = require('chrome').Ci;
+
+var { Task } = require("devtools/shared/task");
+
+var util = require('./util');
+
+function Highlighter(document) {
+ this._document = document;
+ this._nodes = util.createEmptyNodeList(this._document);
+}
+
+Object.defineProperty(Highlighter.prototype, 'nodelist', {
+ set: function(nodes) {
+ Array.prototype.forEach.call(this._nodes, this._unhighlightNode, this);
+ this._nodes = (nodes == null) ?
+ util.createEmptyNodeList(this._document) :
+ nodes;
+ Array.prototype.forEach.call(this._nodes, this._highlightNode, this);
+ },
+ get: function() {
+ return this._nodes;
+ },
+ enumerable: true
+});
+
+Highlighter.prototype.destroy = function() {
+ this.nodelist = null;
+};
+
+Highlighter.prototype._highlightNode = function(node) {
+ // Enable when the highlighter rewrite is done
+};
+
+Highlighter.prototype._unhighlightNode = function(node) {
+ // Enable when the highlighter rewrite is done
+};
+
+exports.Highlighter = Highlighter;
+
+/**
+ * See docs in lib/gcli/util/host.js
+ */
+exports.exec = function(task) {
+ return Task.spawn(task);
+};
+
+/**
+ * The URL API is new enough that we need specific platform help
+ */
+exports.createUrl = function(uristr, base) {
+ return new URL(uristr, base);
+};
+
+/**
+ * Load some HTML into the given document and return a DOM element.
+ * This utility assumes that the html has a single root (other than whitespace)
+ */
+exports.toDom = function(document, html) {
+ var div = util.createElement(document, 'div');
+ util.setContents(div, html);
+ return div.children[0];
+};
+
+/**
+ * When dealing with module paths on windows we want to use the unix
+ * directory separator rather than the windows one, so we avoid using
+ * OS.Path.dirname, and use unix version on all platforms.
+ */
+var resourceDirName = function(path) {
+ var index = path.lastIndexOf('/');
+ if (index == -1) {
+ return '.';
+ }
+ while (index >= 0 && path[index] == '/') {
+ --index;
+ }
+ return path.slice(0, index + 1);
+};
+
+/**
+ * Asynchronously load a text resource
+ * @see lib/gcli/util/host.js
+ */
+exports.staticRequire = function(requistingModule, name) {
+ if (name.match(/\.css$/)) {
+ return Promise.resolve('');
+ }
+ else {
+ return new Promise(function(resolve, reject) {
+ var filename = resourceDirName(requistingModule.id) + '/' + name;
+ filename = filename.replace(/\/\.\//g, '/');
+ filename = 'resource://devtools/shared/gcli/source/lib/' + filename;
+
+ var xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
+ .createInstance(Ci.nsIXMLHttpRequest);
+
+ xhr.onload = function onload() {
+ resolve(xhr.responseText);
+ }.bind(this);
+
+ xhr.onabort = xhr.onerror = xhr.ontimeout = function(err) {
+ reject(err);
+ }.bind(this);
+
+ xhr.open('GET', filename);
+ xhr.send();
+ }.bind(this));
+ }
+};
+
+/**
+ * A group of functions to help scripting. Small enough that it doesn't need
+ * a separate module (it's basically a wrapper around 'eval' in some contexts)
+ */
+var client;
+var target;
+var consoleActor;
+var webConsoleClient;
+
+exports.script = { };
+
+exports.script.onOutput = util.createEvent('Script.onOutput');
+
+/**
+ * Setup the environment to eval JavaScript
+ */
+exports.script.useTarget = function(tgt) {
+ target = tgt;
+
+ // Local debugging needs to make the target remote.
+ var targetPromise = target.isRemote ?
+ Promise.resolve(target) :
+ target.makeRemote();
+
+ return targetPromise.then(function() {
+ return new Promise(function(resolve, reject) {
+ client = target._client;
+
+ client.addListener('pageError', function(packet) {
+ if (packet.from === consoleActor) {
+ // console.log('pageError', packet.pageError);
+ exports.script.onOutput({
+ level: 'exception',
+ message: packet.exception.class
+ });
+ }
+ });
+
+ client.addListener('consoleAPICall', function(type, packet) {
+ if (packet.from === consoleActor) {
+ var data = packet.message;
+
+ var ev = {
+ level: data.level,
+ arguments: data.arguments,
+ };
+
+ if (data.filename !== 'debugger eval code') {
+ ev.source = {
+ filename: data.filename,
+ lineNumber: data.lineNumber,
+ functionName: data.functionName
+ };
+ }
+
+ exports.script.onOutput(ev);
+ }
+ });
+
+ consoleActor = target._form.consoleActor;
+
+ var onAttach = function(response, wcc) {
+ webConsoleClient = wcc;
+
+ if (response.error != null) {
+ reject(response);
+ }
+ else {
+ resolve(response);
+ }
+
+ // TODO: add _onTabNavigated code?
+ };
+
+ var listeners = [ 'PageError', 'ConsoleAPI' ];
+ client.attachConsole(consoleActor, listeners, onAttach);
+ }.bind(this));
+ });
+};
+
+/**
+ * Execute some JavaScript
+ */
+exports.script.evaluate = function(javascript) {
+ return new Promise(function(resolve, reject) {
+ var onResult = function(response) {
+ var output = response.result;
+ if (typeof output === 'object' && output.type === 'undefined') {
+ output = undefined;
+ }
+
+ resolve({
+ input: response.input,
+ output: output,
+ exception: response.exception
+ });
+ };
+
+ webConsoleClient.evaluateJS(javascript, onResult, {});
+ }.bind(this));
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/l10n.js b/devtools/shared/gcli/source/lib/gcli/util/l10n.js
new file mode 100644
index 000000000..6d0c7c8f4
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/l10n.js
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/shared/locales/gcli.properties");
+
+/*
+ * Not supported when embedded - we"re doing things the Mozilla way not the
+ * require.js way.
+ */
+exports.registerStringsSource = function (modulePath) {
+ throw new Error("registerStringsSource is not available in mozilla");
+};
+
+exports.unregisterStringsSource = function (modulePath) {
+ throw new Error("unregisterStringsSource is not available in mozilla");
+};
+
+exports.lookupSwap = function (key, swaps) {
+ throw new Error("lookupSwap is not available in mozilla");
+};
+
+exports.lookupPlural = function (key, ord, swaps) {
+ throw new Error("lookupPlural is not available in mozilla");
+};
+
+exports.getPreferredLocales = function () {
+ return [ "root" ];
+};
+
+/** @see lookup() in lib/gcli/util/l10n.js */
+exports.lookup = function (key) {
+ try {
+ // Our memory leak hunter walks reachable objects trying to work out what
+ // type of thing they are using object.constructor.name. If that causes
+ // problems then we can avoid the unknown-key-exception with the following:
+ /*
+ if (key === "constructor") {
+ return { name: "l10n-mem-leak-defeat" };
+ }
+ */
+
+ return L10N.getStr(key);
+ } catch (ex) {
+ console.error("Failed to lookup ", key, ex);
+ return key;
+ }
+};
+
+/** @see propertyLookup in lib/gcli/util/l10n.js */
+exports.propertyLookup = new Proxy({}, {
+ get: function (rcvr, name) {
+ return exports.lookup(name);
+ }
+});
+
+/** @see lookupFormat in lib/gcli/util/l10n.js */
+exports.lookupFormat = function (key, swaps) {
+ try {
+ return L10N.getFormatStr(key, ...swaps);
+ } catch (ex) {
+ console.error("Failed to format ", key, ex);
+ return key;
+ }
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/legacy.js b/devtools/shared/gcli/source/lib/gcli/util/legacy.js
new file mode 100644
index 000000000..07b0fd71a
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/legacy.js
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/**
+ * Fake a console for IE9
+ */
+if (typeof window !== 'undefined' && window.console == null) {
+ window.console = {};
+}
+'debug,log,warn,error,trace,group,groupEnd'.split(',').forEach(function(f) {
+ if (typeof window !== 'undefined' && !window.console[f]) {
+ window.console[f] = function() {};
+ }
+});
+
+/**
+ * Fake Element.classList for IE9
+ * Based on https://gist.github.com/1381839 by Devon Govett
+ */
+if (typeof document !== 'undefined' && typeof HTMLElement !== 'undefined' &&
+ !('classList' in document.documentElement) && Object.defineProperty) {
+ Object.defineProperty(HTMLElement.prototype, 'classList', {
+ get: function() {
+ var self = this;
+ function update(fn) {
+ return function(value) {
+ var classes = self.className.split(/\s+/);
+ var index = classes.indexOf(value);
+ fn(classes, index, value);
+ self.className = classes.join(' ');
+ };
+ }
+
+ var ret = {
+ add: update(function(classes, index, value) {
+ ~index || classes.push(value);
+ }),
+ remove: update(function(classes, index) {
+ ~index && classes.splice(index, 1);
+ }),
+ toggle: update(function(classes, index, value) {
+ ~index ? classes.splice(index, 1) : classes.push(value);
+ }),
+ contains: function(value) {
+ return !!~self.className.split(/\s+/).indexOf(value);
+ },
+ item: function(i) {
+ return self.className.split(/\s+/)[i] || null;
+ }
+ };
+
+ Object.defineProperty(ret, 'length', {
+ get: function() {
+ return self.className.split(/\s+/).length;
+ }
+ });
+
+ return ret;
+ }
+ });
+}
+
+/**
+ * Array.find
+ * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find
+ */
+if (!Array.prototype.find) {
+ Object.defineProperty(Array.prototype, 'find', {
+ enumerable: false,
+ configurable: true,
+ writable: true,
+ value: function(predicate) {
+ if (this == null) {
+ throw new TypeError('Array.prototype.find called on null or undefined');
+ }
+ if (typeof predicate !== 'function') {
+ throw new TypeError('predicate must be a function');
+ }
+ var list = Object(this);
+ var length = list.length >>> 0;
+ var thisArg = arguments[1];
+ var value;
+
+ for (var i = 0; i < length; i++) {
+ if (i in list) {
+ value = list[i];
+ if (predicate.call(thisArg, value, i, list)) {
+ return value;
+ }
+ }
+ }
+ return undefined;
+ }
+ });
+}
+
+/**
+ * String.prototype.trimLeft is non-standard, but it works in Firefox,
+ * Chrome and Opera. It's easiest to create a shim here.
+ */
+if (!String.prototype.trimLeft) {
+ String.prototype.trimLeft = function() {
+ return String(this).replace(/\s*$/, '');
+ };
+}
+
+/**
+ * Polyfil taken from
+ * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Function/bind
+ */
+if (!Function.prototype.bind) {
+ Function.prototype.bind = function(oThis) {
+ if (typeof this !== 'function') {
+ // closest thing possible to the ECMAScript 5 internal IsCallable function
+ throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable');
+ }
+
+ var aArgs = Array.prototype.slice.call(arguments, 1),
+ fToBind = this,
+ fNOP = function () {},
+ fBound = function () {
+ return fToBind.apply(this instanceof fNOP && oThis
+ ? this
+ : oThis,
+ aArgs.concat(Array.prototype.slice.call(arguments)));
+ };
+
+ fNOP.prototype = this.prototype;
+ fBound.prototype = new fNOP();
+ return fBound;
+ };
+}
diff --git a/devtools/shared/gcli/source/lib/gcli/util/moz.build b/devtools/shared/gcli/source/lib/gcli/util/moz.build
new file mode 100644
index 000000000..0fdeb96ec
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'domtemplate.js',
+ 'fileparser.js',
+ 'filesystem.js',
+ 'host.js',
+ 'l10n.js',
+ 'legacy.js',
+ 'prism.js',
+ 'spell.js',
+ 'util.js',
+)
diff --git a/devtools/shared/gcli/source/lib/gcli/util/prism.js b/devtools/shared/gcli/source/lib/gcli/util/prism.js
new file mode 100644
index 000000000..6f457cf23
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/prism.js
@@ -0,0 +1,361 @@
+/**
+ * Prism: Lightweight, robust, elegant syntax highlighting
+ * MIT license http://www.opensource.org/licenses/mit-license.php/
+ * @author Lea Verou http://lea.verou.me
+ */
+
+'use strict';
+
+// Private helper vars
+var lang = /\blang(?:uage)?-(?!\*)(\w+)\b/i;
+
+var Prism = exports.Prism = {
+ util: {
+ type: function (o) {
+ return Object.prototype.toString.call(o).match(/\[object (\w+)\]/)[1];
+ },
+
+ // Deep clone a language definition (e.g. to extend it)
+ clone: function (o) {
+ var type = Prism.util.type(o);
+
+ switch (type) {
+ case 'Object':
+ var clone = {};
+
+ for (var key in o) {
+ if (o.hasOwnProperty(key)) {
+ clone[key] = Prism.util.clone(o[key]);
+ }
+ }
+
+ return clone;
+
+ case 'Array':
+ return o.slice();
+ }
+
+ return o;
+ }
+ },
+
+ languages: {
+ extend: function (id, redef) {
+ var lang = Prism.util.clone(Prism.languages[id]);
+
+ for (var key in redef) {
+ lang[key] = redef[key];
+ }
+
+ return lang;
+ },
+
+ // Insert a token before another token in a language literal
+ insertBefore: function (inside, before, insert, root) {
+ root = root || Prism.languages;
+ var grammar = root[inside];
+ var ret = {};
+
+ for (var token in grammar) {
+
+ if (grammar.hasOwnProperty(token)) {
+
+ if (token == before) {
+
+ for (var newToken in insert) {
+
+ if (insert.hasOwnProperty(newToken)) {
+ ret[newToken] = insert[newToken];
+ }
+ }
+ }
+
+ ret[token] = grammar[token];
+ }
+ }
+
+ root[inside] = ret;
+ return ret;
+ },
+
+ // Traverse a language definition with Depth First Search
+ DFS: function(o, callback) {
+ for (var i in o) {
+ callback.call(o, i, o[i]);
+
+ if (Prism.util.type(o) === 'Object') {
+ Prism.languages.DFS(o[i], callback);
+ }
+ }
+ }
+ },
+
+ highlightAll: function(async, callback) {
+ var elements = document.querySelectorAll('code[class*="language-"], [class*="language-"] code, code[class*="lang-"], [class*="lang-"] code');
+
+ elements.forEach(function(element) {
+ Prism.highlightElement(element, async === true, callback);
+ });
+ },
+
+ highlightElement: function(element, async, callback) {
+ // Find language
+ var language;
+ var grammar;
+
+ var parent = element;
+ while (parent && !lang.test(parent.className)) {
+ parent = parent.parentNode;
+ }
+
+ if (parent) {
+ language = (parent.className.match(lang) || [,''])[1];
+ grammar = Prism.languages[language];
+ }
+
+ if (!grammar) {
+ return;
+ }
+
+ // Set language on the element, if not present
+ element.className = element.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
+
+ // Set language on the parent, for styling
+ parent = element.parentNode;
+
+ if (/pre/i.test(parent.nodeName)) {
+ parent.className = parent.className.replace(lang, '').replace(/\s+/g, ' ') + ' language-' + language;
+ }
+
+ var code = element.textContent;
+
+ if (!code) {
+ return;
+ }
+
+ code = code.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/\u00a0/g, ' ');
+
+ var env = {
+ element: element,
+ language: language,
+ grammar: grammar,
+ code: code
+ };
+
+ Prism.hooks.run('before-highlight', env);
+
+ env.highlightedCode = Prism.highlight(env.code, env.grammar, env.language);
+
+ Prism.hooks.run('before-insert', env);
+
+ env.element.innerHTML = env.highlightedCode;
+
+ if (callback) {
+ callback.call(element);
+ }
+
+ Prism.hooks.run('after-highlight', env);
+ },
+
+ highlight: function (text, grammar, language) {
+ return Token.stringify(Prism.tokenize(text, grammar), language);
+ },
+
+ tokenize: function(text, grammar, language) {
+ var Token = Prism.Token;
+
+ var strarr = [text];
+
+ var rest = grammar.rest;
+ var token;
+ if (rest) {
+ for (token in rest) {
+ grammar[token] = rest[token];
+ }
+
+ delete grammar.rest;
+ }
+
+ tokenloop:
+ for (token in grammar) {
+ if (!grammar.hasOwnProperty(token) || !grammar[token]) {
+ continue;
+ }
+
+ var pattern = grammar[token],
+ inside = pattern.inside,
+ lookbehind = !!pattern.lookbehind,
+ lookbehindLength = 0;
+
+ pattern = pattern.pattern || pattern;
+
+ for (var i=0; i<strarr.length; i++) { // Don’t cache length as it changes during the loop
+
+ var str = strarr[i];
+
+ if (strarr.length > text.length) {
+ // Something went terribly wrong, ABORT, ABORT!
+ break tokenloop;
+ }
+
+ if (str instanceof Token) {
+ continue;
+ }
+
+ pattern.lastIndex = 0;
+
+ var match = pattern.exec(str);
+
+ if (match) {
+ if (lookbehind) {
+ lookbehindLength = match[1].length;
+ }
+
+ var from = match.index - 1 + lookbehindLength;
+ match = match[0].slice(lookbehindLength);
+ var len = match.length;
+ var to = from + len;
+ var before = str.slice(0, from + 1);
+ var after = str.slice(to + 1);
+
+ var args = [i, 1];
+
+ if (before) {
+ args.push(before);
+ }
+
+ var wrapped = new Token(token, inside? Prism.tokenize(match, inside) : match);
+
+ args.push(wrapped);
+
+ if (after) {
+ args.push(after);
+ }
+
+ Array.prototype.splice.apply(strarr, args);
+ }
+ }
+ }
+
+ return strarr;
+ },
+
+ hooks: {
+ all: {},
+
+ add: function (name, callback) {
+ var hooks = Prism.hooks.all;
+
+ hooks[name] = hooks[name] || [];
+
+ hooks[name].push(callback);
+ },
+
+ run: function (name, env) {
+ var callbacks = Prism.hooks.all[name];
+
+ if (!callbacks || !callbacks.length) {
+ return;
+ }
+
+ callbacks.forEach(function(callback) {
+ callback(env);
+ });
+ }
+ }
+};
+
+var Token = Prism.Token = function(type, content) {
+ this.type = type;
+ this.content = content;
+};
+
+Token.stringify = function(o, language, parent) {
+ if (typeof o == 'string') {
+ return o;
+ }
+
+ if (Object.prototype.toString.call(o) == '[object Array]') {
+ return o.map(function(element) {
+ return Token.stringify(element, language, o);
+ }).join('');
+ }
+
+ var env = {
+ type: o.type,
+ content: Token.stringify(o.content, language, parent),
+ tag: 'span',
+ classes: ['token', o.type],
+ attributes: {},
+ language: language,
+ parent: parent
+ };
+
+ if (env.type == 'comment') {
+ env.attributes.spellcheck = 'true';
+ }
+
+ Prism.hooks.run('wrap', env);
+
+ var attributes = '';
+
+ for (var name in env.attributes) {
+ attributes += name + '="' + (env.attributes[name] || '') + '"';
+ }
+
+ return '<' + env.tag + ' class="' + env.classes.join(' ') + '" ' + attributes + '>' + env.content + '</' + env.tag + '>';
+};
+
+Prism.languages.clike = {
+ 'comment': {
+ pattern: /(^|[^\\])(\/\*[\w\W]*?\*\/|(^|[^:])\/\/.*?(\r?\n|$))/g,
+ lookbehind: true
+ },
+ 'string': /("|')(\\?.)*?\1/g,
+ 'class-name': {
+ pattern: /((?:(?:class|interface|extends|implements|trait|instanceof|new)\s+)|(?:catch\s+\())[a-z0-9_\.\\]+/ig,
+ lookbehind: true,
+ inside: {
+ punctuation: /(\.|\\)/
+ }
+ },
+ 'keyword': /\b(if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/g,
+ 'boolean': /\b(true|false)\b/g,
+ 'function': {
+ pattern: /[a-z0-9_]+\(/ig,
+ inside: {
+ punctuation: /\(/
+ }
+ },
+ 'number': /\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?)\b/g,
+ 'operator': /[-+]{1,2}|!|&lt;=?|>=?|={1,3}|(&amp;){1,2}|\|?\||\?|\*|\/|\~|\^|\%/g,
+ 'ignore': /&(lt|gt|amp);/gi,
+ 'punctuation': /[{}[\];(),.:]/g
+};
+
+Prism.languages.javascript = Prism.languages.extend('clike', {
+ 'keyword': /\b(var|let|if|else|while|do|for|return|in|instanceof|function|new|with|typeof|try|throw|catch|finally|null|break|continue)\b/g,
+ 'number': /\b-?(0x[\dA-Fa-f]+|\d*\.?\d+([Ee]-?\d+)?|NaN|-?Infinity)\b/g
+});
+
+Prism.languages.insertBefore('javascript', 'keyword', {
+ 'regex': {
+ pattern: /(^|[^/])\/(?!\/)(\[.+?]|\\.|[^/\r\n])+\/[gim]{0,3}(?=\s*($|[\r\n,.;})]))/g,
+ lookbehind: true
+ }
+});
+
+if (Prism.languages.markup) {
+ Prism.languages.insertBefore('markup', 'tag', {
+ 'script': {
+ pattern: /(&lt;|<)script[\w\W]*?(>|&gt;)[\w\W]*?(&lt;|<)\/script(>|&gt;)/ig,
+ inside: {
+ 'tag': {
+ pattern: /(&lt;|<)script[\w\W]*?(>|&gt;)|(&lt;|<)\/script(>|&gt;)/ig,
+ inside: Prism.languages.markup.tag.inside
+ },
+ rest: Prism.languages.javascript
+ }
+ }
+ });
+}
diff --git a/devtools/shared/gcli/source/lib/gcli/util/spell.js b/devtools/shared/gcli/source/lib/gcli/util/spell.js
new file mode 100644
index 000000000..f16724f2a
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/spell.js
@@ -0,0 +1,197 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/*
+ * A spell-checker based on Damerau-Levenshtein distance.
+ */
+
+var CASE_CHANGE_COST = 1;
+var INSERTION_COST = 10;
+var DELETION_COST = 10;
+var SWAP_COST = 10;
+var SUBSTITUTION_COST = 20;
+var MAX_EDIT_DISTANCE = 40;
+
+/**
+ * Compute Damerau-Levenshtein Distance, with a modification to allow a low
+ * case-change cost (1/10th of a swap-cost)
+ * @see http://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance
+ */
+var distance = exports.distance = function(wordi, wordj) {
+ var wordiLen = wordi.length;
+ var wordjLen = wordj.length;
+
+ // We only need to store three rows of our dynamic programming matrix.
+ // (Without swap, it would have been two.)
+ var row0 = new Array(wordiLen+1);
+ var row1 = new Array(wordiLen+1);
+ var row2 = new Array(wordiLen+1);
+ var tmp;
+
+ var i, j;
+
+ // The distance between the empty string and a string of size i is the cost
+ // of i insertions.
+ for (i = 0; i <= wordiLen; i++) {
+ row1[i] = i * INSERTION_COST;
+ }
+
+ // Row-by-row, we're computing the edit distance between substrings wordi[0..i]
+ // and wordj[0..j].
+ for (j = 1; j <= wordjLen; j++) {
+ // Edit distance between wordi[0..0] and wordj[0..j] is the cost of j
+ // insertions.
+ row0[0] = j * INSERTION_COST;
+
+ for (i = 1; i <= wordiLen; i++) {
+ // Handle deletion, insertion and substitution: we can reach each cell
+ // from three other cells corresponding to those three operations. We
+ // want the minimum cost.
+ var dc = row0[i - 1] + DELETION_COST;
+ var ic = row1[i] + INSERTION_COST;
+ var sc0;
+ if (wordi[i-1] === wordj[j-1]) {
+ sc0 = 0;
+ }
+ else {
+ if (wordi[i-1].toLowerCase() === wordj[j-1].toLowerCase()) {
+ sc0 = CASE_CHANGE_COST;
+ }
+ else {
+ sc0 = SUBSTITUTION_COST;
+ }
+ }
+ var sc = row1[i-1] + sc0;
+
+ row0[i] = Math.min(dc, ic, sc);
+
+ // We handle swap too, eg. distance between help and hlep should be 1. If
+ // we find such a swap, there's a chance to update row0[1] to be lower.
+ if (i > 1 && j > 1 && wordi[i-1] === wordj[j-2] && wordj[j-1] === wordi[i-2]) {
+ row0[i] = Math.min(row0[i], row2[i-2] + SWAP_COST);
+ }
+ }
+
+ tmp = row2;
+ row2 = row1;
+ row1 = row0;
+ row0 = tmp;
+ }
+
+ return row1[wordiLen];
+};
+
+/**
+ * As distance() except that we say that if word is a prefix of name then we
+ * only count the case changes. This allows us to use words that can be
+ * completed by typing as more likely than short words
+ */
+var distancePrefix = exports.distancePrefix = function(word, name) {
+ var dist = 0;
+
+ for (var i = 0; i < word.length; i++) {
+ if (name[i] !== word[i]) {
+ if (name[i].toLowerCase() === word[i].toLowerCase()) {
+ dist++;
+ }
+ else {
+ // name does not start with word, even ignoring case, use
+ // Damerau-Levenshtein
+ return exports.distance(word, name);
+ }
+ }
+ }
+
+ return dist;
+};
+
+/**
+ * A function that returns the correction for the specified word.
+ */
+exports.correct = function(word, names) {
+ if (names.length === 0) {
+ return undefined;
+ }
+
+ var distances = {};
+ var sortedCandidates;
+
+ names.forEach(function(candidate) {
+ distances[candidate] = exports.distance(word, candidate);
+ });
+
+ sortedCandidates = names.sort(function(worda, wordb) {
+ if (distances[worda] !== distances[wordb]) {
+ return distances[worda] - distances[wordb];
+ }
+ else {
+ // if the score is the same, always return the first string
+ // in the lexicographical order
+ return worda < wordb;
+ }
+ });
+
+ if (distances[sortedCandidates[0]] <= MAX_EDIT_DISTANCE) {
+ return sortedCandidates[0];
+ }
+ else {
+ return undefined;
+ }
+};
+
+/**
+ * Return a ranked list of matches:
+ *
+ * spell.rank('fred', [ 'banana', 'fred', 'ed', 'red' ]);
+ * ↓
+ * [
+ * { name: 'fred', dist: 0 },
+ * { name: 'red', dist: 1 },
+ * { name: 'ed', dist: 2 },
+ * { name: 'banana', dist: 10 },
+ * ]
+ *
+ * @param word The string that we're comparing names against
+ * @param names An array of strings to compare word against
+ * @param options Comparison options:
+ * - noSort: Do not sort the output by distance
+ * - prefixZero: Count prefix matches as edit distance 0 (i.e. word='bana' and
+ * names=['banana'], would return { name:'banana': dist: 0 }) This is useful
+ * if someone is typing the matches and may not have finished yet
+ */
+exports.rank = function(word, names, options) {
+ options = options || {};
+
+ var reply = names.map(function(name) {
+ // If any name starts with the word then the distance is based on the
+ // number of case changes rather than Damerau-Levenshtein
+ var algo = options.prefixZero ? distancePrefix : distance;
+ return {
+ name: name,
+ dist: algo(word, name)
+ };
+ });
+
+ if (!options.noSort) {
+ reply = reply.sort(function(d1, d2) {
+ return d1.dist - d2.dist;
+ });
+ }
+
+ return reply;
+};
diff --git a/devtools/shared/gcli/source/lib/gcli/util/util.js b/devtools/shared/gcli/source/lib/gcli/util/util.js
new file mode 100644
index 000000000..065bf36c0
--- /dev/null
+++ b/devtools/shared/gcli/source/lib/gcli/util/util.js
@@ -0,0 +1,685 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+'use strict';
+
+/*
+ * A number of DOM manipulation and event handling utilities.
+ */
+
+//------------------------------------------------------------------------------
+
+var eventDebug = false;
+
+/**
+ * Patch up broken console API from node
+ */
+if (eventDebug) {
+ if (console.group == null) {
+ console.group = function() { console.log(arguments); };
+ }
+ if (console.groupEnd == null) {
+ console.groupEnd = function() { console.log(arguments); };
+ }
+}
+
+/**
+ * Useful way to create a name for a handler, used in createEvent()
+ */
+function nameFunction(handler) {
+ var scope = handler.scope ? handler.scope.constructor.name + '.' : '';
+ var name = handler.func.name;
+ if (name) {
+ return scope + name;
+ }
+ for (var prop in handler.scope) {
+ if (handler.scope[prop] === handler.func) {
+ return scope + prop;
+ }
+ }
+ return scope + handler.func;
+}
+
+/**
+ * Create an event.
+ * For use as follows:
+ *
+ * function Hat() {
+ * this.putOn = createEvent('Hat.putOn');
+ * ...
+ * }
+ * Hat.prototype.adorn = function(person) {
+ * this.putOn({ hat: hat, person: person });
+ * ...
+ * }
+ *
+ * var hat = new Hat();
+ * hat.putOn.add(function(ev) {
+ * console.log('The hat ', ev.hat, ' has is worn by ', ev.person);
+ * }, scope);
+ *
+ * @param name Optional name to help with debugging
+ */
+exports.createEvent = function(name) {
+ var handlers = [];
+ var fireHoldCount = 0;
+ var heldEvents = [];
+ var eventCombiner;
+
+ /**
+ * This is how the event is triggered.
+ * @param ev The event object to be passed to the event listeners
+ */
+ var event = function(ev) {
+ if (fireHoldCount > 0) {
+ heldEvents.push(ev);
+ if (eventDebug) {
+ console.log('Held fire: ' + name, ev);
+ }
+ return;
+ }
+
+ if (eventDebug) {
+ console.group('Fire: ' + name + ' to ' + handlers.length + ' listeners', ev);
+ }
+
+ // Use for rather than forEach because it step debugs better, which is
+ // important for debugging events
+ for (var i = 0; i < handlers.length; i++) {
+ var handler = handlers[i];
+ if (eventDebug) {
+ console.log(nameFunction(handler));
+ }
+ handler.func.call(handler.scope, ev);
+ }
+
+ if (eventDebug) {
+ console.groupEnd();
+ }
+ };
+
+ /**
+ * Add a new handler function
+ * @param func The function to call when this event is triggered
+ * @param scope Optional 'this' object for the function call
+ */
+ event.add = function(func, scope) {
+ if (typeof func !== 'function') {
+ throw new Error(name + ' add(func,...), 1st param is ' + typeof func);
+ }
+
+ if (eventDebug) {
+ console.log('Adding listener to ' + name);
+ }
+
+ handlers.push({ func: func, scope: scope });
+ };
+
+ /**
+ * Remove a handler function added through add(). Both func and scope must
+ * be strict equals (===) the values used in the call to add()
+ * @param func The function to call when this event is triggered
+ * @param scope Optional 'this' object for the function call
+ */
+ event.remove = function(func, scope) {
+ if (eventDebug) {
+ console.log('Removing listener from ' + name);
+ }
+
+ var found = false;
+ handlers = handlers.filter(function(test) {
+ var match = (test.func === func && test.scope === scope);
+ if (match) {
+ found = true;
+ }
+ return !match;
+ });
+ if (!found) {
+ console.warn('Handler not found. Attached to ' + name);
+ }
+ };
+
+ /**
+ * Remove all handlers.
+ * Reset the state of this event back to it's post create state
+ */
+ event.removeAll = function() {
+ handlers = [];
+ };
+
+ /**
+ * Fire an event just once using a promise.
+ */
+ event.once = function() {
+ if (arguments.length !== 0) {
+ throw new Error('event.once uses promise return values');
+ }
+
+ return new Promise(function(resolve, reject) {
+ var handler = function(arg) {
+ event.remove(handler);
+ resolve(arg);
+ };
+
+ event.add(handler);
+ });
+ };
+
+ /**
+ * Temporarily prevent this event from firing.
+ * @see resumeFire(ev)
+ */
+ event.holdFire = function() {
+ if (eventDebug) {
+ console.group('Holding fire: ' + name);
+ }
+
+ fireHoldCount++;
+ };
+
+ /**
+ * Resume firing events.
+ * If there are heldEvents, then we fire one event to cover them all. If an
+ * event combining function has been provided then we use that to combine the
+ * events. Otherwise the last held event is used.
+ * @see holdFire()
+ */
+ event.resumeFire = function() {
+ if (eventDebug) {
+ console.groupEnd('Resume fire: ' + name);
+ }
+
+ if (fireHoldCount === 0) {
+ throw new Error('fireHoldCount === 0 during resumeFire on ' + name);
+ }
+
+ fireHoldCount--;
+ if (heldEvents.length === 0) {
+ return;
+ }
+
+ if (heldEvents.length === 1) {
+ event(heldEvents[0]);
+ }
+ else {
+ var first = heldEvents[0];
+ var last = heldEvents[heldEvents.length - 1];
+ if (eventCombiner) {
+ event(eventCombiner(first, last, heldEvents));
+ }
+ else {
+ event(last);
+ }
+ }
+
+ heldEvents = [];
+ };
+
+ /**
+ * When resumeFire has a number of events to combine, by default it just
+ * picks the last, however you can provide an eventCombiner which returns a
+ * combined event.
+ * eventCombiners will be passed 3 parameters:
+ * - first The first event to be held
+ * - last The last event to be held
+ * - all An array containing all the held events
+ * The return value from an eventCombiner is expected to be an event object
+ */
+ Object.defineProperty(event, 'eventCombiner', {
+ set: function(newEventCombiner) {
+ if (typeof newEventCombiner !== 'function') {
+ throw new Error('eventCombiner is not a function');
+ }
+ eventCombiner = newEventCombiner;
+ },
+
+ enumerable: true
+ });
+
+ return event;
+};
+
+//------------------------------------------------------------------------------
+
+/**
+ * promiseEach is roughly like Array.forEach except that the action is taken to
+ * be something that completes asynchronously, returning a promise, so we wait
+ * for the action to complete for each array element before moving onto the
+ * next.
+ * @param array An array of objects to enumerate
+ * @param action A function to call for each member of the array
+ * @param scope Optional object to use as 'this' for the function calls
+ * @return A promise which is resolved (with an array of resolution values)
+ * when all the array members have been passed to the action function, and
+ * rejected as soon as any of the action function calls fails 
+ */
+exports.promiseEach = function(array, action, scope) {
+ if (array.length === 0) {
+ return Promise.resolve([]);
+ }
+
+ var allReply = [];
+ var promise = Promise.resolve();
+
+ array.forEach(function(member, i) {
+ promise = promise.then(function() {
+ var reply = action.call(scope, member, i, array);
+ return Promise.resolve(reply).then(function(data) {
+ allReply[i] = data;
+ });
+ });
+ });
+
+ return promise.then(function() {
+ return allReply;
+ });
+};
+
+/**
+ * Catching errors from promises isn't as simple as:
+ * promise.then(handler, console.error);
+ * for a number of reasons:
+ * - chrome's console doesn't have bound functions (why?)
+ * - we don't get stack traces out from console.error(ex);
+ */
+exports.errorHandler = function(ex) {
+ if (ex instanceof Error) {
+ // V8 weirdly includes the exception message in the stack
+ if (ex.stack.indexOf(ex.message) !== -1) {
+ console.error(ex.stack);
+ }
+ else {
+ console.error('' + ex);
+ console.error(ex.stack);
+ }
+ }
+ else {
+ console.error(ex);
+ }
+};
+
+
+//------------------------------------------------------------------------------
+
+/**
+ * Copy the properties from one object to another in a way that preserves
+ * function properties as functions rather than copying the calculated value
+ * as copy time
+ */
+exports.copyProperties = function(src, dest) {
+ for (var key in src) {
+ var descriptor;
+ var obj = src;
+ while (true) {
+ descriptor = Object.getOwnPropertyDescriptor(obj, key);
+ if (descriptor != null) {
+ break;
+ }
+ obj = Object.getPrototypeOf(obj);
+ if (obj == null) {
+ throw new Error('Can\'t find descriptor of ' + key);
+ }
+ }
+
+ if ('value' in descriptor) {
+ dest[key] = src[key];
+ }
+ else if ('get' in descriptor) {
+ Object.defineProperty(dest, key, {
+ get: descriptor.get,
+ set: descriptor.set,
+ enumerable: descriptor.enumerable
+ });
+ }
+ else {
+ throw new Error('Don\'t know how to copy ' + key + ' property.');
+ }
+ }
+};
+
+//------------------------------------------------------------------------------
+
+/**
+ * XHTML namespace
+ */
+exports.NS_XHTML = 'http://www.w3.org/1999/xhtml';
+
+/**
+ * XUL namespace
+ */
+exports.NS_XUL = 'http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul';
+
+/**
+ * Create an HTML or XHTML element depending on whether the document is HTML
+ * or XML based. Where HTML/XHTML elements are distinguished by whether they
+ * are created using doc.createElementNS('http://www.w3.org/1999/xhtml', tag)
+ * or doc.createElement(tag)
+ * If you want to create a XUL element then you don't have a problem knowing
+ * what namespace you want.
+ * @param doc The document in which to create the element
+ * @param tag The name of the tag to create
+ * @returns The created element
+ */
+exports.createElement = function(doc, tag) {
+ if (exports.isXmlDocument(doc)) {
+ return doc.createElementNS(exports.NS_XHTML, tag);
+ }
+ else {
+ return doc.createElement(tag);
+ }
+};
+
+/**
+ * Remove all the child nodes from this node
+ * @param elem The element that should have it's children removed
+ */
+exports.clearElement = function(elem) {
+ while (elem.hasChildNodes()) {
+ elem.removeChild(elem.firstChild);
+ }
+};
+
+var isAllWhitespace = /^\s*$/;
+
+/**
+ * Iterate over the children of a node looking for TextNodes that have only
+ * whitespace content and remove them.
+ * This utility is helpful when you have a template which contains whitespace
+ * so it looks nice, but where the whitespace interferes with the rendering of
+ * the page
+ * @param elem The element which should have blank whitespace trimmed
+ * @param deep Should this node removal include child elements
+ */
+exports.removeWhitespace = function(elem, deep) {
+ var i = 0;
+ while (i < elem.childNodes.length) {
+ var child = elem.childNodes.item(i);
+ if (child.nodeType === 3 /*Node.TEXT_NODE*/ &&
+ isAllWhitespace.test(child.textContent)) {
+ elem.removeChild(child);
+ }
+ else {
+ if (deep && child.nodeType === 1 /*Node.ELEMENT_NODE*/) {
+ exports.removeWhitespace(child, deep);
+ }
+ i++;
+ }
+ }
+};
+
+/**
+ * Create a style element in the document head, and add the given CSS text to
+ * it.
+ * @param cssText The CSS declarations to append
+ * @param doc The document element to work from
+ * @param id Optional id to assign to the created style tag. If the id already
+ * exists on the document, we do not add the CSS again.
+ */
+exports.importCss = function(cssText, doc, id) {
+ if (!cssText) {
+ return undefined;
+ }
+
+ doc = doc || document;
+
+ if (!id) {
+ id = 'hash-' + hash(cssText);
+ }
+
+ var found = doc.getElementById(id);
+ if (found) {
+ if (found.tagName.toLowerCase() !== 'style') {
+ console.error('Warning: importCss passed id=' + id +
+ ', but that pre-exists (and isn\'t a style tag)');
+ }
+ return found;
+ }
+
+ var style = exports.createElement(doc, 'style');
+ style.id = id;
+ style.appendChild(doc.createTextNode(cssText));
+
+ var head = doc.getElementsByTagName('head')[0] || doc.documentElement;
+ head.appendChild(style);
+
+ return style;
+};
+
+/**
+ * Simple hash function which happens to match Java's |String.hashCode()|
+ * Done like this because I we don't need crypto-security, but do need speed,
+ * and I don't want to spend a long time working on it.
+ * @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
+ */
+function hash(str) {
+ var h = 0;
+ if (str.length === 0) {
+ return h;
+ }
+ for (var i = 0; i < str.length; i++) {
+ var character = str.charCodeAt(i);
+ h = ((h << 5) - h) + character;
+ h = h & h; // Convert to 32bit integer
+ }
+ return h;
+}
+
+/**
+ * Shortcut for clearElement/createTextNode/appendChild to make up for the lack
+ * of standards around textContent/innerText
+ */
+exports.setTextContent = function(elem, text) {
+ exports.clearElement(elem);
+ var child = elem.ownerDocument.createTextNode(text);
+ elem.appendChild(child);
+};
+
+/**
+ * There are problems with innerHTML on XML documents, so we need to do a dance
+ * using document.createRange().createContextualFragment() when in XML mode
+ */
+exports.setContents = function(elem, contents) {
+ if (typeof HTMLElement !== 'undefined' && contents instanceof HTMLElement) {
+ exports.clearElement(elem);
+ elem.appendChild(contents);
+ return;
+ }
+
+ if ('innerHTML' in elem) {
+ elem.innerHTML = contents;
+ }
+ else {
+ try {
+ var ns = elem.ownerDocument.documentElement.namespaceURI;
+ if (!ns) {
+ ns = exports.NS_XHTML;
+ }
+ exports.clearElement(elem);
+ contents = '<div xmlns="' + ns + '">' + contents + '</div>';
+ var range = elem.ownerDocument.createRange();
+ var child = range.createContextualFragment(contents).firstChild;
+ while (child.hasChildNodes()) {
+ elem.appendChild(child.firstChild);
+ }
+ }
+ catch (ex) {
+ console.error('Bad XHTML', ex);
+ console.trace();
+ throw ex;
+ }
+ }
+};
+
+/**
+ * How to detect if we're in an XML document.
+ * In a Mozilla we check that document.xmlVersion = null, however in Chrome
+ * we use document.contentType = undefined.
+ * @param doc The document element to work from (defaulted to the global
+ * 'document' if missing
+ */
+exports.isXmlDocument = function(doc) {
+ doc = doc || document;
+ // Best test for Firefox
+ if (doc.contentType && doc.contentType != 'text/html') {
+ return true;
+ }
+ // Best test for Chrome
+ if (doc.xmlVersion != null) {
+ return true;
+ }
+ return false;
+};
+
+/**
+ * We'd really like to be able to do 'new NodeList()'
+ */
+exports.createEmptyNodeList = function(doc) {
+ if (doc.createDocumentFragment) {
+ return doc.createDocumentFragment().childNodes;
+ }
+ return doc.querySelectorAll('x>:root');
+};
+
+//------------------------------------------------------------------------------
+
+/**
+ * Keyboard handling is a mess. http://unixpapa.com/js/key.html
+ * It would be good to use DOM L3 Keyboard events,
+ * http://www.w3.org/TR/2010/WD-DOM-Level-3-Events-20100907/#events-keyboardevents
+ * however only Webkit supports them, and there isn't a shim on Modernizr:
+ * https://github.com/Modernizr/Modernizr/wiki/HTML5-Cross-browser-Polyfills
+ * and when the code that uses this KeyEvent was written, nothing was clear,
+ * so instead, we're using this unmodern shim:
+ * http://stackoverflow.com/questions/5681146/chrome-10-keyevent-or-something-similar-to-firefoxs-keyevent
+ * See BUG 664991: GCLI's keyboard handling should be updated to use DOM-L3
+ * https://bugzilla.mozilla.org/show_bug.cgi?id=664991
+ */
+exports.KeyEvent = {
+ DOM_VK_CANCEL: 3,
+ DOM_VK_HELP: 6,
+ DOM_VK_BACK_SPACE: 8,
+ DOM_VK_TAB: 9,
+ DOM_VK_CLEAR: 12,
+ DOM_VK_RETURN: 13,
+ DOM_VK_SHIFT: 16,
+ DOM_VK_CONTROL: 17,
+ DOM_VK_ALT: 18,
+ DOM_VK_PAUSE: 19,
+ DOM_VK_CAPS_LOCK: 20,
+ DOM_VK_ESCAPE: 27,
+ DOM_VK_SPACE: 32,
+ DOM_VK_PAGE_UP: 33,
+ DOM_VK_PAGE_DOWN: 34,
+ DOM_VK_END: 35,
+ DOM_VK_HOME: 36,
+ DOM_VK_LEFT: 37,
+ DOM_VK_UP: 38,
+ DOM_VK_RIGHT: 39,
+ DOM_VK_DOWN: 40,
+ DOM_VK_PRINTSCREEN: 44,
+ DOM_VK_INSERT: 45,
+ DOM_VK_DELETE: 46,
+ DOM_VK_0: 48,
+ DOM_VK_1: 49,
+ DOM_VK_2: 50,
+ DOM_VK_3: 51,
+ DOM_VK_4: 52,
+ DOM_VK_5: 53,
+ DOM_VK_6: 54,
+ DOM_VK_7: 55,
+ DOM_VK_8: 56,
+ DOM_VK_9: 57,
+ DOM_VK_SEMICOLON: 59,
+ DOM_VK_EQUALS: 61,
+ DOM_VK_A: 65,
+ DOM_VK_B: 66,
+ DOM_VK_C: 67,
+ DOM_VK_D: 68,
+ DOM_VK_E: 69,
+ DOM_VK_F: 70,
+ DOM_VK_G: 71,
+ DOM_VK_H: 72,
+ DOM_VK_I: 73,
+ DOM_VK_J: 74,
+ DOM_VK_K: 75,
+ DOM_VK_L: 76,
+ DOM_VK_M: 77,
+ DOM_VK_N: 78,
+ DOM_VK_O: 79,
+ DOM_VK_P: 80,
+ DOM_VK_Q: 81,
+ DOM_VK_R: 82,
+ DOM_VK_S: 83,
+ DOM_VK_T: 84,
+ DOM_VK_U: 85,
+ DOM_VK_V: 86,
+ DOM_VK_W: 87,
+ DOM_VK_X: 88,
+ DOM_VK_Y: 89,
+ DOM_VK_Z: 90,
+ DOM_VK_CONTEXT_MENU: 93,
+ DOM_VK_NUMPAD0: 96,
+ DOM_VK_NUMPAD1: 97,
+ DOM_VK_NUMPAD2: 98,
+ DOM_VK_NUMPAD3: 99,
+ DOM_VK_NUMPAD4: 100,
+ DOM_VK_NUMPAD5: 101,
+ DOM_VK_NUMPAD6: 102,
+ DOM_VK_NUMPAD7: 103,
+ DOM_VK_NUMPAD8: 104,
+ DOM_VK_NUMPAD9: 105,
+ DOM_VK_MULTIPLY: 106,
+ DOM_VK_ADD: 107,
+ DOM_VK_SEPARATOR: 108,
+ DOM_VK_SUBTRACT: 109,
+ DOM_VK_DECIMAL: 110,
+ DOM_VK_DIVIDE: 111,
+ DOM_VK_F1: 112,
+ DOM_VK_F2: 113,
+ DOM_VK_F3: 114,
+ DOM_VK_F4: 115,
+ DOM_VK_F5: 116,
+ DOM_VK_F6: 117,
+ DOM_VK_F7: 118,
+ DOM_VK_F8: 119,
+ DOM_VK_F9: 120,
+ DOM_VK_F10: 121,
+ DOM_VK_F11: 122,
+ DOM_VK_F12: 123,
+ DOM_VK_F13: 124,
+ DOM_VK_F14: 125,
+ DOM_VK_F15: 126,
+ DOM_VK_F16: 127,
+ DOM_VK_F17: 128,
+ DOM_VK_F18: 129,
+ DOM_VK_F19: 130,
+ DOM_VK_F20: 131,
+ DOM_VK_F21: 132,
+ DOM_VK_F22: 133,
+ DOM_VK_F23: 134,
+ DOM_VK_F24: 135,
+ DOM_VK_NUM_LOCK: 144,
+ DOM_VK_SCROLL_LOCK: 145,
+ DOM_VK_COMMA: 188,
+ DOM_VK_PERIOD: 190,
+ DOM_VK_SLASH: 191,
+ DOM_VK_BACK_QUOTE: 192,
+ DOM_VK_OPEN_BRACKET: 219,
+ DOM_VK_BACK_SLASH: 220,
+ DOM_VK_CLOSE_BRACKET: 221,
+ DOM_VK_QUOTE: 222,
+ DOM_VK_META: 224
+};
diff --git a/devtools/shared/gcli/templater.js b/devtools/shared/gcli/templater.js
new file mode 100644
index 000000000..c5e01b6cf
--- /dev/null
+++ b/devtools/shared/gcli/templater.js
@@ -0,0 +1,602 @@
+/*
+ * Copyright 2012, Mozilla Foundation and contributors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+"use strict";
+
+/* globals document */
+
+/**
+ * For full documentation, see:
+ * https://github.com/mozilla/domtemplate/blob/master/README.md
+ */
+
+/**
+ * Begin a new templating process.
+ * @param node A DOM element or string referring to an element's id
+ * @param data Data to use in filling out the template
+ * @param options Options to customize the template processing. One of:
+ * - allowEval: boolean (default false) Basic template interpolations are
+ * either property paths (e.g. ${a.b.c.d}), or if allowEval=true then we
+ * allow arbitrary JavaScript
+ * - stack: string or array of strings (default empty array) The template
+ * engine maintains a stack of tasks to help debug where it is. This allows
+ * this stack to be prefixed with a template name
+ * - blankNullUndefined: By default DOMTemplate exports null and undefined
+ * values using the strings 'null' and 'undefined', which can be helpful for
+ * debugging, but can introduce unnecessary extra logic in a template to
+ * convert null/undefined to ''. By setting blankNullUndefined:true, this
+ * conversion is handled by DOMTemplate
+ */
+var template = function (node, data, options) {
+ let state = {
+ options: options || {},
+ // We keep a track of the nodes that we've passed through so we can keep
+ // data.__element pointing to the correct node
+ nodes: []
+ };
+
+ state.stack = state.options.stack;
+
+ if (!Array.isArray(state.stack)) {
+ if (typeof state.stack === "string") {
+ state.stack = [ options.stack ];
+ } else {
+ state.stack = [];
+ }
+ }
+
+ processNode(state, node, data);
+};
+
+if (typeof exports !== "undefined") {
+ exports.template = template;
+}
+this.template = template;
+
+/**
+ * Helper for the places where we need to act asynchronously and keep track of
+ * where we are right now
+ */
+function cloneState(state) {
+ return {
+ options: state.options,
+ stack: state.stack.slice(),
+ nodes: state.nodes.slice()
+ };
+}
+
+/**
+ * Regex used to find ${...} sections in some text.
+ * Performance note: This regex uses ( and ) to capture the 'script' for
+ * further processing. Not all of the uses of this regex use this feature so
+ * if use of the capturing group is a performance drain then we should split
+ * this regex in two.
+ */
+var TEMPLATE_REGION = /\$\{([^}]*)\}/g;
+
+/**
+ * Recursive function to walk the tree processing the attributes as it goes.
+ * @param node the node to process. If you pass a string in instead of a DOM
+ * element, it is assumed to be an id for use with document.getElementById()
+ * @param data the data to use for node processing.
+ */
+function processNode(state, node, data) {
+ if (typeof node === "string") {
+ node = document.getElementById(node);
+ }
+ if (data == null) {
+ data = {};
+ }
+ state.stack.push(node.nodeName + (node.id ? "#" + node.id : ""));
+ let pushedNode = false;
+ try {
+ // Process attributes
+ if (node.attributes && node.attributes.length) {
+ // We need to handle 'foreach' and 'if' first because they might stop
+ // some types of processing from happening, and foreach must come first
+ // because it defines new data on which 'if' might depend.
+ if (node.hasAttribute("foreach")) {
+ processForEach(state, node, data);
+ return;
+ }
+ if (node.hasAttribute("if")) {
+ if (!processIf(state, node, data)) {
+ return;
+ }
+ }
+ // Only make the node available once we know it's not going away
+ state.nodes.push(data.__element);
+ data.__element = node;
+ pushedNode = true;
+ // It's good to clean up the attributes when we've processed them,
+ // but if we do it straight away, we mess up the array index
+ let attrs = Array.prototype.slice.call(node.attributes);
+ for (let i = 0; i < attrs.length; i++) {
+ let value = attrs[i].value;
+ let name = attrs[i].name;
+
+ state.stack.push(name);
+ try {
+ if (name === "save") {
+ // Save attributes are a setter using the node
+ value = stripBraces(state, value);
+ property(state, value, data, node);
+ node.removeAttribute("save");
+ } else if (name.substring(0, 2) === "on") {
+ // If this attribute value contains only an expression
+ if (value.substring(0, 2) === "${" && value.slice(-1) === "}" &&
+ value.indexOf("${", 2) === -1) {
+ value = stripBraces(state, value);
+ let func = property(state, value, data);
+ if (typeof func === "function") {
+ node.removeAttribute(name);
+ let capture = node.hasAttribute("capture" + name.substring(2));
+ node.addEventListener(name.substring(2), func, capture);
+ if (capture) {
+ node.removeAttribute("capture" + name.substring(2));
+ }
+ } else {
+ // Attribute value is not a function - use as a DOM-L0 string
+ node.setAttribute(name, func);
+ }
+ } else {
+ // Attribute value is not a single expression use as DOM-L0
+ node.setAttribute(name, processString(state, value, data));
+ }
+ } else {
+ node.removeAttribute(name);
+ // Remove '_' prefix of attribute names so the DOM won't try
+ // to use them before we've processed the template
+ if (name.charAt(0) === "_") {
+ name = name.substring(1);
+ }
+
+ // Async attributes can only work if the whole attribute is async
+ let replacement;
+ if (value.indexOf("${") === 0 &&
+ value.charAt(value.length - 1) === "}") {
+ replacement = envEval(state, value.slice(2, -1), data, value);
+ if (replacement && typeof replacement.then === "function") {
+ node.setAttribute(name, "");
+ /* jshint loopfunc:true */
+ replacement.then(function (newValue) {
+ node.setAttribute(name, newValue);
+ }).then(null, console.error);
+ } else {
+ if (state.options.blankNullUndefined && replacement == null) {
+ replacement = "";
+ }
+ node.setAttribute(name, replacement);
+ }
+ } else {
+ node.setAttribute(name, processString(state, value, data));
+ }
+ }
+ } finally {
+ state.stack.pop();
+ }
+ }
+ }
+
+ // Loop through our children calling processNode. First clone them, so the
+ // set of nodes that we visit will be unaffected by additions or removals.
+ let childNodes = Array.prototype.slice.call(node.childNodes);
+ for (let j = 0; j < childNodes.length; j++) {
+ processNode(state, childNodes[j], data);
+ }
+
+ /* 3 === Node.TEXT_NODE */
+ if (node.nodeType === 3) {
+ processTextNode(state, node, data);
+ }
+ } finally {
+ if (pushedNode) {
+ data.__element = state.nodes.pop();
+ }
+ state.stack.pop();
+ }
+}
+
+/**
+ * Handle attribute values where the output can only be a string
+ */
+function processString(state, value, data) {
+ return value.replace(TEMPLATE_REGION, function (path) {
+ let insert = envEval(state, path.slice(2, -1), data, value);
+ return state.options.blankNullUndefined && insert == null ? "" : insert;
+ });
+}
+
+/**
+ * Handle <x if="${...}">
+ * @param node An element with an 'if' attribute
+ * @param data The data to use with envEval()
+ * @returns true if processing should continue, false otherwise
+ */
+function processIf(state, node, data) {
+ state.stack.push("if");
+ try {
+ let originalValue = node.getAttribute("if");
+ let value = stripBraces(state, originalValue);
+ let recurse = true;
+ try {
+ let reply = envEval(state, value, data, originalValue);
+ recurse = !!reply;
+ } catch (ex) {
+ handleError(state, "Error with '" + value + "'", ex);
+ recurse = false;
+ }
+ if (!recurse) {
+ node.parentNode.removeChild(node);
+ }
+ node.removeAttribute("if");
+ return recurse;
+ } finally {
+ state.stack.pop();
+ }
+}
+
+/**
+ * Handle <x foreach="param in ${array}"> and the special case of
+ * <loop foreach="param in ${array}">.
+ * This function is responsible for extracting what it has to do from the
+ * attributes, and getting the data to work on (including resolving promises
+ * in getting the array). It delegates to processForEachLoop to actually
+ * unroll the data.
+ * @param node An element with a 'foreach' attribute
+ * @param data The data to use with envEval()
+ */
+function processForEach(state, node, data) {
+ state.stack.push("foreach");
+ try {
+ let originalValue = node.getAttribute("foreach");
+ let value = originalValue;
+
+ let paramName = "param";
+ if (value.charAt(0) === "$") {
+ // No custom loop variable name. Use the default: 'param'
+ value = stripBraces(state, value);
+ } else {
+ // Extract the loop variable name from 'NAME in ${ARRAY}'
+ let nameArr = value.split(" in ");
+ paramName = nameArr[0].trim();
+ value = stripBraces(state, nameArr[1].trim());
+ }
+ node.removeAttribute("foreach");
+ try {
+ let evaled = envEval(state, value, data, originalValue);
+ let cState = cloneState(state);
+ handleAsync(evaled, node, function (reply, siblingNode) {
+ processForEachLoop(cState, reply, node, siblingNode, data, paramName);
+ });
+ node.parentNode.removeChild(node);
+ } catch (ex) {
+ handleError(state, "Error with " + value + "'", ex);
+ }
+ } finally {
+ state.stack.pop();
+ }
+}
+
+/**
+ * Called by processForEach to handle looping over the data in a foreach loop.
+ * This works with both arrays and objects.
+ * Calls processForEachMember() for each member of 'set'
+ * @param set The object containing the data to loop over
+ * @param templNode The node to copy for each set member
+ * @param sibling The sibling node to which we add things
+ * @param data the data to use for node processing
+ * @param paramName foreach loops have a name for the parameter currently being
+ * processed. The default is 'param'. e.g. <loop foreach="param in ${x}">...
+ */
+function processForEachLoop(state, set, templNode, sibling, data, paramName) {
+ if (Array.isArray(set)) {
+ set.forEach(function (member, i) {
+ processForEachMember(state, member, templNode, sibling,
+ data, paramName, "" + i);
+ });
+ } else {
+ for (let member in set) {
+ if (set.hasOwnProperty(member)) {
+ processForEachMember(state, member, templNode, sibling,
+ data, paramName, member);
+ }
+ }
+ }
+}
+
+/**
+ * Called by processForEachLoop() to resolve any promises in the array (the
+ * array itself can also be a promise, but that is resolved by
+ * processForEach()). Handle <LOOP> elements (which are taken out of the DOM),
+ * clone the template node, and pass the processing on to processNode().
+ * @param member The data item to use in templating
+ * @param templNode The node to copy for each set member
+ * @param siblingNode The parent node to which we add things
+ * @param data the data to use for node processing
+ * @param paramName The name given to 'member' by the foreach attribute
+ * @param frame A name to push on the stack for debugging
+ */
+function processForEachMember(state, member, templNode, siblingNode, data,
+ paramName, frame) {
+ state.stack.push(frame);
+ try {
+ let cState = cloneState(state);
+ handleAsync(member, siblingNode, function (reply, node) {
+ // Clone data because we can't be sure that we can safely mutate it
+ let newData = Object.create(null);
+ Object.keys(data).forEach(function (key) {
+ newData[key] = data[key];
+ });
+ newData[paramName] = reply;
+ if (node.parentNode != null) {
+ let clone;
+ if (templNode.nodeName.toLowerCase() === "loop") {
+ for (let i = 0; i < templNode.childNodes.length; i++) {
+ clone = templNode.childNodes[i].cloneNode(true);
+ node.parentNode.insertBefore(clone, node);
+ processNode(cState, clone, newData);
+ }
+ } else {
+ clone = templNode.cloneNode(true);
+ clone.removeAttribute("foreach");
+ node.parentNode.insertBefore(clone, node);
+ processNode(cState, clone, newData);
+ }
+ }
+ });
+ } finally {
+ state.stack.pop();
+ }
+}
+
+/**
+ * Take a text node and replace it with another text node with the ${...}
+ * sections parsed out. We replace the node by altering node.parentNode but
+ * we could probably use a DOM Text API to achieve the same thing.
+ * @param node The Text node to work on
+ * @param data The data to use in calls to envEval()
+ */
+function processTextNode(state, node, data) {
+ // Replace references in other attributes
+ let value = node.data;
+ // We can't use the string.replace() with function trick (see generic
+ // attribute processing in processNode()) because we need to support
+ // functions that return DOM nodes, so we can't have the conversion to a
+ // string.
+ // Instead we process the string as an array of parts. In order to split
+ // the string up, we first replace '${' with '\uF001$' and '}' with '\uF002'
+ // We can then split using \uF001 or \uF002 to get an array of strings
+ // where scripts are prefixed with $.
+ // \uF001 and \uF002 are just unicode chars reserved for private use.
+ value = value.replace(TEMPLATE_REGION, "\uF001$$$1\uF002");
+ // Split a string using the unicode chars F001 and F002.
+ let parts = value.split(/\uF001|\uF002/);
+ if (parts.length > 1) {
+ parts.forEach(function (part) {
+ if (part === null || part === undefined || part === "") {
+ return;
+ }
+ if (part.charAt(0) === "$") {
+ part = envEval(state, part.slice(1), data, node.data);
+ }
+ let cState = cloneState(state);
+ handleAsync(part, node, function (reply, siblingNode) {
+ let doc = siblingNode.ownerDocument;
+ if (reply == null) {
+ reply = cState.options.blankNullUndefined ? "" : "" + reply;
+ }
+ if (typeof reply.cloneNode === "function") {
+ // i.e. if (reply instanceof Element) { ...
+ reply = maybeImportNode(cState, reply, doc);
+ siblingNode.parentNode.insertBefore(reply, siblingNode);
+ } else if (typeof reply.item === "function" && reply.length) {
+ // NodeLists can be live, in which case maybeImportNode can
+ // remove them from the document, and thus the NodeList, which in
+ // turn breaks iteration. So first we clone the list
+ let list = Array.prototype.slice.call(reply, 0);
+ list.forEach(function (child) {
+ let imported = maybeImportNode(cState, child, doc);
+ siblingNode.parentNode.insertBefore(imported, siblingNode);
+ });
+ } else {
+ // if thing isn't a DOM element then wrap its string value in one
+ reply = doc.createTextNode(reply.toString());
+ siblingNode.parentNode.insertBefore(reply, siblingNode);
+ }
+ });
+ });
+ node.parentNode.removeChild(node);
+ }
+}
+
+/**
+ * Return node or a import of node, if it's not in the given document
+ * @param node The node that we want to be properly owned
+ * @param doc The document that the given node should belong to
+ * @return A node that belongs to the given document
+ */
+function maybeImportNode(state, node, doc) {
+ return node.ownerDocument === doc ? node : doc.importNode(node, true);
+}
+
+/**
+ * A function to handle the fact that some nodes can be promises, so we check
+ * and resolve if needed using a marker node to keep our place before calling
+ * an inserter function.
+ * @param thing The object which could be real data or a promise of real data
+ * we use it directly if it's not a promise, or resolve it if it is.
+ * @param siblingNode The element before which we insert new elements.
+ * @param inserter The function to to the insertion. If thing is not a promise
+ * then handleAsync() is just 'inserter(thing, siblingNode)'
+ */
+function handleAsync(thing, siblingNode, inserter) {
+ if (thing != null && typeof thing.then === "function") {
+ // Placeholder element to be replaced once we have the real data
+ let tempNode = siblingNode.ownerDocument.createElement("span");
+ siblingNode.parentNode.insertBefore(tempNode, siblingNode);
+ thing.then(function (delayed) {
+ inserter(delayed, tempNode);
+ if (tempNode.parentNode != null) {
+ tempNode.parentNode.removeChild(tempNode);
+ }
+ }).then(null, function (error) {
+ console.error(error.stack);
+ });
+ } else {
+ inserter(thing, siblingNode);
+ }
+}
+
+/**
+ * Warn of string does not begin '${' and end '}'
+ * @param str the string to check.
+ * @return The string stripped of ${ and }, or untouched if it does not match
+ */
+function stripBraces(state, str) {
+ if (!str.match(TEMPLATE_REGION)) {
+ handleError(state, "Expected " + str + " to match ${...}");
+ return str;
+ }
+ return str.slice(2, -1);
+}
+
+/**
+ * Combined getter and setter that works with a path through some data set.
+ * For example:
+ * <ul>
+ * <li>property(state, 'a.b', { a: { b: 99 }}); // returns 99
+ * <li>property(state, 'a', { a: { b: 99 }}); // returns { b: 99 }
+ * <li>property(state, 'a', { a: { b: 99 }}, 42); // returns 99 and alters the
+ * input data to be { a: { b: 42 }}
+ * </ul>
+ * @param path An array of strings indicating the path through the data, or
+ * a string to be cut into an array using <tt>split('.')</tt>
+ * @param data the data to use for node processing
+ * @param newValue (optional) If defined, this value will replace the
+ * original value for the data at the path specified.
+ * @return The value pointed to by <tt>path</tt> before any
+ * <tt>newValue</tt> is applied.
+ */
+function property(state, path, data, newValue) {
+ try {
+ if (typeof path === "string") {
+ path = path.split(".");
+ }
+ let value = data[path[0]];
+ if (path.length === 1) {
+ if (newValue !== undefined) {
+ data[path[0]] = newValue;
+ }
+ if (typeof value === "function") {
+ return value.bind(data);
+ }
+ return value;
+ }
+ if (!value) {
+ handleError(state, "\"" + path[0] + "\" is undefined");
+ return null;
+ }
+ return property(state, path.slice(1), value, newValue);
+ } catch (ex) {
+ handleError(state, "Path error with '" + path + "'", ex);
+ return "${" + path + "}";
+ }
+}
+
+/**
+ * Like eval, but that creates a context of the variables in <tt>env</tt> in
+ * which the script is evaluated.
+ * @param script The string to be evaluated.
+ * @param data The environment in which to eval the script.
+ * @param frame Optional debugging string in case of failure.
+ * @return The return value of the script, or the error message if the script
+ * execution failed.
+ */
+function envEval(state, script, data, frame) {
+ try {
+ state.stack.push(frame.replace(/\s+/g, " "));
+ // Detect if a script is capable of being interpreted using property()
+ if (/^[_a-zA-Z0-9.]*$/.test(script)) {
+ return property(state, script, data);
+ }
+ if (!state.options.allowEval) {
+ handleError(state, "allowEval is not set, however '" + script + "'" +
+ " can not be resolved using a simple property path.");
+ return "${" + script + "}";
+ }
+
+ // What we're looking to do is basically:
+ // with(data) { return eval(script); }
+ // except in strict mode where 'with' is banned.
+ // So we create a function which has a parameter list the same as the
+ // keys in 'data' and with 'script' as its function body.
+ // We then call this function with the values in 'data'
+ let keys = allKeys(data);
+ let func = Function.apply(null, keys.concat("return " + script));
+
+ let values = keys.map((key) => data[key]);
+ return func.apply(null, values);
+
+ // TODO: The 'with' method is different from the code above in the value
+ // of 'this' when calling functions. For example:
+ // envEval(state, 'foo()', { foo: function () { return this; } }, ...);
+ // The global for 'foo' when using 'with' is the data object. However the
+ // code above, the global is null. (Using 'func.apply(data, values)'
+ // changes 'this' in the 'foo()' frame, but not in the inside the body
+ // of 'foo', so that wouldn't help)
+ } catch (ex) {
+ handleError(state, "Template error evaluating '" + script + "'", ex);
+ return "${" + script + "}";
+ } finally {
+ state.stack.pop();
+ }
+}
+
+/**
+ * Object.keys() that respects the prototype chain
+ */
+function allKeys(data) {
+ let keys = [];
+ for (let key in data) {
+ keys.push(key);
+ }
+ return keys;
+}
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ * @param ex optional associated exception.
+ */
+function handleError(state, message, ex) {
+ logError(message + " (In: " + state.stack.join(" > ") + ")");
+ if (ex) {
+ logError(ex);
+ }
+}
+
+/**
+ * A generic way of reporting errors, for easy overloading in different
+ * environments.
+ * @param message the error message to report.
+ */
+function logError(message) {
+ console.error(message);
+}
+
+exports.template = template;
diff --git a/devtools/shared/heapsnapshot/.gitattributes b/devtools/shared/heapsnapshot/.gitattributes
new file mode 100644
index 000000000..44e248a8d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/.gitattributes
@@ -0,0 +1 @@
+CoreDump.pb.* binary
diff --git a/devtools/shared/heapsnapshot/AutoMemMap.cpp b/devtools/shared/heapsnapshot/AutoMemMap.cpp
new file mode 100644
index 000000000..e725a99c6
--- /dev/null
+++ b/devtools/shared/heapsnapshot/AutoMemMap.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/AutoMemMap.h"
+
+#include "mozilla/Unused.h"
+#include "nsDebug.h"
+
+namespace mozilla {
+namespace devtools {
+
+AutoMemMap::~AutoMemMap()
+{
+ if (addr) {
+ Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
+ addr = nullptr;
+ }
+
+ if (fileMap) {
+ Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
+ fileMap = nullptr;
+ }
+
+ if (fd) {
+ Unused << NS_WARN_IF(PR_Close(fd) != PR_SUCCESS);
+ fd = nullptr;
+ }
+}
+
+nsresult
+AutoMemMap::init(const char* filePath, int flags, int mode, PRFileMapProtect prot)
+{
+ MOZ_ASSERT(!fd);
+ MOZ_ASSERT(!fileMap);
+ MOZ_ASSERT(!addr);
+
+ if (PR_GetFileInfo64(filePath, &fileInfo) != PR_SUCCESS)
+ return NS_ERROR_FILE_NOT_FOUND;
+
+ // Check if the file is too big to memmap.
+ if (fileInfo.size > int64_t(UINT32_MAX))
+ return NS_ERROR_INVALID_ARG;
+ auto length = uint32_t(fileInfo.size);
+
+ fd = PR_Open(filePath, flags, flags);
+ if (!fd)
+ return NS_ERROR_UNEXPECTED;
+
+ fileMap = PR_CreateFileMap(fd, fileInfo.size, prot);
+ if (!fileMap)
+ return NS_ERROR_UNEXPECTED;
+
+ addr = PR_MemMap(fileMap, 0, length);
+ if (!addr)
+ return NS_ERROR_UNEXPECTED;
+
+ return NS_OK;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/AutoMemMap.h b/devtools/shared/heapsnapshot/AutoMemMap.h
new file mode 100644
index 000000000..537d68004
--- /dev/null
+++ b/devtools/shared/heapsnapshot/AutoMemMap.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_AutoMemMap_h
+#define mozilla_devtools_AutoMemMap_h
+
+#include <prio.h>
+#include "mozilla/GuardObjects.h"
+
+namespace mozilla {
+namespace devtools {
+
+// # AutoMemMap
+//
+// AutoMemMap is an RAII class to manage mapping a file to memory. It is a
+// wrapper aorund managing opening and closing a file and calling PR_MemMap and
+// PR_MemUnmap.
+//
+// Example usage:
+//
+// {
+// AutoMemMap mm;
+// if (NS_FAILED(mm.init("/path/to/desired/file"))) {
+// // Handle the error however you see fit.
+// return false;
+// }
+//
+// doStuffWithMappedMemory(mm.address());
+// }
+// // The memory is automatically unmapped when the AutoMemMap leaves scope.
+class MOZ_RAII AutoMemMap
+{
+ MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
+
+ PRFileInfo64 fileInfo;
+ PRFileDesc* fd;
+ PRFileMap* fileMap;
+ void* addr;
+
+ AutoMemMap(const AutoMemMap& aOther) = delete;
+ void operator=(const AutoMemMap& aOther) = delete;
+
+public:
+ explicit AutoMemMap(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+ : fd(nullptr)
+ , fileMap(nullptr)
+ , addr(nullptr)
+ {
+ MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+ };
+ ~AutoMemMap();
+
+ // Initialize this AutoMemMap.
+ nsresult init(const char* filePath, int flags = PR_RDONLY, int mode = 0,
+ PRFileMapProtect prot = PR_PROT_READONLY);
+
+ // Get the size of the memory mapped file.
+ uint32_t size() const {
+ MOZ_ASSERT(fileInfo.size <= UINT32_MAX,
+ "Should only call size() if init() succeeded.");
+ return uint32_t(fileInfo.size);
+ }
+
+ // Get the mapped memory.
+ void* address() { MOZ_ASSERT(addr); return addr; }
+ const void* address() const { MOZ_ASSERT(addr); return addr; }
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_AutoMemMap_h
diff --git a/devtools/shared/heapsnapshot/CensusUtils.js b/devtools/shared/heapsnapshot/CensusUtils.js
new file mode 100644
index 000000000..36bdd2d82
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CensusUtils.js
@@ -0,0 +1,489 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { flatten } = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
+
+/** * Visitor ****************************************************************/
+
+/**
+ * A Visitor visits each node and edge of a census report tree as the census
+ * report is being traversed by `walk`.
+ */
+function Visitor() { }
+exports.Visitor = Visitor;
+
+/**
+ * The `enter` method is called when a new sub-report is entered in traversal.
+ *
+ * @param {Object} breakdown
+ * The breakdown for the sub-report that is being entered by traversal.
+ *
+ * @param {Object} report
+ * The report generated by the given breakdown.
+ *
+ * @param {any} edge
+ * The edge leading to this sub-report. The edge is null if (but not iff!
+ * eg, null allocation stack edges) we are entering the root report.
+ */
+Visitor.prototype.enter = function (breakdown, report, edge) { };
+
+/**
+ * The `exit` method is called when traversal of a sub-report has finished.
+ *
+ * @param {Object} breakdown
+ * The breakdown for the sub-report whose traversal has finished.
+ *
+ * @param {Object} report
+ * The report generated by the given breakdown.
+ *
+ * @param {any} edge
+ * The edge leading to this sub-report. The edge is null if (but not iff!
+ * eg, null allocation stack edges) we are entering the root report.
+ */
+Visitor.prototype.exit = function (breakdown, report, edge) { };
+
+/**
+ * The `count` method is called when leaf nodes (reports whose breakdown is
+ * by: "count") in the report tree are encountered.
+ *
+ * @param {Object} breakdown
+ * The count breakdown for this report.
+ *
+ * @param {Object} report
+ * The report generated by a breakdown by "count".
+ *
+ * @param {any|null} edge
+ * The edge leading to this count report. The edge is null if we are
+ * entering the root report.
+ */
+Visitor.prototype.count = function (breakdown, report, edge) { };
+
+/** * getReportEdges *********************************************************/
+
+const EDGES = Object.create(null);
+
+EDGES.count = function (breakdown, report) {
+ return [];
+};
+
+EDGES.bucket = function (breakdown, report) {
+ return [];
+};
+
+EDGES.internalType = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: breakdown.then
+ }));
+};
+
+EDGES.objectClass = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: key === "other" ? breakdown.other : breakdown.then
+ }));
+};
+
+EDGES.coarseType = function (breakdown, report) {
+ return [
+ { edge: "objects", referent: report.objects, breakdown: breakdown.objects },
+ { edge: "scripts", referent: report.scripts, breakdown: breakdown.scripts },
+ { edge: "strings", referent: report.strings, breakdown: breakdown.strings },
+ { edge: "other", referent: report.other, breakdown: breakdown.other },
+ ];
+};
+
+EDGES.allocationStack = function (breakdown, report) {
+ const edges = [];
+ report.forEach((value, key) => {
+ edges.push({
+ edge: key,
+ referent: value,
+ breakdown: key === "noStack" ? breakdown.noStack : breakdown.then
+ });
+ });
+ return edges;
+};
+
+EDGES.filename = function (breakdown, report) {
+ return Object.keys(report).map(key => ({
+ edge: key,
+ referent: report[key],
+ breakdown: key === "noFilename" ? breakdown.noFilename : breakdown.then
+ }));
+};
+
+/**
+ * Get the set of outgoing edges from `report` as specified by the given
+ * breakdown.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ */
+function getReportEdges(breakdown, report) {
+ return EDGES[breakdown.by](breakdown, report);
+}
+exports.getReportEdges = getReportEdges;
+
+/** * walk *******************************************************************/
+
+function recursiveWalk(breakdown, edge, report, visitor) {
+ if (breakdown.by === "count") {
+ visitor.enter(breakdown, report, edge);
+ visitor.count(breakdown, report, edge);
+ visitor.exit(breakdown, report, edge);
+ } else {
+ visitor.enter(breakdown, report, edge);
+ for (let { edge, referent, breakdown: subBreakdown } of getReportEdges(breakdown, report)) {
+ recursiveWalk(subBreakdown, edge, referent, visitor);
+ }
+ visitor.exit(breakdown, report, edge);
+ }
+}
+
+/**
+ * Walk the given `report` that was generated by taking a census with the
+ * specified `breakdown`.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {Visitor} visitor
+ * The Visitor instance to call into while traversing.
+ */
+function walk(breakdown, report, visitor) {
+ recursiveWalk(breakdown, null, report, visitor);
+}
+exports.walk = walk;
+
+/** * diff *******************************************************************/
+
+/**
+ * Return true if the object is a Map, false otherwise. Works with Map objects
+ * from other globals, unlike `instanceof`.
+ *
+ * @returns {Boolean}
+ */
+function isMap(obj) {
+ return Object.prototype.toString.call(obj) === "[object Map]";
+}
+
+/**
+ * A Visitor for computing the difference between the census report being
+ * traversed and the given other census.
+ *
+ * @param {Object} otherCensus
+ * The other census report.
+ */
+function DiffVisitor(otherCensus) {
+ // The other census we are comparing against.
+ this._otherCensus = otherCensus;
+
+ // The total bytes and count of the basis census we are traversing.
+ this._totalBytes = 0;
+ this._totalCount = 0;
+
+ // Stack maintaining the current corresponding sub-report for the other
+ // census we are comparing against.
+ this._otherCensusStack = [];
+
+ // Stack maintaining the set of edges visited at each sub-report.
+ this._edgesVisited = [new Set()];
+
+ // The final delta census. Valid only after traversal.
+ this._results = null;
+
+ // Stack maintaining the results corresponding to each sub-report we are
+ // currently traversing.
+ this._resultsStack = [];
+}
+
+DiffVisitor.prototype = Object.create(Visitor.prototype);
+
+/**
+ * Given a report and an outgoing edge, get the edge's referent.
+ */
+DiffVisitor.prototype._get = function (report, edge) {
+ if (!report) {
+ return undefined;
+ }
+ return isMap(report) ? report.get(edge) : report[edge];
+};
+
+/**
+ * Given a report, an outgoing edge, and a value, set the edge's referent to
+ * the given value.
+ */
+DiffVisitor.prototype._set = function (report, edge, val) {
+ if (isMap(report)) {
+ report.set(edge, val);
+ } else {
+ report[edge] = val;
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+DiffVisitor.prototype.enter = function (breakdown, report, edge) {
+ const isFirstTimeEntering = this._results === null;
+
+ const newResults = breakdown.by === "allocationStack" ? new Map() : {};
+ let newOther;
+
+ if (!this._results) {
+ // This is the first time we have entered a sub-report.
+ this._results = newResults;
+ newOther = this._otherCensus;
+ } else {
+ const topResults = this._resultsStack[this._resultsStack.length - 1];
+ this._set(topResults, edge, newResults);
+
+ const topOther = this._otherCensusStack[this._otherCensusStack.length - 1];
+ newOther = this._get(topOther, edge);
+ }
+
+ this._resultsStack.push(newResults);
+ this._otherCensusStack.push(newOther);
+
+ const visited = this._edgesVisited[this._edgesVisited.length - 1];
+ visited.add(edge);
+ this._edgesVisited.push(new Set());
+};
+
+/**
+ * @overrides Visitor.prototype.exit
+ */
+DiffVisitor.prototype.exit = function (breakdown, report, edge) {
+ // Find all the edges in the other census report that were not traversed and
+ // add them to the results directly.
+ const other = this._otherCensusStack[this._otherCensusStack.length - 1];
+ if (other) {
+ const visited = this._edgesVisited[this._edgesVisited.length - 1];
+ const unvisited = getReportEdges(breakdown, other)
+ .map(e => e.edge)
+ .filter(e => !visited.has(e));
+ const results = this._resultsStack[this._resultsStack.length - 1];
+ for (let edge of unvisited) {
+ this._set(results, edge, this._get(other, edge));
+ }
+ }
+
+ this._otherCensusStack.pop();
+ this._resultsStack.pop();
+ this._edgesVisited.pop();
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+DiffVisitor.prototype.count = function (breakdown, report, edge) {
+ const other = this._otherCensusStack[this._otherCensusStack.length - 1];
+ const results = this._resultsStack[this._resultsStack.length - 1];
+
+ if (breakdown.count) {
+ this._totalCount += report.count;
+ }
+ if (breakdown.bytes) {
+ this._totalBytes += report.bytes;
+ }
+
+ if (other) {
+ if (breakdown.count) {
+ results.count = other.count - report.count;
+ }
+ if (breakdown.bytes) {
+ results.bytes = other.bytes - report.bytes;
+ }
+ } else {
+ if (breakdown.count) {
+ results.count = -report.count;
+ }
+ if (breakdown.bytes) {
+ results.bytes = -report.bytes;
+ }
+ }
+};
+
+const basisTotalBytes = exports.basisTotalBytes = Symbol("basisTotalBytes");
+const basisTotalCount = exports.basisTotalCount = Symbol("basisTotalCount");
+
+/**
+ * Get the resulting report of the difference between the traversed census
+ * report and the other census report.
+ *
+ * @returns {Object}
+ * The delta census report.
+ */
+DiffVisitor.prototype.results = function () {
+ if (!this._results) {
+ throw new Error("Attempt to get results before computing diff!");
+ }
+
+ if (this._resultsStack.length) {
+ throw new Error("Attempt to get results while still computing diff!");
+ }
+
+ this._results[basisTotalBytes] = this._totalBytes;
+ this._results[basisTotalCount] = this._totalCount;
+
+ return this._results;
+};
+
+/**
+ * Take the difference between two censuses. The resulting delta report
+ * contains the number/size of things that are in the `endCensus` that are not
+ * in the `startCensus`.
+ *
+ * @param {Object} breakdown
+ * The breakdown used to generate both census reports.
+ *
+ * @param {Object} startCensus
+ * The first census report.
+ *
+ * @param {Object} endCensus
+ * The second census report.
+ *
+ * @returns {Object}
+ * A delta report mirroring the structure of the two census reports (as
+ * specified by the given breakdown). Has two additional properties:
+ * - {Number} basisTotalBytes: the total number of bytes in the start
+ * census.
+ * - {Number} basisTotalCount: the total count in the start census.
+ */
+function diff(breakdown, startCensus, endCensus) {
+ const visitor = new DiffVisitor(endCensus);
+ walk(breakdown, startCensus, visitor);
+ return visitor.results();
+}
+exports.diff = diff;
+
+/**
+ * Creates a hash map mapping node IDs to its parent node.
+ *
+ * @param {CensusTreeNode} node
+ * @param {Object<number, TreeNode>} aggregator
+ *
+ * @return {Object<number, TreeNode>}
+ */
+const createParentMap = exports.createParentMap = function (node,
+ getId = node => node.id,
+ aggregator = Object.create(null)) {
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ const child = node.children[i];
+ aggregator[getId(child)] = node;
+ createParentMap(child, getId, aggregator);
+ }
+ }
+
+ return aggregator;
+};
+
+const BUCKET = Object.freeze({ by: "bucket" });
+
+/**
+ * Convert a breakdown whose leaves are { by: "count" } to an identical
+ * breakdown, except with { by: "bucket" } leaves.
+ *
+ * @param {Object} breakdown
+ * @returns {Object}
+ */
+exports.countToBucketBreakdown = function (breakdown) {
+ if (typeof breakdown !== "object" || !breakdown) {
+ return breakdown;
+ }
+
+ if (breakdown.by === "count") {
+ return BUCKET;
+ }
+
+ const keys = Object.keys(breakdown);
+ const vals = keys.reduce((vs, k) => {
+ vs.push(exports.countToBucketBreakdown(breakdown[k]));
+ return vs;
+ }, []);
+
+ const result = {};
+ for (let i = 0, length = keys.length; i < length; i++) {
+ result[keys[i]] = vals[i];
+ }
+
+ return Object.freeze(result);
+};
+
+/**
+ * A Visitor for finding report leaves by their DFS index.
+ */
+function GetLeavesVisitor(targetIndices) {
+ this._index = -1;
+ this._targetIndices = targetIndices;
+ this._leaves = [];
+}
+
+GetLeavesVisitor.prototype = Object.create(Visitor.prototype);
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+GetLeavesVisitor.prototype.enter = function (breakdown, report, edge) {
+ this._index++;
+ if (this._targetIndices.has(this._index)) {
+ this._leaves.push(report);
+ }
+};
+
+/**
+ * Get the accumulated report leaves after traversal.
+ */
+GetLeavesVisitor.prototype.leaves = function () {
+ if (this._index === -1) {
+ throw new Error("Attempt to call `leaves` before traversing report!");
+ }
+ return this._leaves;
+};
+
+/**
+ * Given a set of indices of leaves in a pre-order depth-first traversal of the
+ * given census report, return the leaves.
+ *
+ * @param {Set<Number>} indices
+ * @param {Object} breakdown
+ * @param {Object} report
+ *
+ * @returns {Array<Object>}
+ */
+exports.getReportLeaves = function (indices, breakdown, report) {
+ const visitor = new GetLeavesVisitor(indices);
+ walk(breakdown, report, visitor);
+ return visitor.leaves();
+};
+
+/**
+ * Get a list of the individual node IDs that belong to the census report leaves
+ * of the given indices.
+ *
+ * @param {Set<Number>} indices
+ * @param {Object} breakdown
+ * @param {HeapSnapshot} snapshot
+ *
+ * @returns {Array<NodeId>}
+ */
+exports.getCensusIndividuals = function (indices, countBreakdown, snapshot) {
+ const bucketBreakdown = exports.countToBucketBreakdown(countBreakdown);
+ const bucketReport = snapshot.takeCensus({ breakdown: bucketBreakdown });
+ const buckets = exports.getReportLeaves(indices,
+ bucketBreakdown,
+ bucketReport);
+ return flatten(buckets);
+};
diff --git a/devtools/shared/heapsnapshot/CoreDump.pb.cc b/devtools/shared/heapsnapshot/CoreDump.pb.cc
new file mode 100644
index 000000000..6c7c0e8a4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.cc
@@ -0,0 +1,2542 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: CoreDump.proto
+
+#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION
+#include "CoreDump.pb.h"
+
+#include <algorithm>
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/once.h>
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/wire_format_lite_inl.h>
+#include <google/protobuf/descriptor.h>
+#include <google/protobuf/generated_message_reflection.h>
+#include <google/protobuf/reflection_ops.h>
+#include <google/protobuf/wire_format.h>
+// @@protoc_insertion_point(includes)
+
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+namespace {
+
+const ::google::protobuf::Descriptor* Metadata_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+ Metadata_reflection_ = NULL;
+const ::google::protobuf::Descriptor* StackFrame_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+ StackFrame_reflection_ = NULL;
+struct StackFrameOneofInstance {
+ const ::mozilla::devtools::protobuf::StackFrame_Data* data_;
+ ::google::protobuf::uint64 ref_;
+}* StackFrame_default_oneof_instance_ = NULL;
+const ::google::protobuf::Descriptor* StackFrame_Data_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+ StackFrame_Data_reflection_ = NULL;
+struct StackFrame_DataOneofInstance {
+ const ::std::string* source_;
+ ::google::protobuf::uint64 sourceref_;
+ const ::std::string* functiondisplayname_;
+ ::google::protobuf::uint64 functiondisplaynameref_;
+}* StackFrame_Data_default_oneof_instance_ = NULL;
+const ::google::protobuf::Descriptor* Node_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+ Node_reflection_ = NULL;
+struct NodeOneofInstance {
+ const ::std::string* typename__;
+ ::google::protobuf::uint64 typenameref_;
+ const ::std::string* jsobjectclassname_;
+ ::google::protobuf::uint64 jsobjectclassnameref_;
+ const ::std::string* scriptfilename_;
+ ::google::protobuf::uint64 scriptfilenameref_;
+}* Node_default_oneof_instance_ = NULL;
+const ::google::protobuf::Descriptor* Edge_descriptor_ = NULL;
+const ::google::protobuf::internal::GeneratedMessageReflection*
+ Edge_reflection_ = NULL;
+struct EdgeOneofInstance {
+ const ::std::string* name_;
+ ::google::protobuf::uint64 nameref_;
+}* Edge_default_oneof_instance_ = NULL;
+
+} // namespace
+
+
+void protobuf_AssignDesc_CoreDump_2eproto() {
+ protobuf_AddDesc_CoreDump_2eproto();
+ const ::google::protobuf::FileDescriptor* file =
+ ::google::protobuf::DescriptorPool::generated_pool()->FindFileByName(
+ "CoreDump.proto");
+ GOOGLE_CHECK(file != NULL);
+ Metadata_descriptor_ = file->message_type(0);
+ static const int Metadata_offsets_[1] = {
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, timestamp_),
+ };
+ Metadata_reflection_ =
+ new ::google::protobuf::internal::GeneratedMessageReflection(
+ Metadata_descriptor_,
+ Metadata::default_instance_,
+ Metadata_offsets_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, _has_bits_[0]),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Metadata, _unknown_fields_),
+ -1,
+ ::google::protobuf::DescriptorPool::generated_pool(),
+ ::google::protobuf::MessageFactory::generated_factory(),
+ sizeof(Metadata));
+ StackFrame_descriptor_ = file->message_type(1);
+ static const int StackFrame_offsets_[3] = {
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_default_oneof_instance_, data_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_default_oneof_instance_, ref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame, StackFrameType_),
+ };
+ StackFrame_reflection_ =
+ new ::google::protobuf::internal::GeneratedMessageReflection(
+ StackFrame_descriptor_,
+ StackFrame::default_instance_,
+ StackFrame_offsets_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame, _has_bits_[0]),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame, _unknown_fields_),
+ -1,
+ StackFrame_default_oneof_instance_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame, _oneof_case_[0]),
+ ::google::protobuf::DescriptorPool::generated_pool(),
+ ::google::protobuf::MessageFactory::generated_factory(),
+ sizeof(StackFrame));
+ StackFrame_Data_descriptor_ = StackFrame_descriptor_->nested_type(0);
+ static const int StackFrame_Data_offsets_[12] = {
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, id_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, parent_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, line_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, column_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_Data_default_oneof_instance_, source_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_Data_default_oneof_instance_, sourceref_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_Data_default_oneof_instance_, functiondisplayname_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(StackFrame_Data_default_oneof_instance_, functiondisplaynameref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, issystem_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, isselfhosted_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, SourceOrRef_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, FunctionDisplayNameOrRef_),
+ };
+ StackFrame_Data_reflection_ =
+ new ::google::protobuf::internal::GeneratedMessageReflection(
+ StackFrame_Data_descriptor_,
+ StackFrame_Data::default_instance_,
+ StackFrame_Data_offsets_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, _has_bits_[0]),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, _unknown_fields_),
+ -1,
+ StackFrame_Data_default_oneof_instance_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(StackFrame_Data, _oneof_case_[0]),
+ ::google::protobuf::DescriptorPool::generated_pool(),
+ ::google::protobuf::MessageFactory::generated_factory(),
+ sizeof(StackFrame_Data));
+ Node_descriptor_ = file->message_type(2);
+ static const int Node_offsets_[14] = {
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, id_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, typename__),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, typenameref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, size_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, edges_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, allocationstack_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, jsobjectclassname_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, jsobjectclassnameref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, coarsetype_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, scriptfilename_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Node_default_oneof_instance_, scriptfilenameref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, TypeNameOrRef_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, JSObjectClassNameOrRef_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, ScriptFilenameOrRef_),
+ };
+ Node_reflection_ =
+ new ::google::protobuf::internal::GeneratedMessageReflection(
+ Node_descriptor_,
+ Node::default_instance_,
+ Node_offsets_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _has_bits_[0]),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _unknown_fields_),
+ -1,
+ Node_default_oneof_instance_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Node, _oneof_case_[0]),
+ ::google::protobuf::DescriptorPool::generated_pool(),
+ ::google::protobuf::MessageFactory::generated_factory(),
+ sizeof(Node));
+ Edge_descriptor_ = file->message_type(3);
+ static const int Edge_offsets_[4] = {
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, referent_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Edge_default_oneof_instance_, name_),
+ PROTO2_GENERATED_DEFAULT_ONEOF_FIELD_OFFSET(Edge_default_oneof_instance_, nameref_),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, EdgeNameOrRef_),
+ };
+ Edge_reflection_ =
+ new ::google::protobuf::internal::GeneratedMessageReflection(
+ Edge_descriptor_,
+ Edge::default_instance_,
+ Edge_offsets_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, _has_bits_[0]),
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, _unknown_fields_),
+ -1,
+ Edge_default_oneof_instance_,
+ GOOGLE_PROTOBUF_GENERATED_MESSAGE_FIELD_OFFSET(Edge, _oneof_case_[0]),
+ ::google::protobuf::DescriptorPool::generated_pool(),
+ ::google::protobuf::MessageFactory::generated_factory(),
+ sizeof(Edge));
+}
+
+namespace {
+
+GOOGLE_PROTOBUF_DECLARE_ONCE(protobuf_AssignDescriptors_once_);
+inline void protobuf_AssignDescriptorsOnce() {
+ ::google::protobuf::GoogleOnceInit(&protobuf_AssignDescriptors_once_,
+ &protobuf_AssignDesc_CoreDump_2eproto);
+}
+
+void protobuf_RegisterTypes(const ::std::string&) {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+ Metadata_descriptor_, &Metadata::default_instance());
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+ StackFrame_descriptor_, &StackFrame::default_instance());
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+ StackFrame_Data_descriptor_, &StackFrame_Data::default_instance());
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+ Node_descriptor_, &Node::default_instance());
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedMessage(
+ Edge_descriptor_, &Edge::default_instance());
+}
+
+} // namespace
+
+void protobuf_ShutdownFile_CoreDump_2eproto() {
+ delete Metadata::default_instance_;
+ delete Metadata_reflection_;
+ delete StackFrame::default_instance_;
+ delete StackFrame_default_oneof_instance_;
+ delete StackFrame_reflection_;
+ delete StackFrame_Data::default_instance_;
+ delete StackFrame_Data_default_oneof_instance_;
+ delete StackFrame_Data_reflection_;
+ delete Node::default_instance_;
+ delete Node_default_oneof_instance_;
+ delete Node_reflection_;
+ delete Edge::default_instance_;
+ delete Edge_default_oneof_instance_;
+ delete Edge_reflection_;
+}
+
+void protobuf_AddDesc_CoreDump_2eproto() {
+ static bool already_here = false;
+ if (already_here) return;
+ already_here = true;
+ GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+ ::google::protobuf::DescriptorPool::InternalAddGeneratedFile(
+ "\n\016CoreDump.proto\022\031mozilla.devtools.proto"
+ "buf\"\035\n\010Metadata\022\021\n\ttimeStamp\030\001 \001(\004\"\216\003\n\nS"
+ "tackFrame\022:\n\004data\030\001 \001(\0132*.mozilla.devtoo"
+ "ls.protobuf.StackFrame.DataH\000\022\r\n\003ref\030\002 \001"
+ "(\004H\000\032\242\002\n\004Data\022\n\n\002id\030\001 \001(\004\0225\n\006parent\030\002 \001("
+ "\0132%.mozilla.devtools.protobuf.StackFrame"
+ "\022\014\n\004line\030\003 \001(\r\022\016\n\006column\030\004 \001(\r\022\020\n\006source"
+ "\030\005 \001(\014H\000\022\023\n\tsourceRef\030\006 \001(\004H\000\022\035\n\023functio"
+ "nDisplayName\030\007 \001(\014H\001\022 \n\026functionDisplayN"
+ "ameRef\030\010 \001(\004H\001\022\020\n\010isSystem\030\t \001(\010\022\024\n\014isSe"
+ "lfHosted\030\n \001(\010B\r\n\013SourceOrRefB\032\n\030Functio"
+ "nDisplayNameOrRefB\020\n\016StackFrameType\"\210\003\n\004"
+ "Node\022\n\n\002id\030\001 \001(\004\022\022\n\010typeName\030\002 \001(\014H\000\022\025\n\013"
+ "typeNameRef\030\003 \001(\004H\000\022\014\n\004size\030\004 \001(\004\022.\n\005edg"
+ "es\030\005 \003(\0132\037.mozilla.devtools.protobuf.Edg"
+ "e\022>\n\017allocationStack\030\006 \001(\0132%.mozilla.dev"
+ "tools.protobuf.StackFrame\022\033\n\021jsObjectCla"
+ "ssName\030\007 \001(\014H\001\022\036\n\024jsObjectClassNameRef\030\010"
+ " \001(\004H\001\022\025\n\ncoarseType\030\t \001(\r:\0010\022\030\n\016scriptF"
+ "ilename\030\n \001(\014H\002\022\033\n\021scriptFilenameRef\030\013 \001"
+ "(\004H\002B\017\n\rTypeNameOrRefB\030\n\026JSObjectClassNa"
+ "meOrRefB\025\n\023ScriptFilenameOrRef\"L\n\004Edge\022\020"
+ "\n\010referent\030\001 \001(\004\022\016\n\004name\030\002 \001(\014H\000\022\021\n\007name"
+ "Ref\030\003 \001(\004H\000B\017\n\rEdgeNameOrRef", 948);
+ ::google::protobuf::MessageFactory::InternalRegisterGeneratedFile(
+ "CoreDump.proto", &protobuf_RegisterTypes);
+ Metadata::default_instance_ = new Metadata();
+ StackFrame::default_instance_ = new StackFrame();
+ StackFrame_default_oneof_instance_ = new StackFrameOneofInstance;
+ StackFrame_Data::default_instance_ = new StackFrame_Data();
+ StackFrame_Data_default_oneof_instance_ = new StackFrame_DataOneofInstance;
+ Node::default_instance_ = new Node();
+ Node_default_oneof_instance_ = new NodeOneofInstance;
+ Edge::default_instance_ = new Edge();
+ Edge_default_oneof_instance_ = new EdgeOneofInstance;
+ Metadata::default_instance_->InitAsDefaultInstance();
+ StackFrame::default_instance_->InitAsDefaultInstance();
+ StackFrame_Data::default_instance_->InitAsDefaultInstance();
+ Node::default_instance_->InitAsDefaultInstance();
+ Edge::default_instance_->InitAsDefaultInstance();
+ ::google::protobuf::internal::OnShutdown(&protobuf_ShutdownFile_CoreDump_2eproto);
+}
+
+// Force AddDescriptors() to be called at static initialization time.
+struct StaticDescriptorInitializer_CoreDump_2eproto {
+ StaticDescriptorInitializer_CoreDump_2eproto() {
+ protobuf_AddDesc_CoreDump_2eproto();
+ }
+} static_descriptor_initializer_CoreDump_2eproto_;
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Metadata::kTimeStampFieldNumber;
+#endif // !_MSC_VER
+
+Metadata::Metadata()
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Metadata)
+}
+
+void Metadata::InitAsDefaultInstance() {
+}
+
+Metadata::Metadata(const Metadata& from)
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ MergeFrom(from);
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Metadata)
+}
+
+void Metadata::SharedCtor() {
+ _cached_size_ = 0;
+ timestamp_ = GOOGLE_ULONGLONG(0);
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+}
+
+Metadata::~Metadata() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Metadata)
+ SharedDtor();
+}
+
+void Metadata::SharedDtor() {
+ if (this != default_instance_) {
+ }
+}
+
+void Metadata::SetCachedSize(int size) const {
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Metadata::descriptor() {
+ protobuf_AssignDescriptorsOnce();
+ return Metadata_descriptor_;
+}
+
+const Metadata& Metadata::default_instance() {
+ if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+ return *default_instance_;
+}
+
+Metadata* Metadata::default_instance_ = NULL;
+
+Metadata* Metadata::New() const {
+ return new Metadata;
+}
+
+void Metadata::Clear() {
+ timestamp_ = GOOGLE_ULONGLONG(0);
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ mutable_unknown_fields()->Clear();
+}
+
+bool Metadata::MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+ ::google::protobuf::uint32 tag;
+ // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Metadata)
+ for (;;) {
+ ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+ tag = p.first;
+ if (!p.second) goto handle_unusual;
+ switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+ // optional uint64 timeStamp = 1;
+ case 1: {
+ if (tag == 8) {
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &timestamp_)));
+ set_has_timestamp();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectAtEnd()) goto success;
+ break;
+ }
+
+ default: {
+ handle_unusual:
+ if (tag == 0 ||
+ ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+ goto success;
+ }
+ DO_(::google::protobuf::internal::WireFormat::SkipField(
+ input, tag, mutable_unknown_fields()));
+ break;
+ }
+ }
+ }
+success:
+ // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Metadata)
+ return true;
+failure:
+ // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Metadata)
+ return false;
+#undef DO_
+}
+
+void Metadata::SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const {
+ // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Metadata)
+ // optional uint64 timeStamp = 1;
+ if (has_timestamp()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->timestamp(), output);
+ }
+
+ if (!unknown_fields().empty()) {
+ ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+ unknown_fields(), output);
+ }
+ // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Metadata)
+}
+
+::google::protobuf::uint8* Metadata::SerializeWithCachedSizesToArray(
+ ::google::protobuf::uint8* target) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Metadata)
+ // optional uint64 timeStamp = 1;
+ if (has_timestamp()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->timestamp(), target);
+ }
+
+ if (!unknown_fields().empty()) {
+ target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+ unknown_fields(), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Metadata)
+ return target;
+}
+
+int Metadata::ByteSize() const {
+ int total_size = 0;
+
+ if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ // optional uint64 timeStamp = 1;
+ if (has_timestamp()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->timestamp());
+ }
+
+ }
+ if (!unknown_fields().empty()) {
+ total_size +=
+ ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+ unknown_fields());
+ }
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = total_size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+ return total_size;
+}
+
+void Metadata::MergeFrom(const ::google::protobuf::Message& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ const Metadata* source =
+ ::google::protobuf::internal::dynamic_cast_if_available<const Metadata*>(
+ &from);
+ if (source == NULL) {
+ ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+ } else {
+ MergeFrom(*source);
+ }
+}
+
+void Metadata::MergeFrom(const Metadata& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ if (from.has_timestamp()) {
+ set_timestamp(from.timestamp());
+ }
+ }
+ mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Metadata::CopyFrom(const ::google::protobuf::Message& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+void Metadata::CopyFrom(const Metadata& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Metadata::IsInitialized() const {
+
+ return true;
+}
+
+void Metadata::Swap(Metadata* other) {
+ if (other != this) {
+ std::swap(timestamp_, other->timestamp_);
+ std::swap(_has_bits_[0], other->_has_bits_[0]);
+ _unknown_fields_.Swap(&other->_unknown_fields_);
+ std::swap(_cached_size_, other->_cached_size_);
+ }
+}
+
+::google::protobuf::Metadata Metadata::GetMetadata() const {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::Metadata metadata;
+ metadata.descriptor = Metadata_descriptor_;
+ metadata.reflection = Metadata_reflection_;
+ return metadata;
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int StackFrame_Data::kIdFieldNumber;
+const int StackFrame_Data::kParentFieldNumber;
+const int StackFrame_Data::kLineFieldNumber;
+const int StackFrame_Data::kColumnFieldNumber;
+const int StackFrame_Data::kSourceFieldNumber;
+const int StackFrame_Data::kSourceRefFieldNumber;
+const int StackFrame_Data::kFunctionDisplayNameFieldNumber;
+const int StackFrame_Data::kFunctionDisplayNameRefFieldNumber;
+const int StackFrame_Data::kIsSystemFieldNumber;
+const int StackFrame_Data::kIsSelfHostedFieldNumber;
+#endif // !_MSC_VER
+
+StackFrame_Data::StackFrame_Data()
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.StackFrame.Data)
+}
+
+void StackFrame_Data::InitAsDefaultInstance() {
+ parent_ = const_cast< ::mozilla::devtools::protobuf::StackFrame*>(&::mozilla::devtools::protobuf::StackFrame::default_instance());
+ StackFrame_Data_default_oneof_instance_->source_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ StackFrame_Data_default_oneof_instance_->sourceref_ = GOOGLE_ULONGLONG(0);
+ StackFrame_Data_default_oneof_instance_->functiondisplayname_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ StackFrame_Data_default_oneof_instance_->functiondisplaynameref_ = GOOGLE_ULONGLONG(0);
+}
+
+StackFrame_Data::StackFrame_Data(const StackFrame_Data& from)
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ MergeFrom(from);
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.StackFrame.Data)
+}
+
+void StackFrame_Data::SharedCtor() {
+ ::google::protobuf::internal::GetEmptyString();
+ _cached_size_ = 0;
+ id_ = GOOGLE_ULONGLONG(0);
+ parent_ = NULL;
+ line_ = 0u;
+ column_ = 0u;
+ issystem_ = false;
+ isselfhosted_ = false;
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ clear_has_SourceOrRef();
+ clear_has_FunctionDisplayNameOrRef();
+}
+
+StackFrame_Data::~StackFrame_Data() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.StackFrame.Data)
+ SharedDtor();
+}
+
+void StackFrame_Data::SharedDtor() {
+ if (has_SourceOrRef()) {
+ clear_SourceOrRef();
+ }
+ if (has_FunctionDisplayNameOrRef()) {
+ clear_FunctionDisplayNameOrRef();
+ }
+ if (this != default_instance_) {
+ delete parent_;
+ }
+}
+
+void StackFrame_Data::SetCachedSize(int size) const {
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* StackFrame_Data::descriptor() {
+ protobuf_AssignDescriptorsOnce();
+ return StackFrame_Data_descriptor_;
+}
+
+const StackFrame_Data& StackFrame_Data::default_instance() {
+ if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+ return *default_instance_;
+}
+
+StackFrame_Data* StackFrame_Data::default_instance_ = NULL;
+
+StackFrame_Data* StackFrame_Data::New() const {
+ return new StackFrame_Data;
+}
+
+void StackFrame_Data::clear_SourceOrRef() {
+ switch(SourceOrRef_case()) {
+ case kSource: {
+ delete SourceOrRef_.source_;
+ break;
+ }
+ case kSourceRef: {
+ // No need to clear
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[0] = SOURCEORREF_NOT_SET;
+}
+
+void StackFrame_Data::clear_FunctionDisplayNameOrRef() {
+ switch(FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ delete FunctionDisplayNameOrRef_.functiondisplayname_;
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ // No need to clear
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[1] = FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+
+
+void StackFrame_Data::Clear() {
+#define OFFSET_OF_FIELD_(f) (reinterpret_cast<char*>( \
+ &reinterpret_cast<StackFrame_Data*>(16)->f) - \
+ reinterpret_cast<char*>(16))
+
+#define ZR_(first, last) do { \
+ size_t f = OFFSET_OF_FIELD_(first); \
+ size_t n = OFFSET_OF_FIELD_(last) - f + sizeof(last); \
+ ::memset(&first, 0, n); \
+ } while (0)
+
+ if (_has_bits_[0 / 32] & 15) {
+ ZR_(line_, column_);
+ id_ = GOOGLE_ULONGLONG(0);
+ if (has_parent()) {
+ if (parent_ != NULL) parent_->::mozilla::devtools::protobuf::StackFrame::Clear();
+ }
+ }
+ ZR_(issystem_, isselfhosted_);
+
+#undef OFFSET_OF_FIELD_
+#undef ZR_
+
+ clear_SourceOrRef();
+ clear_FunctionDisplayNameOrRef();
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ mutable_unknown_fields()->Clear();
+}
+
+bool StackFrame_Data::MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+ ::google::protobuf::uint32 tag;
+ // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.StackFrame.Data)
+ for (;;) {
+ ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+ tag = p.first;
+ if (!p.second) goto handle_unusual;
+ switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+ // optional uint64 id = 1;
+ case 1: {
+ if (tag == 8) {
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &id_)));
+ set_has_id();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(18)) goto parse_parent;
+ break;
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ case 2: {
+ if (tag == 18) {
+ parse_parent:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+ input, mutable_parent()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(24)) goto parse_line;
+ break;
+ }
+
+ // optional uint32 line = 3;
+ case 3: {
+ if (tag == 24) {
+ parse_line:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint32, ::google::protobuf::internal::WireFormatLite::TYPE_UINT32>(
+ input, &line_)));
+ set_has_line();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(32)) goto parse_column;
+ break;
+ }
+
+ // optional uint32 column = 4;
+ case 4: {
+ if (tag == 32) {
+ parse_column:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint32, ::google::protobuf::internal::WireFormatLite::TYPE_UINT32>(
+ input, &column_)));
+ set_has_column();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(42)) goto parse_source;
+ break;
+ }
+
+ // optional bytes source = 5;
+ case 5: {
+ if (tag == 42) {
+ parse_source:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_source()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(48)) goto parse_sourceRef;
+ break;
+ }
+
+ // optional uint64 sourceRef = 6;
+ case 6: {
+ if (tag == 48) {
+ parse_sourceRef:
+ clear_SourceOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &SourceOrRef_.sourceref_)));
+ set_has_sourceref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(58)) goto parse_functionDisplayName;
+ break;
+ }
+
+ // optional bytes functionDisplayName = 7;
+ case 7: {
+ if (tag == 58) {
+ parse_functionDisplayName:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_functiondisplayname()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(64)) goto parse_functionDisplayNameRef;
+ break;
+ }
+
+ // optional uint64 functionDisplayNameRef = 8;
+ case 8: {
+ if (tag == 64) {
+ parse_functionDisplayNameRef:
+ clear_FunctionDisplayNameOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &FunctionDisplayNameOrRef_.functiondisplaynameref_)));
+ set_has_functiondisplaynameref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(72)) goto parse_isSystem;
+ break;
+ }
+
+ // optional bool isSystem = 9;
+ case 9: {
+ if (tag == 72) {
+ parse_isSystem:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>(
+ input, &issystem_)));
+ set_has_issystem();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(80)) goto parse_isSelfHosted;
+ break;
+ }
+
+ // optional bool isSelfHosted = 10;
+ case 10: {
+ if (tag == 80) {
+ parse_isSelfHosted:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ bool, ::google::protobuf::internal::WireFormatLite::TYPE_BOOL>(
+ input, &isselfhosted_)));
+ set_has_isselfhosted();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectAtEnd()) goto success;
+ break;
+ }
+
+ default: {
+ handle_unusual:
+ if (tag == 0 ||
+ ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+ goto success;
+ }
+ DO_(::google::protobuf::internal::WireFormat::SkipField(
+ input, tag, mutable_unknown_fields()));
+ break;
+ }
+ }
+ }
+success:
+ // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.StackFrame.Data)
+ return true;
+failure:
+ // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.StackFrame.Data)
+ return false;
+#undef DO_
+}
+
+void StackFrame_Data::SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const {
+ // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.StackFrame.Data)
+ // optional uint64 id = 1;
+ if (has_id()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->id(), output);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ if (has_parent()) {
+ ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
+ 2, this->parent(), output);
+ }
+
+ // optional uint32 line = 3;
+ if (has_line()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt32(3, this->line(), output);
+ }
+
+ // optional uint32 column = 4;
+ if (has_column()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt32(4, this->column(), output);
+ }
+
+ // optional bytes source = 5;
+ if (has_source()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 5, this->source(), output);
+ }
+
+ // optional uint64 sourceRef = 6;
+ if (has_sourceref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(6, this->sourceref(), output);
+ }
+
+ // optional bytes functionDisplayName = 7;
+ if (has_functiondisplayname()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 7, this->functiondisplayname(), output);
+ }
+
+ // optional uint64 functionDisplayNameRef = 8;
+ if (has_functiondisplaynameref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(8, this->functiondisplaynameref(), output);
+ }
+
+ // optional bool isSystem = 9;
+ if (has_issystem()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBool(9, this->issystem(), output);
+ }
+
+ // optional bool isSelfHosted = 10;
+ if (has_isselfhosted()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBool(10, this->isselfhosted(), output);
+ }
+
+ if (!unknown_fields().empty()) {
+ ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+ unknown_fields(), output);
+ }
+ // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.StackFrame.Data)
+}
+
+::google::protobuf::uint8* StackFrame_Data::SerializeWithCachedSizesToArray(
+ ::google::protobuf::uint8* target) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.StackFrame.Data)
+ // optional uint64 id = 1;
+ if (has_id()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->id(), target);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ if (has_parent()) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteMessageNoVirtualToArray(
+ 2, this->parent(), target);
+ }
+
+ // optional uint32 line = 3;
+ if (has_line()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt32ToArray(3, this->line(), target);
+ }
+
+ // optional uint32 column = 4;
+ if (has_column()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt32ToArray(4, this->column(), target);
+ }
+
+ // optional bytes source = 5;
+ if (has_source()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 5, this->source(), target);
+ }
+
+ // optional uint64 sourceRef = 6;
+ if (has_sourceref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(6, this->sourceref(), target);
+ }
+
+ // optional bytes functionDisplayName = 7;
+ if (has_functiondisplayname()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 7, this->functiondisplayname(), target);
+ }
+
+ // optional uint64 functionDisplayNameRef = 8;
+ if (has_functiondisplaynameref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(8, this->functiondisplaynameref(), target);
+ }
+
+ // optional bool isSystem = 9;
+ if (has_issystem()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteBoolToArray(9, this->issystem(), target);
+ }
+
+ // optional bool isSelfHosted = 10;
+ if (has_isselfhosted()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteBoolToArray(10, this->isselfhosted(), target);
+ }
+
+ if (!unknown_fields().empty()) {
+ target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+ unknown_fields(), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.StackFrame.Data)
+ return target;
+}
+
+int StackFrame_Data::ByteSize() const {
+ int total_size = 0;
+
+ if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ // optional uint64 id = 1;
+ if (has_id()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->id());
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ if (has_parent()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+ this->parent());
+ }
+
+ // optional uint32 line = 3;
+ if (has_line()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt32Size(
+ this->line());
+ }
+
+ // optional uint32 column = 4;
+ if (has_column()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt32Size(
+ this->column());
+ }
+
+ }
+ if (_has_bits_[8 / 32] & (0xffu << (8 % 32))) {
+ // optional bool isSystem = 9;
+ if (has_issystem()) {
+ total_size += 1 + 1;
+ }
+
+ // optional bool isSelfHosted = 10;
+ if (has_isselfhosted()) {
+ total_size += 1 + 1;
+ }
+
+ }
+ switch (SourceOrRef_case()) {
+ // optional bytes source = 5;
+ case kSource: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->source());
+ break;
+ }
+ // optional uint64 sourceRef = 6;
+ case kSourceRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->sourceref());
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (FunctionDisplayNameOrRef_case()) {
+ // optional bytes functionDisplayName = 7;
+ case kFunctionDisplayName: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->functiondisplayname());
+ break;
+ }
+ // optional uint64 functionDisplayNameRef = 8;
+ case kFunctionDisplayNameRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->functiondisplaynameref());
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (!unknown_fields().empty()) {
+ total_size +=
+ ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+ unknown_fields());
+ }
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = total_size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+ return total_size;
+}
+
+void StackFrame_Data::MergeFrom(const ::google::protobuf::Message& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ const StackFrame_Data* source =
+ ::google::protobuf::internal::dynamic_cast_if_available<const StackFrame_Data*>(
+ &from);
+ if (source == NULL) {
+ ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+ } else {
+ MergeFrom(*source);
+ }
+}
+
+void StackFrame_Data::MergeFrom(const StackFrame_Data& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ switch (from.SourceOrRef_case()) {
+ case kSource: {
+ set_source(from.source());
+ break;
+ }
+ case kSourceRef: {
+ set_sourceref(from.sourceref());
+ break;
+ }
+ case SOURCEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.FunctionDisplayNameOrRef_case()) {
+ case kFunctionDisplayName: {
+ set_functiondisplayname(from.functiondisplayname());
+ break;
+ }
+ case kFunctionDisplayNameRef: {
+ set_functiondisplaynameref(from.functiondisplaynameref());
+ break;
+ }
+ case FUNCTIONDISPLAYNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ if (from.has_id()) {
+ set_id(from.id());
+ }
+ if (from.has_parent()) {
+ mutable_parent()->::mozilla::devtools::protobuf::StackFrame::MergeFrom(from.parent());
+ }
+ if (from.has_line()) {
+ set_line(from.line());
+ }
+ if (from.has_column()) {
+ set_column(from.column());
+ }
+ }
+ if (from._has_bits_[8 / 32] & (0xffu << (8 % 32))) {
+ if (from.has_issystem()) {
+ set_issystem(from.issystem());
+ }
+ if (from.has_isselfhosted()) {
+ set_isselfhosted(from.isselfhosted());
+ }
+ }
+ mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void StackFrame_Data::CopyFrom(const ::google::protobuf::Message& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+void StackFrame_Data::CopyFrom(const StackFrame_Data& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool StackFrame_Data::IsInitialized() const {
+
+ return true;
+}
+
+void StackFrame_Data::Swap(StackFrame_Data* other) {
+ if (other != this) {
+ std::swap(id_, other->id_);
+ std::swap(parent_, other->parent_);
+ std::swap(line_, other->line_);
+ std::swap(column_, other->column_);
+ std::swap(issystem_, other->issystem_);
+ std::swap(isselfhosted_, other->isselfhosted_);
+ std::swap(SourceOrRef_, other->SourceOrRef_);
+ std::swap(_oneof_case_[0], other->_oneof_case_[0]);
+ std::swap(FunctionDisplayNameOrRef_, other->FunctionDisplayNameOrRef_);
+ std::swap(_oneof_case_[1], other->_oneof_case_[1]);
+ std::swap(_has_bits_[0], other->_has_bits_[0]);
+ _unknown_fields_.Swap(&other->_unknown_fields_);
+ std::swap(_cached_size_, other->_cached_size_);
+ }
+}
+
+::google::protobuf::Metadata StackFrame_Data::GetMetadata() const {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::Metadata metadata;
+ metadata.descriptor = StackFrame_Data_descriptor_;
+ metadata.reflection = StackFrame_Data_reflection_;
+ return metadata;
+}
+
+
+// -------------------------------------------------------------------
+
+#ifndef _MSC_VER
+const int StackFrame::kDataFieldNumber;
+const int StackFrame::kRefFieldNumber;
+#endif // !_MSC_VER
+
+StackFrame::StackFrame()
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.StackFrame)
+}
+
+void StackFrame::InitAsDefaultInstance() {
+ StackFrame_default_oneof_instance_->data_ = const_cast< ::mozilla::devtools::protobuf::StackFrame_Data*>(&::mozilla::devtools::protobuf::StackFrame_Data::default_instance());
+ StackFrame_default_oneof_instance_->ref_ = GOOGLE_ULONGLONG(0);
+}
+
+StackFrame::StackFrame(const StackFrame& from)
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ MergeFrom(from);
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.StackFrame)
+}
+
+void StackFrame::SharedCtor() {
+ _cached_size_ = 0;
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ clear_has_StackFrameType();
+}
+
+StackFrame::~StackFrame() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.StackFrame)
+ SharedDtor();
+}
+
+void StackFrame::SharedDtor() {
+ if (has_StackFrameType()) {
+ clear_StackFrameType();
+ }
+ if (this != default_instance_) {
+ }
+}
+
+void StackFrame::SetCachedSize(int size) const {
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* StackFrame::descriptor() {
+ protobuf_AssignDescriptorsOnce();
+ return StackFrame_descriptor_;
+}
+
+const StackFrame& StackFrame::default_instance() {
+ if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+ return *default_instance_;
+}
+
+StackFrame* StackFrame::default_instance_ = NULL;
+
+StackFrame* StackFrame::New() const {
+ return new StackFrame;
+}
+
+void StackFrame::clear_StackFrameType() {
+ switch(StackFrameType_case()) {
+ case kData: {
+ delete StackFrameType_.data_;
+ break;
+ }
+ case kRef: {
+ // No need to clear
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[0] = STACKFRAMETYPE_NOT_SET;
+}
+
+
+void StackFrame::Clear() {
+ clear_StackFrameType();
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ mutable_unknown_fields()->Clear();
+}
+
+bool StackFrame::MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+ ::google::protobuf::uint32 tag;
+ // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.StackFrame)
+ for (;;) {
+ ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+ tag = p.first;
+ if (!p.second) goto handle_unusual;
+ switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+ // optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ case 1: {
+ if (tag == 10) {
+ DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+ input, mutable_data()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(16)) goto parse_ref;
+ break;
+ }
+
+ // optional uint64 ref = 2;
+ case 2: {
+ if (tag == 16) {
+ parse_ref:
+ clear_StackFrameType();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &StackFrameType_.ref_)));
+ set_has_ref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectAtEnd()) goto success;
+ break;
+ }
+
+ default: {
+ handle_unusual:
+ if (tag == 0 ||
+ ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+ goto success;
+ }
+ DO_(::google::protobuf::internal::WireFormat::SkipField(
+ input, tag, mutable_unknown_fields()));
+ break;
+ }
+ }
+ }
+success:
+ // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.StackFrame)
+ return true;
+failure:
+ // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.StackFrame)
+ return false;
+#undef DO_
+}
+
+void StackFrame::SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const {
+ // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.StackFrame)
+ // optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ if (has_data()) {
+ ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
+ 1, this->data(), output);
+ }
+
+ // optional uint64 ref = 2;
+ if (has_ref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(2, this->ref(), output);
+ }
+
+ if (!unknown_fields().empty()) {
+ ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+ unknown_fields(), output);
+ }
+ // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.StackFrame)
+}
+
+::google::protobuf::uint8* StackFrame::SerializeWithCachedSizesToArray(
+ ::google::protobuf::uint8* target) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.StackFrame)
+ // optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ if (has_data()) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteMessageNoVirtualToArray(
+ 1, this->data(), target);
+ }
+
+ // optional uint64 ref = 2;
+ if (has_ref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(2, this->ref(), target);
+ }
+
+ if (!unknown_fields().empty()) {
+ target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+ unknown_fields(), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.StackFrame)
+ return target;
+}
+
+int StackFrame::ByteSize() const {
+ int total_size = 0;
+
+ switch (StackFrameType_case()) {
+ // optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ case kData: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+ this->data());
+ break;
+ }
+ // optional uint64 ref = 2;
+ case kRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->ref());
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ if (!unknown_fields().empty()) {
+ total_size +=
+ ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+ unknown_fields());
+ }
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = total_size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+ return total_size;
+}
+
+void StackFrame::MergeFrom(const ::google::protobuf::Message& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ const StackFrame* source =
+ ::google::protobuf::internal::dynamic_cast_if_available<const StackFrame*>(
+ &from);
+ if (source == NULL) {
+ ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+ } else {
+ MergeFrom(*source);
+ }
+}
+
+void StackFrame::MergeFrom(const StackFrame& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ switch (from.StackFrameType_case()) {
+ case kData: {
+ mutable_data()->::mozilla::devtools::protobuf::StackFrame_Data::MergeFrom(from.data());
+ break;
+ }
+ case kRef: {
+ set_ref(from.ref());
+ break;
+ }
+ case STACKFRAMETYPE_NOT_SET: {
+ break;
+ }
+ }
+ mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void StackFrame::CopyFrom(const ::google::protobuf::Message& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+void StackFrame::CopyFrom(const StackFrame& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool StackFrame::IsInitialized() const {
+
+ return true;
+}
+
+void StackFrame::Swap(StackFrame* other) {
+ if (other != this) {
+ std::swap(StackFrameType_, other->StackFrameType_);
+ std::swap(_oneof_case_[0], other->_oneof_case_[0]);
+ std::swap(_has_bits_[0], other->_has_bits_[0]);
+ _unknown_fields_.Swap(&other->_unknown_fields_);
+ std::swap(_cached_size_, other->_cached_size_);
+ }
+}
+
+::google::protobuf::Metadata StackFrame::GetMetadata() const {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::Metadata metadata;
+ metadata.descriptor = StackFrame_descriptor_;
+ metadata.reflection = StackFrame_reflection_;
+ return metadata;
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Node::kIdFieldNumber;
+const int Node::kTypeNameFieldNumber;
+const int Node::kTypeNameRefFieldNumber;
+const int Node::kSizeFieldNumber;
+const int Node::kEdgesFieldNumber;
+const int Node::kAllocationStackFieldNumber;
+const int Node::kJsObjectClassNameFieldNumber;
+const int Node::kJsObjectClassNameRefFieldNumber;
+const int Node::kCoarseTypeFieldNumber;
+const int Node::kScriptFilenameFieldNumber;
+const int Node::kScriptFilenameRefFieldNumber;
+#endif // !_MSC_VER
+
+Node::Node()
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Node)
+}
+
+void Node::InitAsDefaultInstance() {
+ Node_default_oneof_instance_->typename__ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ Node_default_oneof_instance_->typenameref_ = GOOGLE_ULONGLONG(0);
+ allocationstack_ = const_cast< ::mozilla::devtools::protobuf::StackFrame*>(&::mozilla::devtools::protobuf::StackFrame::default_instance());
+ Node_default_oneof_instance_->jsobjectclassname_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ Node_default_oneof_instance_->jsobjectclassnameref_ = GOOGLE_ULONGLONG(0);
+ Node_default_oneof_instance_->scriptfilename_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ Node_default_oneof_instance_->scriptfilenameref_ = GOOGLE_ULONGLONG(0);
+}
+
+Node::Node(const Node& from)
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ MergeFrom(from);
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Node)
+}
+
+void Node::SharedCtor() {
+ ::google::protobuf::internal::GetEmptyString();
+ _cached_size_ = 0;
+ id_ = GOOGLE_ULONGLONG(0);
+ size_ = GOOGLE_ULONGLONG(0);
+ allocationstack_ = NULL;
+ coarsetype_ = 0u;
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ clear_has_TypeNameOrRef();
+ clear_has_JSObjectClassNameOrRef();
+ clear_has_ScriptFilenameOrRef();
+}
+
+Node::~Node() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Node)
+ SharedDtor();
+}
+
+void Node::SharedDtor() {
+ if (has_TypeNameOrRef()) {
+ clear_TypeNameOrRef();
+ }
+ if (has_JSObjectClassNameOrRef()) {
+ clear_JSObjectClassNameOrRef();
+ }
+ if (has_ScriptFilenameOrRef()) {
+ clear_ScriptFilenameOrRef();
+ }
+ if (this != default_instance_) {
+ delete allocationstack_;
+ }
+}
+
+void Node::SetCachedSize(int size) const {
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Node::descriptor() {
+ protobuf_AssignDescriptorsOnce();
+ return Node_descriptor_;
+}
+
+const Node& Node::default_instance() {
+ if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+ return *default_instance_;
+}
+
+Node* Node::default_instance_ = NULL;
+
+Node* Node::New() const {
+ return new Node;
+}
+
+void Node::clear_TypeNameOrRef() {
+ switch(TypeNameOrRef_case()) {
+ case kTypeName: {
+ delete TypeNameOrRef_.typename__;
+ break;
+ }
+ case kTypeNameRef: {
+ // No need to clear
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[0] = TYPENAMEORREF_NOT_SET;
+}
+
+void Node::clear_JSObjectClassNameOrRef() {
+ switch(JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ delete JSObjectClassNameOrRef_.jsobjectclassname_;
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ // No need to clear
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+
+void Node::clear_ScriptFilenameOrRef() {
+ switch(ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ delete ScriptFilenameOrRef_.scriptfilename_;
+ break;
+ }
+ case kScriptFilenameRef: {
+ // No need to clear
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
+
+
+void Node::Clear() {
+#define OFFSET_OF_FIELD_(f) (reinterpret_cast<char*>( \
+ &reinterpret_cast<Node*>(16)->f) - \
+ reinterpret_cast<char*>(16))
+
+#define ZR_(first, last) do { \
+ size_t f = OFFSET_OF_FIELD_(first); \
+ size_t n = OFFSET_OF_FIELD_(last) - f + sizeof(last); \
+ ::memset(&first, 0, n); \
+ } while (0)
+
+ if (_has_bits_[0 / 32] & 41) {
+ ZR_(id_, size_);
+ if (has_allocationstack()) {
+ if (allocationstack_ != NULL) allocationstack_->::mozilla::devtools::protobuf::StackFrame::Clear();
+ }
+ }
+ coarsetype_ = 0u;
+
+#undef OFFSET_OF_FIELD_
+#undef ZR_
+
+ edges_.Clear();
+ clear_TypeNameOrRef();
+ clear_JSObjectClassNameOrRef();
+ clear_ScriptFilenameOrRef();
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ mutable_unknown_fields()->Clear();
+}
+
+bool Node::MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+ ::google::protobuf::uint32 tag;
+ // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Node)
+ for (;;) {
+ ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+ tag = p.first;
+ if (!p.second) goto handle_unusual;
+ switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+ // optional uint64 id = 1;
+ case 1: {
+ if (tag == 8) {
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &id_)));
+ set_has_id();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(18)) goto parse_typeName;
+ break;
+ }
+
+ // optional bytes typeName = 2;
+ case 2: {
+ if (tag == 18) {
+ parse_typeName:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_typename_()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(24)) goto parse_typeNameRef;
+ break;
+ }
+
+ // optional uint64 typeNameRef = 3;
+ case 3: {
+ if (tag == 24) {
+ parse_typeNameRef:
+ clear_TypeNameOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &TypeNameOrRef_.typenameref_)));
+ set_has_typenameref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(32)) goto parse_size;
+ break;
+ }
+
+ // optional uint64 size = 4;
+ case 4: {
+ if (tag == 32) {
+ parse_size:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &size_)));
+ set_has_size();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(42)) goto parse_edges;
+ break;
+ }
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ case 5: {
+ if (tag == 42) {
+ parse_edges:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+ input, add_edges()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(42)) goto parse_edges;
+ if (input->ExpectTag(50)) goto parse_allocationStack;
+ break;
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ case 6: {
+ if (tag == 50) {
+ parse_allocationStack:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadMessageNoVirtual(
+ input, mutable_allocationstack()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(58)) goto parse_jsObjectClassName;
+ break;
+ }
+
+ // optional bytes jsObjectClassName = 7;
+ case 7: {
+ if (tag == 58) {
+ parse_jsObjectClassName:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_jsobjectclassname()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(64)) goto parse_jsObjectClassNameRef;
+ break;
+ }
+
+ // optional uint64 jsObjectClassNameRef = 8;
+ case 8: {
+ if (tag == 64) {
+ parse_jsObjectClassNameRef:
+ clear_JSObjectClassNameOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &JSObjectClassNameOrRef_.jsobjectclassnameref_)));
+ set_has_jsobjectclassnameref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(72)) goto parse_coarseType;
+ break;
+ }
+
+ // optional uint32 coarseType = 9 [default = 0];
+ case 9: {
+ if (tag == 72) {
+ parse_coarseType:
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint32, ::google::protobuf::internal::WireFormatLite::TYPE_UINT32>(
+ input, &coarsetype_)));
+ set_has_coarsetype();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(82)) goto parse_scriptFilename;
+ break;
+ }
+
+ // optional bytes scriptFilename = 10;
+ case 10: {
+ if (tag == 82) {
+ parse_scriptFilename:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_scriptfilename()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(88)) goto parse_scriptFilenameRef;
+ break;
+ }
+
+ // optional uint64 scriptFilenameRef = 11;
+ case 11: {
+ if (tag == 88) {
+ parse_scriptFilenameRef:
+ clear_ScriptFilenameOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &ScriptFilenameOrRef_.scriptfilenameref_)));
+ set_has_scriptfilenameref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectAtEnd()) goto success;
+ break;
+ }
+
+ default: {
+ handle_unusual:
+ if (tag == 0 ||
+ ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+ goto success;
+ }
+ DO_(::google::protobuf::internal::WireFormat::SkipField(
+ input, tag, mutable_unknown_fields()));
+ break;
+ }
+ }
+ }
+success:
+ // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Node)
+ return true;
+failure:
+ // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Node)
+ return false;
+#undef DO_
+}
+
+void Node::SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const {
+ // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Node)
+ // optional uint64 id = 1;
+ if (has_id()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->id(), output);
+ }
+
+ // optional bytes typeName = 2;
+ if (has_typename_()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 2, this->typename_(), output);
+ }
+
+ // optional uint64 typeNameRef = 3;
+ if (has_typenameref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(3, this->typenameref(), output);
+ }
+
+ // optional uint64 size = 4;
+ if (has_size()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(4, this->size(), output);
+ }
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ for (int i = 0; i < this->edges_size(); i++) {
+ ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
+ 5, this->edges(i), output);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ if (has_allocationstack()) {
+ ::google::protobuf::internal::WireFormatLite::WriteMessageMaybeToArray(
+ 6, this->allocationstack(), output);
+ }
+
+ // optional bytes jsObjectClassName = 7;
+ if (has_jsobjectclassname()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 7, this->jsobjectclassname(), output);
+ }
+
+ // optional uint64 jsObjectClassNameRef = 8;
+ if (has_jsobjectclassnameref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(8, this->jsobjectclassnameref(), output);
+ }
+
+ // optional uint32 coarseType = 9 [default = 0];
+ if (has_coarsetype()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt32(9, this->coarsetype(), output);
+ }
+
+ // optional bytes scriptFilename = 10;
+ if (has_scriptfilename()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 10, this->scriptfilename(), output);
+ }
+
+ // optional uint64 scriptFilenameRef = 11;
+ if (has_scriptfilenameref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(11, this->scriptfilenameref(), output);
+ }
+
+ if (!unknown_fields().empty()) {
+ ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+ unknown_fields(), output);
+ }
+ // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Node)
+}
+
+::google::protobuf::uint8* Node::SerializeWithCachedSizesToArray(
+ ::google::protobuf::uint8* target) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Node)
+ // optional uint64 id = 1;
+ if (has_id()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->id(), target);
+ }
+
+ // optional bytes typeName = 2;
+ if (has_typename_()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 2, this->typename_(), target);
+ }
+
+ // optional uint64 typeNameRef = 3;
+ if (has_typenameref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(3, this->typenameref(), target);
+ }
+
+ // optional uint64 size = 4;
+ if (has_size()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(4, this->size(), target);
+ }
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ for (int i = 0; i < this->edges_size(); i++) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteMessageNoVirtualToArray(
+ 5, this->edges(i), target);
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ if (has_allocationstack()) {
+ target = ::google::protobuf::internal::WireFormatLite::
+ WriteMessageNoVirtualToArray(
+ 6, this->allocationstack(), target);
+ }
+
+ // optional bytes jsObjectClassName = 7;
+ if (has_jsobjectclassname()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 7, this->jsobjectclassname(), target);
+ }
+
+ // optional uint64 jsObjectClassNameRef = 8;
+ if (has_jsobjectclassnameref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(8, this->jsobjectclassnameref(), target);
+ }
+
+ // optional uint32 coarseType = 9 [default = 0];
+ if (has_coarsetype()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt32ToArray(9, this->coarsetype(), target);
+ }
+
+ // optional bytes scriptFilename = 10;
+ if (has_scriptfilename()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 10, this->scriptfilename(), target);
+ }
+
+ // optional uint64 scriptFilenameRef = 11;
+ if (has_scriptfilenameref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(11, this->scriptfilenameref(), target);
+ }
+
+ if (!unknown_fields().empty()) {
+ target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+ unknown_fields(), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Node)
+ return target;
+}
+
+int Node::ByteSize() const {
+ int total_size = 0;
+
+ if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ // optional uint64 id = 1;
+ if (has_id()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->id());
+ }
+
+ // optional uint64 size = 4;
+ if (has_size()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->size());
+ }
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ if (has_allocationstack()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+ this->allocationstack());
+ }
+
+ }
+ if (_has_bits_[8 / 32] & (0xffu << (8 % 32))) {
+ // optional uint32 coarseType = 9 [default = 0];
+ if (has_coarsetype()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt32Size(
+ this->coarsetype());
+ }
+
+ }
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ total_size += 1 * this->edges_size();
+ for (int i = 0; i < this->edges_size(); i++) {
+ total_size +=
+ ::google::protobuf::internal::WireFormatLite::MessageSizeNoVirtual(
+ this->edges(i));
+ }
+
+ switch (TypeNameOrRef_case()) {
+ // optional bytes typeName = 2;
+ case kTypeName: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->typename_());
+ break;
+ }
+ // optional uint64 typeNameRef = 3;
+ case kTypeNameRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->typenameref());
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (JSObjectClassNameOrRef_case()) {
+ // optional bytes jsObjectClassName = 7;
+ case kJsObjectClassName: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->jsobjectclassname());
+ break;
+ }
+ // optional uint64 jsObjectClassNameRef = 8;
+ case kJsObjectClassNameRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->jsobjectclassnameref());
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (ScriptFilenameOrRef_case()) {
+ // optional bytes scriptFilename = 10;
+ case kScriptFilename: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->scriptfilename());
+ break;
+ }
+ // optional uint64 scriptFilenameRef = 11;
+ case kScriptFilenameRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->scriptfilenameref());
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (!unknown_fields().empty()) {
+ total_size +=
+ ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+ unknown_fields());
+ }
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = total_size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+ return total_size;
+}
+
+void Node::MergeFrom(const ::google::protobuf::Message& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ const Node* source =
+ ::google::protobuf::internal::dynamic_cast_if_available<const Node*>(
+ &from);
+ if (source == NULL) {
+ ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+ } else {
+ MergeFrom(*source);
+ }
+}
+
+void Node::MergeFrom(const Node& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ edges_.MergeFrom(from.edges_);
+ switch (from.TypeNameOrRef_case()) {
+ case kTypeName: {
+ set_typename_(from.typename_());
+ break;
+ }
+ case kTypeNameRef: {
+ set_typenameref(from.typenameref());
+ break;
+ }
+ case TYPENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.JSObjectClassNameOrRef_case()) {
+ case kJsObjectClassName: {
+ set_jsobjectclassname(from.jsobjectclassname());
+ break;
+ }
+ case kJsObjectClassNameRef: {
+ set_jsobjectclassnameref(from.jsobjectclassnameref());
+ break;
+ }
+ case JSOBJECTCLASSNAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ switch (from.ScriptFilenameOrRef_case()) {
+ case kScriptFilename: {
+ set_scriptfilename(from.scriptfilename());
+ break;
+ }
+ case kScriptFilenameRef: {
+ set_scriptfilenameref(from.scriptfilenameref());
+ break;
+ }
+ case SCRIPTFILENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ if (from.has_id()) {
+ set_id(from.id());
+ }
+ if (from.has_size()) {
+ set_size(from.size());
+ }
+ if (from.has_allocationstack()) {
+ mutable_allocationstack()->::mozilla::devtools::protobuf::StackFrame::MergeFrom(from.allocationstack());
+ }
+ }
+ if (from._has_bits_[8 / 32] & (0xffu << (8 % 32))) {
+ if (from.has_coarsetype()) {
+ set_coarsetype(from.coarsetype());
+ }
+ }
+ mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Node::CopyFrom(const ::google::protobuf::Message& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+void Node::CopyFrom(const Node& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Node::IsInitialized() const {
+
+ return true;
+}
+
+void Node::Swap(Node* other) {
+ if (other != this) {
+ std::swap(id_, other->id_);
+ std::swap(size_, other->size_);
+ edges_.Swap(&other->edges_);
+ std::swap(allocationstack_, other->allocationstack_);
+ std::swap(coarsetype_, other->coarsetype_);
+ std::swap(TypeNameOrRef_, other->TypeNameOrRef_);
+ std::swap(_oneof_case_[0], other->_oneof_case_[0]);
+ std::swap(JSObjectClassNameOrRef_, other->JSObjectClassNameOrRef_);
+ std::swap(_oneof_case_[1], other->_oneof_case_[1]);
+ std::swap(ScriptFilenameOrRef_, other->ScriptFilenameOrRef_);
+ std::swap(_oneof_case_[2], other->_oneof_case_[2]);
+ std::swap(_has_bits_[0], other->_has_bits_[0]);
+ _unknown_fields_.Swap(&other->_unknown_fields_);
+ std::swap(_cached_size_, other->_cached_size_);
+ }
+}
+
+::google::protobuf::Metadata Node::GetMetadata() const {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::Metadata metadata;
+ metadata.descriptor = Node_descriptor_;
+ metadata.reflection = Node_reflection_;
+ return metadata;
+}
+
+
+// ===================================================================
+
+#ifndef _MSC_VER
+const int Edge::kReferentFieldNumber;
+const int Edge::kNameFieldNumber;
+const int Edge::kNameRefFieldNumber;
+#endif // !_MSC_VER
+
+Edge::Edge()
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ // @@protoc_insertion_point(constructor:mozilla.devtools.protobuf.Edge)
+}
+
+void Edge::InitAsDefaultInstance() {
+ Edge_default_oneof_instance_->name_ = &::google::protobuf::internal::GetEmptyStringAlreadyInited();
+ Edge_default_oneof_instance_->nameref_ = GOOGLE_ULONGLONG(0);
+}
+
+Edge::Edge(const Edge& from)
+ : ::google::protobuf::Message() {
+ SharedCtor();
+ MergeFrom(from);
+ // @@protoc_insertion_point(copy_constructor:mozilla.devtools.protobuf.Edge)
+}
+
+void Edge::SharedCtor() {
+ ::google::protobuf::internal::GetEmptyString();
+ _cached_size_ = 0;
+ referent_ = GOOGLE_ULONGLONG(0);
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ clear_has_EdgeNameOrRef();
+}
+
+Edge::~Edge() {
+ // @@protoc_insertion_point(destructor:mozilla.devtools.protobuf.Edge)
+ SharedDtor();
+}
+
+void Edge::SharedDtor() {
+ if (has_EdgeNameOrRef()) {
+ clear_EdgeNameOrRef();
+ }
+ if (this != default_instance_) {
+ }
+}
+
+void Edge::SetCachedSize(int size) const {
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+}
+const ::google::protobuf::Descriptor* Edge::descriptor() {
+ protobuf_AssignDescriptorsOnce();
+ return Edge_descriptor_;
+}
+
+const Edge& Edge::default_instance() {
+ if (default_instance_ == NULL) protobuf_AddDesc_CoreDump_2eproto();
+ return *default_instance_;
+}
+
+Edge* Edge::default_instance_ = NULL;
+
+Edge* Edge::New() const {
+ return new Edge;
+}
+
+void Edge::clear_EdgeNameOrRef() {
+ switch(EdgeNameOrRef_case()) {
+ case kName: {
+ delete EdgeNameOrRef_.name_;
+ break;
+ }
+ case kNameRef: {
+ // No need to clear
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ _oneof_case_[0] = EDGENAMEORREF_NOT_SET;
+}
+
+
+void Edge::Clear() {
+ referent_ = GOOGLE_ULONGLONG(0);
+ clear_EdgeNameOrRef();
+ ::memset(_has_bits_, 0, sizeof(_has_bits_));
+ mutable_unknown_fields()->Clear();
+}
+
+bool Edge::MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input) {
+#define DO_(EXPRESSION) if (!(EXPRESSION)) goto failure
+ ::google::protobuf::uint32 tag;
+ // @@protoc_insertion_point(parse_start:mozilla.devtools.protobuf.Edge)
+ for (;;) {
+ ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoff(127);
+ tag = p.first;
+ if (!p.second) goto handle_unusual;
+ switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) {
+ // optional uint64 referent = 1;
+ case 1: {
+ if (tag == 8) {
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &referent_)));
+ set_has_referent();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(18)) goto parse_name;
+ break;
+ }
+
+ // optional bytes name = 2;
+ case 2: {
+ if (tag == 18) {
+ parse_name:
+ DO_(::google::protobuf::internal::WireFormatLite::ReadBytes(
+ input, this->mutable_name()));
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectTag(24)) goto parse_nameRef;
+ break;
+ }
+
+ // optional uint64 nameRef = 3;
+ case 3: {
+ if (tag == 24) {
+ parse_nameRef:
+ clear_EdgeNameOrRef();
+ DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive<
+ ::google::protobuf::uint64, ::google::protobuf::internal::WireFormatLite::TYPE_UINT64>(
+ input, &EdgeNameOrRef_.nameref_)));
+ set_has_nameref();
+ } else {
+ goto handle_unusual;
+ }
+ if (input->ExpectAtEnd()) goto success;
+ break;
+ }
+
+ default: {
+ handle_unusual:
+ if (tag == 0 ||
+ ::google::protobuf::internal::WireFormatLite::GetTagWireType(tag) ==
+ ::google::protobuf::internal::WireFormatLite::WIRETYPE_END_GROUP) {
+ goto success;
+ }
+ DO_(::google::protobuf::internal::WireFormat::SkipField(
+ input, tag, mutable_unknown_fields()));
+ break;
+ }
+ }
+ }
+success:
+ // @@protoc_insertion_point(parse_success:mozilla.devtools.protobuf.Edge)
+ return true;
+failure:
+ // @@protoc_insertion_point(parse_failure:mozilla.devtools.protobuf.Edge)
+ return false;
+#undef DO_
+}
+
+void Edge::SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const {
+ // @@protoc_insertion_point(serialize_start:mozilla.devtools.protobuf.Edge)
+ // optional uint64 referent = 1;
+ if (has_referent()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(1, this->referent(), output);
+ }
+
+ // optional bytes name = 2;
+ if (has_name()) {
+ ::google::protobuf::internal::WireFormatLite::WriteBytesMaybeAliased(
+ 2, this->name(), output);
+ }
+
+ // optional uint64 nameRef = 3;
+ if (has_nameref()) {
+ ::google::protobuf::internal::WireFormatLite::WriteUInt64(3, this->nameref(), output);
+ }
+
+ if (!unknown_fields().empty()) {
+ ::google::protobuf::internal::WireFormat::SerializeUnknownFields(
+ unknown_fields(), output);
+ }
+ // @@protoc_insertion_point(serialize_end:mozilla.devtools.protobuf.Edge)
+}
+
+::google::protobuf::uint8* Edge::SerializeWithCachedSizesToArray(
+ ::google::protobuf::uint8* target) const {
+ // @@protoc_insertion_point(serialize_to_array_start:mozilla.devtools.protobuf.Edge)
+ // optional uint64 referent = 1;
+ if (has_referent()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(1, this->referent(), target);
+ }
+
+ // optional bytes name = 2;
+ if (has_name()) {
+ target =
+ ::google::protobuf::internal::WireFormatLite::WriteBytesToArray(
+ 2, this->name(), target);
+ }
+
+ // optional uint64 nameRef = 3;
+ if (has_nameref()) {
+ target = ::google::protobuf::internal::WireFormatLite::WriteUInt64ToArray(3, this->nameref(), target);
+ }
+
+ if (!unknown_fields().empty()) {
+ target = ::google::protobuf::internal::WireFormat::SerializeUnknownFieldsToArray(
+ unknown_fields(), target);
+ }
+ // @@protoc_insertion_point(serialize_to_array_end:mozilla.devtools.protobuf.Edge)
+ return target;
+}
+
+int Edge::ByteSize() const {
+ int total_size = 0;
+
+ if (_has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ // optional uint64 referent = 1;
+ if (has_referent()) {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->referent());
+ }
+
+ }
+ switch (EdgeNameOrRef_case()) {
+ // optional bytes name = 2;
+ case kName: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::BytesSize(
+ this->name());
+ break;
+ }
+ // optional uint64 nameRef = 3;
+ case kNameRef: {
+ total_size += 1 +
+ ::google::protobuf::internal::WireFormatLite::UInt64Size(
+ this->nameref());
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (!unknown_fields().empty()) {
+ total_size +=
+ ::google::protobuf::internal::WireFormat::ComputeUnknownFieldsSize(
+ unknown_fields());
+ }
+ GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN();
+ _cached_size_ = total_size;
+ GOOGLE_SAFE_CONCURRENT_WRITES_END();
+ return total_size;
+}
+
+void Edge::MergeFrom(const ::google::protobuf::Message& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ const Edge* source =
+ ::google::protobuf::internal::dynamic_cast_if_available<const Edge*>(
+ &from);
+ if (source == NULL) {
+ ::google::protobuf::internal::ReflectionOps::Merge(from, this);
+ } else {
+ MergeFrom(*source);
+ }
+}
+
+void Edge::MergeFrom(const Edge& from) {
+ GOOGLE_CHECK_NE(&from, this);
+ switch (from.EdgeNameOrRef_case()) {
+ case kName: {
+ set_name(from.name());
+ break;
+ }
+ case kNameRef: {
+ set_nameref(from.nameref());
+ break;
+ }
+ case EDGENAMEORREF_NOT_SET: {
+ break;
+ }
+ }
+ if (from._has_bits_[0 / 32] & (0xffu << (0 % 32))) {
+ if (from.has_referent()) {
+ set_referent(from.referent());
+ }
+ }
+ mutable_unknown_fields()->MergeFrom(from.unknown_fields());
+}
+
+void Edge::CopyFrom(const ::google::protobuf::Message& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+void Edge::CopyFrom(const Edge& from) {
+ if (&from == this) return;
+ Clear();
+ MergeFrom(from);
+}
+
+bool Edge::IsInitialized() const {
+
+ return true;
+}
+
+void Edge::Swap(Edge* other) {
+ if (other != this) {
+ std::swap(referent_, other->referent_);
+ std::swap(EdgeNameOrRef_, other->EdgeNameOrRef_);
+ std::swap(_oneof_case_[0], other->_oneof_case_[0]);
+ std::swap(_has_bits_[0], other->_has_bits_[0]);
+ _unknown_fields_.Swap(&other->_unknown_fields_);
+ std::swap(_cached_size_, other->_cached_size_);
+ }
+}
+
+::google::protobuf::Metadata Edge::GetMetadata() const {
+ protobuf_AssignDescriptorsOnce();
+ ::google::protobuf::Metadata metadata;
+ metadata.descriptor = Edge_descriptor_;
+ metadata.reflection = Edge_reflection_;
+ return metadata;
+}
+
+
+// @@protoc_insertion_point(namespace_scope)
+
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+
+// @@protoc_insertion_point(global_scope)
diff --git a/devtools/shared/heapsnapshot/CoreDump.pb.h b/devtools/shared/heapsnapshot/CoreDump.pb.h
new file mode 100644
index 000000000..584c2e379
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.pb.h
@@ -0,0 +1,1893 @@
+// Generated by the protocol buffer compiler. DO NOT EDIT!
+// source: CoreDump.proto
+
+#ifndef PROTOBUF_CoreDump_2eproto__INCLUDED
+#define PROTOBUF_CoreDump_2eproto__INCLUDED
+
+#include <string>
+
+#include <google/protobuf/stubs/common.h>
+
+#if GOOGLE_PROTOBUF_VERSION < 2006000
+#error This file was generated by a newer version of protoc which is
+#error incompatible with your Protocol Buffer headers. Please update
+#error your headers.
+#endif
+#if 2006001 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION
+#error This file was generated by an older version of protoc which is
+#error incompatible with your Protocol Buffer headers. Please
+#error regenerate this file with a newer version of protoc.
+#endif
+
+#include <google/protobuf/generated_message_util.h>
+#include <google/protobuf/message.h>
+#include <google/protobuf/repeated_field.h>
+#include <google/protobuf/extension_set.h>
+#include <google/protobuf/unknown_field_set.h>
+// @@protoc_insertion_point(includes)
+
+namespace mozilla {
+namespace devtools {
+namespace protobuf {
+
+// Internal implementation detail -- do not call these.
+void protobuf_AddDesc_CoreDump_2eproto();
+void protobuf_AssignDesc_CoreDump_2eproto();
+void protobuf_ShutdownFile_CoreDump_2eproto();
+
+class Metadata;
+class StackFrame;
+class StackFrame_Data;
+class Node;
+class Edge;
+
+// ===================================================================
+
+class Metadata : public ::google::protobuf::Message {
+ public:
+ Metadata();
+ virtual ~Metadata();
+
+ Metadata(const Metadata& from);
+
+ inline Metadata& operator=(const Metadata& from) {
+ CopyFrom(from);
+ return *this;
+ }
+
+ inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+ return _unknown_fields_;
+ }
+
+ inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+ return &_unknown_fields_;
+ }
+
+ static const ::google::protobuf::Descriptor* descriptor();
+ static const Metadata& default_instance();
+
+ void Swap(Metadata* other);
+
+ // implements Message ----------------------------------------------
+
+ Metadata* New() const;
+ void CopyFrom(const ::google::protobuf::Message& from);
+ void MergeFrom(const ::google::protobuf::Message& from);
+ void CopyFrom(const Metadata& from);
+ void MergeFrom(const Metadata& from);
+ void Clear();
+ bool IsInitialized() const;
+
+ int ByteSize() const;
+ bool MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input);
+ void SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const;
+ ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+ int GetCachedSize() const { return _cached_size_; }
+ private:
+ void SharedCtor();
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ public:
+ ::google::protobuf::Metadata GetMetadata() const;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ // optional uint64 timeStamp = 1;
+ inline bool has_timestamp() const;
+ inline void clear_timestamp();
+ static const int kTimeStampFieldNumber = 1;
+ inline ::google::protobuf::uint64 timestamp() const;
+ inline void set_timestamp(::google::protobuf::uint64 value);
+
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Metadata)
+ private:
+ inline void set_has_timestamp();
+ inline void clear_has_timestamp();
+
+ ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+ ::google::protobuf::uint32 _has_bits_[1];
+ mutable int _cached_size_;
+ ::google::protobuf::uint64 timestamp_;
+ friend void protobuf_AddDesc_CoreDump_2eproto();
+ friend void protobuf_AssignDesc_CoreDump_2eproto();
+ friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+ void InitAsDefaultInstance();
+ static Metadata* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class StackFrame_Data : public ::google::protobuf::Message {
+ public:
+ StackFrame_Data();
+ virtual ~StackFrame_Data();
+
+ StackFrame_Data(const StackFrame_Data& from);
+
+ inline StackFrame_Data& operator=(const StackFrame_Data& from) {
+ CopyFrom(from);
+ return *this;
+ }
+
+ inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+ return _unknown_fields_;
+ }
+
+ inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+ return &_unknown_fields_;
+ }
+
+ static const ::google::protobuf::Descriptor* descriptor();
+ static const StackFrame_Data& default_instance();
+
+ enum SourceOrRefCase {
+ kSource = 5,
+ kSourceRef = 6,
+ SOURCEORREF_NOT_SET = 0,
+ };
+
+ enum FunctionDisplayNameOrRefCase {
+ kFunctionDisplayName = 7,
+ kFunctionDisplayNameRef = 8,
+ FUNCTIONDISPLAYNAMEORREF_NOT_SET = 0,
+ };
+
+ void Swap(StackFrame_Data* other);
+
+ // implements Message ----------------------------------------------
+
+ StackFrame_Data* New() const;
+ void CopyFrom(const ::google::protobuf::Message& from);
+ void MergeFrom(const ::google::protobuf::Message& from);
+ void CopyFrom(const StackFrame_Data& from);
+ void MergeFrom(const StackFrame_Data& from);
+ void Clear();
+ bool IsInitialized() const;
+
+ int ByteSize() const;
+ bool MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input);
+ void SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const;
+ ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+ int GetCachedSize() const { return _cached_size_; }
+ private:
+ void SharedCtor();
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ public:
+ ::google::protobuf::Metadata GetMetadata() const;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ // optional uint64 id = 1;
+ inline bool has_id() const;
+ inline void clear_id();
+ static const int kIdFieldNumber = 1;
+ inline ::google::protobuf::uint64 id() const;
+ inline void set_id(::google::protobuf::uint64 value);
+
+ // optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+ inline bool has_parent() const;
+ inline void clear_parent();
+ static const int kParentFieldNumber = 2;
+ inline const ::mozilla::devtools::protobuf::StackFrame& parent() const;
+ inline ::mozilla::devtools::protobuf::StackFrame* mutable_parent();
+ inline ::mozilla::devtools::protobuf::StackFrame* release_parent();
+ inline void set_allocated_parent(::mozilla::devtools::protobuf::StackFrame* parent);
+
+ // optional uint32 line = 3;
+ inline bool has_line() const;
+ inline void clear_line();
+ static const int kLineFieldNumber = 3;
+ inline ::google::protobuf::uint32 line() const;
+ inline void set_line(::google::protobuf::uint32 value);
+
+ // optional uint32 column = 4;
+ inline bool has_column() const;
+ inline void clear_column();
+ static const int kColumnFieldNumber = 4;
+ inline ::google::protobuf::uint32 column() const;
+ inline void set_column(::google::protobuf::uint32 value);
+
+ // optional bytes source = 5;
+ inline bool has_source() const;
+ inline void clear_source();
+ static const int kSourceFieldNumber = 5;
+ inline const ::std::string& source() const;
+ inline void set_source(const ::std::string& value);
+ inline void set_source(const char* value);
+ inline void set_source(const void* value, size_t size);
+ inline ::std::string* mutable_source();
+ inline ::std::string* release_source();
+ inline void set_allocated_source(::std::string* source);
+
+ // optional uint64 sourceRef = 6;
+ inline bool has_sourceref() const;
+ inline void clear_sourceref();
+ static const int kSourceRefFieldNumber = 6;
+ inline ::google::protobuf::uint64 sourceref() const;
+ inline void set_sourceref(::google::protobuf::uint64 value);
+
+ // optional bytes functionDisplayName = 7;
+ inline bool has_functiondisplayname() const;
+ inline void clear_functiondisplayname();
+ static const int kFunctionDisplayNameFieldNumber = 7;
+ inline const ::std::string& functiondisplayname() const;
+ inline void set_functiondisplayname(const ::std::string& value);
+ inline void set_functiondisplayname(const char* value);
+ inline void set_functiondisplayname(const void* value, size_t size);
+ inline ::std::string* mutable_functiondisplayname();
+ inline ::std::string* release_functiondisplayname();
+ inline void set_allocated_functiondisplayname(::std::string* functiondisplayname);
+
+ // optional uint64 functionDisplayNameRef = 8;
+ inline bool has_functiondisplaynameref() const;
+ inline void clear_functiondisplaynameref();
+ static const int kFunctionDisplayNameRefFieldNumber = 8;
+ inline ::google::protobuf::uint64 functiondisplaynameref() const;
+ inline void set_functiondisplaynameref(::google::protobuf::uint64 value);
+
+ // optional bool isSystem = 9;
+ inline bool has_issystem() const;
+ inline void clear_issystem();
+ static const int kIsSystemFieldNumber = 9;
+ inline bool issystem() const;
+ inline void set_issystem(bool value);
+
+ // optional bool isSelfHosted = 10;
+ inline bool has_isselfhosted() const;
+ inline void clear_isselfhosted();
+ static const int kIsSelfHostedFieldNumber = 10;
+ inline bool isselfhosted() const;
+ inline void set_isselfhosted(bool value);
+
+ inline SourceOrRefCase SourceOrRef_case() const;
+ inline FunctionDisplayNameOrRefCase FunctionDisplayNameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.StackFrame.Data)
+ private:
+ inline void set_has_id();
+ inline void clear_has_id();
+ inline void set_has_parent();
+ inline void clear_has_parent();
+ inline void set_has_line();
+ inline void clear_has_line();
+ inline void set_has_column();
+ inline void clear_has_column();
+ inline void set_has_source();
+ inline void set_has_sourceref();
+ inline void set_has_functiondisplayname();
+ inline void set_has_functiondisplaynameref();
+ inline void set_has_issystem();
+ inline void clear_has_issystem();
+ inline void set_has_isselfhosted();
+ inline void clear_has_isselfhosted();
+
+ inline bool has_SourceOrRef();
+ void clear_SourceOrRef();
+ inline void clear_has_SourceOrRef();
+
+ inline bool has_FunctionDisplayNameOrRef();
+ void clear_FunctionDisplayNameOrRef();
+ inline void clear_has_FunctionDisplayNameOrRef();
+
+ ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+ ::google::protobuf::uint32 _has_bits_[1];
+ mutable int _cached_size_;
+ ::google::protobuf::uint64 id_;
+ ::mozilla::devtools::protobuf::StackFrame* parent_;
+ ::google::protobuf::uint32 line_;
+ ::google::protobuf::uint32 column_;
+ bool issystem_;
+ bool isselfhosted_;
+ union SourceOrRefUnion {
+ ::std::string* source_;
+ ::google::protobuf::uint64 sourceref_;
+ } SourceOrRef_;
+ union FunctionDisplayNameOrRefUnion {
+ ::std::string* functiondisplayname_;
+ ::google::protobuf::uint64 functiondisplaynameref_;
+ } FunctionDisplayNameOrRef_;
+ ::google::protobuf::uint32 _oneof_case_[2];
+
+ friend void protobuf_AddDesc_CoreDump_2eproto();
+ friend void protobuf_AssignDesc_CoreDump_2eproto();
+ friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+ void InitAsDefaultInstance();
+ static StackFrame_Data* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class StackFrame : public ::google::protobuf::Message {
+ public:
+ StackFrame();
+ virtual ~StackFrame();
+
+ StackFrame(const StackFrame& from);
+
+ inline StackFrame& operator=(const StackFrame& from) {
+ CopyFrom(from);
+ return *this;
+ }
+
+ inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+ return _unknown_fields_;
+ }
+
+ inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+ return &_unknown_fields_;
+ }
+
+ static const ::google::protobuf::Descriptor* descriptor();
+ static const StackFrame& default_instance();
+
+ enum StackFrameTypeCase {
+ kData = 1,
+ kRef = 2,
+ STACKFRAMETYPE_NOT_SET = 0,
+ };
+
+ void Swap(StackFrame* other);
+
+ // implements Message ----------------------------------------------
+
+ StackFrame* New() const;
+ void CopyFrom(const ::google::protobuf::Message& from);
+ void MergeFrom(const ::google::protobuf::Message& from);
+ void CopyFrom(const StackFrame& from);
+ void MergeFrom(const StackFrame& from);
+ void Clear();
+ bool IsInitialized() const;
+
+ int ByteSize() const;
+ bool MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input);
+ void SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const;
+ ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+ int GetCachedSize() const { return _cached_size_; }
+ private:
+ void SharedCtor();
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ public:
+ ::google::protobuf::Metadata GetMetadata() const;
+
+ // nested types ----------------------------------------------------
+
+ typedef StackFrame_Data Data;
+
+ // accessors -------------------------------------------------------
+
+ // optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+ inline bool has_data() const;
+ inline void clear_data();
+ static const int kDataFieldNumber = 1;
+ inline const ::mozilla::devtools::protobuf::StackFrame_Data& data() const;
+ inline ::mozilla::devtools::protobuf::StackFrame_Data* mutable_data();
+ inline ::mozilla::devtools::protobuf::StackFrame_Data* release_data();
+ inline void set_allocated_data(::mozilla::devtools::protobuf::StackFrame_Data* data);
+
+ // optional uint64 ref = 2;
+ inline bool has_ref() const;
+ inline void clear_ref();
+ static const int kRefFieldNumber = 2;
+ inline ::google::protobuf::uint64 ref() const;
+ inline void set_ref(::google::protobuf::uint64 value);
+
+ inline StackFrameTypeCase StackFrameType_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.StackFrame)
+ private:
+ inline void set_has_data();
+ inline void set_has_ref();
+
+ inline bool has_StackFrameType();
+ void clear_StackFrameType();
+ inline void clear_has_StackFrameType();
+
+ ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+ ::google::protobuf::uint32 _has_bits_[1];
+ mutable int _cached_size_;
+ union StackFrameTypeUnion {
+ ::mozilla::devtools::protobuf::StackFrame_Data* data_;
+ ::google::protobuf::uint64 ref_;
+ } StackFrameType_;
+ ::google::protobuf::uint32 _oneof_case_[1];
+
+ friend void protobuf_AddDesc_CoreDump_2eproto();
+ friend void protobuf_AssignDesc_CoreDump_2eproto();
+ friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+ void InitAsDefaultInstance();
+ static StackFrame* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class Node : public ::google::protobuf::Message {
+ public:
+ Node();
+ virtual ~Node();
+
+ Node(const Node& from);
+
+ inline Node& operator=(const Node& from) {
+ CopyFrom(from);
+ return *this;
+ }
+
+ inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+ return _unknown_fields_;
+ }
+
+ inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+ return &_unknown_fields_;
+ }
+
+ static const ::google::protobuf::Descriptor* descriptor();
+ static const Node& default_instance();
+
+ enum TypeNameOrRefCase {
+ kTypeName = 2,
+ kTypeNameRef = 3,
+ TYPENAMEORREF_NOT_SET = 0,
+ };
+
+ enum JSObjectClassNameOrRefCase {
+ kJsObjectClassName = 7,
+ kJsObjectClassNameRef = 8,
+ JSOBJECTCLASSNAMEORREF_NOT_SET = 0,
+ };
+
+ enum ScriptFilenameOrRefCase {
+ kScriptFilename = 10,
+ kScriptFilenameRef = 11,
+ SCRIPTFILENAMEORREF_NOT_SET = 0,
+ };
+
+ void Swap(Node* other);
+
+ // implements Message ----------------------------------------------
+
+ Node* New() const;
+ void CopyFrom(const ::google::protobuf::Message& from);
+ void MergeFrom(const ::google::protobuf::Message& from);
+ void CopyFrom(const Node& from);
+ void MergeFrom(const Node& from);
+ void Clear();
+ bool IsInitialized() const;
+
+ int ByteSize() const;
+ bool MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input);
+ void SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const;
+ ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+ int GetCachedSize() const { return _cached_size_; }
+ private:
+ void SharedCtor();
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ public:
+ ::google::protobuf::Metadata GetMetadata() const;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ // optional uint64 id = 1;
+ inline bool has_id() const;
+ inline void clear_id();
+ static const int kIdFieldNumber = 1;
+ inline ::google::protobuf::uint64 id() const;
+ inline void set_id(::google::protobuf::uint64 value);
+
+ // optional bytes typeName = 2;
+ inline bool has_typename_() const;
+ inline void clear_typename_();
+ static const int kTypeNameFieldNumber = 2;
+ inline const ::std::string& typename_() const;
+ inline void set_typename_(const ::std::string& value);
+ inline void set_typename_(const char* value);
+ inline void set_typename_(const void* value, size_t size);
+ inline ::std::string* mutable_typename_();
+ inline ::std::string* release_typename_();
+ inline void set_allocated_typename_(::std::string* typename_);
+
+ // optional uint64 typeNameRef = 3;
+ inline bool has_typenameref() const;
+ inline void clear_typenameref();
+ static const int kTypeNameRefFieldNumber = 3;
+ inline ::google::protobuf::uint64 typenameref() const;
+ inline void set_typenameref(::google::protobuf::uint64 value);
+
+ // optional uint64 size = 4;
+ inline bool has_size() const;
+ inline void clear_size();
+ static const int kSizeFieldNumber = 4;
+ inline ::google::protobuf::uint64 size() const;
+ inline void set_size(::google::protobuf::uint64 value);
+
+ // repeated .mozilla.devtools.protobuf.Edge edges = 5;
+ inline int edges_size() const;
+ inline void clear_edges();
+ static const int kEdgesFieldNumber = 5;
+ inline const ::mozilla::devtools::protobuf::Edge& edges(int index) const;
+ inline ::mozilla::devtools::protobuf::Edge* mutable_edges(int index);
+ inline ::mozilla::devtools::protobuf::Edge* add_edges();
+ inline const ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+ edges() const;
+ inline ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+ mutable_edges();
+
+ // optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+ inline bool has_allocationstack() const;
+ inline void clear_allocationstack();
+ static const int kAllocationStackFieldNumber = 6;
+ inline const ::mozilla::devtools::protobuf::StackFrame& allocationstack() const;
+ inline ::mozilla::devtools::protobuf::StackFrame* mutable_allocationstack();
+ inline ::mozilla::devtools::protobuf::StackFrame* release_allocationstack();
+ inline void set_allocated_allocationstack(::mozilla::devtools::protobuf::StackFrame* allocationstack);
+
+ // optional bytes jsObjectClassName = 7;
+ inline bool has_jsobjectclassname() const;
+ inline void clear_jsobjectclassname();
+ static const int kJsObjectClassNameFieldNumber = 7;
+ inline const ::std::string& jsobjectclassname() const;
+ inline void set_jsobjectclassname(const ::std::string& value);
+ inline void set_jsobjectclassname(const char* value);
+ inline void set_jsobjectclassname(const void* value, size_t size);
+ inline ::std::string* mutable_jsobjectclassname();
+ inline ::std::string* release_jsobjectclassname();
+ inline void set_allocated_jsobjectclassname(::std::string* jsobjectclassname);
+
+ // optional uint64 jsObjectClassNameRef = 8;
+ inline bool has_jsobjectclassnameref() const;
+ inline void clear_jsobjectclassnameref();
+ static const int kJsObjectClassNameRefFieldNumber = 8;
+ inline ::google::protobuf::uint64 jsobjectclassnameref() const;
+ inline void set_jsobjectclassnameref(::google::protobuf::uint64 value);
+
+ // optional uint32 coarseType = 9 [default = 0];
+ inline bool has_coarsetype() const;
+ inline void clear_coarsetype();
+ static const int kCoarseTypeFieldNumber = 9;
+ inline ::google::protobuf::uint32 coarsetype() const;
+ inline void set_coarsetype(::google::protobuf::uint32 value);
+
+ // optional bytes scriptFilename = 10;
+ inline bool has_scriptfilename() const;
+ inline void clear_scriptfilename();
+ static const int kScriptFilenameFieldNumber = 10;
+ inline const ::std::string& scriptfilename() const;
+ inline void set_scriptfilename(const ::std::string& value);
+ inline void set_scriptfilename(const char* value);
+ inline void set_scriptfilename(const void* value, size_t size);
+ inline ::std::string* mutable_scriptfilename();
+ inline ::std::string* release_scriptfilename();
+ inline void set_allocated_scriptfilename(::std::string* scriptfilename);
+
+ // optional uint64 scriptFilenameRef = 11;
+ inline bool has_scriptfilenameref() const;
+ inline void clear_scriptfilenameref();
+ static const int kScriptFilenameRefFieldNumber = 11;
+ inline ::google::protobuf::uint64 scriptfilenameref() const;
+ inline void set_scriptfilenameref(::google::protobuf::uint64 value);
+
+ inline TypeNameOrRefCase TypeNameOrRef_case() const;
+ inline JSObjectClassNameOrRefCase JSObjectClassNameOrRef_case() const;
+ inline ScriptFilenameOrRefCase ScriptFilenameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Node)
+ private:
+ inline void set_has_id();
+ inline void clear_has_id();
+ inline void set_has_typename_();
+ inline void set_has_typenameref();
+ inline void set_has_size();
+ inline void clear_has_size();
+ inline void set_has_allocationstack();
+ inline void clear_has_allocationstack();
+ inline void set_has_jsobjectclassname();
+ inline void set_has_jsobjectclassnameref();
+ inline void set_has_coarsetype();
+ inline void clear_has_coarsetype();
+ inline void set_has_scriptfilename();
+ inline void set_has_scriptfilenameref();
+
+ inline bool has_TypeNameOrRef();
+ void clear_TypeNameOrRef();
+ inline void clear_has_TypeNameOrRef();
+
+ inline bool has_JSObjectClassNameOrRef();
+ void clear_JSObjectClassNameOrRef();
+ inline void clear_has_JSObjectClassNameOrRef();
+
+ inline bool has_ScriptFilenameOrRef();
+ void clear_ScriptFilenameOrRef();
+ inline void clear_has_ScriptFilenameOrRef();
+
+ ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+ ::google::protobuf::uint32 _has_bits_[1];
+ mutable int _cached_size_;
+ ::google::protobuf::uint64 id_;
+ ::google::protobuf::uint64 size_;
+ ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge > edges_;
+ ::mozilla::devtools::protobuf::StackFrame* allocationstack_;
+ ::google::protobuf::uint32 coarsetype_;
+ union TypeNameOrRefUnion {
+ ::std::string* typename__;
+ ::google::protobuf::uint64 typenameref_;
+ } TypeNameOrRef_;
+ union JSObjectClassNameOrRefUnion {
+ ::std::string* jsobjectclassname_;
+ ::google::protobuf::uint64 jsobjectclassnameref_;
+ } JSObjectClassNameOrRef_;
+ union ScriptFilenameOrRefUnion {
+ ::std::string* scriptfilename_;
+ ::google::protobuf::uint64 scriptfilenameref_;
+ } ScriptFilenameOrRef_;
+ ::google::protobuf::uint32 _oneof_case_[3];
+
+ friend void protobuf_AddDesc_CoreDump_2eproto();
+ friend void protobuf_AssignDesc_CoreDump_2eproto();
+ friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+ void InitAsDefaultInstance();
+ static Node* default_instance_;
+};
+// -------------------------------------------------------------------
+
+class Edge : public ::google::protobuf::Message {
+ public:
+ Edge();
+ virtual ~Edge();
+
+ Edge(const Edge& from);
+
+ inline Edge& operator=(const Edge& from) {
+ CopyFrom(from);
+ return *this;
+ }
+
+ inline const ::google::protobuf::UnknownFieldSet& unknown_fields() const {
+ return _unknown_fields_;
+ }
+
+ inline ::google::protobuf::UnknownFieldSet* mutable_unknown_fields() {
+ return &_unknown_fields_;
+ }
+
+ static const ::google::protobuf::Descriptor* descriptor();
+ static const Edge& default_instance();
+
+ enum EdgeNameOrRefCase {
+ kName = 2,
+ kNameRef = 3,
+ EDGENAMEORREF_NOT_SET = 0,
+ };
+
+ void Swap(Edge* other);
+
+ // implements Message ----------------------------------------------
+
+ Edge* New() const;
+ void CopyFrom(const ::google::protobuf::Message& from);
+ void MergeFrom(const ::google::protobuf::Message& from);
+ void CopyFrom(const Edge& from);
+ void MergeFrom(const Edge& from);
+ void Clear();
+ bool IsInitialized() const;
+
+ int ByteSize() const;
+ bool MergePartialFromCodedStream(
+ ::google::protobuf::io::CodedInputStream* input);
+ void SerializeWithCachedSizes(
+ ::google::protobuf::io::CodedOutputStream* output) const;
+ ::google::protobuf::uint8* SerializeWithCachedSizesToArray(::google::protobuf::uint8* output) const;
+ int GetCachedSize() const { return _cached_size_; }
+ private:
+ void SharedCtor();
+ void SharedDtor();
+ void SetCachedSize(int size) const;
+ public:
+ ::google::protobuf::Metadata GetMetadata() const;
+
+ // nested types ----------------------------------------------------
+
+ // accessors -------------------------------------------------------
+
+ // optional uint64 referent = 1;
+ inline bool has_referent() const;
+ inline void clear_referent();
+ static const int kReferentFieldNumber = 1;
+ inline ::google::protobuf::uint64 referent() const;
+ inline void set_referent(::google::protobuf::uint64 value);
+
+ // optional bytes name = 2;
+ inline bool has_name() const;
+ inline void clear_name();
+ static const int kNameFieldNumber = 2;
+ inline const ::std::string& name() const;
+ inline void set_name(const ::std::string& value);
+ inline void set_name(const char* value);
+ inline void set_name(const void* value, size_t size);
+ inline ::std::string* mutable_name();
+ inline ::std::string* release_name();
+ inline void set_allocated_name(::std::string* name);
+
+ // optional uint64 nameRef = 3;
+ inline bool has_nameref() const;
+ inline void clear_nameref();
+ static const int kNameRefFieldNumber = 3;
+ inline ::google::protobuf::uint64 nameref() const;
+ inline void set_nameref(::google::protobuf::uint64 value);
+
+ inline EdgeNameOrRefCase EdgeNameOrRef_case() const;
+ // @@protoc_insertion_point(class_scope:mozilla.devtools.protobuf.Edge)
+ private:
+ inline void set_has_referent();
+ inline void clear_has_referent();
+ inline void set_has_name();
+ inline void set_has_nameref();
+
+ inline bool has_EdgeNameOrRef();
+ void clear_EdgeNameOrRef();
+ inline void clear_has_EdgeNameOrRef();
+
+ ::google::protobuf::UnknownFieldSet _unknown_fields_;
+
+ ::google::protobuf::uint32 _has_bits_[1];
+ mutable int _cached_size_;
+ ::google::protobuf::uint64 referent_;
+ union EdgeNameOrRefUnion {
+ ::std::string* name_;
+ ::google::protobuf::uint64 nameref_;
+ } EdgeNameOrRef_;
+ ::google::protobuf::uint32 _oneof_case_[1];
+
+ friend void protobuf_AddDesc_CoreDump_2eproto();
+ friend void protobuf_AssignDesc_CoreDump_2eproto();
+ friend void protobuf_ShutdownFile_CoreDump_2eproto();
+
+ void InitAsDefaultInstance();
+ static Edge* default_instance_;
+};
+// ===================================================================
+
+
+// ===================================================================
+
+// Metadata
+
+// optional uint64 timeStamp = 1;
+inline bool Metadata::has_timestamp() const {
+ return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Metadata::set_has_timestamp() {
+ _has_bits_[0] |= 0x00000001u;
+}
+inline void Metadata::clear_has_timestamp() {
+ _has_bits_[0] &= ~0x00000001u;
+}
+inline void Metadata::clear_timestamp() {
+ timestamp_ = GOOGLE_ULONGLONG(0);
+ clear_has_timestamp();
+}
+inline ::google::protobuf::uint64 Metadata::timestamp() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Metadata.timeStamp)
+ return timestamp_;
+}
+inline void Metadata::set_timestamp(::google::protobuf::uint64 value) {
+ set_has_timestamp();
+ timestamp_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Metadata.timeStamp)
+}
+
+// -------------------------------------------------------------------
+
+// StackFrame_Data
+
+// optional uint64 id = 1;
+inline bool StackFrame_Data::has_id() const {
+ return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void StackFrame_Data::set_has_id() {
+ _has_bits_[0] |= 0x00000001u;
+}
+inline void StackFrame_Data::clear_has_id() {
+ _has_bits_[0] &= ~0x00000001u;
+}
+inline void StackFrame_Data::clear_id() {
+ id_ = GOOGLE_ULONGLONG(0);
+ clear_has_id();
+}
+inline ::google::protobuf::uint64 StackFrame_Data::id() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.id)
+ return id_;
+}
+inline void StackFrame_Data::set_id(::google::protobuf::uint64 value) {
+ set_has_id();
+ id_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.id)
+}
+
+// optional .mozilla.devtools.protobuf.StackFrame parent = 2;
+inline bool StackFrame_Data::has_parent() const {
+ return (_has_bits_[0] & 0x00000002u) != 0;
+}
+inline void StackFrame_Data::set_has_parent() {
+ _has_bits_[0] |= 0x00000002u;
+}
+inline void StackFrame_Data::clear_has_parent() {
+ _has_bits_[0] &= ~0x00000002u;
+}
+inline void StackFrame_Data::clear_parent() {
+ if (parent_ != NULL) parent_->::mozilla::devtools::protobuf::StackFrame::Clear();
+ clear_has_parent();
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& StackFrame_Data::parent() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.parent)
+ return parent_ != NULL ? *parent_ : *default_instance_->parent_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::mutable_parent() {
+ set_has_parent();
+ if (parent_ == NULL) parent_ = new ::mozilla::devtools::protobuf::StackFrame;
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.StackFrame.Data.parent)
+ return parent_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* StackFrame_Data::release_parent() {
+ clear_has_parent();
+ ::mozilla::devtools::protobuf::StackFrame* temp = parent_;
+ parent_ = NULL;
+ return temp;
+}
+inline void StackFrame_Data::set_allocated_parent(::mozilla::devtools::protobuf::StackFrame* parent) {
+ delete parent_;
+ parent_ = parent;
+ if (parent) {
+ set_has_parent();
+ } else {
+ clear_has_parent();
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.StackFrame.Data.parent)
+}
+
+// optional uint32 line = 3;
+inline bool StackFrame_Data::has_line() const {
+ return (_has_bits_[0] & 0x00000004u) != 0;
+}
+inline void StackFrame_Data::set_has_line() {
+ _has_bits_[0] |= 0x00000004u;
+}
+inline void StackFrame_Data::clear_has_line() {
+ _has_bits_[0] &= ~0x00000004u;
+}
+inline void StackFrame_Data::clear_line() {
+ line_ = 0u;
+ clear_has_line();
+}
+inline ::google::protobuf::uint32 StackFrame_Data::line() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.line)
+ return line_;
+}
+inline void StackFrame_Data::set_line(::google::protobuf::uint32 value) {
+ set_has_line();
+ line_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.line)
+}
+
+// optional uint32 column = 4;
+inline bool StackFrame_Data::has_column() const {
+ return (_has_bits_[0] & 0x00000008u) != 0;
+}
+inline void StackFrame_Data::set_has_column() {
+ _has_bits_[0] |= 0x00000008u;
+}
+inline void StackFrame_Data::clear_has_column() {
+ _has_bits_[0] &= ~0x00000008u;
+}
+inline void StackFrame_Data::clear_column() {
+ column_ = 0u;
+ clear_has_column();
+}
+inline ::google::protobuf::uint32 StackFrame_Data::column() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.column)
+ return column_;
+}
+inline void StackFrame_Data::set_column(::google::protobuf::uint32 value) {
+ set_has_column();
+ column_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.column)
+}
+
+// optional bytes source = 5;
+inline bool StackFrame_Data::has_source() const {
+ return SourceOrRef_case() == kSource;
+}
+inline void StackFrame_Data::set_has_source() {
+ _oneof_case_[0] = kSource;
+}
+inline void StackFrame_Data::clear_source() {
+ if (has_source()) {
+ delete SourceOrRef_.source_;
+ clear_has_SourceOrRef();
+ }
+}
+inline const ::std::string& StackFrame_Data::source() const {
+ if (has_source()) {
+ return *SourceOrRef_.source_;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void StackFrame_Data::set_source(const ::std::string& value) {
+ if (!has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ SourceOrRef_.source_ = new ::std::string;
+ }
+ SourceOrRef_.source_->assign(value);
+}
+inline void StackFrame_Data::set_source(const char* value) {
+ if (!has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ SourceOrRef_.source_ = new ::std::string;
+ }
+ SourceOrRef_.source_->assign(value);
+}
+inline void StackFrame_Data::set_source(const void* value, size_t size) {
+ if (!has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ SourceOrRef_.source_ = new ::std::string;
+ }
+ SourceOrRef_.source_->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* StackFrame_Data::mutable_source() {
+ if (!has_source()) {
+ clear_SourceOrRef();
+ set_has_source();
+ SourceOrRef_.source_ = new ::std::string;
+ }
+ return SourceOrRef_.source_;
+}
+inline ::std::string* StackFrame_Data::release_source() {
+ if (has_source()) {
+ clear_has_SourceOrRef();
+ ::std::string* temp = SourceOrRef_.source_;
+ SourceOrRef_.source_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void StackFrame_Data::set_allocated_source(::std::string* source) {
+ clear_SourceOrRef();
+ if (source) {
+ set_has_source();
+ SourceOrRef_.source_ = source;
+ }
+}
+
+// optional uint64 sourceRef = 6;
+inline bool StackFrame_Data::has_sourceref() const {
+ return SourceOrRef_case() == kSourceRef;
+}
+inline void StackFrame_Data::set_has_sourceref() {
+ _oneof_case_[0] = kSourceRef;
+}
+inline void StackFrame_Data::clear_sourceref() {
+ if (has_sourceref()) {
+ SourceOrRef_.sourceref_ = GOOGLE_ULONGLONG(0);
+ clear_has_SourceOrRef();
+ }
+}
+inline ::google::protobuf::uint64 StackFrame_Data::sourceref() const {
+ if (has_sourceref()) {
+ return SourceOrRef_.sourceref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void StackFrame_Data::set_sourceref(::google::protobuf::uint64 value) {
+ if (!has_sourceref()) {
+ clear_SourceOrRef();
+ set_has_sourceref();
+ }
+ SourceOrRef_.sourceref_ = value;
+}
+
+// optional bytes functionDisplayName = 7;
+inline bool StackFrame_Data::has_functiondisplayname() const {
+ return FunctionDisplayNameOrRef_case() == kFunctionDisplayName;
+}
+inline void StackFrame_Data::set_has_functiondisplayname() {
+ _oneof_case_[1] = kFunctionDisplayName;
+}
+inline void StackFrame_Data::clear_functiondisplayname() {
+ if (has_functiondisplayname()) {
+ delete FunctionDisplayNameOrRef_.functiondisplayname_;
+ clear_has_FunctionDisplayNameOrRef();
+ }
+}
+inline const ::std::string& StackFrame_Data::functiondisplayname() const {
+ if (has_functiondisplayname()) {
+ return *FunctionDisplayNameOrRef_.functiondisplayname_;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void StackFrame_Data::set_functiondisplayname(const ::std::string& value) {
+ if (!has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ FunctionDisplayNameOrRef_.functiondisplayname_ = new ::std::string;
+ }
+ FunctionDisplayNameOrRef_.functiondisplayname_->assign(value);
+}
+inline void StackFrame_Data::set_functiondisplayname(const char* value) {
+ if (!has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ FunctionDisplayNameOrRef_.functiondisplayname_ = new ::std::string;
+ }
+ FunctionDisplayNameOrRef_.functiondisplayname_->assign(value);
+}
+inline void StackFrame_Data::set_functiondisplayname(const void* value, size_t size) {
+ if (!has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ FunctionDisplayNameOrRef_.functiondisplayname_ = new ::std::string;
+ }
+ FunctionDisplayNameOrRef_.functiondisplayname_->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* StackFrame_Data::mutable_functiondisplayname() {
+ if (!has_functiondisplayname()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplayname();
+ FunctionDisplayNameOrRef_.functiondisplayname_ = new ::std::string;
+ }
+ return FunctionDisplayNameOrRef_.functiondisplayname_;
+}
+inline ::std::string* StackFrame_Data::release_functiondisplayname() {
+ if (has_functiondisplayname()) {
+ clear_has_FunctionDisplayNameOrRef();
+ ::std::string* temp = FunctionDisplayNameOrRef_.functiondisplayname_;
+ FunctionDisplayNameOrRef_.functiondisplayname_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void StackFrame_Data::set_allocated_functiondisplayname(::std::string* functiondisplayname) {
+ clear_FunctionDisplayNameOrRef();
+ if (functiondisplayname) {
+ set_has_functiondisplayname();
+ FunctionDisplayNameOrRef_.functiondisplayname_ = functiondisplayname;
+ }
+}
+
+// optional uint64 functionDisplayNameRef = 8;
+inline bool StackFrame_Data::has_functiondisplaynameref() const {
+ return FunctionDisplayNameOrRef_case() == kFunctionDisplayNameRef;
+}
+inline void StackFrame_Data::set_has_functiondisplaynameref() {
+ _oneof_case_[1] = kFunctionDisplayNameRef;
+}
+inline void StackFrame_Data::clear_functiondisplaynameref() {
+ if (has_functiondisplaynameref()) {
+ FunctionDisplayNameOrRef_.functiondisplaynameref_ = GOOGLE_ULONGLONG(0);
+ clear_has_FunctionDisplayNameOrRef();
+ }
+}
+inline ::google::protobuf::uint64 StackFrame_Data::functiondisplaynameref() const {
+ if (has_functiondisplaynameref()) {
+ return FunctionDisplayNameOrRef_.functiondisplaynameref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void StackFrame_Data::set_functiondisplaynameref(::google::protobuf::uint64 value) {
+ if (!has_functiondisplaynameref()) {
+ clear_FunctionDisplayNameOrRef();
+ set_has_functiondisplaynameref();
+ }
+ FunctionDisplayNameOrRef_.functiondisplaynameref_ = value;
+}
+
+// optional bool isSystem = 9;
+inline bool StackFrame_Data::has_issystem() const {
+ return (_has_bits_[0] & 0x00000100u) != 0;
+}
+inline void StackFrame_Data::set_has_issystem() {
+ _has_bits_[0] |= 0x00000100u;
+}
+inline void StackFrame_Data::clear_has_issystem() {
+ _has_bits_[0] &= ~0x00000100u;
+}
+inline void StackFrame_Data::clear_issystem() {
+ issystem_ = false;
+ clear_has_issystem();
+}
+inline bool StackFrame_Data::issystem() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.isSystem)
+ return issystem_;
+}
+inline void StackFrame_Data::set_issystem(bool value) {
+ set_has_issystem();
+ issystem_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.isSystem)
+}
+
+// optional bool isSelfHosted = 10;
+inline bool StackFrame_Data::has_isselfhosted() const {
+ return (_has_bits_[0] & 0x00000200u) != 0;
+}
+inline void StackFrame_Data::set_has_isselfhosted() {
+ _has_bits_[0] |= 0x00000200u;
+}
+inline void StackFrame_Data::clear_has_isselfhosted() {
+ _has_bits_[0] &= ~0x00000200u;
+}
+inline void StackFrame_Data::clear_isselfhosted() {
+ isselfhosted_ = false;
+ clear_has_isselfhosted();
+}
+inline bool StackFrame_Data::isselfhosted() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.StackFrame.Data.isSelfHosted)
+ return isselfhosted_;
+}
+inline void StackFrame_Data::set_isselfhosted(bool value) {
+ set_has_isselfhosted();
+ isselfhosted_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.StackFrame.Data.isSelfHosted)
+}
+
+inline bool StackFrame_Data::has_SourceOrRef() {
+ return SourceOrRef_case() != SOURCEORREF_NOT_SET;
+}
+inline void StackFrame_Data::clear_has_SourceOrRef() {
+ _oneof_case_[0] = SOURCEORREF_NOT_SET;
+}
+inline bool StackFrame_Data::has_FunctionDisplayNameOrRef() {
+ return FunctionDisplayNameOrRef_case() != FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+inline void StackFrame_Data::clear_has_FunctionDisplayNameOrRef() {
+ _oneof_case_[1] = FUNCTIONDISPLAYNAMEORREF_NOT_SET;
+}
+inline StackFrame_Data::SourceOrRefCase StackFrame_Data::SourceOrRef_case() const {
+ return StackFrame_Data::SourceOrRefCase(_oneof_case_[0]);
+}
+inline StackFrame_Data::FunctionDisplayNameOrRefCase StackFrame_Data::FunctionDisplayNameOrRef_case() const {
+ return StackFrame_Data::FunctionDisplayNameOrRefCase(_oneof_case_[1]);
+}
+// -------------------------------------------------------------------
+
+// StackFrame
+
+// optional .mozilla.devtools.protobuf.StackFrame.Data data = 1;
+inline bool StackFrame::has_data() const {
+ return StackFrameType_case() == kData;
+}
+inline void StackFrame::set_has_data() {
+ _oneof_case_[0] = kData;
+}
+inline void StackFrame::clear_data() {
+ if (has_data()) {
+ delete StackFrameType_.data_;
+ clear_has_StackFrameType();
+ }
+}
+inline const ::mozilla::devtools::protobuf::StackFrame_Data& StackFrame::data() const {
+ return has_data() ? *StackFrameType_.data_
+ : ::mozilla::devtools::protobuf::StackFrame_Data::default_instance();
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::mutable_data() {
+ if (!has_data()) {
+ clear_StackFrameType();
+ set_has_data();
+ StackFrameType_.data_ = new ::mozilla::devtools::protobuf::StackFrame_Data;
+ }
+ return StackFrameType_.data_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame_Data* StackFrame::release_data() {
+ if (has_data()) {
+ clear_has_StackFrameType();
+ ::mozilla::devtools::protobuf::StackFrame_Data* temp = StackFrameType_.data_;
+ StackFrameType_.data_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void StackFrame::set_allocated_data(::mozilla::devtools::protobuf::StackFrame_Data* data) {
+ clear_StackFrameType();
+ if (data) {
+ set_has_data();
+ StackFrameType_.data_ = data;
+ }
+}
+
+// optional uint64 ref = 2;
+inline bool StackFrame::has_ref() const {
+ return StackFrameType_case() == kRef;
+}
+inline void StackFrame::set_has_ref() {
+ _oneof_case_[0] = kRef;
+}
+inline void StackFrame::clear_ref() {
+ if (has_ref()) {
+ StackFrameType_.ref_ = GOOGLE_ULONGLONG(0);
+ clear_has_StackFrameType();
+ }
+}
+inline ::google::protobuf::uint64 StackFrame::ref() const {
+ if (has_ref()) {
+ return StackFrameType_.ref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void StackFrame::set_ref(::google::protobuf::uint64 value) {
+ if (!has_ref()) {
+ clear_StackFrameType();
+ set_has_ref();
+ }
+ StackFrameType_.ref_ = value;
+}
+
+inline bool StackFrame::has_StackFrameType() {
+ return StackFrameType_case() != STACKFRAMETYPE_NOT_SET;
+}
+inline void StackFrame::clear_has_StackFrameType() {
+ _oneof_case_[0] = STACKFRAMETYPE_NOT_SET;
+}
+inline StackFrame::StackFrameTypeCase StackFrame::StackFrameType_case() const {
+ return StackFrame::StackFrameTypeCase(_oneof_case_[0]);
+}
+// -------------------------------------------------------------------
+
+// Node
+
+// optional uint64 id = 1;
+inline bool Node::has_id() const {
+ return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Node::set_has_id() {
+ _has_bits_[0] |= 0x00000001u;
+}
+inline void Node::clear_has_id() {
+ _has_bits_[0] &= ~0x00000001u;
+}
+inline void Node::clear_id() {
+ id_ = GOOGLE_ULONGLONG(0);
+ clear_has_id();
+}
+inline ::google::protobuf::uint64 Node::id() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.id)
+ return id_;
+}
+inline void Node::set_id(::google::protobuf::uint64 value) {
+ set_has_id();
+ id_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.id)
+}
+
+// optional bytes typeName = 2;
+inline bool Node::has_typename_() const {
+ return TypeNameOrRef_case() == kTypeName;
+}
+inline void Node::set_has_typename_() {
+ _oneof_case_[0] = kTypeName;
+}
+inline void Node::clear_typename_() {
+ if (has_typename_()) {
+ delete TypeNameOrRef_.typename__;
+ clear_has_TypeNameOrRef();
+ }
+}
+inline const ::std::string& Node::typename_() const {
+ if (has_typename_()) {
+ return *TypeNameOrRef_.typename__;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::set_typename_(const ::std::string& value) {
+ if (!has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ TypeNameOrRef_.typename__ = new ::std::string;
+ }
+ TypeNameOrRef_.typename__->assign(value);
+}
+inline void Node::set_typename_(const char* value) {
+ if (!has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ TypeNameOrRef_.typename__ = new ::std::string;
+ }
+ TypeNameOrRef_.typename__->assign(value);
+}
+inline void Node::set_typename_(const void* value, size_t size) {
+ if (!has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ TypeNameOrRef_.typename__ = new ::std::string;
+ }
+ TypeNameOrRef_.typename__->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* Node::mutable_typename_() {
+ if (!has_typename_()) {
+ clear_TypeNameOrRef();
+ set_has_typename_();
+ TypeNameOrRef_.typename__ = new ::std::string;
+ }
+ return TypeNameOrRef_.typename__;
+}
+inline ::std::string* Node::release_typename_() {
+ if (has_typename_()) {
+ clear_has_TypeNameOrRef();
+ ::std::string* temp = TypeNameOrRef_.typename__;
+ TypeNameOrRef_.typename__ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void Node::set_allocated_typename_(::std::string* typename_) {
+ clear_TypeNameOrRef();
+ if (typename_) {
+ set_has_typename_();
+ TypeNameOrRef_.typename__ = typename_;
+ }
+}
+
+// optional uint64 typeNameRef = 3;
+inline bool Node::has_typenameref() const {
+ return TypeNameOrRef_case() == kTypeNameRef;
+}
+inline void Node::set_has_typenameref() {
+ _oneof_case_[0] = kTypeNameRef;
+}
+inline void Node::clear_typenameref() {
+ if (has_typenameref()) {
+ TypeNameOrRef_.typenameref_ = GOOGLE_ULONGLONG(0);
+ clear_has_TypeNameOrRef();
+ }
+}
+inline ::google::protobuf::uint64 Node::typenameref() const {
+ if (has_typenameref()) {
+ return TypeNameOrRef_.typenameref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void Node::set_typenameref(::google::protobuf::uint64 value) {
+ if (!has_typenameref()) {
+ clear_TypeNameOrRef();
+ set_has_typenameref();
+ }
+ TypeNameOrRef_.typenameref_ = value;
+}
+
+// optional uint64 size = 4;
+inline bool Node::has_size() const {
+ return (_has_bits_[0] & 0x00000008u) != 0;
+}
+inline void Node::set_has_size() {
+ _has_bits_[0] |= 0x00000008u;
+}
+inline void Node::clear_has_size() {
+ _has_bits_[0] &= ~0x00000008u;
+}
+inline void Node::clear_size() {
+ size_ = GOOGLE_ULONGLONG(0);
+ clear_has_size();
+}
+inline ::google::protobuf::uint64 Node::size() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.size)
+ return size_;
+}
+inline void Node::set_size(::google::protobuf::uint64 value) {
+ set_has_size();
+ size_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.size)
+}
+
+// repeated .mozilla.devtools.protobuf.Edge edges = 5;
+inline int Node::edges_size() const {
+ return edges_.size();
+}
+inline void Node::clear_edges() {
+ edges_.Clear();
+}
+inline const ::mozilla::devtools::protobuf::Edge& Node::edges(int index) const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.edges)
+ return edges_.Get(index);
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::mutable_edges(int index) {
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.edges)
+ return edges_.Mutable(index);
+}
+inline ::mozilla::devtools::protobuf::Edge* Node::add_edges() {
+ // @@protoc_insertion_point(field_add:mozilla.devtools.protobuf.Node.edges)
+ return edges_.Add();
+}
+inline const ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >&
+Node::edges() const {
+ // @@protoc_insertion_point(field_list:mozilla.devtools.protobuf.Node.edges)
+ return edges_;
+}
+inline ::google::protobuf::RepeatedPtrField< ::mozilla::devtools::protobuf::Edge >*
+Node::mutable_edges() {
+ // @@protoc_insertion_point(field_mutable_list:mozilla.devtools.protobuf.Node.edges)
+ return &edges_;
+}
+
+// optional .mozilla.devtools.protobuf.StackFrame allocationStack = 6;
+inline bool Node::has_allocationstack() const {
+ return (_has_bits_[0] & 0x00000020u) != 0;
+}
+inline void Node::set_has_allocationstack() {
+ _has_bits_[0] |= 0x00000020u;
+}
+inline void Node::clear_has_allocationstack() {
+ _has_bits_[0] &= ~0x00000020u;
+}
+inline void Node::clear_allocationstack() {
+ if (allocationstack_ != NULL) allocationstack_->::mozilla::devtools::protobuf::StackFrame::Clear();
+ clear_has_allocationstack();
+}
+inline const ::mozilla::devtools::protobuf::StackFrame& Node::allocationstack() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.allocationStack)
+ return allocationstack_ != NULL ? *allocationstack_ : *default_instance_->allocationstack_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::mutable_allocationstack() {
+ set_has_allocationstack();
+ if (allocationstack_ == NULL) allocationstack_ = new ::mozilla::devtools::protobuf::StackFrame;
+ // @@protoc_insertion_point(field_mutable:mozilla.devtools.protobuf.Node.allocationStack)
+ return allocationstack_;
+}
+inline ::mozilla::devtools::protobuf::StackFrame* Node::release_allocationstack() {
+ clear_has_allocationstack();
+ ::mozilla::devtools::protobuf::StackFrame* temp = allocationstack_;
+ allocationstack_ = NULL;
+ return temp;
+}
+inline void Node::set_allocated_allocationstack(::mozilla::devtools::protobuf::StackFrame* allocationstack) {
+ delete allocationstack_;
+ allocationstack_ = allocationstack;
+ if (allocationstack) {
+ set_has_allocationstack();
+ } else {
+ clear_has_allocationstack();
+ }
+ // @@protoc_insertion_point(field_set_allocated:mozilla.devtools.protobuf.Node.allocationStack)
+}
+
+// optional bytes jsObjectClassName = 7;
+inline bool Node::has_jsobjectclassname() const {
+ return JSObjectClassNameOrRef_case() == kJsObjectClassName;
+}
+inline void Node::set_has_jsobjectclassname() {
+ _oneof_case_[1] = kJsObjectClassName;
+}
+inline void Node::clear_jsobjectclassname() {
+ if (has_jsobjectclassname()) {
+ delete JSObjectClassNameOrRef_.jsobjectclassname_;
+ clear_has_JSObjectClassNameOrRef();
+ }
+}
+inline const ::std::string& Node::jsobjectclassname() const {
+ if (has_jsobjectclassname()) {
+ return *JSObjectClassNameOrRef_.jsobjectclassname_;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::set_jsobjectclassname(const ::std::string& value) {
+ if (!has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ JSObjectClassNameOrRef_.jsobjectclassname_ = new ::std::string;
+ }
+ JSObjectClassNameOrRef_.jsobjectclassname_->assign(value);
+}
+inline void Node::set_jsobjectclassname(const char* value) {
+ if (!has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ JSObjectClassNameOrRef_.jsobjectclassname_ = new ::std::string;
+ }
+ JSObjectClassNameOrRef_.jsobjectclassname_->assign(value);
+}
+inline void Node::set_jsobjectclassname(const void* value, size_t size) {
+ if (!has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ JSObjectClassNameOrRef_.jsobjectclassname_ = new ::std::string;
+ }
+ JSObjectClassNameOrRef_.jsobjectclassname_->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* Node::mutable_jsobjectclassname() {
+ if (!has_jsobjectclassname()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassname();
+ JSObjectClassNameOrRef_.jsobjectclassname_ = new ::std::string;
+ }
+ return JSObjectClassNameOrRef_.jsobjectclassname_;
+}
+inline ::std::string* Node::release_jsobjectclassname() {
+ if (has_jsobjectclassname()) {
+ clear_has_JSObjectClassNameOrRef();
+ ::std::string* temp = JSObjectClassNameOrRef_.jsobjectclassname_;
+ JSObjectClassNameOrRef_.jsobjectclassname_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void Node::set_allocated_jsobjectclassname(::std::string* jsobjectclassname) {
+ clear_JSObjectClassNameOrRef();
+ if (jsobjectclassname) {
+ set_has_jsobjectclassname();
+ JSObjectClassNameOrRef_.jsobjectclassname_ = jsobjectclassname;
+ }
+}
+
+// optional uint64 jsObjectClassNameRef = 8;
+inline bool Node::has_jsobjectclassnameref() const {
+ return JSObjectClassNameOrRef_case() == kJsObjectClassNameRef;
+}
+inline void Node::set_has_jsobjectclassnameref() {
+ _oneof_case_[1] = kJsObjectClassNameRef;
+}
+inline void Node::clear_jsobjectclassnameref() {
+ if (has_jsobjectclassnameref()) {
+ JSObjectClassNameOrRef_.jsobjectclassnameref_ = GOOGLE_ULONGLONG(0);
+ clear_has_JSObjectClassNameOrRef();
+ }
+}
+inline ::google::protobuf::uint64 Node::jsobjectclassnameref() const {
+ if (has_jsobjectclassnameref()) {
+ return JSObjectClassNameOrRef_.jsobjectclassnameref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void Node::set_jsobjectclassnameref(::google::protobuf::uint64 value) {
+ if (!has_jsobjectclassnameref()) {
+ clear_JSObjectClassNameOrRef();
+ set_has_jsobjectclassnameref();
+ }
+ JSObjectClassNameOrRef_.jsobjectclassnameref_ = value;
+}
+
+// optional uint32 coarseType = 9 [default = 0];
+inline bool Node::has_coarsetype() const {
+ return (_has_bits_[0] & 0x00000100u) != 0;
+}
+inline void Node::set_has_coarsetype() {
+ _has_bits_[0] |= 0x00000100u;
+}
+inline void Node::clear_has_coarsetype() {
+ _has_bits_[0] &= ~0x00000100u;
+}
+inline void Node::clear_coarsetype() {
+ coarsetype_ = 0u;
+ clear_has_coarsetype();
+}
+inline ::google::protobuf::uint32 Node::coarsetype() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Node.coarseType)
+ return coarsetype_;
+}
+inline void Node::set_coarsetype(::google::protobuf::uint32 value) {
+ set_has_coarsetype();
+ coarsetype_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Node.coarseType)
+}
+
+// optional bytes scriptFilename = 10;
+inline bool Node::has_scriptfilename() const {
+ return ScriptFilenameOrRef_case() == kScriptFilename;
+}
+inline void Node::set_has_scriptfilename() {
+ _oneof_case_[2] = kScriptFilename;
+}
+inline void Node::clear_scriptfilename() {
+ if (has_scriptfilename()) {
+ delete ScriptFilenameOrRef_.scriptfilename_;
+ clear_has_ScriptFilenameOrRef();
+ }
+}
+inline const ::std::string& Node::scriptfilename() const {
+ if (has_scriptfilename()) {
+ return *ScriptFilenameOrRef_.scriptfilename_;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Node::set_scriptfilename(const ::std::string& value) {
+ if (!has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+ }
+ ScriptFilenameOrRef_.scriptfilename_->assign(value);
+}
+inline void Node::set_scriptfilename(const char* value) {
+ if (!has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+ }
+ ScriptFilenameOrRef_.scriptfilename_->assign(value);
+}
+inline void Node::set_scriptfilename(const void* value, size_t size) {
+ if (!has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+ }
+ ScriptFilenameOrRef_.scriptfilename_->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* Node::mutable_scriptfilename() {
+ if (!has_scriptfilename()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilename();
+ ScriptFilenameOrRef_.scriptfilename_ = new ::std::string;
+ }
+ return ScriptFilenameOrRef_.scriptfilename_;
+}
+inline ::std::string* Node::release_scriptfilename() {
+ if (has_scriptfilename()) {
+ clear_has_ScriptFilenameOrRef();
+ ::std::string* temp = ScriptFilenameOrRef_.scriptfilename_;
+ ScriptFilenameOrRef_.scriptfilename_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void Node::set_allocated_scriptfilename(::std::string* scriptfilename) {
+ clear_ScriptFilenameOrRef();
+ if (scriptfilename) {
+ set_has_scriptfilename();
+ ScriptFilenameOrRef_.scriptfilename_ = scriptfilename;
+ }
+}
+
+// optional uint64 scriptFilenameRef = 11;
+inline bool Node::has_scriptfilenameref() const {
+ return ScriptFilenameOrRef_case() == kScriptFilenameRef;
+}
+inline void Node::set_has_scriptfilenameref() {
+ _oneof_case_[2] = kScriptFilenameRef;
+}
+inline void Node::clear_scriptfilenameref() {
+ if (has_scriptfilenameref()) {
+ ScriptFilenameOrRef_.scriptfilenameref_ = GOOGLE_ULONGLONG(0);
+ clear_has_ScriptFilenameOrRef();
+ }
+}
+inline ::google::protobuf::uint64 Node::scriptfilenameref() const {
+ if (has_scriptfilenameref()) {
+ return ScriptFilenameOrRef_.scriptfilenameref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void Node::set_scriptfilenameref(::google::protobuf::uint64 value) {
+ if (!has_scriptfilenameref()) {
+ clear_ScriptFilenameOrRef();
+ set_has_scriptfilenameref();
+ }
+ ScriptFilenameOrRef_.scriptfilenameref_ = value;
+}
+
+inline bool Node::has_TypeNameOrRef() {
+ return TypeNameOrRef_case() != TYPENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_TypeNameOrRef() {
+ _oneof_case_[0] = TYPENAMEORREF_NOT_SET;
+}
+inline bool Node::has_JSObjectClassNameOrRef() {
+ return JSObjectClassNameOrRef_case() != JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_JSObjectClassNameOrRef() {
+ _oneof_case_[1] = JSOBJECTCLASSNAMEORREF_NOT_SET;
+}
+inline bool Node::has_ScriptFilenameOrRef() {
+ return ScriptFilenameOrRef_case() != SCRIPTFILENAMEORREF_NOT_SET;
+}
+inline void Node::clear_has_ScriptFilenameOrRef() {
+ _oneof_case_[2] = SCRIPTFILENAMEORREF_NOT_SET;
+}
+inline Node::TypeNameOrRefCase Node::TypeNameOrRef_case() const {
+ return Node::TypeNameOrRefCase(_oneof_case_[0]);
+}
+inline Node::JSObjectClassNameOrRefCase Node::JSObjectClassNameOrRef_case() const {
+ return Node::JSObjectClassNameOrRefCase(_oneof_case_[1]);
+}
+inline Node::ScriptFilenameOrRefCase Node::ScriptFilenameOrRef_case() const {
+ return Node::ScriptFilenameOrRefCase(_oneof_case_[2]);
+}
+// -------------------------------------------------------------------
+
+// Edge
+
+// optional uint64 referent = 1;
+inline bool Edge::has_referent() const {
+ return (_has_bits_[0] & 0x00000001u) != 0;
+}
+inline void Edge::set_has_referent() {
+ _has_bits_[0] |= 0x00000001u;
+}
+inline void Edge::clear_has_referent() {
+ _has_bits_[0] &= ~0x00000001u;
+}
+inline void Edge::clear_referent() {
+ referent_ = GOOGLE_ULONGLONG(0);
+ clear_has_referent();
+}
+inline ::google::protobuf::uint64 Edge::referent() const {
+ // @@protoc_insertion_point(field_get:mozilla.devtools.protobuf.Edge.referent)
+ return referent_;
+}
+inline void Edge::set_referent(::google::protobuf::uint64 value) {
+ set_has_referent();
+ referent_ = value;
+ // @@protoc_insertion_point(field_set:mozilla.devtools.protobuf.Edge.referent)
+}
+
+// optional bytes name = 2;
+inline bool Edge::has_name() const {
+ return EdgeNameOrRef_case() == kName;
+}
+inline void Edge::set_has_name() {
+ _oneof_case_[0] = kName;
+}
+inline void Edge::clear_name() {
+ if (has_name()) {
+ delete EdgeNameOrRef_.name_;
+ clear_has_EdgeNameOrRef();
+ }
+}
+inline const ::std::string& Edge::name() const {
+ if (has_name()) {
+ return *EdgeNameOrRef_.name_;
+ }
+ return ::google::protobuf::internal::GetEmptyStringAlreadyInited();
+}
+inline void Edge::set_name(const ::std::string& value) {
+ if (!has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ EdgeNameOrRef_.name_ = new ::std::string;
+ }
+ EdgeNameOrRef_.name_->assign(value);
+}
+inline void Edge::set_name(const char* value) {
+ if (!has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ EdgeNameOrRef_.name_ = new ::std::string;
+ }
+ EdgeNameOrRef_.name_->assign(value);
+}
+inline void Edge::set_name(const void* value, size_t size) {
+ if (!has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ EdgeNameOrRef_.name_ = new ::std::string;
+ }
+ EdgeNameOrRef_.name_->assign(
+ reinterpret_cast<const char*>(value), size);
+}
+inline ::std::string* Edge::mutable_name() {
+ if (!has_name()) {
+ clear_EdgeNameOrRef();
+ set_has_name();
+ EdgeNameOrRef_.name_ = new ::std::string;
+ }
+ return EdgeNameOrRef_.name_;
+}
+inline ::std::string* Edge::release_name() {
+ if (has_name()) {
+ clear_has_EdgeNameOrRef();
+ ::std::string* temp = EdgeNameOrRef_.name_;
+ EdgeNameOrRef_.name_ = NULL;
+ return temp;
+ } else {
+ return NULL;
+ }
+}
+inline void Edge::set_allocated_name(::std::string* name) {
+ clear_EdgeNameOrRef();
+ if (name) {
+ set_has_name();
+ EdgeNameOrRef_.name_ = name;
+ }
+}
+
+// optional uint64 nameRef = 3;
+inline bool Edge::has_nameref() const {
+ return EdgeNameOrRef_case() == kNameRef;
+}
+inline void Edge::set_has_nameref() {
+ _oneof_case_[0] = kNameRef;
+}
+inline void Edge::clear_nameref() {
+ if (has_nameref()) {
+ EdgeNameOrRef_.nameref_ = GOOGLE_ULONGLONG(0);
+ clear_has_EdgeNameOrRef();
+ }
+}
+inline ::google::protobuf::uint64 Edge::nameref() const {
+ if (has_nameref()) {
+ return EdgeNameOrRef_.nameref_;
+ }
+ return GOOGLE_ULONGLONG(0);
+}
+inline void Edge::set_nameref(::google::protobuf::uint64 value) {
+ if (!has_nameref()) {
+ clear_EdgeNameOrRef();
+ set_has_nameref();
+ }
+ EdgeNameOrRef_.nameref_ = value;
+}
+
+inline bool Edge::has_EdgeNameOrRef() {
+ return EdgeNameOrRef_case() != EDGENAMEORREF_NOT_SET;
+}
+inline void Edge::clear_has_EdgeNameOrRef() {
+ _oneof_case_[0] = EDGENAMEORREF_NOT_SET;
+}
+inline Edge::EdgeNameOrRefCase Edge::EdgeNameOrRef_case() const {
+ return Edge::EdgeNameOrRefCase(_oneof_case_[0]);
+}
+
+// @@protoc_insertion_point(namespace_scope)
+
+} // namespace protobuf
+} // namespace devtools
+} // namespace mozilla
+
+#ifndef SWIG
+namespace google {
+namespace protobuf {
+
+
+} // namespace google
+} // namespace protobuf
+#endif // SWIG
+
+// @@protoc_insertion_point(global_scope)
+
+#endif // PROTOBUF_CoreDump_2eproto__INCLUDED
diff --git a/devtools/shared/heapsnapshot/CoreDump.proto b/devtools/shared/heapsnapshot/CoreDump.proto
new file mode 100644
index 000000000..24a223e11
--- /dev/null
+++ b/devtools/shared/heapsnapshot/CoreDump.proto
@@ -0,0 +1,143 @@
+/* -*- Mode: protobuf; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// # Core Dumps
+//
+// A core dump is a serialized snapshot of the heap graph. We serialize the heap
+// as a series of protobuf messages with each message prefixed by its Varint32
+// byte size so we can delimit individual protobuf messages (protobuf parsers
+// cannot determine where a message ends on their own).
+//
+// The first protobuf message is an instance of the `Metadata` message. All
+// subsequent messages will be instances of the `Node` message. The first of
+// these `Node` messages is the root node of the serialized heap graph. Here is
+// a diagram of our core dump format:
+//
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of following `Metadata` message. |
+// +-----------------------------------------------------------------------+
+// | message: The core dump `Metadata` message. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: The first `Node` message. This is the root node. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: A `Node` message. |
+// +-----------------------------------------------------------------------+
+// | Varint32: The size of the following `Node` message. |
+// +-----------------------------------------------------------------------+
+// | message: A `Node` message. |
+// +-----------------------------------------------------------------------+
+// | . |
+// | . |
+// | . |
+// +-----------------------------------------------------------------------+
+//
+// Core dumps should always be written with a
+// `google::protobuf::io::GzipOutputStream` and read from a
+// `google::protobuf::io::GzipInputStream`.
+//
+// Note that all strings are de-duplicated. The first time the N^th unique
+// string is encountered, the full string is serialized. Subsequent times that
+// same string is encountered, it is referenced by N. This de-duplication
+// happens across string properties, not on a per-property basis. For example,
+// if the same K^th unique string is first used as an Edge::EdgeNameOrRef and
+// then as a StackFrame::Data::FunctionDisplayNameOrRef, the first will be the
+// actual string as the functionDisplayName oneof property, and the second will
+// be a reference to the first as the edgeNameRef oneof property whose value is
+// K.
+//
+// We would ordinarily abstract these de-duplicated strings with messages of
+// their own, but unfortunately, the protobuf compiler does not have a way to
+// inline a messsage within another message and the child message must be
+// referenced by pointer. This leads to extra mallocs that we wish to avoid.
+
+
+package mozilla.devtools.protobuf;
+
+// A collection of metadata about this core dump.
+message Metadata {
+ // Number of microseconds since midnight (00:00:00) 1 January 1970 UTC.
+ optional uint64 timeStamp = 1;
+}
+
+// A serialized version of `JS::ubi::StackFrame`. Older parent frame tails are
+// de-duplicated to cut down on [de]serialization and size costs.
+message StackFrame {
+ oneof StackFrameType {
+ // This is the first time this stack frame has been serialized, and so
+ // here is all of its data.
+ Data data = 1;
+ // A reference to a stack frame that has already been serialized and has
+ // the given number as its id.
+ uint64 ref = 2;
+ }
+
+ message Data {
+ optional uint64 id = 1;
+ optional StackFrame parent = 2;
+ optional uint32 line = 3;
+ optional uint32 column = 4;
+
+ // De-duplicated two-byte string.
+ oneof SourceOrRef {
+ bytes source = 5;
+ uint64 sourceRef = 6;
+ }
+
+ // De-duplicated two-byte string.
+ oneof FunctionDisplayNameOrRef {
+ bytes functionDisplayName = 7;
+ uint64 functionDisplayNameRef = 8;
+ }
+
+ optional bool isSystem = 9;
+ optional bool isSelfHosted = 10;
+ }
+}
+
+// A serialized version of `JS::ubi::Node` and its outgoing edges.
+message Node {
+ optional uint64 id = 1;
+
+ // De-duplicated two-byte string.
+ oneof TypeNameOrRef {
+ bytes typeName = 2;
+ uint64 typeNameRef = 3;
+ }
+
+ optional uint64 size = 4;
+ repeated Edge edges = 5;
+ optional StackFrame allocationStack = 6;
+
+ // De-duplicated one-byte string.
+ oneof JSObjectClassNameOrRef {
+ bytes jsObjectClassName = 7;
+ uint64 jsObjectClassNameRef = 8;
+ }
+
+ // JS::ubi::CoarseType. Defaults to Other.
+ optional uint32 coarseType = 9 [default = 0];
+
+ // De-duplicated one-byte string.
+ oneof ScriptFilenameOrRef {
+ bytes scriptFilename = 10;
+ uint64 scriptFilenameRef = 11;
+ }
+}
+
+// A serialized edge from the heap graph.
+message Edge {
+ optional uint64 referent = 1;
+
+ // De-duplicated two-byte string.
+ oneof EdgeNameOrRef {
+ bytes name = 2;
+ uint64 nameRef = 3;
+ }
+}
diff --git a/devtools/shared/heapsnapshot/DeserializedNode.cpp b/devtools/shared/heapsnapshot/DeserializedNode.cpp
new file mode 100644
index 000000000..fac4cccb9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -0,0 +1,150 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "nsCRTGlue.h"
+
+namespace mozilla {
+namespace devtools {
+
+DeserializedEdge::DeserializedEdge(DeserializedEdge&& rhs)
+{
+ referent = rhs.referent;
+ name = rhs.name;
+}
+
+DeserializedEdge& DeserializedEdge::operator=(DeserializedEdge&& rhs)
+{
+ MOZ_ASSERT(&rhs != this);
+ this->~DeserializedEdge();
+ new(this) DeserializedEdge(Move(rhs));
+ return *this;
+}
+
+JS::ubi::Node
+DeserializedNode::getEdgeReferent(const DeserializedEdge& edge)
+{
+ auto ptr = owner->nodes.lookup(edge.referent);
+ MOZ_ASSERT(ptr);
+
+ // `HashSets` only provide const access to their values, because mutating a
+ // value might change its hash, rendering it unfindable in the set.
+ // Unfortunately, the `ubi::Node` constructor requires a non-const pointer to
+ // its referent. However, the only aspect of a `DeserializedNode` we hash on
+ // is its id, which can't be changed via `ubi::Node`, so this cast can't cause
+ // the trouble `HashSet` is concerned a non-const reference would cause.
+ return JS::ubi::Node(const_cast<DeserializedNode*>(&*ptr));
+}
+
+JS::ubi::StackFrame
+DeserializedStackFrame::getParentStackFrame() const
+{
+ MOZ_ASSERT(parent.isSome());
+ auto ptr = owner->frames.lookup(parent.ref());
+ MOZ_ASSERT(ptr);
+ // See above comment in DeserializedNode::getEdgeReferent about why this
+ // const_cast is needed and safe.
+ return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+namespace JS {
+namespace ubi {
+
+using mozilla::devtools::DeserializedEdge;
+
+const char16_t Concrete<DeserializedNode>::concreteTypeName[] =
+ u"mozilla::devtools::DeserializedNode";
+
+const char16_t*
+Concrete<DeserializedNode>::typeName() const
+{
+ return get().typeName;
+}
+
+Node::Size
+Concrete<DeserializedNode>::size(mozilla::MallocSizeOf mallocSizeof) const
+{
+ return get().size;
+}
+
+class DeserializedEdgeRange : public EdgeRange
+{
+ DeserializedNode* node;
+ Edge currentEdge;
+ size_t i;
+
+ void settle() {
+ if (i >= node->edges.length()) {
+ front_ = nullptr;
+ return;
+ }
+
+ auto& edge = node->edges[i];
+ auto referent = node->getEdgeReferent(edge);
+ currentEdge = mozilla::Move(Edge(edge.name ? NS_strdup(edge.name) : nullptr,
+ referent));
+ front_ = &currentEdge;
+ }
+
+public:
+ explicit DeserializedEdgeRange(DeserializedNode& node)
+ : node(&node)
+ , i(0)
+ {
+ settle();
+ }
+
+ void popFront() override
+ {
+ i++;
+ settle();
+ }
+};
+
+StackFrame
+Concrete<DeserializedNode>::allocationStack() const
+{
+ MOZ_ASSERT(hasAllocationStack());
+ auto id = get().allocationStack.ref();
+ auto ptr = get().owner->frames.lookup(id);
+ MOZ_ASSERT(ptr);
+ // See above comment in DeserializedNode::getEdgeReferent about why this
+ // const_cast is needed and safe.
+ return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
+}
+
+
+js::UniquePtr<EdgeRange>
+Concrete<DeserializedNode>::edges(JSContext* cx, bool) const
+{
+ js::UniquePtr<DeserializedEdgeRange> range(js_new<DeserializedEdgeRange>(get()));
+
+ if (!range)
+ return nullptr;
+
+ return js::UniquePtr<EdgeRange>(range.release());
+}
+
+StackFrame
+ConcreteStackFrame<DeserializedStackFrame>::parent() const
+{
+ return get().parent.isNothing() ? StackFrame() : get().getParentStackFrame();
+}
+
+bool
+ConcreteStackFrame<DeserializedStackFrame>::constructSavedFrameStack(
+ JSContext* cx,
+ MutableHandleObject outSavedFrameStack) const
+{
+ StackFrame f(&get());
+ return ConstructSavedFrameStackSlow(cx, f, outSavedFrameStack);
+}
+
+} // namespace ubi
+} // namespace JS
diff --git a/devtools/shared/heapsnapshot/DeserializedNode.h b/devtools/shared/heapsnapshot/DeserializedNode.h
new file mode 100644
index 000000000..60d1fb408
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_DeserializedNode__
+#define mozilla_devtools_DeserializedNode__
+
+#include "js/UbiNode.h"
+#include "js/UniquePtr.h"
+#include "mozilla/devtools/CoreDump.pb.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Move.h"
+#include "mozilla/Vector.h"
+
+// `Deserialized{Node,Edge}` translate protobuf messages from our core dump
+// format into structures we can rely upon for implementing `JS::ubi::Node`
+// specializations on top of. All of the properties of the protobuf messages are
+// optional for future compatibility, and this is the layer where we validate
+// that the properties that do actually exist in any given message fulfill our
+// semantic requirements.
+//
+// Both `DeserializedNode` and `DeserializedEdge` are always owned by a
+// `HeapSnapshot` instance, and their lifetimes must not extend after that of
+// their owning `HeapSnapshot`.
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshot;
+
+using NodeId = uint64_t;
+using StackFrameId = uint64_t;
+
+// A `DeserializedEdge` represents an edge in the heap graph pointing to the
+// node with id equal to `DeserializedEdge::referent` that we deserialized from
+// a core dump.
+struct DeserializedEdge {
+ NodeId referent;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char16_t* name;
+
+ explicit DeserializedEdge(NodeId referent, const char16_t* edgeName = nullptr)
+ : referent(referent)
+ , name(edgeName)
+ { }
+ DeserializedEdge(DeserializedEdge&& rhs);
+ DeserializedEdge& operator=(DeserializedEdge&& rhs);
+
+private:
+ DeserializedEdge(const DeserializedEdge&) = delete;
+ DeserializedEdge& operator=(const DeserializedEdge&) = delete;
+};
+
+// A `DeserializedNode` is a node in the heap graph that we deserialized from a
+// core dump.
+struct DeserializedNode {
+ using EdgeVector = Vector<DeserializedEdge>;
+ using UniqueStringPtr = UniquePtr<char16_t[]>;
+
+ NodeId id;
+ JS::ubi::CoarseType coarseType;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char16_t* typeName;
+ uint64_t size;
+ EdgeVector edges;
+ Maybe<StackFrameId> allocationStack;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char* jsObjectClassName;
+ // A borrowed reference to a string owned by this node's owning HeapSnapshot.
+ const char* scriptFilename;
+ // A weak pointer to this node's owning `HeapSnapshot`. Safe without
+ // AddRef'ing because this node's lifetime is equal to that of its owner.
+ HeapSnapshot* owner;
+
+ DeserializedNode(NodeId id,
+ JS::ubi::CoarseType coarseType,
+ const char16_t* typeName,
+ uint64_t size,
+ EdgeVector&& edges,
+ Maybe<StackFrameId> allocationStack,
+ const char* className,
+ const char* filename,
+ HeapSnapshot& owner)
+ : id(id)
+ , coarseType(coarseType)
+ , typeName(typeName)
+ , size(size)
+ , edges(Move(edges))
+ , allocationStack(allocationStack)
+ , jsObjectClassName(className)
+ , scriptFilename(filename)
+ , owner(&owner)
+ { }
+ virtual ~DeserializedNode() { }
+
+ DeserializedNode(DeserializedNode&& rhs)
+ : id(rhs.id)
+ , coarseType(rhs.coarseType)
+ , typeName(rhs.typeName)
+ , size(rhs.size)
+ , edges(Move(rhs.edges))
+ , allocationStack(rhs.allocationStack)
+ , jsObjectClassName(rhs.jsObjectClassName)
+ , scriptFilename(rhs.scriptFilename)
+ , owner(rhs.owner)
+ { }
+
+ DeserializedNode& operator=(DeserializedNode&& rhs)
+ {
+ MOZ_ASSERT(&rhs != this);
+ this->~DeserializedNode();
+ new(this) DeserializedNode(Move(rhs));
+ return *this;
+ }
+
+ // Get a borrowed reference to the given edge's referent. This method is
+ // virtual to provide a hook for gmock and gtest.
+ virtual JS::ubi::Node getEdgeReferent(const DeserializedEdge& edge);
+
+ struct HashPolicy;
+
+protected:
+ // This is only for use with `MockDeserializedNode` in testing.
+ DeserializedNode(NodeId id, const char16_t* typeName, uint64_t size)
+ : id(id)
+ , coarseType(JS::ubi::CoarseType::Other)
+ , typeName(typeName)
+ , size(size)
+ , edges()
+ , allocationStack(Nothing())
+ , jsObjectClassName(nullptr)
+ , scriptFilename(nullptr)
+ , owner(nullptr)
+ { }
+
+private:
+ DeserializedNode(const DeserializedNode&) = delete;
+ DeserializedNode& operator=(const DeserializedNode&) = delete;
+};
+
+static inline js::HashNumber
+hashIdDerivedFromPtr(uint64_t id)
+{
+ // NodeIds and StackFrameIds are always 64 bits, but they are derived from
+ // the original referents' addresses, which could have been either 32 or 64
+ // bits long. As such, NodeId and StackFrameId have little entropy in their
+ // bottom three bits, and may or may not have entropy in their upper 32
+ // bits. This hash should manage both cases well.
+ id >>= 3;
+ return js::HashNumber((id >> 32) ^ id);
+}
+
+struct DeserializedNode::HashPolicy
+{
+ using Lookup = NodeId;
+
+ static js::HashNumber hash(const Lookup& lookup) {
+ return hashIdDerivedFromPtr(lookup);
+ }
+
+ static bool match(const DeserializedNode& existing, const Lookup& lookup) {
+ return existing.id == lookup;
+ }
+};
+
+// A `DeserializedStackFrame` is a stack frame referred to by a thing in the
+// heap graph that we deserialized from a core dump.
+struct DeserializedStackFrame {
+ StackFrameId id;
+ Maybe<StackFrameId> parent;
+ uint32_t line;
+ uint32_t column;
+ // Borrowed references to strings owned by this DeserializedStackFrame's
+ // owning HeapSnapshot.
+ const char16_t* source;
+ const char16_t* functionDisplayName;
+ bool isSystem;
+ bool isSelfHosted;
+ // A weak pointer to this frame's owning `HeapSnapshot`. Safe without
+ // AddRef'ing because this frame's lifetime is equal to that of its owner.
+ HeapSnapshot* owner;
+
+ explicit DeserializedStackFrame(StackFrameId id,
+ const Maybe<StackFrameId>& parent,
+ uint32_t line,
+ uint32_t column,
+ const char16_t* source,
+ const char16_t* functionDisplayName,
+ bool isSystem,
+ bool isSelfHosted,
+ HeapSnapshot& owner)
+ : id(id)
+ , parent(parent)
+ , line(line)
+ , column(column)
+ , source(source)
+ , functionDisplayName(functionDisplayName)
+ , isSystem(isSystem)
+ , isSelfHosted(isSelfHosted)
+ , owner(&owner)
+ {
+ MOZ_ASSERT(source);
+ }
+
+ JS::ubi::StackFrame getParentStackFrame() const;
+
+ struct HashPolicy;
+
+protected:
+ // This is exposed only for MockDeserializedStackFrame in the gtests.
+ explicit DeserializedStackFrame()
+ : id(0)
+ , parent(Nothing())
+ , line(0)
+ , column(0)
+ , source(nullptr)
+ , functionDisplayName(nullptr)
+ , isSystem(false)
+ , isSelfHosted(false)
+ , owner(nullptr)
+ { };
+};
+
+struct DeserializedStackFrame::HashPolicy {
+ using Lookup = StackFrameId;
+
+ static js::HashNumber hash(const Lookup& lookup) {
+ return hashIdDerivedFromPtr(lookup);
+ }
+
+ static bool match(const DeserializedStackFrame& existing, const Lookup& lookup) {
+ return existing.id == lookup;
+ }
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+namespace JS {
+namespace ubi {
+
+using mozilla::devtools::DeserializedNode;
+using mozilla::devtools::DeserializedStackFrame;
+
+template<>
+class Concrete<DeserializedNode> : public Base
+{
+protected:
+ explicit Concrete(DeserializedNode* ptr) : Base(ptr) { }
+ DeserializedNode& get() const {
+ return *static_cast<DeserializedNode*>(ptr);
+ }
+
+public:
+ static void construct(void* storage, DeserializedNode* ptr) {
+ new (storage) Concrete(ptr);
+ }
+
+ CoarseType coarseType() const final { return get().coarseType; }
+ Id identifier() const override { return get().id; }
+ bool isLive() const override { return false; }
+ const char16_t* typeName() const override;
+ Node::Size size(mozilla::MallocSizeOf mallocSizeof) const override;
+ const char* jsObjectClassName() const override { return get().jsObjectClassName; }
+ const char* scriptFilename() const final { return get().scriptFilename; }
+
+ bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
+ StackFrame allocationStack() const override;
+
+ // We ignore the `bool wantNames` parameter because we can't control whether
+ // the core dump was serialized with edge names or not.
+ js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
+
+ static const char16_t concreteTypeName[];
+};
+
+template<>
+class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame
+{
+protected:
+ explicit ConcreteStackFrame(DeserializedStackFrame* ptr)
+ : BaseStackFrame(ptr)
+ { }
+
+ DeserializedStackFrame& get() const {
+ return *static_cast<DeserializedStackFrame*>(ptr);
+ }
+
+public:
+ static void construct(void* storage, DeserializedStackFrame* ptr) {
+ new (storage) ConcreteStackFrame(ptr);
+ }
+
+ uint64_t identifier() const override { return get().id; }
+ uint32_t line() const override { return get().line; }
+ uint32_t column() const override { return get().column; }
+ bool isSystem() const override { return get().isSystem; }
+ bool isSelfHosted(JSContext* cx) const override { return get().isSelfHosted; }
+ void trace(JSTracer* trc) override { }
+ AtomOrTwoByteChars source() const override {
+ return AtomOrTwoByteChars(get().source);
+ }
+ AtomOrTwoByteChars functionDisplayName() const override {
+ return AtomOrTwoByteChars(get().functionDisplayName);
+ }
+
+ StackFrame parent() const override;
+ bool constructSavedFrameStack(JSContext* cx,
+ MutableHandleObject outSavedFrameStack)
+ const override;
+};
+
+} // namespace ubi
+} // namespace JS
+
+#endif // mozilla_devtools_DeserializedNode__
diff --git a/devtools/shared/heapsnapshot/DominatorTree.cpp b/devtools/shared/heapsnapshot/DominatorTree.cpp
new file mode 100644
index 000000000..e53c196cf
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTree.cpp
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/DominatorTree.h"
+#include "mozilla/dom/DominatorTreeBinding.h"
+
+namespace mozilla {
+namespace devtools {
+
+dom::Nullable<uint64_t>
+DominatorTree::GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv)
+{
+ JS::ubi::Node::Id id(aNodeId);
+ auto node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing())
+ return dom::Nullable<uint64_t>();
+
+ auto mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ JS::ubi::Node::Size size = 0;
+ if (!mDominatorTree.getRetainedSize(*node, mallocSizeOf, size)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return dom::Nullable<uint64_t>();
+ }
+
+ MOZ_ASSERT(size != 0,
+ "The node should not have been unknown since we got it from the heap snapshot.");
+ return dom::Nullable<uint64_t>(size);
+}
+
+struct NodeAndRetainedSize
+{
+ JS::ubi::Node mNode;
+ JS::ubi::Node::Size mSize;
+
+ NodeAndRetainedSize(const JS::ubi::Node& aNode, JS::ubi::Node::Size aSize)
+ : mNode(aNode)
+ , mSize(aSize)
+ { }
+
+ struct Comparator
+ {
+ static bool
+ Equals(const NodeAndRetainedSize& aLhs, const NodeAndRetainedSize& aRhs)
+ {
+ return aLhs.mSize == aRhs.mSize;
+ }
+
+ static bool
+ LessThan(const NodeAndRetainedSize& aLhs, const NodeAndRetainedSize& aRhs)
+ {
+ // Use > because we want to sort from greatest to least retained size.
+ return aLhs.mSize > aRhs.mSize;
+ }
+ };
+};
+
+void
+DominatorTree::GetImmediatelyDominated(uint64_t aNodeId,
+ dom::Nullable<nsTArray<uint64_t>>& aOutResult,
+ ErrorResult& aRv)
+{
+ MOZ_ASSERT(aOutResult.IsNull());
+
+ JS::ubi::Node::Id id(aNodeId);
+ Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing())
+ return;
+
+ // Get all immediately dominated nodes and their retained sizes.
+ MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ Maybe<JS::ubi::DominatorTree::DominatedSetRange> range = mDominatorTree.getDominatedSet(*node);
+ MOZ_ASSERT(range.isSome(), "The node should be known, since we got it from the heap snapshot.");
+ size_t length = range->length();
+ nsTArray<NodeAndRetainedSize> dominatedNodes(length);
+ for (const JS::ubi::Node& dominatedNode : *range) {
+ JS::ubi::Node::Size retainedSize = 0;
+ if (NS_WARN_IF(!mDominatorTree.getRetainedSize(dominatedNode, mallocSizeOf, retainedSize))) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ MOZ_ASSERT(retainedSize != 0,
+ "retainedSize should not be zero since we know the node is in the dominator tree.");
+
+ dominatedNodes.AppendElement(NodeAndRetainedSize(dominatedNode, retainedSize));
+ }
+
+ // Sort them by retained size.
+ NodeAndRetainedSize::Comparator comparator;
+ dominatedNodes.Sort(comparator);
+
+ // Fill the result with the nodes' ids.
+ JS::ubi::Node root = mDominatorTree.root();
+ aOutResult.SetValue(nsTArray<uint64_t>(length));
+ for (const NodeAndRetainedSize& entry : dominatedNodes) {
+ // The root dominates itself, but we don't want to expose that to JS.
+ if (entry.mNode == root)
+ continue;
+
+ aOutResult.Value().AppendElement(entry.mNode.identifier());
+ }
+}
+
+dom::Nullable<uint64_t>
+DominatorTree::GetImmediateDominator(uint64_t aNodeId) const
+{
+ JS::ubi::Node::Id id(aNodeId);
+ Maybe<JS::ubi::Node> node = mHeapSnapshot->getNodeById(id);
+ if (node.isNothing())
+ return dom::Nullable<uint64_t>();
+
+ JS::ubi::Node dominator = mDominatorTree.getImmediateDominator(*node);
+ if (!dominator || dominator == *node)
+ return dom::Nullable<uint64_t>();
+
+ return dom::Nullable<uint64_t>(dominator.identifier());
+}
+
+
+/*** Cycle Collection Boilerplate *****************************************************************/
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(DominatorTree, mParent, mHeapSnapshot)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(DominatorTree)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(DominatorTree)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DominatorTree)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* virtual */ JSObject*
+DominatorTree::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+ return dom::DominatorTreeBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/DominatorTree.h b/devtools/shared/heapsnapshot/DominatorTree.h
new file mode 100644
index 000000000..f785d4916
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTree.h
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_DominatorTree__
+#define mozilla_devtools_DominatorTree__
+
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/RefCounted.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace devtools {
+
+class DominatorTree final : public nsISupports
+ , public nsWrapperCache
+{
+protected:
+ nsCOMPtr<nsISupports> mParent;
+
+ virtual ~DominatorTree() { }
+
+private:
+ JS::ubi::DominatorTree mDominatorTree;
+ RefPtr<HeapSnapshot> mHeapSnapshot;
+
+public:
+ explicit DominatorTree(JS::ubi::DominatorTree&& aDominatorTree, HeapSnapshot* aHeapSnapshot,
+ nsISupports* aParent)
+ : mParent(aParent)
+ , mDominatorTree(Move(aDominatorTree))
+ , mHeapSnapshot(aHeapSnapshot)
+ {
+ MOZ_ASSERT(aParent);
+ MOZ_ASSERT(aHeapSnapshot);
+ };
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS;
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(DominatorTree);
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ // readonly attribute NodeId root
+ uint64_t Root() const { return mDominatorTree.root().identifier(); }
+
+ // [Throws] NodeSize getRetainedSize(NodeId node)
+ dom::Nullable<uint64_t> GetRetainedSize(uint64_t aNodeId, ErrorResult& aRv);
+
+ // [Throws] sequence<NodeId>? getImmediatelyDominated(NodeId node);
+ void GetImmediatelyDominated(uint64_t aNodeId, dom::Nullable<nsTArray<uint64_t>>& aOutDominated,
+ ErrorResult& aRv);
+
+ // NodeId? getImmediateDominator(NodeId node);
+ dom::Nullable<uint64_t> GetImmediateDominator(uint64_t aNodeId) const;
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_DominatorTree__
diff --git a/devtools/shared/heapsnapshot/DominatorTreeNode.js b/devtools/shared/heapsnapshot/DominatorTreeNode.js
new file mode 100644
index 000000000..13a847fd0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/DominatorTreeNode.js
@@ -0,0 +1,336 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { immutableUpdate } = require("resource://devtools/shared/ThreadSafeDevToolsUtils.js");
+const { Visitor, walk } = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+const { deduplicatePaths } = require("resource://devtools/shared/heapsnapshot/shortest-paths");
+
+const DEFAULT_MAX_DEPTH = 4;
+const DEFAULT_MAX_SIBLINGS = 15;
+const DEFAULT_MAX_NUM_PATHS = 5;
+
+/**
+ * A single node in a dominator tree.
+ *
+ * @param {NodeId} nodeId
+ * @param {NodeSize} retainedSize
+ */
+function DominatorTreeNode(nodeId, label, shallowSize, retainedSize) {
+ // The id of this node.
+ this.nodeId = nodeId;
+
+ // The label structure generated by describing the given node.
+ this.label = label;
+
+ // The shallow size of this node.
+ this.shallowSize = shallowSize;
+
+ // The retained size of this node.
+ this.retainedSize = retainedSize;
+
+ // The id of this node's parent or undefined if this node is the root.
+ this.parentId = undefined;
+
+ // An array of immediately dominated child `DominatorTreeNode`s, or undefined.
+ this.children = undefined;
+
+ // An object of the form returned by `deduplicatePaths`, encoding the set of
+ // the N shortest retaining paths for this node as a graph.
+ this.shortestPaths = undefined;
+
+ // True iff the `children` property does not contain every immediately
+ // dominated node.
+ //
+ // * If children is an array and this property is true: the array does not
+ // contain the complete set of immediately dominated children.
+ // * If children is an array and this property is false: the array contains
+ // the complete set of immediately dominated children.
+ // * If children is undefined and this property is true: there exist
+ // immediately dominated children for this node, but they have not been
+ // loaded yet.
+ // * If children is undefined and this property is false: this node does not
+ // dominate any others and therefore has no children.
+ this.moreChildrenAvailable = true;
+}
+
+DominatorTreeNode.prototype = null;
+
+module.exports = DominatorTreeNode;
+
+/**
+ * Add `child` to the `parent`'s set of children.
+ *
+ * @param {DominatorTreeNode} parent
+ * @param {DominatorTreeNode} child
+ */
+DominatorTreeNode.addChild = function (parent, child) {
+ if (parent.children === undefined) {
+ parent.children = [];
+ }
+
+ parent.children.push(child);
+ child.parentId = parent.nodeId;
+};
+
+/**
+ * A Visitor that is used to generate a label for a node in the heap snapshot
+ * and get its shallow size as well while we are at it.
+ */
+function LabelAndShallowSizeVisitor() {
+ // As we walk the description, we accumulate edges in this array.
+ this._labelPieces = [];
+
+ // Once we reach the non-zero count leaf node in the description, we move the
+ // labelPieces here to signify that we no longer need to accumulate edges.
+ this._label = undefined;
+
+ // Once we reach the non-zero count leaf node in the description, we grab the
+ // shallow size and place it here.
+ this._shallowSize = 0;
+}
+
+DominatorTreeNode.LabelAndShallowSizeVisitor = LabelAndShallowSizeVisitor;
+
+LabelAndShallowSizeVisitor.prototype = Object.create(Visitor);
+
+/**
+ * @overrides Visitor.prototype.enter
+ */
+LabelAndShallowSizeVisitor.prototype.enter = function (breakdown, report, edge) {
+ if (this._labelPieces && edge) {
+ this._labelPieces.push(edge);
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.exit
+ */
+LabelAndShallowSizeVisitor.prototype.exit = function (breakdown, report, edge) {
+ if (this._labelPieces && edge) {
+ this._labelPieces.pop();
+ }
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+LabelAndShallowSizeVisitor.prototype.count = function (breakdown, report, edge) {
+ if (report.count === 0) {
+ return;
+ }
+
+ this._label = this._labelPieces;
+ this._labelPieces = undefined;
+
+ this._shallowSize = report.bytes;
+};
+
+/**
+ * Get the generated label structure accumulated by this visitor.
+ *
+ * @returns {Object}
+ */
+LabelAndShallowSizeVisitor.prototype.label = function () {
+ return this._label;
+};
+
+/**
+ * Get the shallow size of the node this visitor visited.
+ *
+ * @returns {Number}
+ */
+LabelAndShallowSizeVisitor.prototype.shallowSize = function () {
+ return this._shallowSize;
+};
+
+/**
+ * Generate a label structure for the node with the given id and grab its
+ * shallow size.
+ *
+ * What is a "label" structure? HeapSnapshot.describeNode essentially takes a
+ * census of a single node rather than the whole heap graph. The resulting
+ * report has only one count leaf that is non-zero. The label structure is the
+ * path in this report from the root to the non-zero count leaf.
+ *
+ * @param {Number} nodeId
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ *
+ * @returns {Object}
+ * An object with the following properties:
+ * - {Number} shallowSize
+ * - {Object} label
+ */
+DominatorTreeNode.getLabelAndShallowSize = function (nodeId,
+ snapshot,
+ breakdown) {
+ const description = snapshot.describeNode(breakdown, nodeId);
+
+ const visitor = new LabelAndShallowSizeVisitor();
+ walk(breakdown, description, visitor);
+
+ return {
+ label: visitor.label(),
+ shallowSize: visitor.shallowSize(),
+ };
+};
+
+/**
+ * Do a partial traversal of the given dominator tree and convert it into a tree
+ * of `DominatorTreeNode`s. Dominator trees have a node for every node in the
+ * snapshot's heap graph, so we must not allocate a JS object for every node. It
+ * would be way too many and the node count is effectively unbounded.
+ *
+ * Go no deeper down the tree than `maxDepth` and only consider at most
+ * `maxSiblings` within any single node's children.
+ *
+ * @param {DominatorTree} dominatorTree
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ * @param {Number} maxDepth
+ * @param {Number} maxSiblings
+ *
+ * @returns {DominatorTreeNode}
+ */
+DominatorTreeNode.partialTraversal = function (dominatorTree,
+ snapshot,
+ breakdown,
+ maxDepth = DEFAULT_MAX_DEPTH,
+ maxSiblings = DEFAULT_MAX_SIBLINGS) {
+ function dfs(nodeId, depth) {
+ const { label, shallowSize } =
+ DominatorTreeNode.getLabelAndShallowSize(nodeId, snapshot, breakdown);
+ const retainedSize = dominatorTree.getRetainedSize(nodeId);
+ const node = new DominatorTreeNode(nodeId, label, shallowSize, retainedSize);
+ const childNodeIds = dominatorTree.getImmediatelyDominated(nodeId);
+
+ const newDepth = depth + 1;
+ if (newDepth < maxDepth) {
+ const endIdx = Math.min(childNodeIds.length, maxSiblings);
+ for (let i = 0; i < endIdx; i++) {
+ DominatorTreeNode.addChild(node, dfs(childNodeIds[i], newDepth));
+ }
+ node.moreChildrenAvailable = endIdx < childNodeIds.length;
+ } else {
+ node.moreChildrenAvailable = childNodeIds.length > 0;
+ }
+
+ return node;
+ }
+
+ return dfs(dominatorTree.root, 0);
+};
+
+/**
+ * Insert more children into the given (partially complete) dominator tree.
+ *
+ * The tree is updated in an immutable and persistent manner: a new tree is
+ * returned, but all unmodified subtrees (which is most) are shared with the
+ * original tree. Only the modified nodes are re-allocated.
+ *
+ * @param {DominatorTreeNode} tree
+ * @param {Array<NodeId>} path
+ * @param {Array<DominatorTreeNode>} newChildren
+ * @param {Boolean} moreChildrenAvailable
+ *
+ * @returns {DominatorTreeNode}
+ */
+DominatorTreeNode.insert = function (tree, path, newChildren, moreChildrenAvailable) {
+ function insert(tree, i) {
+ if (tree.nodeId !== path[i]) {
+ return tree;
+ }
+
+ if (i == path.length - 1) {
+ return immutableUpdate(tree, {
+ children: (tree.children || []).concat(newChildren),
+ moreChildrenAvailable,
+ });
+ }
+
+ return tree.children
+ ? immutableUpdate(tree, {
+ children: tree.children.map(c => insert(c, i + 1))
+ })
+ : tree;
+ }
+
+ return insert(tree, 0);
+};
+
+/**
+ * Get the new canonical node with the given `id` in `tree` that exists along
+ * `path`. If there is no such node along `path`, return null.
+ *
+ * This is useful if we have a reference to a now-outdated DominatorTreeNode due
+ * to a recent call to DominatorTreeNode.insert and want to get the up-to-date
+ * version. We don't have to walk the whole tree: if there is an updated version
+ * of the node then it *must* be along the path.
+ *
+ * @param {NodeId} id
+ * @param {DominatorTreeNode} tree
+ * @param {Array<NodeId>} path
+ *
+ * @returns {DominatorTreeNode|null}
+ */
+DominatorTreeNode.getNodeByIdAlongPath = function (id, tree, path) {
+ function find(node, i) {
+ if (!node || node.nodeId !== path[i]) {
+ return null;
+ }
+
+ if (node.nodeId === id) {
+ return node;
+ }
+
+ if (i === path.length - 1 || !node.children) {
+ return null;
+ }
+
+ const nextId = path[i + 1];
+ return find(node.children.find(c => c.nodeId === nextId), i + 1);
+ }
+
+ return find(tree, 0);
+};
+
+/**
+ * Find the shortest retaining paths for the given set of DominatorTreeNodes,
+ * and populate each node's `shortestPaths` property with them in place.
+ *
+ * @param {HeapSnapshot} snapshot
+ * @param {Object} breakdown
+ * @param {NodeId} start
+ * @param {Array<DominatorTreeNode>} treeNodes
+ * @param {Number} maxNumPaths
+ */
+DominatorTreeNode.attachShortestPaths = function (snapshot,
+ breakdown,
+ start,
+ treeNodes,
+ maxNumPaths = DEFAULT_MAX_NUM_PATHS) {
+ const idToTreeNode = new Map();
+ const targets = [];
+ for (let node of treeNodes) {
+ const id = node.nodeId;
+ idToTreeNode.set(id, node);
+ targets.push(id);
+ }
+
+ const shortestPaths = snapshot.computeShortestPaths(start,
+ targets,
+ maxNumPaths);
+
+ for (let [target, paths] of shortestPaths) {
+ const deduped = deduplicatePaths(target, paths);
+ deduped.nodes = deduped.nodes.map(id => {
+ const { label } =
+ DominatorTreeNode.getLabelAndShallowSize(id, snapshot, breakdown);
+ return { id, label };
+ });
+
+ idToTreeNode.get(target).shortestPaths = deduped;
+ }
+};
diff --git a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
new file mode 100644
index 000000000..72a289558
--- /dev/null
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
@@ -0,0 +1,86 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/FileDescriptorOutputStream.h"
+#include "private/pprio.h"
+
+namespace mozilla {
+namespace devtools {
+
+/* static */ already_AddRefed<FileDescriptorOutputStream>
+FileDescriptorOutputStream::Create(const ipc::FileDescriptor& fileDescriptor)
+{
+ if (NS_WARN_IF(!fileDescriptor.IsValid()))
+ return nullptr;
+
+ auto rawFD = fileDescriptor.ClonePlatformHandle();
+ PRFileDesc* prfd = PR_ImportFile(PROsfd(rawFD.release()));
+ if (NS_WARN_IF(!prfd))
+ return nullptr;
+
+ RefPtr<FileDescriptorOutputStream> stream = new FileDescriptorOutputStream(prfd);
+ return stream.forget();
+}
+
+NS_IMPL_ISUPPORTS(FileDescriptorOutputStream, nsIOutputStream);
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Close()
+{
+ // Repeatedly closing is idempotent.
+ if (!fd)
+ return NS_OK;
+
+ if (PR_Close(fd) != PR_SUCCESS)
+ return NS_ERROR_FAILURE;
+ fd = nullptr;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Write(const char* buf, uint32_t count, uint32_t* retval)
+{
+ if (NS_WARN_IF(!fd))
+ return NS_ERROR_FAILURE;
+
+ auto written = PR_Write(fd, buf, count);
+ if (written < 0)
+ return NS_ERROR_FAILURE;
+ *retval = written;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::Flush()
+{
+ if (NS_WARN_IF(!fd))
+ return NS_ERROR_FAILURE;
+
+ return PR_Sync(fd) == PR_SUCCESS ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::WriteFrom(nsIInputStream* fromStream, uint32_t count,
+ uint32_t* retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::WriteSegments(nsReadSegmentFun reader, void* closure,
+ uint32_t count, uint32_t* retval)
+{
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+FileDescriptorOutputStream::IsNonBlocking(bool* retval)
+{
+ *retval = false;
+ return NS_OK;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h
new file mode 100644
index 000000000..6990f1fc3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_FileDescriptorOutputStream_h
+#define mozilla_devtools_FileDescriptorOutputStream_h
+
+#include <prio.h>
+
+#include "mozilla/AlreadyAddRefed.h"
+#include "mozilla/ipc/FileDescriptor.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace devtools {
+
+class FileDescriptorOutputStream final : public nsIOutputStream
+{
+private:
+ PRFileDesc* fd;
+
+public:
+ static already_AddRefed<FileDescriptorOutputStream>
+ Create(const ipc::FileDescriptor& fileDescriptor);
+
+private:
+ explicit FileDescriptorOutputStream(PRFileDesc* prfd)
+ : fd(prfd)
+ { }
+
+ virtual ~FileDescriptorOutputStream() { }
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOUTPUTSTREAM
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_FileDescriptorOutputStream_h
diff --git a/devtools/shared/heapsnapshot/HeapAnalysesClient.js b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
new file mode 100644
index 000000000..98601a2b1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapAnalysesClient.js
@@ -0,0 +1,277 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { DevToolsWorker } = require("devtools/shared/worker/worker");
+
+const WORKER_URL =
+ "resource://devtools/shared/heapsnapshot/HeapAnalysesWorker.js";
+var workerCounter = 0;
+
+/**
+ * A HeapAnalysesClient instance provides a developer-friendly interface for
+ * interacting with a HeapAnalysesWorker. This enables users to be ignorant of
+ * the message passing protocol used to communicate with the worker. The
+ * HeapAnalysesClient owns the worker, and terminating the worker is done by
+ * terminating the client (see the `destroy` method).
+ */
+const HeapAnalysesClient = module.exports = function () {
+ this._worker = new DevToolsWorker(WORKER_URL, {
+ name: `HeapAnalyses-${workerCounter++}`,
+ verbose: DevToolsUtils.dumpv.wantVerbose
+ });
+};
+
+/**
+ * Destroy the worker, causing it to release its resources (such as heap
+ * snapshots it has deserialized and read into memory). The client is no longer
+ * usable after calling this method.
+ */
+HeapAnalysesClient.prototype.destroy = function () {
+ this._worker.destroy();
+ this._worker = null;
+};
+
+/**
+ * Tell the worker to read into memory the heap snapshot at the given file
+ * path. This is a prerequisite for asking the worker to perform various
+ * analyses on a heap snapshot.
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @returns Promise
+ * The promise is fulfilled if the heap snapshot is successfully
+ * deserialized and read into memory. The promise is rejected if that
+ * does not happen, eg due to a bad file path or malformed heap
+ * snapshot file.
+ */
+HeapAnalysesClient.prototype.readHeapSnapshot = function (snapshotFilePath) {
+ return this._worker.performTask("readHeapSnapshot", { snapshotFilePath });
+};
+
+/**
+ * Tell the worker to delete all references to the snapshot and dominator trees
+ * linked to the provided snapshot file path.
+ *
+ * @param {String} snapshotFilePath
+ * @return Promise<undefined>
+ */
+HeapAnalysesClient.prototype.deleteHeapSnapshot = function (snapshotFilePath) {
+ return this._worker.performTask("deleteHeapSnapshot", { snapshotFilePath });
+};
+
+/**
+ * Request the creation time given a snapshot file path. Returns `null`
+ * if snapshot does not exist.
+ *
+ * @param {String} snapshotFilePath
+ * The path to the snapshot.
+ * @return {Number?}
+ * The unix timestamp of the creation time of the snapshot, or null if
+ * snapshot does not exist.
+ */
+HeapAnalysesClient.prototype.getCreationTime = function (snapshotFilePath) {
+ return this._worker.performTask("getCreationTime", snapshotFilePath);
+};
+
+/** * Censuses *****************************************************************/
+
+/**
+ * Ask the worker to perform a census analysis on the heap snapshot with the
+ * given path. The heap snapshot at the given path must have already been read
+ * into memory by the worker (see `readHeapSnapshot`).
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @param {Object} censusOptions
+ * A structured-cloneable object specifying the requested census's
+ * breakdown. See the "takeCensus" section of
+ * `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
+ *
+ * @param {Object} requestOptions
+ * An object specifying options of this worker request.
+ * - {Boolean} asTreeNode
+ * Whether or not the census is returned as a CensusTreeNode,
+ * or just a breakdown report. Defaults to false.
+ * @see `devtools/shared/heapsnapshot/census-tree-node.js`
+ * - {Boolean} asInvertedTreeNode
+ * Whether or not the census is returned as an inverted
+ * CensusTreeNode. Defaults to false.
+ * - {String} filter
+ * A filter string to prune the resulting tree with. Only applies if
+ * either asTreeNode or asInvertedTreeNode is true.
+ *
+ * @returns Promise<Object>
+ * An object with the following properties:
+ * - report:
+ * The report generated by the given census breakdown, or a
+ * CensusTreeNode generated by the given census breakdown if
+ * `asTreeNode` is true.
+ * - parentMap:
+ * The result of calling CensusUtils.createParentMap on the generated
+ * report. Only exists if asTreeNode or asInvertedTreeNode are set.
+ */
+HeapAnalysesClient.prototype.takeCensus = function (snapshotFilePath,
+ censusOptions,
+ requestOptions = {}) {
+ return this._worker.performTask("takeCensus", {
+ snapshotFilePath,
+ censusOptions,
+ requestOptions,
+ });
+};
+
+/**
+ * Get the individual nodes that correspond to the given census report leaf
+ * indices.
+ *
+ * @param {Object} opts
+ * An object with the following properties:
+ * - {DominatorTreeId} dominatorTreeId: The id of the dominator tree.
+ * - {Set<Number>} indices: The indices of the census report leaves we
+ * would like to get the individuals for.
+ * - {Object} censusBreakdown: The breakdown used to generate the census.
+ * - {Object} labelBreakdown: The breakdown we would like to use when
+ * labeling the resulting nodes.
+ * - {Number} maxRetainingPaths: The maximum number of retaining paths to
+ * compute for each node.
+ * - {Number} maxIndividuals: The maximum number of individual nodes to
+ * return.
+ *
+ * @returns {Promise<Object>}
+ * A promise of an object with the following properties:
+ * - {Array<DominatorTreeNode>} nodes: An array of `DominatorTreeNode`s
+ * with their shortest paths attached, and without any dominator tree
+ * child/parent information attached. The results are sorted by
+ * retained size.
+ *
+ */
+HeapAnalysesClient.prototype.getCensusIndividuals = function (opts) {
+ return this._worker.performTask("getCensusIndividuals", opts);
+};
+
+/**
+ * Request that the worker take a census on the heap snapshots with the given
+ * paths and then return the difference between them. Both heap snapshots must
+ * have already been read into memory by the worker (see `readHeapSnapshot`).
+ *
+ * @param {String} firstSnapshotFilePath
+ * The first snapshot file path.
+ *
+ * @param {String} secondSnapshotFilePath
+ * The second snapshot file path.
+ *
+ * @param {Object} censusOptions
+ * A structured-cloneable object specifying the requested census's
+ * breakdown. See the "takeCensus" section of
+ * `js/src/doc/Debugger/Debugger.Memory.md` for detailed documentation.
+ *
+ * @param {Object} requestOptions
+ * An object specifying options for this request.
+ * - {Boolean} asTreeNode
+ * Whether the resulting delta report should be converted to a census
+ * tree node before returned. Defaults to false.
+ * - {Boolean} asInvertedTreeNode
+ * Whether or not the census is returned as an inverted
+ * CensusTreeNode. Defaults to false.
+ * - {String} filter
+ * A filter string to prune the resulting tree with. Only applies if
+ * either asTreeNode or asInvertedTreeNode is true.
+ *
+ * @returns Promise<Object>
+ * - delta:
+ * The delta report generated by diffing the two census reports, or a
+ * CensusTreeNode generated from the delta report if
+ * `requestOptions.asTreeNode` was true.
+ * - parentMap:
+ * The result of calling CensusUtils.createParentMap on the generated
+ * delta. Only exists if asTreeNode or asInvertedTreeNode are set.
+ */
+HeapAnalysesClient.prototype.takeCensusDiff = function (firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions = {}) {
+ return this._worker.performTask("takeCensusDiff", {
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions
+ });
+};
+
+/** * Dominator Trees **********************************************************/
+
+/**
+ * Compute the dominator tree of the heap snapshot loaded from the given file
+ * path. Returns the id of the computed dominator tree.
+ *
+ * @param {String} snapshotFilePath
+ *
+ * @returns {Promise<DominatorTreeId>}
+ */
+HeapAnalysesClient.prototype.computeDominatorTree = function (snapshotFilePath) {
+ return this._worker.performTask("computeDominatorTree", snapshotFilePath);
+};
+
+/**
+ * Get the initial, partial view of the dominator tree with the given id.
+ *
+ * @param {Object} opts
+ * An object specifying options for this request.
+ * - {DominatorTreeId} dominatorTreeId
+ * The id of the dominator tree.
+ * - {Object} breakdown
+ * The breakdown used to generate node labels.
+ * - {Number} maxDepth
+ * The maximum depth to traverse down the tree to create this initial
+ * view.
+ * - {Number} maxSiblings
+ * The maximum number of siblings to visit within each traversed node's
+ * children.
+ * - {Number} maxRetainingPaths
+ * The maximum number of retaining paths to find for each node.
+ *
+ * @returns {Promise<DominatorTreeNode>}
+ */
+HeapAnalysesClient.prototype.getDominatorTree = function (opts) {
+ return this._worker.performTask("getDominatorTree", opts);
+};
+
+/**
+ * Get a subset of a nodes children in the dominator tree.
+ *
+ * @param {Object} opts
+ * An object specifying options for this request.
+ * - {DominatorTreeId} dominatorTreeId
+ * The id of the dominator tree.
+ * - {NodeId} nodeId
+ * The id of the node whose children are being found.
+ * - {Object} breakdown
+ * The breakdown used to generate node labels.
+ * - {Number} startIndex
+ * The starting index within the full set of immediately dominated
+ * children of the children being requested. Children are always sorted
+ * by greatest to least retained size.
+ * - {Number} maxCount
+ * The maximum number of children to return.
+ * - {Number} maxRetainingPaths
+ * The maximum number of retaining paths to find for each node.
+ *
+ * @returns {Promise<Object>}
+ * A promise of an object with the following properties:
+ * - {Array<DominatorTreeNode>} nodes
+ * The requested nodes that are immediately dominated by the node
+ * identified by `opts.nodeId`.
+ * - {Boolean} moreChildrenAvailable
+ * True iff there are more children available after the returned
+ * nodes.
+ * - {Array<NodeId>} path
+ * The path through the tree from the root to these node's parent, eg
+ * [root's id, child of root's id, child of child of root's id, ..., `nodeId`].
+ */
+HeapAnalysesClient.prototype.getImmediatelyDominated = function (opts) {
+ return this._worker.performTask("getImmediatelyDominated", opts);
+};
diff --git a/devtools/shared/heapsnapshot/HeapAnalysesWorker.js b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
new file mode 100644
index 000000000..d07d67f80
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapAnalysesWorker.js
@@ -0,0 +1,303 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* global ThreadSafeChromeUtils*/
+
+// This is a worker which reads offline heap snapshots into memory and performs
+// heavyweight analyses on them without blocking the main thread. A
+// HeapAnalysesWorker is owned and communicated with by a HeapAnalysesClient
+// instance. See HeapAnalysesClient.js.
+
+"use strict";
+
+importScripts("resource://gre/modules/workers/require.js");
+importScripts("resource://devtools/shared/worker/helper.js");
+const { censusReportToCensusTreeNode } = require("resource://devtools/shared/heapsnapshot/census-tree-node.js");
+const DominatorTreeNode = require("resource://devtools/shared/heapsnapshot/DominatorTreeNode.js");
+const CensusUtils = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+
+const DEFAULT_START_INDEX = 0;
+const DEFAULT_MAX_COUNT = 50;
+
+/**
+ * The set of HeapSnapshot instances this worker has read into memory. Keyed by
+ * snapshot file path.
+ */
+const snapshots = Object.create(null);
+
+/**
+ * The set of `DominatorTree`s that have been computed, mapped by their id (aka
+ * the index into this array).
+ *
+ * @see /dom/webidl/DominatorTree.webidl
+ */
+const dominatorTrees = [];
+
+/**
+ * The i^th HeapSnapshot in this array is the snapshot used to generate the i^th
+ * dominator tree in `dominatorTrees` above. This lets us map from a dominator
+ * tree id to the snapshot it came from.
+ */
+const dominatorTreeSnapshots = [];
+
+/**
+ * @see HeapAnalysesClient.prototype.readHeapSnapshot
+ */
+workerHelper.createTask(self, "readHeapSnapshot", ({ snapshotFilePath }) => {
+ snapshots[snapshotFilePath] =
+ ThreadSafeChromeUtils.readHeapSnapshot(snapshotFilePath);
+ return true;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.deleteHeapSnapshot
+ */
+workerHelper.createTask(self, "deleteHeapSnapshot", ({ snapshotFilePath }) => {
+ let snapshot = snapshots[snapshotFilePath];
+ if (!snapshot) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ snapshots[snapshotFilePath] = undefined;
+
+ let dominatorTreeId = dominatorTreeSnapshots.indexOf(snapshot);
+ if (dominatorTreeId != -1) {
+ dominatorTreeSnapshots[dominatorTreeId] = undefined;
+ dominatorTrees[dominatorTreeId] = undefined;
+ }
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.takeCensus
+ */
+workerHelper.createTask(self, "takeCensus", ({ snapshotFilePath, censusOptions, requestOptions }) => {
+ if (!snapshots[snapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ let report = snapshots[snapshotFilePath].takeCensus(censusOptions);
+ let parentMap;
+
+ if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
+ const opts = { filter: requestOptions.filter || null };
+ if (requestOptions.asInvertedTreeNode) {
+ opts.invert = true;
+ }
+ report = censusReportToCensusTreeNode(censusOptions.breakdown, report, opts);
+ parentMap = CensusUtils.createParentMap(report);
+ }
+
+ return { report, parentMap };
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getCensusIndividuals
+ */
+workerHelper.createTask(self, "getCensusIndividuals", request => {
+ const {
+ dominatorTreeId,
+ indices,
+ censusBreakdown,
+ labelBreakdown,
+ maxRetainingPaths,
+ maxIndividuals,
+ } = request;
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ if (!dominatorTree) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+ }
+
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+ const nodeIds = CensusUtils.getCensusIndividuals(indices, censusBreakdown, snapshot);
+
+ const nodes = nodeIds
+ .sort((a, b) => dominatorTree.getRetainedSize(b) - dominatorTree.getRetainedSize(a))
+ .slice(0, maxIndividuals)
+ .map(id => {
+ const { label, shallowSize } =
+ DominatorTreeNode.getLabelAndShallowSize(id, snapshot, labelBreakdown);
+ const retainedSize = dominatorTree.getRetainedSize(id);
+ const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
+ node.moreChildrenAvailable = false;
+ return node;
+ });
+
+ DominatorTreeNode.attachShortestPaths(snapshot,
+ labelBreakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths);
+
+ return { nodes };
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.takeCensusDiff
+ */
+workerHelper.createTask(self, "takeCensusDiff", request => {
+ const {
+ firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ censusOptions,
+ requestOptions
+ } = request;
+
+ if (!snapshots[firstSnapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${firstSnapshotFilePath}'`);
+ }
+
+ if (!snapshots[secondSnapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${secondSnapshotFilePath}'`);
+ }
+
+ const first = snapshots[firstSnapshotFilePath].takeCensus(censusOptions);
+ const second = snapshots[secondSnapshotFilePath].takeCensus(censusOptions);
+ let delta = CensusUtils.diff(censusOptions.breakdown, first, second);
+ let parentMap;
+
+ if (requestOptions.asTreeNode || requestOptions.asInvertedTreeNode) {
+ const opts = { filter: requestOptions.filter || null };
+ if (requestOptions.asInvertedTreeNode) {
+ opts.invert = true;
+ }
+ delta = censusReportToCensusTreeNode(censusOptions.breakdown, delta, opts);
+ parentMap = CensusUtils.createParentMap(delta);
+ }
+
+ return { delta, parentMap };
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getCreationTime
+ */
+workerHelper.createTask(self, "getCreationTime", snapshotFilePath => {
+ if (!snapshots[snapshotFilePath]) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+ return snapshots[snapshotFilePath].creationTime;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.computeDominatorTree
+ */
+workerHelper.createTask(self, "computeDominatorTree", snapshotFilePath => {
+ const snapshot = snapshots[snapshotFilePath];
+ if (!snapshot) {
+ throw new Error(`No known heap snapshot for '${snapshotFilePath}'`);
+ }
+
+ const id = dominatorTrees.length;
+ dominatorTrees.push(snapshot.computeDominatorTree());
+ dominatorTreeSnapshots.push(snapshot);
+ return id;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getDominatorTree
+ */
+workerHelper.createTask(self, "getDominatorTree", request => {
+ const {
+ dominatorTreeId,
+ breakdown,
+ maxDepth,
+ maxSiblings,
+ maxRetainingPaths,
+ } = request;
+
+ if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+ }
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+
+ const tree = DominatorTreeNode.partialTraversal(dominatorTree,
+ snapshot,
+ breakdown,
+ maxDepth,
+ maxSiblings);
+
+ const nodes = [];
+ (function getNodes(node) {
+ nodes.push(node);
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ getNodes(node.children[i]);
+ }
+ }
+ }(tree));
+
+ DominatorTreeNode.attachShortestPaths(snapshot,
+ breakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths);
+
+ return tree;
+});
+
+/**
+ * @see HeapAnalysesClient.prototype.getImmediatelyDominated
+ */
+workerHelper.createTask(self, "getImmediatelyDominated", request => {
+ const {
+ dominatorTreeId,
+ nodeId,
+ breakdown,
+ startIndex,
+ maxCount,
+ maxRetainingPaths,
+ } = request;
+
+ if (!(0 <= dominatorTreeId && dominatorTreeId < dominatorTrees.length)) {
+ throw new Error(
+ `There does not exist a DominatorTree with the id ${dominatorTreeId}`);
+ }
+
+ const dominatorTree = dominatorTrees[dominatorTreeId];
+ const snapshot = dominatorTreeSnapshots[dominatorTreeId];
+
+ const childIds = dominatorTree.getImmediatelyDominated(nodeId);
+ if (!childIds) {
+ throw new Error(`${nodeId} is not a node id in the dominator tree`);
+ }
+
+ const start = startIndex || DEFAULT_START_INDEX;
+ const count = maxCount || DEFAULT_MAX_COUNT;
+ const end = start + count;
+
+ const nodes = childIds
+ .slice(start, end)
+ .map(id => {
+ const { label, shallowSize } =
+ DominatorTreeNode.getLabelAndShallowSize(id, snapshot, breakdown);
+ const retainedSize = dominatorTree.getRetainedSize(id);
+ const node = new DominatorTreeNode(id, label, shallowSize, retainedSize);
+ node.parentId = nodeId;
+ // DominatorTree.getImmediatelyDominated will always return non-null here
+ // because we got the id directly from the dominator tree.
+ node.moreChildrenAvailable = dominatorTree.getImmediatelyDominated(id).length > 0;
+ return node;
+ });
+
+ const path = [];
+ let id = nodeId;
+ do {
+ path.push(id);
+ id = dominatorTree.getImmediateDominator(id);
+ } while (id !== null);
+ path.reverse();
+
+ const moreChildrenAvailable = childIds.length > end;
+
+ DominatorTreeNode.attachShortestPaths(snapshot,
+ breakdown,
+ dominatorTree.root,
+ nodes,
+ maxRetainingPaths);
+
+ return { nodes, moreChildrenAvailable, path };
+});
diff --git a/devtools/shared/heapsnapshot/HeapSnapshot.cpp b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
new file mode 100644
index 000000000..17f43f34e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -0,0 +1,1652 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeapSnapshot.h"
+
+#include <google/protobuf/io/coded_stream.h>
+#include <google/protobuf/io/gzip_stream.h>
+#include <google/protobuf/io/zero_copy_stream_impl_lite.h>
+
+#include "js/Debug.h"
+#include "js/TypeDecls.h"
+#include "js/UbiNodeBreadthFirst.h"
+#include "js/UbiNodeCensus.h"
+#include "js/UbiNodeDominatorTree.h"
+#include "js/UbiNodeShortestPaths.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/devtools/AutoMemMap.h"
+#include "mozilla/devtools/CoreDump.pb.h"
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/devtools/DominatorTree.h"
+#include "mozilla/devtools/FileDescriptorOutputStream.h"
+#include "mozilla/devtools/HeapSnapshotTempFileHelperChild.h"
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/HeapSnapshotBinding.h"
+#include "mozilla/RangedPtr.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Unused.h"
+
+#include "jsapi.h"
+#include "jsfriendapi.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsCRTGlue.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIFile.h"
+#include "nsIOutputStream.h"
+#include "nsISupportsImpl.h"
+#include "nsNetUtil.h"
+#include "nsPrintfCString.h"
+#include "prerror.h"
+#include "prio.h"
+#include "prtypes.h"
+
+namespace mozilla {
+namespace devtools {
+
+using namespace JS;
+using namespace dom;
+
+using ::google::protobuf::io::ArrayInputStream;
+using ::google::protobuf::io::CodedInputStream;
+using ::google::protobuf::io::GzipInputStream;
+using ::google::protobuf::io::ZeroCopyInputStream;
+
+using JS::ubi::AtomOrTwoByteChars;
+using JS::ubi::ShortestPaths;
+
+MallocSizeOf
+GetCurrentThreadDebuggerMallocSizeOf()
+{
+ auto ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx);
+ auto cx = ccjscx->Context();
+ MOZ_ASSERT(cx);
+ auto mallocSizeOf = JS::dbg::GetDebuggerMallocSizeOf(cx);
+ MOZ_ASSERT(mallocSizeOf);
+ return mallocSizeOf;
+}
+
+/*** Cycle Collection Boilerplate *****************************************************************/
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(HeapSnapshot, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(HeapSnapshot)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(HeapSnapshot)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(HeapSnapshot)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+/* virtual */ JSObject*
+HeapSnapshot::WrapObject(JSContext* aCx, HandleObject aGivenProto)
+{
+ return HeapSnapshotBinding::Wrap(aCx, this, aGivenProto);
+}
+
+/*** Reading Heap Snapshots ***********************************************************************/
+
+/* static */ already_AddRefed<HeapSnapshot>
+HeapSnapshot::Create(JSContext* cx,
+ GlobalObject& global,
+ const uint8_t* buffer,
+ uint32_t size,
+ ErrorResult& rv)
+{
+ RefPtr<HeapSnapshot> snapshot = new HeapSnapshot(cx, global.GetAsSupports());
+ if (!snapshot->init(cx, buffer, size)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ return snapshot.forget();
+}
+
+template<typename MessageType>
+static bool
+parseMessage(ZeroCopyInputStream& stream, uint32_t sizeOfMessage, MessageType& message)
+{
+ // We need to create a new `CodedInputStream` for each message so that the
+ // 64MB limit is applied per-message rather than to the whole stream.
+ CodedInputStream codedStream(&stream);
+
+ // The protobuf message nesting that core dumps exhibit is dominated by
+ // allocation stacks' frames. In the most deeply nested case, each frame has
+ // two messages: a StackFrame message and a StackFrame::Data message. These
+ // frames are on top of a small constant of other messages. There are a
+ // MAX_STACK_DEPTH number of frames, so we multiply this by 3 to make room for
+ // the two messages per frame plus some head room for the constant number of
+ // non-dominating messages.
+ codedStream.SetRecursionLimit(HeapSnapshot::MAX_STACK_DEPTH * 3);
+
+ auto limit = codedStream.PushLimit(sizeOfMessage);
+ if (NS_WARN_IF(!message.ParseFromCodedStream(&codedStream)) ||
+ NS_WARN_IF(!codedStream.ConsumedEntireMessage()) ||
+ NS_WARN_IF(codedStream.BytesUntilLimit() != 0))
+ {
+ return false;
+ }
+
+ codedStream.PopLimit(limit);
+ return true;
+}
+
+template<typename CharT, typename InternedStringSet>
+struct GetOrInternStringMatcher
+{
+ InternedStringSet& internedStrings;
+
+ explicit GetOrInternStringMatcher(InternedStringSet& strings) : internedStrings(strings) { }
+
+ const CharT* match(const std::string* str) {
+ MOZ_ASSERT(str);
+ size_t length = str->length() / sizeof(CharT);
+ auto tempString = reinterpret_cast<const CharT*>(str->data());
+
+ UniquePtr<CharT[], NSFreePolicy> owned(NS_strndup(tempString, length));
+ if (!owned || !internedStrings.append(Move(owned)))
+ return nullptr;
+
+ return internedStrings.back().get();
+ }
+
+ const CharT* match(uint64_t ref) {
+ if (MOZ_LIKELY(ref < internedStrings.length())) {
+ auto& string = internedStrings[ref];
+ MOZ_ASSERT(string);
+ return string.get();
+ }
+
+ return nullptr;
+ }
+};
+
+template<
+ // Either char or char16_t.
+ typename CharT,
+ // A reference to either `internedOneByteStrings` or `internedTwoByteStrings`
+ // if CharT is char or char16_t respectively.
+ typename InternedStringSet>
+const CharT*
+HeapSnapshot::getOrInternString(InternedStringSet& internedStrings,
+ Maybe<StringOrRef>& maybeStrOrRef)
+{
+ // Incomplete message: has neither a string nor a reference to an already
+ // interned string.
+ if (MOZ_UNLIKELY(maybeStrOrRef.isNothing()))
+ return nullptr;
+
+ GetOrInternStringMatcher<CharT, InternedStringSet> m(internedStrings);
+ return maybeStrOrRef->match(m);
+}
+
+// Get a de-duplicated string as a Maybe<StringOrRef> from the given `msg`.
+#define GET_STRING_OR_REF_WITH_PROP_NAMES(msg, strPropertyName, refPropertyName) \
+ (msg.has_##refPropertyName() \
+ ? Some(StringOrRef(msg.refPropertyName())) \
+ : msg.has_##strPropertyName() \
+ ? Some(StringOrRef(&msg.strPropertyName())) \
+ : Nothing())
+
+#define GET_STRING_OR_REF(msg, property) \
+ (msg.has_##property##ref() \
+ ? Some(StringOrRef(msg.property##ref())) \
+ : msg.has_##property() \
+ ? Some(StringOrRef(&msg.property())) \
+ : Nothing())
+
+bool
+HeapSnapshot::saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents)
+{
+ // NB: de-duplicated string properties must be read back and interned in the
+ // same order here as they are written and serialized in
+ // `CoreDumpWriter::writeNode` or else indices in references to already
+ // serialized strings will be off.
+
+ if (NS_WARN_IF(!node.has_id()))
+ return false;
+ NodeId id = node.id();
+
+ // NodeIds are derived from pointers (at most 48 bits) and we rely on them
+ // fitting into JS numbers (IEEE 754 doubles, can precisely store 53 bit
+ // integers) despite storing them on disk as 64 bit integers.
+ if (NS_WARN_IF(!JS::Value::isNumberRepresentable(id)))
+ return false;
+
+ // Should only deserialize each node once.
+ if (NS_WARN_IF(nodes.has(id)))
+ return false;
+
+ if (NS_WARN_IF(!JS::ubi::Uint32IsValidCoarseType(node.coarsetype())))
+ return false;
+ auto coarseType = JS::ubi::Uint32ToCoarseType(node.coarsetype());
+
+ Maybe<StringOrRef> typeNameOrRef = GET_STRING_OR_REF_WITH_PROP_NAMES(node, typename_, typenameref);
+ auto typeName = getOrInternString<char16_t>(internedTwoByteStrings, typeNameOrRef);
+ if (NS_WARN_IF(!typeName))
+ return false;
+
+ if (NS_WARN_IF(!node.has_size()))
+ return false;
+ uint64_t size = node.size();
+
+ auto edgesLength = node.edges_size();
+ DeserializedNode::EdgeVector edges;
+ if (NS_WARN_IF(!edges.reserve(edgesLength)))
+ return false;
+ for (decltype(edgesLength) i = 0; i < edgesLength; i++) {
+ auto& protoEdge = node.edges(i);
+
+ if (NS_WARN_IF(!protoEdge.has_referent()))
+ return false;
+ NodeId referent = protoEdge.referent();
+
+ if (NS_WARN_IF(!edgeReferents.put(referent)))
+ return false;
+
+ const char16_t* edgeName = nullptr;
+ if (protoEdge.EdgeNameOrRef_case() != protobuf::Edge::EDGENAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> edgeNameOrRef = GET_STRING_OR_REF(protoEdge, name);
+ edgeName = getOrInternString<char16_t>(internedTwoByteStrings, edgeNameOrRef);
+ if (NS_WARN_IF(!edgeName))
+ return false;
+ }
+
+ edges.infallibleAppend(DeserializedEdge(referent, edgeName));
+ }
+
+ Maybe<StackFrameId> allocationStack;
+ if (node.has_allocationstack()) {
+ StackFrameId id = 0;
+ if (NS_WARN_IF(!saveStackFrame(node.allocationstack(), id)))
+ return false;
+ allocationStack.emplace(id);
+ }
+ MOZ_ASSERT(allocationStack.isSome() == node.has_allocationstack());
+
+ const char* jsObjectClassName = nullptr;
+ if (node.JSObjectClassNameOrRef_case() != protobuf::Node::JSOBJECTCLASSNAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> clsNameOrRef = GET_STRING_OR_REF(node, jsobjectclassname);
+ jsObjectClassName = getOrInternString<char>(internedOneByteStrings, clsNameOrRef);
+ if (NS_WARN_IF(!jsObjectClassName))
+ return false;
+ }
+
+ const char* scriptFilename = nullptr;
+ if (node.ScriptFilenameOrRef_case() != protobuf::Node::SCRIPTFILENAMEORREF_NOT_SET) {
+ Maybe<StringOrRef> scriptFilenameOrRef = GET_STRING_OR_REF(node, scriptfilename);
+ scriptFilename = getOrInternString<char>(internedOneByteStrings, scriptFilenameOrRef);
+ if (NS_WARN_IF(!scriptFilename))
+ return false;
+ }
+
+ if (NS_WARN_IF(!nodes.putNew(id, DeserializedNode(id, coarseType, typeName,
+ size, Move(edges),
+ allocationStack,
+ jsObjectClassName,
+ scriptFilename, *this))))
+ {
+ return false;
+ };
+
+ return true;
+}
+
+bool
+HeapSnapshot::saveStackFrame(const protobuf::StackFrame& frame,
+ StackFrameId& outFrameId)
+{
+ // NB: de-duplicated string properties must be read in the same order here as
+ // they are written in `CoreDumpWriter::getProtobufStackFrame` or else indices
+ // in references to already serialized strings will be off.
+
+ if (frame.has_ref()) {
+ // We should only get a reference to the previous frame if we have already
+ // seen the previous frame.
+ if (!frames.has(frame.ref()))
+ return false;
+
+ outFrameId = frame.ref();
+ return true;
+ }
+
+ // Incomplete message.
+ if (!frame.has_data())
+ return false;
+
+ auto data = frame.data();
+
+ if (!data.has_id())
+ return false;
+ StackFrameId id = data.id();
+
+ // This should be the first and only time we see this frame.
+ if (frames.has(id))
+ return false;
+
+ if (!data.has_line())
+ return false;
+ uint32_t line = data.line();
+
+ if (!data.has_column())
+ return false;
+ uint32_t column = data.column();
+
+ if (!data.has_issystem())
+ return false;
+ bool isSystem = data.issystem();
+
+ if (!data.has_isselfhosted())
+ return false;
+ bool isSelfHosted = data.isselfhosted();
+
+ Maybe<StringOrRef> sourceOrRef = GET_STRING_OR_REF(data, source);
+ auto source = getOrInternString<char16_t>(internedTwoByteStrings, sourceOrRef);
+ if (!source)
+ return false;
+
+ const char16_t* functionDisplayName = nullptr;
+ if (data.FunctionDisplayNameOrRef_case() !=
+ protobuf::StackFrame_Data::FUNCTIONDISPLAYNAMEORREF_NOT_SET)
+ {
+ Maybe<StringOrRef> nameOrRef = GET_STRING_OR_REF(data, functiondisplayname);
+ functionDisplayName = getOrInternString<char16_t>(internedTwoByteStrings, nameOrRef);
+ if (!functionDisplayName)
+ return false;
+ }
+
+ Maybe<StackFrameId> parent;
+ if (data.has_parent()) {
+ StackFrameId parentId = 0;
+ if (!saveStackFrame(data.parent(), parentId))
+ return false;
+ parent = Some(parentId);
+ }
+
+ if (!frames.putNew(id, DeserializedStackFrame(id, parent, line, column,
+ source, functionDisplayName,
+ isSystem, isSelfHosted, *this)))
+ {
+ return false;
+ }
+
+ outFrameId = id;
+ return true;
+}
+
+#undef GET_STRING_OR_REF_WITH_PROP_NAMES
+#undef GET_STRING_OR_REF
+
+// Because protobuf messages aren't self-delimiting, we serialize each message
+// preceded by its size in bytes. When deserializing, we read this size and then
+// limit reading from the stream to the given byte size. If we didn't, then the
+// first message would consume the entire stream.
+static bool
+readSizeOfNextMessage(ZeroCopyInputStream& stream, uint32_t* sizep)
+{
+ MOZ_ASSERT(sizep);
+ CodedInputStream codedStream(&stream);
+ return codedStream.ReadVarint32(sizep) && *sizep > 0;
+}
+
+bool
+HeapSnapshot::init(JSContext* cx, const uint8_t* buffer, uint32_t size)
+{
+ if (!nodes.init() || !frames.init())
+ return false;
+
+ ArrayInputStream stream(buffer, size);
+ GzipInputStream gzipStream(&stream);
+ uint32_t sizeOfMessage = 0;
+
+ // First is the metadata.
+
+ protobuf::Metadata metadata;
+ if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
+ return false;
+ if (!parseMessage(gzipStream, sizeOfMessage, metadata))
+ return false;
+ if (metadata.has_timestamp())
+ timestamp.emplace(metadata.timestamp());
+
+ // Next is the root node.
+
+ protobuf::Node root;
+ if (NS_WARN_IF(!readSizeOfNextMessage(gzipStream, &sizeOfMessage)))
+ return false;
+ if (!parseMessage(gzipStream, sizeOfMessage, root))
+ return false;
+
+ // Although the id is optional in the protobuf format for future proofing, we
+ // can't currently do anything without it.
+ if (NS_WARN_IF(!root.has_id()))
+ return false;
+ rootId = root.id();
+
+ // The set of all node ids we've found edges pointing to.
+ NodeIdSet edgeReferents(cx);
+ if (NS_WARN_IF(!edgeReferents.init()))
+ return false;
+
+ if (NS_WARN_IF(!saveNode(root, edgeReferents)))
+ return false;
+
+ // Finally, the rest of the nodes in the core dump.
+
+ // Test for the end of the stream. The protobuf library gives no way to tell
+ // the difference between an underlying read error and the stream being
+ // done. All we can do is attempt to read the size of the next message and
+ // extrapolate guestimations from the result of that operation.
+ while (readSizeOfNextMessage(gzipStream, &sizeOfMessage)) {
+ protobuf::Node node;
+ if (!parseMessage(gzipStream, sizeOfMessage, node))
+ return false;
+ if (NS_WARN_IF(!saveNode(node, edgeReferents)))
+ return false;
+ }
+
+ // Check the set of node ids referred to by edges we found and ensure that we
+ // have the node corresponding to each id. If we don't have all of them, it is
+ // unsafe to perform analyses of this heap snapshot.
+ for (auto range = edgeReferents.all(); !range.empty(); range.popFront()) {
+ if (NS_WARN_IF(!nodes.has(range.front())))
+ return false;
+ }
+
+ return true;
+}
+
+
+/*** Heap Snapshot Analyses ***********************************************************************/
+
+void
+HeapSnapshot::TakeCensus(JSContext* cx, JS::HandleObject options,
+ JS::MutableHandleValue rval, ErrorResult& rv)
+{
+ JS::ubi::Census census(cx);
+ if (NS_WARN_IF(!census.init())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::ubi::CountTypePtr rootType;
+ if (NS_WARN_IF(!JS::ubi::ParseCensusOptions(cx, census, options, rootType))) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
+ if (NS_WARN_IF(!rootCount)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
+
+ {
+ JS::AutoCheckCannotGC nogc;
+
+ JS::ubi::CensusTraversal traversal(cx, handler, nogc);
+ if (NS_WARN_IF(!traversal.init())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (NS_WARN_IF(!traversal.traverse())) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ if (NS_WARN_IF(!handler.report(cx, rval))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+void
+HeapSnapshot::DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
+ JS::MutableHandleValue rval, ErrorResult& rv) {
+ MOZ_ASSERT(breakdown);
+ JS::RootedValue breakdownVal(cx, JS::ObjectValue(*breakdown));
+ JS::ubi::CountTypePtr rootType = JS::ubi::ParseBreakdown(cx, breakdownVal);
+ if (NS_WARN_IF(!rootType)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ JS::ubi::RootedCount rootCount(cx, rootType->makeCount());
+ if (NS_WARN_IF(!rootCount)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::ubi::Node::Id id(nodeId);
+ Maybe<JS::ubi::Node> node = getNodeById(id);
+ if (NS_WARN_IF(node.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ MallocSizeOf mallocSizeOf = GetCurrentThreadDebuggerMallocSizeOf();
+ if (NS_WARN_IF(!rootCount->count(mallocSizeOf, *node))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ if (NS_WARN_IF(!rootCount->report(cx, rval))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+}
+
+
+already_AddRefed<DominatorTree>
+HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
+{
+ Maybe<JS::ubi::DominatorTree> maybeTree;
+ {
+ auto ccjscx = CycleCollectedJSContext::Get();
+ MOZ_ASSERT(ccjscx);
+ auto cx = ccjscx->Context();
+ MOZ_ASSERT(cx);
+ JS::AutoCheckCannotGC nogc(cx);
+ maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
+ }
+
+ if (NS_WARN_IF(maybeTree.isNothing())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
+}
+
+void
+HeapSnapshot::ComputeShortestPaths(JSContext*cx, uint64_t start,
+ const Sequence<uint64_t>& targets,
+ uint64_t maxNumPaths,
+ JS::MutableHandleObject results,
+ ErrorResult& rv)
+{
+ // First ensure that our inputs are valid.
+
+ if (NS_WARN_IF(maxNumPaths == 0)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ Maybe<JS::ubi::Node> startNode = getNodeById(start);
+ if (NS_WARN_IF(startNode.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (NS_WARN_IF(targets.Length() == 0)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ // Aggregate the targets into a set and make sure that they exist in the heap
+ // snapshot.
+
+ JS::ubi::NodeSet targetsSet;
+ if (NS_WARN_IF(!targetsSet.init())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ for (const auto& target : targets) {
+ Maybe<JS::ubi::Node> targetNode = getNodeById(target);
+ if (NS_WARN_IF(targetNode.isNothing())) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return;
+ }
+
+ if (NS_WARN_IF(!targetsSet.put(*targetNode))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ // Walk the heap graph and find the shortest paths.
+
+ Maybe<ShortestPaths> maybeShortestPaths;
+ {
+ JS::AutoCheckCannotGC nogc(cx);
+ maybeShortestPaths = ShortestPaths::Create(cx, nogc, maxNumPaths, *startNode,
+ Move(targetsSet));
+ }
+
+ if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ auto& shortestPaths = *maybeShortestPaths;
+
+ // Convert the results into a Map object mapping target node IDs to arrays of
+ // paths found.
+
+ RootedObject resultsMap(cx, JS::NewMapObject(cx));
+ if (NS_WARN_IF(!resultsMap)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ for (auto range = shortestPaths.eachTarget(); !range.empty(); range.popFront()) {
+ JS::RootedValue key(cx, JS::NumberValue(range.front().identifier()));
+ JS::AutoValueVector paths(cx);
+
+ bool ok = shortestPaths.forEachPath(range.front(), [&](JS::ubi::Path& path) {
+ JS::AutoValueVector pathValues(cx);
+
+ for (JS::ubi::BackEdge* edge : path) {
+ JS::RootedObject pathPart(cx, JS_NewPlainObject(cx));
+ if (!pathPart) {
+ return false;
+ }
+
+ JS::RootedValue predecessor(cx, NumberValue(edge->predecessor().identifier()));
+ if (!JS_DefineProperty(cx, pathPart, "predecessor", predecessor, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ RootedValue edgeNameVal(cx, NullValue());
+ if (edge->name()) {
+ RootedString edgeName(cx, JS_AtomizeUCString(cx, edge->name().get()));
+ if (!edgeName) {
+ return false;
+ }
+ edgeNameVal = StringValue(edgeName);
+ }
+
+ if (!JS_DefineProperty(cx, pathPart, "edge", edgeNameVal, JSPROP_ENUMERATE)) {
+ return false;
+ }
+
+ if (!pathValues.append(ObjectValue(*pathPart))) {
+ return false;
+ }
+ }
+
+ RootedObject pathObj(cx, JS_NewArrayObject(cx, pathValues));
+ return pathObj && paths.append(ObjectValue(*pathObj));
+ });
+
+ if (NS_WARN_IF(!ok)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::RootedObject pathsArray(cx, JS_NewArrayObject(cx, paths));
+ if (NS_WARN_IF(!pathsArray)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ JS::RootedValue pathsVal(cx, ObjectValue(*pathsArray));
+ if (NS_WARN_IF(!JS::MapSet(cx, resultsMap, key, pathsVal))) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+ }
+
+ results.set(resultsMap);
+}
+
+/*** Saving Heap Snapshots ************************************************************************/
+
+// If we are only taking a snapshot of the heap affected by the given set of
+// globals, find the set of compartments the globals are allocated
+// within. Returns false on OOM failure.
+static bool
+PopulateCompartmentsWithGlobals(CompartmentSet& compartments, AutoObjectVector& globals)
+{
+ if (!compartments.init())
+ return false;
+
+ unsigned length = globals.length();
+ for (unsigned i = 0; i < length; i++) {
+ if (!compartments.put(GetObjectCompartment(globals[i])))
+ return false;
+ }
+
+ return true;
+}
+
+// Add the given set of globals as explicit roots in the given roots
+// list. Returns false on OOM failure.
+static bool
+AddGlobalsAsRoots(AutoObjectVector& globals, ubi::RootList& roots)
+{
+ unsigned length = globals.length();
+ for (unsigned i = 0; i < length; i++) {
+ if (!roots.addRoot(ubi::Node(globals[i].get()),
+ u"heap snapshot global"))
+ {
+ return false;
+ }
+ }
+ return true;
+}
+
+// Choose roots and limits for a traversal, given `boundaries`. Set `roots` to
+// the set of nodes within the boundaries that are referred to by nodes
+// outside. If `boundaries` does not include all JS compartments, initialize
+// `compartments` to the set of included compartments; otherwise, leave
+// `compartments` uninitialized. (You can use compartments.initialized() to
+// check.)
+//
+// If `boundaries` is incoherent, or we encounter an error while trying to
+// handle it, or we run out of memory, set `rv` appropriately and return
+// `false`.
+static bool
+EstablishBoundaries(JSContext* cx,
+ ErrorResult& rv,
+ const HeapSnapshotBoundaries& boundaries,
+ ubi::RootList& roots,
+ CompartmentSet& compartments)
+{
+ MOZ_ASSERT(!roots.initialized());
+ MOZ_ASSERT(!compartments.initialized());
+
+ bool foundBoundaryProperty = false;
+
+ if (boundaries.mRuntime.WasPassed()) {
+ foundBoundaryProperty = true;
+
+ if (!boundaries.mRuntime.Value()) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ if (!roots.init()) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ }
+
+ if (boundaries.mDebugger.WasPassed()) {
+ if (foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+ foundBoundaryProperty = true;
+
+ JSObject* dbgObj = boundaries.mDebugger.Value();
+ if (!dbgObj || !dbg::IsDebugger(*dbgObj)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ AutoObjectVector globals(cx);
+ if (!dbg::GetDebuggeeGlobals(cx, *dbgObj, globals) ||
+ !PopulateCompartmentsWithGlobals(compartments, globals) ||
+ !roots.init(compartments) ||
+ !AddGlobalsAsRoots(globals, roots))
+ {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ }
+
+ if (boundaries.mGlobals.WasPassed()) {
+ if (foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+ foundBoundaryProperty = true;
+
+ uint32_t length = boundaries.mGlobals.Value().Length();
+ if (length == 0) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ AutoObjectVector globals(cx);
+ for (uint32_t i = 0; i < length; i++) {
+ JSObject* global = boundaries.mGlobals.Value().ElementAt(i);
+ if (!JS_IsGlobalObject(global)) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+ if (!globals.append(global)) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ }
+
+ if (!PopulateCompartmentsWithGlobals(compartments, globals) ||
+ !roots.init(compartments) ||
+ !AddGlobalsAsRoots(globals, roots))
+ {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return false;
+ }
+ }
+
+ if (!foundBoundaryProperty) {
+ rv.Throw(NS_ERROR_INVALID_ARG);
+ return false;
+ }
+
+ MOZ_ASSERT(roots.initialized());
+ MOZ_ASSERT_IF(boundaries.mDebugger.WasPassed(), compartments.initialized());
+ MOZ_ASSERT_IF(boundaries.mGlobals.WasPassed(), compartments.initialized());
+ return true;
+}
+
+
+// A variant covering all the various two-byte strings that we can get from the
+// ubi::Node API.
+class TwoByteString : public Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>
+{
+ using Base = Variant<JSAtom*, const char16_t*, JS::ubi::EdgeName>;
+
+ struct AsTwoByteStringMatcher
+ {
+ TwoByteString match(JSAtom* atom) {
+ return TwoByteString(atom);
+ }
+
+ TwoByteString match(const char16_t* chars) {
+ return TwoByteString(chars);
+ }
+ };
+
+ struct IsNonNullMatcher
+ {
+ template<typename T>
+ bool match(const T& t) { return t != nullptr; }
+ };
+
+ struct LengthMatcher
+ {
+ size_t match(JSAtom* atom) {
+ MOZ_ASSERT(atom);
+ JS::ubi::AtomOrTwoByteChars s(atom);
+ return s.length();
+ }
+
+ size_t match(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+ return NS_strlen(chars);
+ }
+
+ size_t match(const JS::ubi::EdgeName& ptr) {
+ MOZ_ASSERT(ptr);
+ return NS_strlen(ptr.get());
+ }
+ };
+
+ struct CopyToBufferMatcher
+ {
+ RangedPtr<char16_t> destination;
+ size_t maxLength;
+
+ CopyToBufferMatcher(RangedPtr<char16_t> destination, size_t maxLength)
+ : destination(destination)
+ , maxLength(maxLength)
+ { }
+
+ size_t match(JS::ubi::EdgeName& ptr) {
+ return ptr ? match(ptr.get()) : 0;
+ }
+
+ size_t match(JSAtom* atom) {
+ MOZ_ASSERT(atom);
+ JS::ubi::AtomOrTwoByteChars s(atom);
+ return s.copyToBuffer(destination, maxLength);
+ }
+
+ size_t match(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+ JS::ubi::AtomOrTwoByteChars s(chars);
+ return s.copyToBuffer(destination, maxLength);
+ }
+ };
+
+public:
+ template<typename T>
+ MOZ_IMPLICIT TwoByteString(T&& rhs) : Base(Forward<T>(rhs)) { }
+
+ template<typename T>
+ TwoByteString& operator=(T&& rhs) {
+ MOZ_ASSERT(this != &rhs, "self-move disallowed");
+ this->~TwoByteString();
+ new (this) TwoByteString(Forward<T>(rhs));
+ return *this;
+ }
+
+ TwoByteString(const TwoByteString&) = delete;
+ TwoByteString& operator=(const TwoByteString&) = delete;
+
+ // Rewrap the inner value of a JS::ubi::AtomOrTwoByteChars as a TwoByteString.
+ static TwoByteString from(JS::ubi::AtomOrTwoByteChars&& s) {
+ AsTwoByteStringMatcher m;
+ return s.match(m);
+ }
+
+ // Returns true if the given TwoByteString is non-null, false otherwise.
+ bool isNonNull() const {
+ IsNonNullMatcher m;
+ return match(m);
+ }
+
+ // Return the length of the string, 0 if it is null.
+ size_t length() const {
+ LengthMatcher m;
+ return match(m);
+ }
+
+ // Copy the contents of a TwoByteString into the provided buffer. The buffer
+ // is NOT null terminated. The number of characters written is returned.
+ size_t copyToBuffer(RangedPtr<char16_t> destination, size_t maxLength) {
+ CopyToBufferMatcher m(destination, maxLength);
+ return match(m);
+ }
+
+ struct HashPolicy;
+};
+
+// A hashing policy for TwoByteString.
+//
+// Atoms are pointer hashed and use pointer equality, which means that we
+// tolerate some duplication across atoms and the other two types of two-byte
+// strings. In practice, we expect the amount of this duplication to be very low
+// because each type is generally a different semantic thing in addition to
+// having a slightly different representation. For example, the set of edge
+// names and the set stack frames' source names naturally tend not to overlap
+// very much if at all.
+struct TwoByteString::HashPolicy {
+ using Lookup = TwoByteString;
+
+ struct HashingMatcher {
+ js::HashNumber match(const JSAtom* atom) {
+ return js::DefaultHasher<const JSAtom*>::hash(atom);
+ }
+
+ js::HashNumber match(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+ auto length = NS_strlen(chars);
+ return HashString(chars, length);
+ }
+
+ js::HashNumber match(const JS::ubi::EdgeName& ptr) {
+ MOZ_ASSERT(ptr);
+ return match(ptr.get());
+ }
+ };
+
+ static js::HashNumber hash(const Lookup& l) {
+ HashingMatcher hasher;
+ return l.match(hasher);
+ }
+
+ struct EqualityMatcher {
+ const TwoByteString& rhs;
+ explicit EqualityMatcher(const TwoByteString& rhs) : rhs(rhs) { }
+
+ bool match(const JSAtom* atom) {
+ return rhs.is<JSAtom*>() && rhs.as<JSAtom*>() == atom;
+ }
+
+ bool match(const char16_t* chars) {
+ MOZ_ASSERT(chars);
+
+ const char16_t* rhsChars = nullptr;
+ if (rhs.is<const char16_t*>())
+ rhsChars = rhs.as<const char16_t*>();
+ else if (rhs.is<JS::ubi::EdgeName>())
+ rhsChars = rhs.as<JS::ubi::EdgeName>().get();
+ else
+ return false;
+ MOZ_ASSERT(rhsChars);
+
+ auto length = NS_strlen(chars);
+ if (NS_strlen(rhsChars) != length)
+ return false;
+
+ return memcmp(chars, rhsChars, length * sizeof(char16_t)) == 0;
+ }
+
+ bool match(const JS::ubi::EdgeName& ptr) {
+ MOZ_ASSERT(ptr);
+ return match(ptr.get());
+ }
+ };
+
+ static bool match(const TwoByteString& k, const Lookup& l) {
+ EqualityMatcher eq(l);
+ return k.match(eq);
+ }
+
+ static void rekey(TwoByteString& k, TwoByteString&& newKey) {
+ k = Move(newKey);
+ }
+};
+
+// Returns whether `edge` should be included in a heap snapshot of
+// `compartments`. The optional `policy` out-param is set to INCLUDE_EDGES
+// if we want to include the referent's edges, or EXCLUDE_EDGES if we don't
+// want to include them.
+static bool
+ShouldIncludeEdge(JS::CompartmentSet* compartments,
+ const ubi::Node& origin, const ubi::Edge& edge,
+ CoreDumpWriter::EdgePolicy* policy = nullptr)
+{
+ if (policy) {
+ *policy = CoreDumpWriter::INCLUDE_EDGES;
+ }
+
+ if (!compartments) {
+ // We aren't targeting a particular set of compartments, so serialize all the
+ // things!
+ return true;
+ }
+
+ // We are targeting a particular set of compartments. If this node is in our target
+ // set, serialize it and all of its edges. If this node is _not_ in our
+ // target set, we also serialize under the assumption that it is a shared
+ // resource being used by something in our target compartments since we reached it
+ // by traversing the heap graph. However, we do not serialize its outgoing
+ // edges and we abandon further traversal from this node.
+ //
+ // If the node does not belong to any compartment, we also serialize its outgoing
+ // edges. This case is relevant for Shapes: they don't belong to a specific
+ // compartment and contain edges to parent/kids Shapes we want to include. Note
+ // that these Shapes may contain pointers into our target compartment (the
+ // Shape's getter/setter JSObjects). However, we do not serialize nodes in other
+ // compartments that are reachable from these non-compartment nodes.
+
+ JSCompartment* compartment = edge.referent.compartment();
+
+ if (!compartment || compartments->has(compartment)) {
+ return true;
+ }
+
+ if (policy) {
+ *policy = CoreDumpWriter::EXCLUDE_EDGES;
+ }
+
+ return !!origin.compartment();
+}
+
+// A `CoreDumpWriter` that serializes nodes to protobufs and writes them to the
+// given `ZeroCopyOutputStream`.
+class MOZ_STACK_CLASS StreamWriter : public CoreDumpWriter
+{
+ using FrameSet = js::HashSet<uint64_t>;
+ using TwoByteStringMap = js::HashMap<TwoByteString, uint64_t, TwoByteString::HashPolicy>;
+ using OneByteStringMap = js::HashMap<const char*, uint64_t>;
+
+ JSContext* cx;
+ bool wantNames;
+ // The set of |JS::ubi::StackFrame::identifier()|s that have already been
+ // serialized and written to the core dump.
+ FrameSet framesAlreadySerialized;
+ // The set of two-byte strings that have already been serialized and written
+ // to the core dump.
+ TwoByteStringMap twoByteStringsAlreadySerialized;
+ // The set of one-byte strings that have already been serialized and written
+ // to the core dump.
+ OneByteStringMap oneByteStringsAlreadySerialized;
+
+ ::google::protobuf::io::ZeroCopyOutputStream& stream;
+
+ JS::CompartmentSet* compartments;
+
+ bool writeMessage(const ::google::protobuf::MessageLite& message) {
+ // We have to create a new CodedOutputStream when writing each message so
+ // that the 64MB size limit used by Coded{Output,Input}Stream to prevent
+ // integer overflow is enforced per message rather than on the whole stream.
+ ::google::protobuf::io::CodedOutputStream codedStream(&stream);
+ codedStream.WriteVarint32(message.ByteSize());
+ message.SerializeWithCachedSizes(&codedStream);
+ return !codedStream.HadError();
+ }
+
+ // Attach the full two-byte string or a reference to a two-byte string that
+ // has already been serialized to a protobuf message.
+ template <typename SetStringFunction,
+ typename SetRefFunction>
+ bool attachTwoByteString(TwoByteString& string, SetStringFunction setString,
+ SetRefFunction setRef) {
+ auto ptr = twoByteStringsAlreadySerialized.lookupForAdd(string);
+ if (ptr) {
+ setRef(ptr->value());
+ return true;
+ }
+
+ auto length = string.length();
+ auto stringData = MakeUnique<std::string>(length * sizeof(char16_t), '\0');
+ if (!stringData)
+ return false;
+
+ auto buf = const_cast<char16_t*>(reinterpret_cast<const char16_t*>(stringData->data()));
+ string.copyToBuffer(RangedPtr<char16_t>(buf, length), length);
+
+ uint64_t ref = twoByteStringsAlreadySerialized.count();
+ if (!twoByteStringsAlreadySerialized.add(ptr, Move(string), ref))
+ return false;
+
+ setString(stringData.release());
+ return true;
+ }
+
+ // Attach the full one-byte string or a reference to a one-byte string that
+ // has already been serialized to a protobuf message.
+ template <typename SetStringFunction,
+ typename SetRefFunction>
+ bool attachOneByteString(const char* string, SetStringFunction setString,
+ SetRefFunction setRef) {
+ auto ptr = oneByteStringsAlreadySerialized.lookupForAdd(string);
+ if (ptr) {
+ setRef(ptr->value());
+ return true;
+ }
+
+ auto length = strlen(string);
+ auto stringData = MakeUnique<std::string>(string, length);
+ if (!stringData)
+ return false;
+
+ uint64_t ref = oneByteStringsAlreadySerialized.count();
+ if (!oneByteStringsAlreadySerialized.add(ptr, string, ref))
+ return false;
+
+ setString(stringData.release());
+ return true;
+ }
+
+ protobuf::StackFrame* getProtobufStackFrame(JS::ubi::StackFrame& frame,
+ size_t depth = 1) {
+ // NB: de-duplicated string properties must be written in the same order
+ // here as they are read in `HeapSnapshot::saveStackFrame` or else indices
+ // in references to already serialized strings will be off.
+
+ MOZ_ASSERT(frame,
+ "null frames should be represented as the lack of a serialized "
+ "stack frame");
+
+ auto id = frame.identifier();
+ auto protobufStackFrame = MakeUnique<protobuf::StackFrame>();
+ if (!protobufStackFrame)
+ return nullptr;
+
+ if (framesAlreadySerialized.has(id)) {
+ protobufStackFrame->set_ref(id);
+ return protobufStackFrame.release();
+ }
+
+ auto data = MakeUnique<protobuf::StackFrame_Data>();
+ if (!data)
+ return nullptr;
+
+ data->set_id(id);
+ data->set_line(frame.line());
+ data->set_column(frame.column());
+ data->set_issystem(frame.isSystem());
+ data->set_isselfhosted(frame.isSelfHosted(cx));
+
+ auto dupeSource = TwoByteString::from(frame.source());
+ if (!attachTwoByteString(dupeSource,
+ [&] (std::string* source) { data->set_allocated_source(source); },
+ [&] (uint64_t ref) { data->set_sourceref(ref); }))
+ {
+ return nullptr;
+ }
+
+ auto dupeName = TwoByteString::from(frame.functionDisplayName());
+ if (dupeName.isNonNull()) {
+ if (!attachTwoByteString(dupeName,
+ [&] (std::string* name) { data->set_allocated_functiondisplayname(name); },
+ [&] (uint64_t ref) { data->set_functiondisplaynameref(ref); }))
+ {
+ return nullptr;
+ }
+ }
+
+ auto parent = frame.parent();
+ if (parent && depth < HeapSnapshot::MAX_STACK_DEPTH) {
+ auto protobufParent = getProtobufStackFrame(parent, depth + 1);
+ if (!protobufParent)
+ return nullptr;
+ data->set_allocated_parent(protobufParent);
+ }
+
+ protobufStackFrame->set_allocated_data(data.release());
+
+ if (!framesAlreadySerialized.put(id))
+ return nullptr;
+
+ return protobufStackFrame.release();
+ }
+
+public:
+ StreamWriter(JSContext* cx,
+ ::google::protobuf::io::ZeroCopyOutputStream& stream,
+ bool wantNames,
+ JS::CompartmentSet* compartments)
+ : cx(cx)
+ , wantNames(wantNames)
+ , framesAlreadySerialized(cx)
+ , twoByteStringsAlreadySerialized(cx)
+ , oneByteStringsAlreadySerialized(cx)
+ , stream(stream)
+ , compartments(compartments)
+ { }
+
+ bool init() {
+ return framesAlreadySerialized.init() &&
+ twoByteStringsAlreadySerialized.init() &&
+ oneByteStringsAlreadySerialized.init();
+ }
+
+ ~StreamWriter() override { }
+
+ virtual bool writeMetadata(uint64_t timestamp) final {
+ protobuf::Metadata metadata;
+ metadata.set_timestamp(timestamp);
+ return writeMessage(metadata);
+ }
+
+ virtual bool writeNode(const JS::ubi::Node& ubiNode,
+ EdgePolicy includeEdges) override final {
+ // NB: de-duplicated string properties must be written in the same order
+ // here as they are read in `HeapSnapshot::saveNode` or else indices in
+ // references to already serialized strings will be off.
+
+ protobuf::Node protobufNode;
+ protobufNode.set_id(ubiNode.identifier());
+
+ protobufNode.set_coarsetype(JS::ubi::CoarseTypeToUint32(ubiNode.coarseType()));
+
+ auto typeName = TwoByteString(ubiNode.typeName());
+ if (NS_WARN_IF(!attachTwoByteString(typeName,
+ [&] (std::string* name) { protobufNode.set_allocated_typename_(name); },
+ [&] (uint64_t ref) { protobufNode.set_typenameref(ref); })))
+ {
+ return false;
+ }
+
+ mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
+ MOZ_ASSERT(mallocSizeOf);
+ protobufNode.set_size(ubiNode.size(mallocSizeOf));
+
+ if (includeEdges) {
+ auto edges = ubiNode.edges(cx, wantNames);
+ if (NS_WARN_IF(!edges))
+ return false;
+
+ for ( ; !edges->empty(); edges->popFront()) {
+ ubi::Edge& ubiEdge = edges->front();
+ if (!ShouldIncludeEdge(compartments, ubiNode, ubiEdge)) {
+ continue;
+ }
+
+ protobuf::Edge* protobufEdge = protobufNode.add_edges();
+ if (NS_WARN_IF(!protobufEdge)) {
+ return false;
+ }
+
+ protobufEdge->set_referent(ubiEdge.referent.identifier());
+
+ if (wantNames && ubiEdge.name) {
+ TwoByteString edgeName(Move(ubiEdge.name));
+ if (NS_WARN_IF(!attachTwoByteString(edgeName,
+ [&] (std::string* name) { protobufEdge->set_allocated_name(name); },
+ [&] (uint64_t ref) { protobufEdge->set_nameref(ref); })))
+ {
+ return false;
+ }
+ }
+ }
+ }
+
+ if (ubiNode.hasAllocationStack()) {
+ auto ubiStackFrame = ubiNode.allocationStack();
+ auto protoStackFrame = getProtobufStackFrame(ubiStackFrame);
+ if (NS_WARN_IF(!protoStackFrame))
+ return false;
+ protobufNode.set_allocated_allocationstack(protoStackFrame);
+ }
+
+ if (auto className = ubiNode.jsObjectClassName()) {
+ if (NS_WARN_IF(!attachOneByteString(className,
+ [&] (std::string* name) { protobufNode.set_allocated_jsobjectclassname(name); },
+ [&] (uint64_t ref) { protobufNode.set_jsobjectclassnameref(ref); })))
+ {
+ return false;
+ }
+ }
+
+ if (auto scriptFilename = ubiNode.scriptFilename()) {
+ if (NS_WARN_IF(!attachOneByteString(scriptFilename,
+ [&] (std::string* name) { protobufNode.set_allocated_scriptfilename(name); },
+ [&] (uint64_t ref) { protobufNode.set_scriptfilenameref(ref); })))
+ {
+ return false;
+ }
+ }
+
+ return writeMessage(protobufNode);
+ }
+};
+
+// A JS::ubi::BreadthFirst handler that serializes a snapshot of the heap into a
+// core dump.
+class MOZ_STACK_CLASS HeapSnapshotHandler
+{
+ CoreDumpWriter& writer;
+ JS::CompartmentSet* compartments;
+
+public:
+ // For telemetry.
+ uint32_t nodeCount;
+ uint32_t edgeCount;
+
+ HeapSnapshotHandler(CoreDumpWriter& writer,
+ JS::CompartmentSet* compartments)
+ : writer(writer),
+ compartments(compartments)
+ { }
+
+ // JS::ubi::BreadthFirst handler interface.
+
+ class NodeData { };
+ typedef JS::ubi::BreadthFirst<HeapSnapshotHandler> Traversal;
+ bool operator() (Traversal& traversal,
+ JS::ubi::Node origin,
+ const JS::ubi::Edge& edge,
+ NodeData*,
+ bool first)
+ {
+ edgeCount++;
+
+ // We're only interested in the first time we reach edge.referent, not in
+ // every edge arriving at that node. "But, don't we want to serialize every
+ // edge in the heap graph?" you ask. Don't worry! This edge is still
+ // serialized into the core dump. Serializing a node also serializes each of
+ // its edges, and if we are traversing a given edge, we must have already
+ // visited and serialized the origin node and its edges.
+ if (!first)
+ return true;
+
+ CoreDumpWriter::EdgePolicy policy;
+ if (!ShouldIncludeEdge(compartments, origin, edge, &policy))
+ return true;
+
+ nodeCount++;
+
+ if (policy == CoreDumpWriter::EXCLUDE_EDGES)
+ traversal.abandonReferent();
+
+ return writer.writeNode(edge.referent, policy);
+ }
+};
+
+
+bool
+WriteHeapGraph(JSContext* cx,
+ const JS::ubi::Node& node,
+ CoreDumpWriter& writer,
+ bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC,
+ uint32_t& outNodeCount,
+ uint32_t& outEdgeCount)
+{
+ // Serialize the starting node to the core dump.
+
+ if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
+ return false;
+ }
+
+ // Walk the heap graph starting from the given node and serialize it into the
+ // core dump.
+
+ HeapSnapshotHandler handler(writer, compartments);
+ HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
+ if (!traversal.init())
+ return false;
+ traversal.wantNames = wantNames;
+
+ bool ok = traversal.addStartVisited(node) &&
+ traversal.traverse();
+
+ if (ok) {
+ outNodeCount = handler.nodeCount;
+ outEdgeCount = handler.edgeCount;
+ }
+
+ return ok;
+}
+
+static unsigned long
+msSinceProcessCreation(const TimeStamp& now)
+{
+ bool ignored;
+ auto duration = now - TimeStamp::ProcessCreation(ignored);
+ return (unsigned long) duration.ToMilliseconds();
+}
+
+/* static */ already_AddRefed<nsIFile>
+HeapSnapshot::CreateUniqueCoreDumpFile(ErrorResult& rv,
+ const TimeStamp& now,
+ nsAString& outFilePath)
+{
+ nsCOMPtr<nsIFile> file;
+ rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(file));
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ auto ms = msSinceProcessCreation(now);
+ rv = file->AppendNative(nsPrintfCString("%lu.fxsnapshot", ms));
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ rv = file->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0666);
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ rv = file->GetPath(outFilePath);
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ return file.forget();
+}
+
+// Deletion policy for cleaning up PHeapSnapshotTempFileHelperChild pointers.
+class DeleteHeapSnapshotTempFileHelperChild
+{
+public:
+ constexpr DeleteHeapSnapshotTempFileHelperChild() { }
+
+ void operator()(PHeapSnapshotTempFileHelperChild* ptr) const {
+ Unused << NS_WARN_IF(!HeapSnapshotTempFileHelperChild::Send__delete__(ptr));
+ }
+};
+
+// A UniquePtr alias to automatically manage PHeapSnapshotTempFileHelperChild
+// pointers.
+using UniqueHeapSnapshotTempFileHelperChild = UniquePtr<PHeapSnapshotTempFileHelperChild,
+ DeleteHeapSnapshotTempFileHelperChild>;
+
+// Get an nsIOutputStream that we can write the heap snapshot to. In non-e10s
+// and in the e10s parent process, open a file directly and create an output
+// stream for it. In e10s child processes, we are sandboxed without access to
+// the filesystem. Use IPDL to request a file descriptor from the parent
+// process.
+static already_AddRefed<nsIOutputStream>
+getCoreDumpOutputStream(ErrorResult& rv, TimeStamp& start, nsAString& outFilePath)
+{
+ if (XRE_IsParentProcess()) {
+ // Create the file and open the output stream directly.
+
+ nsCOMPtr<nsIFile> file = HeapSnapshot::CreateUniqueCoreDumpFile(rv,
+ start,
+ outFilePath);
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), file,
+ PR_WRONLY, -1, 0);
+ if (NS_WARN_IF(rv.Failed()))
+ return nullptr;
+
+ return outputStream.forget();
+ } else {
+ // Request a file descriptor from the parent process over IPDL.
+
+ auto cc = ContentChild::GetSingleton();
+ if (!cc) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ UniqueHeapSnapshotTempFileHelperChild helper(
+ cc->SendPHeapSnapshotTempFileHelperConstructor());
+ if (NS_WARN_IF(!helper)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ OpenHeapSnapshotTempFileResponse response;
+ if (!helper->SendOpenHeapSnapshotTempFile(&response)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+ if (response.type() == OpenHeapSnapshotTempFileResponse::Tnsresult) {
+ rv.Throw(response.get_nsresult());
+ return nullptr;
+ }
+
+ auto opened = response.get_OpenedFile();
+ outFilePath = opened.path();
+ nsCOMPtr<nsIOutputStream> outputStream =
+ FileDescriptorOutputStream::Create(opened.descriptor());
+ if (NS_WARN_IF(!outputStream)) {
+ rv.Throw(NS_ERROR_UNEXPECTED);
+ return nullptr;
+ }
+
+ return outputStream.forget();
+ }
+}
+
+} // namespace devtools
+
+namespace dom {
+
+using namespace JS;
+using namespace devtools;
+
+/* static */ void
+ThreadSafeChromeUtils::SaveHeapSnapshot(GlobalObject& global,
+ const HeapSnapshotBoundaries& boundaries,
+ nsAString& outFilePath,
+ ErrorResult& rv)
+{
+ auto start = TimeStamp::Now();
+
+ bool wantNames = true;
+ CompartmentSet compartments;
+ uint32_t nodeCount = 0;
+ uint32_t edgeCount = 0;
+
+ nsCOMPtr<nsIOutputStream> outputStream = getCoreDumpOutputStream(rv, start, outFilePath);
+ if (NS_WARN_IF(rv.Failed()))
+ return;
+
+ ZeroCopyNSIOutputStream zeroCopyStream(outputStream);
+ ::google::protobuf::io::GzipOutputStream gzipStream(&zeroCopyStream);
+
+ JSContext* cx = global.Context();
+
+ {
+ Maybe<AutoCheckCannotGC> maybeNoGC;
+ ubi::RootList rootList(cx, maybeNoGC, wantNames);
+ if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
+ return;
+
+ StreamWriter writer(cx, gzipStream, wantNames,
+ compartments.initialized() ? &compartments : nullptr);
+ if (NS_WARN_IF(!writer.init())) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return;
+ }
+
+ MOZ_ASSERT(maybeNoGC.isSome());
+ ubi::Node roots(&rootList);
+
+ // Serialize the initial heap snapshot metadata to the core dump.
+ if (!writer.writeMetadata(PR_Now()) ||
+ // Serialize the heap graph to the core dump, starting from our list of
+ // roots.
+ !WriteHeapGraph(cx,
+ roots,
+ writer,
+ wantNames,
+ compartments.initialized() ? &compartments : nullptr,
+ maybeNoGC.ref(),
+ nodeCount,
+ edgeCount))
+ {
+ rv.Throw(zeroCopyStream.failed()
+ ? zeroCopyStream.result()
+ : NS_ERROR_UNEXPECTED);
+ return;
+ }
+ }
+
+ Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_SAVE_HEAP_SNAPSHOT_MS,
+ start);
+ Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_NODE_COUNT,
+ nodeCount);
+ Telemetry::Accumulate(Telemetry::DEVTOOLS_HEAP_SNAPSHOT_EDGE_COUNT,
+ edgeCount);
+}
+
+/* static */ already_AddRefed<HeapSnapshot>
+ThreadSafeChromeUtils::ReadHeapSnapshot(GlobalObject& global,
+ const nsAString& filePath,
+ ErrorResult& rv)
+{
+ auto start = TimeStamp::Now();
+
+ UniquePtr<char[]> path(ToNewCString(filePath));
+ if (!path) {
+ rv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ AutoMemMap mm;
+ rv = mm.init(path.get());
+ if (rv.Failed())
+ return nullptr;
+
+ RefPtr<HeapSnapshot> snapshot = HeapSnapshot::Create(
+ global.Context(), global, reinterpret_cast<const uint8_t*>(mm.address()),
+ mm.size(), rv);
+
+ if (!rv.Failed())
+ Telemetry::AccumulateTimeDelta(Telemetry::DEVTOOLS_READ_HEAP_SNAPSHOT_MS,
+ start);
+
+ return snapshot.forget();
+}
+
+} // namespace dom
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/HeapSnapshot.h b/devtools/shared/heapsnapshot/HeapSnapshot.h
new file mode 100644
index 000000000..0428033f6
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.h
@@ -0,0 +1,239 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshot__
+#define mozilla_devtools_HeapSnapshot__
+
+#include "js/HashTable.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/devtools/DeserializedNode.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefCounted.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+
+#include "CoreDump.pb.h"
+#include "nsCOMPtr.h"
+#include "nsCRTGlue.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsISupports.h"
+#include "nsWrapperCache.h"
+#include "nsXPCOM.h"
+
+namespace mozilla {
+namespace devtools {
+
+class DominatorTree;
+
+struct NSFreePolicy {
+ void operator()(void* ptr) {
+ NS_Free(ptr);
+ }
+};
+
+using UniqueTwoByteString = UniquePtr<char16_t[], NSFreePolicy>;
+using UniqueOneByteString = UniquePtr<char[], NSFreePolicy>;
+
+class HeapSnapshot final : public nsISupports
+ , public nsWrapperCache
+{
+ friend struct DeserializedNode;
+ friend struct DeserializedEdge;
+ friend struct DeserializedStackFrame;
+ friend class JS::ubi::Concrete<JS::ubi::DeserializedNode>;
+
+ explicit HeapSnapshot(JSContext* cx, nsISupports* aParent)
+ : timestamp(Nothing())
+ , rootId(0)
+ , nodes(cx)
+ , frames(cx)
+ , mParent(aParent)
+ {
+ MOZ_ASSERT(aParent);
+ };
+
+ // Initialize this HeapSnapshot from the given buffer that contains a
+ // serialized core dump. Do NOT take ownership of the buffer, only borrow it
+ // for the duration of the call. Return false on failure.
+ bool init(JSContext* cx, const uint8_t* buffer, uint32_t size);
+
+ using NodeIdSet = js::HashSet<NodeId>;
+
+ // Save the given `protobuf::Node` message in this `HeapSnapshot` as a
+ // `DeserializedNode`.
+ bool saveNode(const protobuf::Node& node, NodeIdSet& edgeReferents);
+
+ // Save the given `protobuf::StackFrame` message in this `HeapSnapshot` as a
+ // `DeserializedStackFrame`. The saved stack frame's id is returned via the
+ // out parameter.
+ bool saveStackFrame(const protobuf::StackFrame& frame,
+ StackFrameId& outFrameId);
+
+public:
+ // The maximum number of stack frames that we will serialize into a core
+ // dump. This helps prevent over-recursion in the protobuf library when
+ // deserializing stacks.
+ static const size_t MAX_STACK_DEPTH = 60;
+
+private:
+ // If present, a timestamp in the same units that `PR_Now` gives.
+ Maybe<uint64_t> timestamp;
+
+ // The id of the root node for this deserialized heap graph.
+ NodeId rootId;
+
+ // The set of nodes in this deserialized heap graph, keyed by id.
+ using NodeSet = js::HashSet<DeserializedNode, DeserializedNode::HashPolicy>;
+ NodeSet nodes;
+
+ // The set of stack frames in this deserialized heap graph, keyed by id.
+ using FrameSet = js::HashSet<DeserializedStackFrame,
+ DeserializedStackFrame::HashPolicy>;
+ FrameSet frames;
+
+ Vector<UniqueTwoByteString> internedTwoByteStrings;
+ Vector<UniqueOneByteString> internedOneByteStrings;
+
+ using StringOrRef = Variant<const std::string*, uint64_t>;
+
+ template<typename CharT,
+ typename InternedStringSet>
+ const CharT* getOrInternString(InternedStringSet& internedStrings,
+ Maybe<StringOrRef>& maybeStrOrRef);
+
+protected:
+ nsCOMPtr<nsISupports> mParent;
+
+ virtual ~HeapSnapshot() { }
+
+public:
+ // Create a `HeapSnapshot` from the given buffer that contains a serialized
+ // core dump. Do NOT take ownership of the buffer, only borrow it for the
+ // duration of the call.
+ static already_AddRefed<HeapSnapshot> Create(JSContext* cx,
+ dom::GlobalObject& global,
+ const uint8_t* buffer,
+ uint32_t size,
+ ErrorResult& rv);
+
+ // Creates the `$TEMP_DIR/XXXXXX-XXX.fxsnapshot` core dump file that heap
+ // snapshots are serialized into.
+ static already_AddRefed<nsIFile> CreateUniqueCoreDumpFile(ErrorResult& rv,
+ const TimeStamp& now,
+ nsAString& outFilePath);
+
+ NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+ NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(HeapSnapshot)
+ MOZ_DECLARE_REFCOUNTED_TYPENAME(HeapSnapshot)
+
+ nsISupports* GetParentObject() const { return mParent; }
+
+ virtual JSObject* WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) override;
+
+ const char16_t* borrowUniqueString(const char16_t* duplicateString,
+ size_t length);
+
+ // Get the root node of this heap snapshot's graph.
+ JS::ubi::Node getRoot() {
+ MOZ_ASSERT(nodes.initialized());
+ auto p = nodes.lookup(rootId);
+ MOZ_ASSERT(p);
+ const DeserializedNode& node = *p;
+ return JS::ubi::Node(const_cast<DeserializedNode*>(&node));
+ }
+
+ Maybe<JS::ubi::Node> getNodeById(JS::ubi::Node::Id nodeId) {
+ auto p = nodes.lookup(nodeId);
+ if (!p)
+ return Nothing();
+ return Some(JS::ubi::Node(const_cast<DeserializedNode*>(&*p)));
+ }
+
+ void TakeCensus(JSContext* cx, JS::HandleObject options,
+ JS::MutableHandleValue rval, ErrorResult& rv);
+
+ void DescribeNode(JSContext* cx, JS::HandleObject breakdown, uint64_t nodeId,
+ JS::MutableHandleValue rval, ErrorResult& rv);
+
+ already_AddRefed<DominatorTree> ComputeDominatorTree(ErrorResult& rv);
+
+ void ComputeShortestPaths(JSContext*cx, uint64_t start,
+ const dom::Sequence<uint64_t>& targets,
+ uint64_t maxNumPaths,
+ JS::MutableHandleObject results,
+ ErrorResult& rv);
+
+ dom::Nullable<uint64_t> GetCreationTime() {
+ static const uint64_t maxTime = uint64_t(1) << 53;
+ if (timestamp.isSome() && timestamp.ref() <= maxTime) {
+ return dom::Nullable<uint64_t>(timestamp.ref());
+ }
+
+ return dom::Nullable<uint64_t>();
+ }
+};
+
+// A `CoreDumpWriter` is given the data we wish to save in a core dump and
+// serializes it to disk, or memory, or a socket, etc.
+class CoreDumpWriter
+{
+public:
+ virtual ~CoreDumpWriter() { };
+
+ // Write the given bits of metadata we would like to associate with this core
+ // dump.
+ virtual bool writeMetadata(uint64_t timestamp) = 0;
+
+ enum EdgePolicy : bool {
+ INCLUDE_EDGES = true,
+ EXCLUDE_EDGES = false
+ };
+
+ // Write the given `JS::ubi::Node` to the core dump. The given `EdgePolicy`
+ // dictates whether its outgoing edges should also be written to the core
+ // dump, or excluded.
+ virtual bool writeNode(const JS::ubi::Node& node,
+ EdgePolicy includeEdges) = 0;
+};
+
+// Serialize the heap graph as seen from `node` with the given `CoreDumpWriter`.
+// If `wantNames` is true, capture edge names. If `zones` is non-null, only
+// capture the sub-graph within the zone set, otherwise capture the whole heap
+// graph. Returns false on failure.
+bool
+WriteHeapGraph(JSContext* cx,
+ const JS::ubi::Node& node,
+ CoreDumpWriter& writer,
+ bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC,
+ uint32_t& outNodeCount,
+ uint32_t& outEdgeCount);
+inline bool
+WriteHeapGraph(JSContext* cx,
+ const JS::ubi::Node& node,
+ CoreDumpWriter& writer,
+ bool wantNames,
+ JS::CompartmentSet* compartments,
+ JS::AutoCheckCannotGC& noGC)
+{
+ uint32_t ignoreNodeCount;
+ uint32_t ignoreEdgeCount;
+ return WriteHeapGraph(cx, node, writer, wantNames, compartments, noGC,
+ ignoreNodeCount, ignoreEdgeCount);
+}
+
+// Get the mozilla::MallocSizeOf for the current thread's JSRuntime.
+MallocSizeOf GetCurrentThreadDebuggerMallocSizeOf();
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshot__
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js b/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js
new file mode 100644
index 000000000..abd44fc30
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotFileUtils.js
@@ -0,0 +1,95 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Heap snapshots are always saved in the temp directory, and have a regular
+// naming convention. This module provides helpers for working with heap
+// snapshot files in a safe manner. Because we attempt to avoid unnecessary
+// copies of the heap snapshot files by checking the local filesystem for a heap
+// snapshot file with the given snapshot id, we want to ensure that we are only
+// attempting to open heap snapshot files and not `~/.ssh/id_rsa`, for
+// example. Therefore, the RDP only talks about snapshot ids, or transfering the
+// bulk file data. A file path can be recovered from a snapshot id, which allows
+// one to check for the presence of the heap snapshot file on the local file
+// system, but we don't have to worry about opening arbitrary files.
+//
+// The heap snapshot file path conventions permits the following forms:
+//
+// $TEMP_DIRECTORY/XXXXXXXXXX.fxsnapshot
+// $TEMP_DIRECTORY/XXXXXXXXXX-XXXXX.fxsnapshot
+//
+// Where the strings of "X" are zero or more digits.
+
+"use strict";
+
+const { Ci } = require("chrome");
+loader.lazyRequireGetter(this, "FileUtils",
+ "resource://gre/modules/FileUtils.jsm", true);
+loader.lazyRequireGetter(this, "OS", "resource://gre/modules/osfile.jsm", true);
+
+function getHeapSnapshotFileTemplate() {
+ return OS.Path.join(OS.Constants.Path.tmpDir, `${Date.now()}.fxsnapshot`);
+}
+
+/**
+ * Get a unique temp file path for a new heap snapshot. The file is guaranteed
+ * not to exist before this call.
+ *
+ * @returns String
+ */
+exports.getNewUniqueHeapSnapshotTempFilePath = function () {
+ let file = new FileUtils.File(getHeapSnapshotFileTemplate());
+ // The call to createUnique will append "-N" after the leaf name (but before
+ // the extension) until a new file is found and create it. This guarantees we
+ // won't accidentally choose the same file twice.
+ file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0o666);
+ return file.path;
+};
+
+function isValidSnapshotFileId(snapshotId) {
+ return /^\d+(\-\d+)?$/.test(snapshotId);
+}
+
+/**
+ * Get the file path for the given snapshot id.
+ *
+ * @param {String} snapshotId
+ *
+ * @returns String | null
+ */
+exports.getHeapSnapshotTempFilePath = function (snapshotId) {
+ // Don't want anyone sneaking "../../../.." strings into the snapshot id and
+ // trying to make us open arbitrary files.
+ if (!isValidSnapshotFileId(snapshotId)) {
+ return null;
+ }
+ return OS.Path.join(OS.Constants.Path.tmpDir, snapshotId + ".fxsnapshot");
+};
+
+/**
+ * Return true if we have the heap snapshot file for the given snapshot id on
+ * the local file system. False is returned otherwise.
+ *
+ * @returns Promise<Boolean>
+ */
+exports.haveHeapSnapshotTempFile = function (snapshotId) {
+ const path = exports.getHeapSnapshotTempFilePath(snapshotId);
+ if (!path) {
+ return Promise.resolve(false);
+ }
+
+ return OS.File.stat(path).then(() => true,
+ () => false);
+};
+
+/**
+ * Given a heap snapshot's file path, extricate the snapshot id.
+ *
+ * @param {String} path
+ *
+ * @returns String
+ */
+exports.getSnapshotIdFromPath = function (path) {
+ return path.slice(OS.Constants.Path.tmpDir.length + 1,
+ path.length - ".fxsnapshot".length);
+};
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h
new file mode 100644
index 000000000..a1d433a5e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperChild.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshotTempFileHelperChild_h
+#define mozilla_devtools_HeapSnapshotTempFileHelperChild_h
+
+#include "mozilla/devtools/PHeapSnapshotTempFileHelperChild.h"
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshotTempFileHelperChild : public PHeapSnapshotTempFileHelperChild
+{
+ explicit HeapSnapshotTempFileHelperChild() { }
+
+public:
+ static inline PHeapSnapshotTempFileHelperChild* Create();
+};
+
+/* static */ inline PHeapSnapshotTempFileHelperChild*
+HeapSnapshotTempFileHelperChild::Create()
+{
+ return new HeapSnapshotTempFileHelperChild();
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshotTempFileHelperChild_h
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp
new file mode 100644
index 000000000..7246a9daa
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
+#include "mozilla/ErrorResult.h"
+#include "private/pprio.h"
+
+#include "nsIFile.h"
+
+namespace mozilla {
+namespace devtools {
+
+using ipc::FileDescriptor;
+
+static bool
+openFileFailure(ErrorResult& rv,
+ OpenHeapSnapshotTempFileResponse* outResponse)
+{
+ *outResponse = rv.StealNSResult();
+ return true;
+}
+
+bool
+HeapSnapshotTempFileHelperParent::RecvOpenHeapSnapshotTempFile(
+ OpenHeapSnapshotTempFileResponse* outResponse)
+{
+ auto start = TimeStamp::Now();
+ ErrorResult rv;
+ nsAutoString filePath;
+ nsCOMPtr<nsIFile> file = HeapSnapshot::CreateUniqueCoreDumpFile(rv,
+ start,
+ filePath);
+ if (NS_WARN_IF(rv.Failed()))
+ return openFileFailure(rv, outResponse);
+
+ PRFileDesc* prfd;
+ rv = file->OpenNSPRFileDesc(PR_WRONLY, 0, &prfd);
+ if (NS_WARN_IF(rv.Failed()))
+ return openFileFailure(rv, outResponse);
+
+ FileDescriptor::PlatformHandleType handle =
+ FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(prfd));
+ FileDescriptor fd(handle);
+ *outResponse = OpenedFile(filePath, fd);
+ return true;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h
new file mode 100644
index 000000000..1582279da
--- /dev/null
+++ b/devtools/shared/heapsnapshot/HeapSnapshotTempFileHelperParent.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set sw=4 ts=8 et tw=80 : */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_HeapSnapshotTempFileHelperParent_h
+#define mozilla_devtools_HeapSnapshotTempFileHelperParent_h
+
+#include "mozilla/devtools/PHeapSnapshotTempFileHelperParent.h"
+
+namespace mozilla {
+namespace devtools {
+
+class HeapSnapshotTempFileHelperParent : public PHeapSnapshotTempFileHelperParent
+{
+ explicit HeapSnapshotTempFileHelperParent() { }
+ void ActorDestroy(ActorDestroyReason why) override { }
+ bool RecvOpenHeapSnapshotTempFile(OpenHeapSnapshotTempFileResponse* outResponse)
+ override;
+
+ public:
+ static inline PHeapSnapshotTempFileHelperParent* Create();
+};
+
+/* static */ inline PHeapSnapshotTempFileHelperParent*
+HeapSnapshotTempFileHelperParent::Create()
+{
+ return new HeapSnapshotTempFileHelperParent();
+}
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_HeapSnapshotTempFileHelperParent_h
diff --git a/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl b/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl
new file mode 100644
index 000000000..2576470e2
--- /dev/null
+++ b/devtools/shared/heapsnapshot/PHeapSnapshotTempFileHelper.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PContent;
+
+namespace mozilla {
+namespace devtools {
+
+struct OpenedFile
+{
+ nsString path;
+ FileDescriptor descriptor;
+};
+
+union OpenHeapSnapshotTempFileResponse
+{
+ nsresult;
+ OpenedFile;
+};
+
+sync protocol PHeapSnapshotTempFileHelper
+{
+ manager PContent;
+
+parent:
+ sync OpenHeapSnapshotTempFile() returns (OpenHeapSnapshotTempFileResponse response);
+
+ async __delete__();
+};
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
new file mode 100644
index 000000000..0c29db7f9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/devtools/ZeroCopyNSIOutputStream.h"
+
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+namespace devtools {
+
+ZeroCopyNSIOutputStream::ZeroCopyNSIOutputStream(nsCOMPtr<nsIOutputStream>& out)
+ : out(out)
+ , result_(NS_OK)
+ , amountUsed(0)
+ , writtenCount(0)
+{
+ DebugOnly<bool> nonBlocking = false;
+ MOZ_ASSERT(out->IsNonBlocking(&nonBlocking) == NS_OK);
+ MOZ_ASSERT(!nonBlocking);
+}
+
+ZeroCopyNSIOutputStream::~ZeroCopyNSIOutputStream()
+{
+ if (!failed())
+ Unused << NS_WARN_IF(NS_FAILED(writeBuffer()));
+}
+
+nsresult
+ZeroCopyNSIOutputStream::writeBuffer()
+{
+ if (failed())
+ return result_;
+
+ if (amountUsed == 0)
+ return NS_OK;
+
+ int32_t amountWritten = 0;
+ while (amountWritten < amountUsed) {
+ uint32_t justWritten = 0;
+
+ result_ = out->Write(buffer + amountWritten,
+ amountUsed - amountWritten,
+ &justWritten);
+ if (NS_WARN_IF(NS_FAILED(result_)))
+ return result_;
+
+ amountWritten += justWritten;
+ }
+
+ writtenCount += amountUsed;
+ amountUsed = 0;
+ return NS_OK;
+}
+
+// ZeroCopyOutputStream Interface
+
+bool
+ZeroCopyNSIOutputStream::Next(void** data, int* size)
+{
+ MOZ_ASSERT(data != nullptr);
+ MOZ_ASSERT(size != nullptr);
+
+ if (failed())
+ return false;
+
+ if (amountUsed == BUFFER_SIZE) {
+ if (NS_FAILED(writeBuffer()))
+ return false;
+ }
+
+ *data = buffer + amountUsed;
+ *size = BUFFER_SIZE - amountUsed;
+ amountUsed = BUFFER_SIZE;
+ return true;
+}
+
+void
+ZeroCopyNSIOutputStream::BackUp(int count)
+{
+ MOZ_ASSERT(count >= 0,
+ "Cannot back up a negative amount of bytes.");
+ MOZ_ASSERT(amountUsed == BUFFER_SIZE,
+ "Can only call BackUp directly after calling Next.");
+ MOZ_ASSERT(count <= amountUsed,
+ "Can't back up further than we've given out.");
+
+ amountUsed -= count;
+}
+
+::google::protobuf::int64
+ZeroCopyNSIOutputStream::ByteCount() const
+{
+ return writtenCount + amountUsed;
+}
+
+} // namespace devtools
+} // namespace mozilla
diff --git a/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h
new file mode 100644
index 000000000..117fc0f87
--- /dev/null
+++ b/devtools/shared/heapsnapshot/ZeroCopyNSIOutputStream.h
@@ -0,0 +1,70 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_ZeroCopyNSIOutputStream__
+#define mozilla_devtools_ZeroCopyNSIOutputStream__
+
+#include <google/protobuf/io/zero_copy_stream.h>
+#include <google/protobuf/stubs/common.h>
+
+#include "nsCOMPtr.h"
+#include "nsIOutputStream.h"
+
+namespace mozilla {
+namespace devtools {
+
+// A `google::protobuf::io::ZeroCopyOutputStream` implementation that uses an
+// `nsIOutputStream` under the covers.
+//
+// This class will automatically write and flush its data to the
+// `nsIOutputStream` in its destructor, but if you care whether that call
+// succeeds or fails, then you should call the `flush` method yourself. Errors
+// will be logged, however.
+class MOZ_STACK_CLASS ZeroCopyNSIOutputStream
+ : public ::google::protobuf::io::ZeroCopyOutputStream
+{
+ static const int BUFFER_SIZE = 8192;
+
+ // The nsIOutputStream we are streaming to.
+ nsCOMPtr<nsIOutputStream>& out;
+
+ // The buffer we write data to before passing it to the output stream.
+ char buffer[BUFFER_SIZE];
+
+ // The status of writing to the underlying output stream.
+ nsresult result_;
+
+ // The number of bytes in the buffer that have been used thus far.
+ int amountUsed;
+
+ // Excluding the amount of the buffer currently used (which hasn't been
+ // written and flushed yet), this is the number of bytes written to the output
+ // stream.
+ int64_t writtenCount;
+
+ // Write the internal buffer to the output stream and flush it.
+ nsresult writeBuffer();
+
+public:
+ explicit ZeroCopyNSIOutputStream(nsCOMPtr<nsIOutputStream>& out);
+
+ nsresult flush() { return writeBuffer(); }
+
+ // Return true if writing to the underlying output stream ever failed.
+ bool failed() const { return NS_FAILED(result_); }
+
+ nsresult result() const { return result_; }
+
+ // ZeroCopyOutputStream Interface
+ virtual ~ZeroCopyNSIOutputStream() override;
+ virtual bool Next(void** data, int* size) override;
+ virtual void BackUp(int count) override;
+ virtual ::google::protobuf::int64 ByteCount() const override;
+};
+
+} // namespace devtools
+} // namespace mozilla
+
+#endif // mozilla_devtools_ZeroCopyNSIOutputStream__
diff --git a/devtools/shared/heapsnapshot/census-tree-node.js b/devtools/shared/heapsnapshot/census-tree-node.js
new file mode 100644
index 000000000..b041e77f9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/census-tree-node.js
@@ -0,0 +1,748 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// CensusTreeNode is an intermediate representation of a census report that
+// exists between after a report is generated by taking a census and before the
+// report is rendered in the DOM. It must be dead simple to render, with no
+// further data processing or massaging needed before rendering DOM nodes. Our
+// goal is to do the census report to CensusTreeNode transformation in the
+// HeapAnalysesWorker, and ensure that the **only** work that the main thread
+// has to do is strictly DOM rendering work.
+
+const {
+ Visitor,
+ walk,
+ basisTotalBytes,
+ basisTotalCount,
+} = require("resource://devtools/shared/heapsnapshot/CensusUtils.js");
+
+// Monotonically increasing integer for CensusTreeNode `id`s.
+let censusTreeNodeIdCounter = 0;
+
+/**
+ * Return true if the given object is a SavedFrame stack object, false otherwise.
+ *
+ * @param {any} obj
+ * @returns {Boolean}
+ */
+function isSavedFrame(obj) {
+ return Object.prototype.toString.call(obj) === "[object SavedFrame]";
+}
+
+/**
+ * A CensusTreeNodeCache maps from SavedFrames to CensusTreeNodes. It is used when
+ * aggregating multiple SavedFrame allocation stack keys into a tree of many
+ * CensusTreeNodes. Each stack may share older frames, and we want to preserve
+ * this sharing when converting to CensusTreeNode, so before creating a new
+ * CensusTreeNode, we look for an existing one in one of our CensusTreeNodeCaches.
+ */
+function CensusTreeNodeCache() {}
+CensusTreeNodeCache.prototype = null;
+
+/**
+ * The value of a single entry stored in a CensusTreeNodeCache. It is a pair of
+ * the CensusTreeNode for this cache value, and the subsequent
+ * CensusTreeNodeCache for this node's children.
+ *
+ * @param {SavedFrame} frame
+ * The frame being cached.
+ */
+function CensusTreeNodeCacheValue() {
+ // The CensusTreeNode for this cache value.
+ this.node = undefined;
+ // The CensusTreeNodeCache for this frame's children.
+ this.children = undefined;
+}
+
+CensusTreeNodeCacheValue.prototype = null;
+
+/**
+ * Create a unique string for the given SavedFrame (ignoring the frame's parent
+ * chain) that can be used as a hash to key this frame within a CensusTreeNodeCache.
+ *
+ * NB: We manually hash rather than using an ES6 Map because we are purposely
+ * ignoring the parent chain and wish to consider frames with everything the
+ * same except their parents as the same.
+ *
+ * @param {SavedFrame} frame
+ * The SavedFrame object we would like to lookup in or insert into a
+ * CensusTreeNodeCache.
+ *
+ * @returns {String}
+ * The unique string that can be used as a key in a CensusTreeNodeCache.
+ */
+CensusTreeNodeCache.hashFrame = function (frame) {
+ return `FRAME,${frame.functionDisplayName},${frame.source},${frame.line},${frame.column},${frame.asyncCause}`;
+};
+
+/**
+ * Create a unique string for the given CensusTreeNode **with regards to
+ * siblings at the current depth of the tree, not within the whole tree.** It
+ * can be used as a hash to key this node within a CensusTreeNodeCache.
+ *
+ * @param {CensusTreeNode} node
+ * The node we would like to lookup in or insert into a cache.
+ *
+ * @returns {String}
+ * The unique string that can be used as a key in a CensusTreeNodeCache.
+ */
+CensusTreeNodeCache.hashNode = function (node) {
+ return isSavedFrame(node.name)
+ ? CensusTreeNodeCache.hashFrame(node.name)
+ : `NODE,${node.name}`;
+};
+
+/**
+ * Insert the given CensusTreeNodeCacheValue whose node.name is a SavedFrame
+ * object in the given cache.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * @param {CensusTreeNodeCacheValue} value
+ */
+CensusTreeNodeCache.insertFrame = function (cache, value) {
+ cache[CensusTreeNodeCache.hashFrame(value.node.name)] = value;
+};
+
+/**
+ * Insert the given value in the cache.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * @param {CensusTreeNodeCacheValue} value
+ */
+CensusTreeNodeCache.insertNode = function (cache, value) {
+ if (isSavedFrame(value.node.name)) {
+ CensusTreeNodeCache.insertFrame(cache, value);
+ } else {
+ cache[CensusTreeNodeCache.hashNode(value.node)] = value;
+ }
+};
+
+/**
+ * Lookup `frame` in `cache` and return its value if it exists.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * @param {SavedFrame} frame
+ *
+ * @returns {undefined|CensusTreeNodeCacheValue}
+ */
+CensusTreeNodeCache.lookupFrame = function (cache, frame) {
+ return cache[CensusTreeNodeCache.hashFrame(frame)];
+};
+
+/**
+ * Lookup `node` in `cache` and return its value if it exists.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * @param {CensusTreeNode} node
+ *
+ * @returns {undefined|CensusTreeNodeCacheValue}
+ */
+CensusTreeNodeCache.lookupNode = function (cache, node) {
+ return isSavedFrame(node.name)
+ ? CensusTreeNodeCache.lookupFrame(cache, node.name)
+ : cache[CensusTreeNodeCache.hashNode(node)];
+};
+
+/**
+ * Add `child` to `parent`'s set of children and store the parent ID
+ * on the child.
+ *
+ * @param {CensusTreeNode} parent
+ * @param {CensusTreeNode} child
+ */
+function addChild(parent, child) {
+ if (!parent.children) {
+ parent.children = [];
+ }
+ child.parent = parent.id;
+ parent.children.push(child);
+}
+
+/**
+ * Get an array of each frame in the provided stack.
+ *
+ * @param {SavedFrame} stack
+ * @returns {Array<SavedFrame>}
+ */
+function getArrayOfFrames(stack) {
+ const frames = [];
+ let frame = stack;
+ while (frame) {
+ frames.push(frame);
+ frame = frame.parent;
+ }
+ frames.reverse();
+ return frames;
+}
+
+/**
+ * Given an `edge` to a sub-`report` whose structure is described by
+ * `breakdown`, create a CensusTreeNode tree.
+ *
+ * @param {Object} breakdown
+ * The breakdown specifying the structure of the given report.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {null|String|SavedFrame} edge
+ * The edge leading to this report from the parent report.
+ *
+ * @param {CensusTreeNodeCache} cache
+ * The cache of CensusTreeNodes we have already made for the siblings of
+ * the node being created. The existing nodes are reused when possible.
+ *
+ * @param {Object} outParams
+ * The return values are attached to this object after this function
+ * returns. Because we create a CensusTreeNode for each frame in a
+ * SavedFrame stack edge, there may multiple nodes per sub-report.
+ *
+ * - top: The deepest node in the CensusTreeNode subtree created.
+ *
+ * - bottom: The shallowest node in the CensusTreeNode subtree created.
+ * This is null if the shallowest node in the subtree was
+ * found in the `cache` and reused.
+ *
+ * Note that top and bottom are not necessarily different. In the case
+ * where there is a 1:1 correspondence between an edge in the report and
+ * a CensusTreeNode, top and bottom refer to the same node.
+ */
+function makeCensusTreeNodeSubTree(breakdown, report, edge, cache, outParams) {
+ if (!isSavedFrame(edge)) {
+ const node = new CensusTreeNode(edge);
+ outParams.top = outParams.bottom = node;
+ return;
+ }
+
+ const frames = getArrayOfFrames(edge);
+ let currentCache = cache;
+ let prevNode;
+ for (let i = 0, length = frames.length; i < length; i++) {
+ const frame = frames[i];
+
+ // Get or create the CensusTreeNodeCacheValue for this frame. If we already
+ // have a CensusTreeNodeCacheValue (and hence a CensusTreeNode) for this
+ // frame, we don't need to add the node to the previous node's children as
+ // we have already done that. If we don't have a CensusTreeNodeCacheValue
+ // and CensusTreeNode for this frame, then create one and make sure to hook
+ // it up as a child of the previous node.
+ let isNewNode = false;
+ let val = CensusTreeNodeCache.lookupFrame(currentCache, frame);
+ if (!val) {
+ isNewNode = true;
+ val = new CensusTreeNodeCacheValue();
+ val.node = new CensusTreeNode(frame);
+
+ CensusTreeNodeCache.insertFrame(currentCache, val);
+ if (prevNode) {
+ addChild(prevNode, val.node);
+ }
+ }
+
+ if (i === 0) {
+ outParams.bottom = isNewNode ? val.node : null;
+ }
+ if (i === length - 1) {
+ outParams.top = val.node;
+ }
+
+ prevNode = val.node;
+
+ if (i !== length - 1 && !val.children) {
+ // This is not the last frame and therefore this node will have
+ // children, which we must cache.
+ val.children = new CensusTreeNodeCache();
+ }
+
+ currentCache = val.children;
+ }
+}
+
+/**
+ * A Visitor that walks a census report and creates the corresponding
+ * CensusTreeNode tree.
+ */
+function CensusTreeNodeVisitor() {
+ // The root of the resulting CensusTreeNode tree.
+ this._root = null;
+
+ // The stack of CensusTreeNodes that we are in the process of building while
+ // walking the census report.
+ this._nodeStack = [];
+
+ // To avoid unnecessary allocations, we reuse the same out parameter object
+ // passed to `makeCensusTreeNodeSubTree` every time we call it.
+ this._outParams = {
+ top: null,
+ bottom: null,
+ };
+
+ // The stack of `CensusTreeNodeCache`s that we use to aggregate many
+ // SavedFrame stacks into a single CensusTreeNode tree.
+ this._cacheStack = [new CensusTreeNodeCache()];
+
+ // The current index in the DFS of the census report tree.
+ this._index = -1;
+}
+
+CensusTreeNodeVisitor.prototype = Object.create(Visitor);
+
+/**
+ * Create the CensusTreeNode subtree for this sub-report and link it to the
+ * parent CensusTreeNode.
+ *
+ * @overrides Visitor.prototype.enter
+ */
+CensusTreeNodeVisitor.prototype.enter = function (breakdown, report, edge) {
+ this._index++;
+
+ const cache = this._cacheStack[this._cacheStack.length - 1];
+ makeCensusTreeNodeSubTree(breakdown, report, edge, cache, this._outParams);
+ const { top, bottom } = this._outParams;
+
+ if (!this._root) {
+ this._root = bottom;
+ } else if (bottom) {
+ addChild(this._nodeStack[this._nodeStack.length - 1], bottom);
+ }
+
+ this._cacheStack.push(new CensusTreeNodeCache());
+ this._nodeStack.push(top);
+};
+
+function values(cache) {
+ return Object.keys(cache).map(k => cache[k]);
+}
+
+function isNonEmpty(node) {
+ return (node.children !== undefined && node.children.length)
+ || node.bytes !== 0
+ || node.count !== 0;
+}
+
+/**
+ * We have finished adding children to the CensusTreeNode subtree for the
+ * current sub-report. Make sure that the children are sorted for every node in
+ * the subtree.
+ *
+ * @overrides Visitor.prototype.exit
+ */
+CensusTreeNodeVisitor.prototype.exit = function (breakdown, report, edge) {
+ // Ensure all children are sorted and have their counts/bytes aggregated. We
+ // only need to consider cache children here, because other children
+ // correspond to other sub-reports and we already fixed them up in an earlier
+ // invocation of `exit`.
+
+ function dfs(node, childrenCache) {
+ if (childrenCache) {
+ const childValues = values(childrenCache);
+ for (let i = 0, length = childValues.length; i < length; i++) {
+ dfs(childValues[i].node, childValues[i].children);
+ }
+ }
+
+ node.totalCount = node.count;
+ node.totalBytes = node.bytes;
+
+ if (node.children) {
+ // Prune empty leaves.
+ node.children = node.children.filter(isNonEmpty);
+
+ node.children.sort(compareByTotal);
+
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ node.totalCount += node.children[i].totalCount;
+ node.totalBytes += node.children[i].totalBytes;
+ }
+ }
+ }
+
+ const top = this._nodeStack.pop();
+ const cache = this._cacheStack.pop();
+ dfs(top, cache);
+};
+
+/**
+ * @overrides Visitor.prototype.count
+ */
+CensusTreeNodeVisitor.prototype.count = function (breakdown, report, edge) {
+ const node = this._nodeStack[this._nodeStack.length - 1];
+ node.reportLeafIndex = this._index;
+
+ if (breakdown.count) {
+ node.count = report.count;
+ }
+
+ if (breakdown.bytes) {
+ node.bytes = report.bytes;
+ }
+};
+
+/**
+ * Get the root of the resulting CensusTreeNode tree.
+ *
+ * @returns {CensusTreeNode}
+ */
+CensusTreeNodeVisitor.prototype.root = function () {
+ if (!this._root) {
+ throw new Error("Attempt to get the root before walking the census report!");
+ }
+
+ if (this._nodeStack.length) {
+ throw new Error("Attempt to get the root while walking the census report!");
+ }
+
+ return this._root;
+};
+
+/**
+ * Create a single, uninitialized CensusTreeNode.
+ *
+ * @param {null|String|SavedFrame} name
+ */
+function CensusTreeNode(name) {
+ // Display name for this CensusTreeNode. Either null, a string, or a
+ // SavedFrame.
+ this.name = name;
+
+ // The number of bytes occupied by matching things in the heap snapshot.
+ this.bytes = 0;
+
+ // The sum of `this.bytes` and `child.totalBytes` for each child in
+ // `this.children`.
+ this.totalBytes = 0;
+
+ // The number of things in the heap snapshot that match this node in the
+ // census tree.
+ this.count = 0;
+
+ // The sum of `this.count` and `child.totalCount` for each child in
+ // `this.children`.
+ this.totalCount = 0;
+
+ // An array of this node's children, or undefined if it has no children.
+ this.children = undefined;
+
+ // The unique ID of this node.
+ this.id = ++censusTreeNodeIdCounter;
+
+ // If present, the unique ID of this node's parent. If this node does not have
+ // a parent, then undefined.
+ this.parent = undefined;
+
+ // The `reportLeafIndex` property allows mapping a CensusTreeNode node back to
+ // a leaf in the census report it was generated from. It is always one of the
+ // following variants:
+ //
+ // * A `Number` index pointing a leaf report in a pre-order DFS traversal of
+ // this CensusTreeNode's census report.
+ //
+ // * A `Set` object containing such indices, when this is part of an inverted
+ // CensusTreeNode tree and multiple leaves in the report map onto this node.
+ //
+ // * Finally, `undefined` when no leaves in the census report correspond with
+ // this node.
+ //
+ // The first and third cases are the common cases. The second case is rather
+ // uncommon, and to avoid doubling the number of allocations when creating
+ // CensusTreeNode trees, and objects that get structured cloned when sending
+ // such trees from the HeapAnalysesWorker to the main thread, we only allocate
+ // a Set object once a node actually does have multiple leaves it corresponds
+ // to.
+ this.reportLeafIndex = undefined;
+}
+
+CensusTreeNode.prototype = null;
+
+/**
+ * Compare the given nodes by their `totalBytes` properties, and breaking ties
+ * with the `totalCount`, `bytes`, and `count` properties (in that order).
+ *
+ * @param {CensusTreeNode} node1
+ * @param {CensusTreeNode} node2
+ *
+ * @returns {Number}
+ * A number suitable for using with Array.prototype.sort.
+ */
+function compareByTotal(node1, node2) {
+ return Math.abs(node2.totalBytes) - Math.abs(node1.totalBytes)
+ || Math.abs(node2.totalCount) - Math.abs(node1.totalCount)
+ || Math.abs(node2.bytes) - Math.abs(node1.bytes)
+ || Math.abs(node2.count) - Math.abs(node1.count);
+}
+
+/**
+ * Compare the given nodes by their `bytes` properties, and breaking ties with
+ * the `count`, `totalBytes`, and `totalCount` properties (in that order).
+ *
+ * @param {CensusTreeNode} node1
+ * @param {CensusTreeNode} node2
+ *
+ * @returns {Number}
+ * A number suitable for using with Array.prototype.sort.
+ */
+function compareBySelf(node1, node2) {
+ return Math.abs(node2.bytes) - Math.abs(node1.bytes)
+ || Math.abs(node2.count) - Math.abs(node1.count)
+ || Math.abs(node2.totalBytes) - Math.abs(node1.totalBytes)
+ || Math.abs(node2.totalCount) - Math.abs(node1.totalCount);
+}
+
+/**
+ * Given a parent cache value from a tree we are building and a child node from
+ * a tree we are basing the new tree off of, if we already have a corresponding
+ * node in the parent's children cache, merge this node's counts with
+ * it. Otherwise, create the corresponding node, add it to the parent's children
+ * cache, and create the parent->child edge.
+ *
+ * @param {CensusTreeNodeCacheValue} parentCachevalue
+ * @param {CensusTreeNode} node
+ *
+ * @returns {CensusTreeNodeCacheValue}
+ * The new or extant child node's corresponding cache value.
+ */
+function insertOrMergeNode(parentCacheValue, node) {
+ if (!parentCacheValue.children) {
+ parentCacheValue.children = new CensusTreeNodeCache();
+ }
+
+ let val = CensusTreeNodeCache.lookupNode(parentCacheValue.children, node);
+
+ if (val) {
+ // When inverting, it is possible that multiple leaves in the census report
+ // get merged into a single CensusTreeNode node. When this occurs, switch
+ // from a single index to a set of indices.
+ if (val.node.reportLeafIndex !== undefined &&
+ val.node.reportLeafIndex !== node.reportLeafIndex) {
+ if (typeof val.node.reportLeafIndex === "number") {
+ const oldIndex = val.node.reportLeafIndex;
+ val.node.reportLeafIndex = new Set();
+ val.node.reportLeafIndex.add(oldIndex);
+ val.node.reportLeafIndex.add(node.reportLeafIndex);
+ } else {
+ val.node.reportLeafIndex.add(node.reportLeafIndex);
+ }
+ }
+
+ val.node.count += node.count;
+ val.node.bytes += node.bytes;
+ } else {
+ val = new CensusTreeNodeCacheValue();
+
+ val.node = new CensusTreeNode(node.name);
+ val.node.reportLeafIndex = node.reportLeafIndex;
+ val.node.count = node.count;
+ val.node.totalCount = node.totalCount;
+ val.node.bytes = node.bytes;
+ val.node.totalBytes = node.totalBytes;
+
+ addChild(parentCacheValue.node, val.node);
+ CensusTreeNodeCache.insertNode(parentCacheValue.children, val);
+ }
+
+ return val;
+}
+
+/**
+ * Given an un-inverted CensusTreeNode tree, return the corresponding inverted
+ * CensusTreeNode tree. The input tree is not modified. The resulting inverted
+ * tree is sorted by self bytes rather than by total bytes.
+ *
+ * @param {CensusTreeNode} tree
+ * The un-inverted tree.
+ *
+ * @returns {CensusTreeNode}
+ * The corresponding inverted tree.
+ */
+function invert(tree) {
+ const inverted = new CensusTreeNodeCacheValue();
+ inverted.node = new CensusTreeNode(null);
+
+ // Do a depth-first search of the un-inverted tree. As we reach each leaf,
+ // take the path from the old root to the leaf, reverse that path, and add it
+ // to the new, inverted tree's root.
+
+ const path = [];
+ (function addInvertedPaths(node) {
+ path.push(node);
+
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ addInvertedPaths(node.children[i]);
+ }
+ } else {
+ // We found a leaf node, add the reverse path to the inverted tree.
+ let currentCacheValue = inverted;
+ for (let i = path.length - 1; i >= 0; i--) {
+ currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
+ }
+ }
+
+ path.pop();
+ }(tree));
+
+ // Ensure that the root node always has the totals.
+ inverted.node.totalBytes = tree.totalBytes;
+ inverted.node.totalCount = tree.totalCount;
+
+ return inverted.node;
+}
+
+/**
+ * Given a CensusTreeNode tree and predicate function, create the tree
+ * containing only the nodes in any path `(node_0, node_1, ..., node_n-1)` in
+ * the given tree where `predicate(node_j)` is true for `0 <= j < n`, `node_0`
+ * is the given tree's root, and `node_n-1` is a leaf in the given tree. The
+ * given tree is left unmodified.
+ *
+ * @param {CensusTreeNode} tree
+ * @param {Function} predicate
+ *
+ * @returns {CensusTreeNode}
+ */
+function filter(tree, predicate) {
+ const filtered = new CensusTreeNodeCacheValue();
+ filtered.node = new CensusTreeNode(null);
+
+ // Do a DFS over the given tree. If the predicate returns true for any node,
+ // add that node and its whole subtree to the filtered tree.
+
+ const path = [];
+ let match = false;
+
+ function addMatchingNodes(node) {
+ path.push(node);
+
+ let oldMatch = match;
+ if (!match && predicate(node)) {
+ match = true;
+ }
+
+ if (node.children) {
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ addMatchingNodes(node.children[i]);
+ }
+ } else if (match) {
+ // We found a matching leaf node, add it to the filtered tree.
+ let currentCacheValue = filtered;
+ for (let i = 0, length = path.length; i < length; i++) {
+ currentCacheValue = insertOrMergeNode(currentCacheValue, path[i]);
+ }
+ }
+
+ match = oldMatch;
+ path.pop();
+ }
+
+ if (tree.children) {
+ for (let i = 0, length = tree.children.length; i < length; i++) {
+ addMatchingNodes(tree.children[i]);
+ }
+ }
+
+ filtered.node.count = tree.count;
+ filtered.node.totalCount = tree.totalCount;
+ filtered.node.bytes = tree.bytes;
+ filtered.node.totalBytes = tree.totalBytes;
+
+ return filtered.node;
+}
+
+/**
+ * Given a filter string, return a predicate function that takes a node and
+ * returns true iff the node matches the filter.
+ *
+ * @param {String} filterString
+ * @returns {Function}
+ */
+function makeFilterPredicate(filterString) {
+ return function (node) {
+ if (!node.name) {
+ return false;
+ }
+
+ if (isSavedFrame(node.name)) {
+ return node.name.source.includes(filterString)
+ || (node.name.functionDisplayName || "").includes(filterString)
+ || (node.name.asyncCause || "").includes(filterString);
+ }
+
+ return String(node.name).includes(filterString);
+ };
+}
+
+/**
+ * Takes a report from a census (`dbg.memory.takeCensus()`) and the breakdown
+ * used to generate the census and returns a structure used to render
+ * a tree to display the data.
+ *
+ * Returns a recursive "CensusTreeNode" object, looking like:
+ *
+ * CensusTreeNode = {
+ * // `children` if it exists, is sorted by `bytes`, if they are leaf nodes.
+ * children: ?[<CensusTreeNode...>],
+ * name: <?String>
+ * count: <?Number>
+ * bytes: <?Number>
+ * id: <?Number>
+ * parent: <?Number>
+ * }
+ *
+ * @param {Object} breakdown
+ * The breakdown used to generate the census report.
+ *
+ * @param {Object} report
+ * The census report generated with the specified breakdown.
+ *
+ * @param {Object} options
+ * Configuration options.
+ * - invert: Whether to invert the resulting tree or not. Defaults to
+ * false, ie uninverted.
+ *
+ * @returns {CensusTreeNode}
+ */
+exports.censusReportToCensusTreeNode = function (breakdown, report,
+ options = {
+ invert: false,
+ filter: null
+ }) {
+ // Reset the counter so that turning the same census report into a
+ // CensusTreeNode tree repeatedly is idempotent.
+ censusTreeNodeIdCounter = 0;
+
+ const visitor = new CensusTreeNodeVisitor();
+ walk(breakdown, report, visitor);
+ let result = visitor.root();
+
+ if (options.invert) {
+ result = invert(result);
+ }
+
+ if (typeof options.filter === "string") {
+ result = filter(result, makeFilterPredicate(options.filter));
+ }
+
+ // If the report is a delta report that was generated by diffing two other
+ // reports, make sure to use the basis totals rather than the totals of the
+ // difference.
+ if (typeof report[basisTotalBytes] === "number") {
+ result.totalBytes = report[basisTotalBytes];
+ result.totalCount = report[basisTotalCount];
+ }
+
+ // Inverting and filtering could have messed up the sort order, so do a
+ // depth-first search of the tree and ensure that siblings are sorted.
+ const comparator = options.invert ? compareBySelf : compareByTotal;
+ (function ensureSorted(node) {
+ if (node.children) {
+ node.children.sort(comparator);
+ for (let i = 0, length = node.children.length; i < length; i++) {
+ ensureSorted(node.children[i]);
+ }
+ }
+ }(result));
+
+ return result;
+};
diff --git a/devtools/shared/heapsnapshot/generate-core-dump-sources.sh b/devtools/shared/heapsnapshot/generate-core-dump-sources.sh
new file mode 100755
index 000000000..97e492ff0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/generate-core-dump-sources.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env bash
+
+# A script to generate devtools/server/CoreDump.pb.{h,cc} from
+# devtools/server/CoreDump.proto. This script assumes you have
+# downloaded and installed the protocol buffer compiler, and that it is either
+# on your $PATH or located at $PROTOC_PATH.
+#
+# These files were last compiled with libprotoc 2.4.1.
+
+set -e
+
+cd $(dirname $0)
+
+if [ -n $PROTOC_PATH ]; then
+ PROTOC_PATH=`which protoc`
+fi
+
+if [ ! -e $PROTOC_PATH ]; then
+ echo You must install the protocol compiler from
+ echo https://code.google.com/p/protobuf/downloads/list
+ exit 1
+fi
+
+echo Using $PROTOC_PATH as the protocol compiler
+
+$PROTOC_PATH --cpp_out="." CoreDump.proto
diff --git a/devtools/shared/heapsnapshot/moz.build b/devtools/shared/heapsnapshot/moz.build
new file mode 100644
index 000000000..d020da727
--- /dev/null
+++ b/devtools/shared/heapsnapshot/moz.build
@@ -0,0 +1,62 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files('**'):
+ BUG_COMPONENT = ('Firefox', 'Developer Tools: Memory')
+
+if CONFIG['ENABLE_TESTS']:
+ DIRS += ['tests/gtest']
+
+XPCSHELL_TESTS_MANIFESTS += [ 'tests/unit/xpcshell.ini' ]
+MOCHITEST_MANIFESTS += [ 'tests/mochitest/mochitest.ini' ]
+MOCHITEST_CHROME_MANIFESTS += [ 'tests/mochitest/chrome.ini' ]
+
+EXPORTS.mozilla.devtools += [
+ 'AutoMemMap.h',
+ 'CoreDump.pb.h',
+ 'DeserializedNode.h',
+ 'DominatorTree.h',
+ 'FileDescriptorOutputStream.h',
+ 'HeapSnapshot.h',
+ 'HeapSnapshotTempFileHelperChild.h',
+ 'HeapSnapshotTempFileHelperParent.h',
+ 'ZeroCopyNSIOutputStream.h',
+]
+
+IPDL_SOURCES += [
+ 'PHeapSnapshotTempFileHelper.ipdl',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+SOURCES += [
+ 'AutoMemMap.cpp',
+ 'CoreDump.pb.cc',
+ 'DeserializedNode.cpp',
+ 'DominatorTree.cpp',
+ 'FileDescriptorOutputStream.cpp',
+ 'HeapSnapshot.cpp',
+ 'HeapSnapshotTempFileHelperParent.cpp',
+ 'ZeroCopyNSIOutputStream.cpp',
+]
+
+# Disable RTTI in google protocol buffer
+DEFINES['GOOGLE_PROTOBUF_NO_RTTI'] = True
+
+FINAL_LIBRARY = 'xul'
+
+DevToolsModules(
+ 'census-tree-node.js',
+ 'CensusUtils.js',
+ 'DominatorTreeNode.js',
+ 'HeapAnalysesClient.js',
+ 'HeapAnalysesWorker.js',
+ 'HeapSnapshotFileUtils.js',
+ 'shortest-paths.js',
+)
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
diff --git a/devtools/shared/heapsnapshot/shortest-paths.js b/devtools/shared/heapsnapshot/shortest-paths.js
new file mode 100644
index 000000000..2d97b7de9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/shortest-paths.js
@@ -0,0 +1,91 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+/**
+ * Compress a set of paths leading to `target` into a single graph, returned as
+ * a set of nodes and a set of edges.
+ *
+ * @param {NodeId} target
+ * The target node passed to `HeapSnapshot.computeShortestPaths`.
+ *
+ * @param {Array<Path>} paths
+ * An array of paths to `target`, as returned by
+ * `HeapSnapshot.computeShortestPaths`.
+ *
+ * @returns {Object}
+ * An object with two properties:
+ * - edges: An array of unique objects of the form:
+ * {
+ * from: <node ID>,
+ * to: <node ID>,
+ * name: <string or null>
+ * }
+ * - nodes: An array of unique node IDs. Every `from` and `to` id is
+ * guaranteed to be in this array exactly once.
+ */
+exports.deduplicatePaths = function (target, paths) {
+ // Use this structure to de-duplicate edges among many retaining paths from
+ // start to target.
+ //
+ // Map<FromNodeId, Map<ToNodeId, Set<EdgeName>>>
+ const deduped = new Map();
+
+ function insert(from, to, name) {
+ let toMap = deduped.get(from);
+ if (!toMap) {
+ toMap = new Map();
+ deduped.set(from, toMap);
+ }
+
+ let nameSet = toMap.get(to);
+ if (!nameSet) {
+ nameSet = new Set();
+ toMap.set(to, nameSet);
+ }
+
+ nameSet.add(name);
+ }
+
+ outer: for (let path of paths) {
+ const pathLength = path.length;
+
+ // Check for duplicate predecessors in the path, and skip paths that contain
+ // them.
+ const predecessorsSeen = new Set();
+ predecessorsSeen.add(target);
+ for (let i = 0; i < pathLength; i++) {
+ if (predecessorsSeen.has(path[i].predecessor)) {
+ continue outer;
+ }
+ predecessorsSeen.add(path[i].predecessor);
+ }
+
+ for (let i = 0; i < pathLength - 1; i++) {
+ insert(path[i].predecessor, path[i + 1].predecessor, path[i].edge);
+ }
+
+ insert(path[pathLength - 1].predecessor, target, path[pathLength - 1].edge);
+ }
+
+ const nodes = [target];
+ const edges = [];
+
+ for (let [from, toMap] of deduped) {
+ // If the second/third/etc shortest path contains the `target` anywhere
+ // other than the very last node, we could accidentally put the `target` in
+ // `nodes` more than once.
+ if (from !== target) {
+ nodes.push(from);
+ }
+
+ for (let [to, edgeNameSet] of toMap) {
+ for (let name of edgeNameSet) {
+ edges.push({ from, to, name });
+ }
+ }
+ }
+
+ return { nodes, edges };
+};
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
new file mode 100644
index 000000000..e236a0acf
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that the `JS::ubi::Node`s we create from
+// `mozilla::devtools::DeserializedNode` instances look and behave as we would
+// like.
+
+#include "DevTools.h"
+#include "js/TypeDecls.h"
+#include "mozilla/devtools/DeserializedNode.h"
+
+using testing::Field;
+using testing::ReturnRef;
+
+// A mock DeserializedNode for testing.
+struct MockDeserializedNode : public DeserializedNode
+{
+ MockDeserializedNode(NodeId id, const char16_t* typeName, uint64_t size)
+ : DeserializedNode(id, typeName, size)
+ { }
+
+ bool addEdge(DeserializedEdge&& edge)
+ {
+ return edges.append(Move(edge));
+ }
+
+ MOCK_METHOD1(getEdgeReferent, JS::ubi::Node(const DeserializedEdge&));
+};
+
+size_t fakeMallocSizeOf(const void*) {
+ EXPECT_TRUE(false);
+ MOZ_ASSERT_UNREACHABLE("fakeMallocSizeOf should never be called because "
+ "DeserializedNodes report the deserialized size.");
+ return 0;
+}
+
+DEF_TEST(DeserializedNodeUbiNodes, {
+ const char16_t* typeName = u"TestTypeName";
+ const char* className = "MyObjectClassName";
+ const char* filename = "my-cool-filename.js";
+
+ NodeId id = uint64_t(1) << 33;
+ uint64_t size = uint64_t(1) << 60;
+ MockDeserializedNode mocked(id, typeName, size);
+ mocked.coarseType = JS::ubi::CoarseType::Script;
+ mocked.jsObjectClassName = className;
+ mocked.scriptFilename = filename;
+
+ DeserializedNode& deserialized = mocked;
+ JS::ubi::Node ubi(&deserialized);
+
+ // Test the ubi::Node accessors.
+
+ EXPECT_EQ(size, ubi.size(fakeMallocSizeOf));
+ EXPECT_EQ(typeName, ubi.typeName());
+ EXPECT_EQ(JS::ubi::CoarseType::Script, ubi.coarseType());
+ EXPECT_EQ(id, ubi.identifier());
+ EXPECT_FALSE(ubi.isLive());
+ EXPECT_EQ(ubi.jsObjectClassName(), className);
+ EXPECT_EQ(ubi.scriptFilename(), filename);
+
+ // Test the ubi::Node's edges.
+
+ UniquePtr<DeserializedNode> referent1(new MockDeserializedNode(1,
+ nullptr,
+ 10));
+ DeserializedEdge edge1(referent1->id);
+ mocked.addEdge(Move(edge1));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent1->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent1.get())));
+
+ UniquePtr<DeserializedNode> referent2(new MockDeserializedNode(2,
+ nullptr,
+ 20));
+ DeserializedEdge edge2(referent2->id);
+ mocked.addEdge(Move(edge2));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent2->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent2.get())));
+
+ UniquePtr<DeserializedNode> referent3(new MockDeserializedNode(3,
+ nullptr,
+ 30));
+ DeserializedEdge edge3(referent3->id);
+ mocked.addEdge(Move(edge3));
+ EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
+ .Times(1)
+ .WillOnce(Return(JS::ubi::Node(referent3.get())));
+
+ auto range = ubi.edges(cx);
+ ASSERT_TRUE(!!range);
+
+ for ( ; !range->empty(); range->popFront()) {
+ // Nothing to do here. This loop ensures that we get each edge referent
+ // that we expect above.
+ }
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp
new file mode 100644
index 000000000..72e363934
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedStackFrameUbiStackFrames.cpp
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that the `JS::ubi::StackFrame`s we create from
+// `mozilla::devtools::DeserializedStackFrame` instances look and behave as we would
+// like.
+
+#include "DevTools.h"
+#include "js/TypeDecls.h"
+#include "mozilla/devtools/DeserializedNode.h"
+
+using testing::Field;
+using testing::ReturnRef;
+
+// A mock DeserializedStackFrame for testing.
+struct MockDeserializedStackFrame : public DeserializedStackFrame
+{
+ MockDeserializedStackFrame() : DeserializedStackFrame() { }
+};
+
+DEF_TEST(DeserializedStackFrameUbiStackFrames, {
+ StackFrameId id = uint64_t(1) << 42;
+ uint32_t line = 1337;
+ uint32_t column = 9; // 3 space tabs!?
+ const char16_t* source = u"my-javascript-file.js";
+ const char16_t* functionDisplayName = u"myFunctionName";
+
+ MockDeserializedStackFrame mocked;
+ mocked.id = id;
+ mocked.line = line;
+ mocked.column = column;
+ mocked.source = source;
+ mocked.functionDisplayName = functionDisplayName;
+
+ DeserializedStackFrame& deserialized = mocked;
+ JS::ubi::StackFrame ubiFrame(&deserialized);
+
+ // Test the JS::ubi::StackFrame accessors.
+
+ EXPECT_EQ(id, ubiFrame.identifier());
+ EXPECT_EQ(JS::ubi::StackFrame(), ubiFrame.parent());
+ EXPECT_EQ(line, ubiFrame.line());
+ EXPECT_EQ(column, ubiFrame.column());
+ EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(source), ubiFrame.source());
+ EXPECT_EQ(JS::ubi::AtomOrTwoByteChars(functionDisplayName),
+ ubiFrame.functionDisplayName());
+ EXPECT_FALSE(ubiFrame.isSelfHosted(cx));
+ EXPECT_FALSE(ubiFrame.isSystem());
+
+ JS::RootedObject savedFrame(cx);
+ EXPECT_TRUE(ubiFrame.constructSavedFrameStack(cx, &savedFrame));
+
+ uint32_t frameLine;
+ ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameLine(cx, savedFrame, &frameLine));
+ EXPECT_EQ(line, frameLine);
+
+ uint32_t frameColumn;
+ ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameColumn(cx, savedFrame, &frameColumn));
+ EXPECT_EQ(column, frameColumn);
+
+ JS::RootedObject parent(cx);
+ ASSERT_EQ(JS::SavedFrameResult::Ok, JS::GetSavedFrameParent(cx, savedFrame, &parent));
+ EXPECT_EQ(nullptr, parent);
+
+ ASSERT_EQ(NS_strlen(source), 21U);
+ char16_t sourceBuf[21] = {};
+
+ // Test when the length is shorter than the string length.
+ auto written = ubiFrame.source(RangedPtr<char16_t>(sourceBuf), 3);
+ EXPECT_EQ(written, 3U);
+ for (size_t i = 0; i < 3; i++) {
+ EXPECT_EQ(sourceBuf[i], source[i]);
+ }
+
+ written = ubiFrame.source(RangedPtr<char16_t>(sourceBuf), 21);
+ EXPECT_EQ(written, 21U);
+ for (size_t i = 0; i < 21; i++) {
+ EXPECT_EQ(sourceBuf[i], source[i]);
+ }
+
+ ASSERT_EQ(NS_strlen(functionDisplayName), 14U);
+ char16_t nameBuf[14] = {};
+
+ written = ubiFrame.functionDisplayName(RangedPtr<char16_t>(nameBuf), 14);
+ EXPECT_EQ(written, 14U);
+ for (size_t i = 0; i < 14; i++) {
+ EXPECT_EQ(nameBuf[i], functionDisplayName[i]);
+ }
+});
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
new file mode 100644
index 000000000..6eb5cfe21
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -0,0 +1,276 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_devtools_gtest_DevTools__
+#define mozilla_devtools_gtest_DevTools__
+
+#include "CoreDump.pb.h"
+#include "jsapi.h"
+#include "jspubtd.h"
+#include "nsCRTGlue.h"
+
+#include "gtest/gtest.h"
+#include "gmock/gmock.h"
+#include "mozilla/devtools/HeapSnapshot.h"
+#include "mozilla/dom/ChromeUtils.h"
+#include "mozilla/CycleCollectedJSContext.h"
+#include "mozilla/Move.h"
+#include "js/Principals.h"
+#include "js/UbiNode.h"
+#include "js/UniquePtr.h"
+
+using namespace mozilla;
+using namespace mozilla::devtools;
+using namespace mozilla::dom;
+using namespace testing;
+
+// GTest fixture class that all of our tests derive from.
+struct DevTools : public ::testing::Test {
+ bool _initialized;
+ JSContext* cx;
+ JSCompartment* compartment;
+ JS::Zone* zone;
+ JS::PersistentRootedObject global;
+
+ DevTools()
+ : _initialized(false),
+ cx(nullptr)
+ { }
+
+ virtual void SetUp() {
+ MOZ_ASSERT(!_initialized);
+
+ cx = getContext();
+ if (!cx)
+ return;
+
+ JS_BeginRequest(cx);
+
+ global.init(cx, createGlobal());
+ if (!global)
+ return;
+ JS_EnterCompartment(cx, global);
+
+ compartment = js::GetContextCompartment(cx);
+ zone = js::GetContextZone(cx);
+
+ _initialized = true;
+ }
+
+ JSContext* getContext() {
+ return CycleCollectedJSContext::Get()->Context();
+ }
+
+ static void reportError(JSContext* cx, const char* message, JSErrorReport* report) {
+ fprintf(stderr, "%s:%u:%s\n",
+ report->filename ? report->filename : "<no filename>",
+ (unsigned int) report->lineno,
+ message);
+ }
+
+ static const JSClass* getGlobalClass() {
+ static const JSClassOps globalClassOps = {
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr, nullptr,
+ nullptr, nullptr, nullptr,
+ JS_GlobalObjectTraceHook
+ };
+ static const JSClass globalClass = {
+ "global", JSCLASS_GLOBAL_FLAGS,
+ &globalClassOps
+ };
+ return &globalClass;
+ }
+
+ JSObject* createGlobal()
+ {
+ /* Create the global object. */
+ JS::RootedObject newGlobal(cx);
+ JS::CompartmentOptions options;
+ options.behaviors().setVersion(JSVERSION_LATEST);
+ newGlobal = JS_NewGlobalObject(cx, getGlobalClass(), nullptr,
+ JS::FireOnNewGlobalHook, options);
+ if (!newGlobal)
+ return nullptr;
+
+ JSAutoCompartment ac(cx, newGlobal);
+
+ /* Populate the global object with the standard globals, like Object and
+ Array. */
+ if (!JS_InitStandardClasses(cx, newGlobal))
+ return nullptr;
+
+ return newGlobal;
+ }
+
+ virtual void TearDown() {
+ _initialized = false;
+
+ if (global) {
+ JS_LeaveCompartment(cx, nullptr);
+ global = nullptr;
+ }
+ if (cx)
+ JS_EndRequest(cx);
+ }
+};
+
+
+// Helper to define a test and ensure that the fixture is initialized properly.
+#define DEF_TEST(name, body) \
+ TEST_F(DevTools, name) { \
+ ASSERT_TRUE(_initialized); \
+ body \
+ }
+
+
+// Fake JS::ubi::Node implementation
+class MOZ_STACK_CLASS FakeNode
+{
+public:
+ JS::ubi::EdgeVector edges;
+ JSCompartment* compartment;
+ JS::Zone* zone;
+ size_t size;
+
+ explicit FakeNode()
+ : edges(),
+ compartment(nullptr),
+ zone(nullptr),
+ size(1)
+ { }
+};
+
+namespace JS {
+namespace ubi {
+
+template<>
+class Concrete<FakeNode> : public Base
+{
+ const char16_t* typeName() const override {
+ return concreteTypeName;
+ }
+
+ js::UniquePtr<EdgeRange> edges(JSContext*, bool) const override {
+ return js::UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
+ }
+
+ Size size(mozilla::MallocSizeOf) const override {
+ return get().size;
+ }
+
+ JS::Zone* zone() const override {
+ return get().zone;
+ }
+
+ JSCompartment* compartment() const override {
+ return get().compartment;
+ }
+
+protected:
+ explicit Concrete(FakeNode* ptr) : Base(ptr) { }
+ FakeNode& get() const { return *static_cast<FakeNode*>(ptr); }
+
+public:
+ static const char16_t concreteTypeName[];
+ static void construct(void* storage, FakeNode* ptr) {
+ new (storage) Concrete(ptr);
+ }
+};
+
+const char16_t Concrete<FakeNode>::concreteTypeName[] = u"FakeNode";
+
+} // namespace ubi
+} // namespace JS
+
+void AddEdge(FakeNode& node, FakeNode& referent, const char16_t* edgeName = nullptr) {
+ char16_t* ownedEdgeName = nullptr;
+ if (edgeName) {
+ ownedEdgeName = NS_strdup(edgeName);
+ ASSERT_NE(ownedEdgeName, nullptr);
+ }
+
+ JS::ubi::Edge edge(ownedEdgeName, &referent);
+ ASSERT_TRUE(node.edges.append(mozilla::Move(edge)));
+}
+
+
+// Custom GMock Matchers
+
+// Use the testing namespace to avoid static analysis failures in the gmock
+// matcher classes that get generated from MATCHER_P macros.
+namespace testing {
+
+// Ensure that given node has the expected number of edges.
+MATCHER_P2(EdgesLength, cx, expectedLength, "") {
+ auto edges = arg.edges(cx);
+ if (!edges)
+ return false;
+
+ int actualLength = 0;
+ for ( ; !edges->empty(); edges->popFront())
+ actualLength++;
+
+ return Matcher<int>(Eq(expectedLength))
+ .MatchAndExplain(actualLength, result_listener);
+}
+
+// Get the nth edge and match it with the given matcher.
+MATCHER_P3(Edge, cx, n, matcher, "") {
+ auto edges = arg.edges(cx);
+ if (!edges)
+ return false;
+
+ int i = 0;
+ for ( ; !edges->empty(); edges->popFront()) {
+ if (i == n) {
+ return Matcher<const JS::ubi::Edge&>(matcher)
+ .MatchAndExplain(edges->front(), result_listener);
+ }
+
+ i++;
+ }
+
+ return false;
+}
+
+// Ensures that two char16_t* strings are equal.
+MATCHER_P(UTF16StrEq, str, "") {
+ return NS_strcmp(arg, str) == 0;
+}
+
+MATCHER_P(UniqueUTF16StrEq, str, "") {
+ return NS_strcmp(arg.get(), str) == 0;
+}
+
+MATCHER(UniqueIsNull, "") {
+ return arg.get() == nullptr;
+}
+
+// Matches an edge whose referent is the node with the given id.
+MATCHER_P(EdgeTo, id, "") {
+ return Matcher<const DeserializedEdge&>(Field(&DeserializedEdge::referent, id))
+ .MatchAndExplain(arg, result_listener);
+}
+
+} // namespace testing
+
+
+// A mock `Writer` class to be used with testing `WriteHeapGraph`.
+class MockWriter : public CoreDumpWriter
+{
+public:
+ virtual ~MockWriter() override { }
+ MOCK_METHOD2(writeNode, bool(const JS::ubi::Node&, CoreDumpWriter::EdgePolicy));
+ MOCK_METHOD1(writeMetadata, bool(uint64_t));
+};
+
+void ExpectWriteNode(MockWriter& writer, FakeNode& node) {
+ EXPECT_CALL(writer, writeNode(Eq(JS::ubi::Node(&node)), _))
+ .Times(1)
+ .WillOnce(Return(true));
+}
+
+#endif // mozilla_devtools_gtest_DevTools__
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
new file mode 100644
index 000000000..bc517d6d9
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
@@ -0,0 +1,73 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that heap snapshots cross compartment boundaries when expected.
+
+#include "DevTools.h"
+
+DEF_TEST(DoesCrossCompartmentBoundaries, {
+ // Create a new global to get a new compartment.
+ JS::CompartmentOptions options;
+ JS::RootedObject newGlobal(cx, JS_NewGlobalObject(cx,
+ getGlobalClass(),
+ nullptr,
+ JS::FireOnNewGlobalHook,
+ options));
+ ASSERT_TRUE(newGlobal);
+ JSCompartment* newCompartment = nullptr;
+ {
+ JSAutoCompartment ac(cx, newGlobal);
+ ASSERT_TRUE(JS_InitStandardClasses(cx, newGlobal));
+ newCompartment = js::GetContextCompartment(cx);
+ }
+ ASSERT_TRUE(newCompartment);
+ ASSERT_NE(newCompartment, compartment);
+
+ // Our set of target compartments is both the old and new compartments.
+ JS::CompartmentSet targetCompartments;
+ ASSERT_TRUE(targetCompartments.init());
+ ASSERT_TRUE(targetCompartments.put(compartment));
+ ASSERT_TRUE(targetCompartments.put(newCompartment));
+
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+ FakeNode nodeD;
+
+ nodeA.compartment = compartment;
+ nodeB.compartment = nullptr;
+ nodeC.compartment = newCompartment;
+ nodeD.compartment = nullptr;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeA, nodeC);
+ AddEdge(nodeB, nodeD);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize nodeA, because it is in one of our target compartments.
+ ExpectWriteNode(writer, nodeA);
+
+ // Should serialize nodeB, because it doesn't belong to a compartment and is
+ // therefore assumed to be shared.
+ ExpectWriteNode(writer, nodeB);
+
+ // Should also serialize nodeC, which is in our target compartments, but a
+ // different compartment than A.
+ ExpectWriteNode(writer, nodeC);
+
+ // Should serialize nodeD because it's reachable via B and both nodes B and D
+ // don't belong to a specific compartment.
+ ExpectWriteNode(writer, nodeD);
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx,
+ JS::ubi::Node(&nodeA),
+ writer,
+ /* wantNames = */ false,
+ &targetCompartments,
+ noGC));
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
new file mode 100644
index 000000000..2fe5e6ace
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that heap snapshots walk the compartment boundaries correctly.
+
+#include "DevTools.h"
+
+DEF_TEST(DoesntCrossCompartmentBoundaries, {
+ // Create a new global to get a new compartment.
+ JS::CompartmentOptions options;
+ JS::RootedObject newGlobal(cx, JS_NewGlobalObject(cx,
+ getGlobalClass(),
+ nullptr,
+ JS::FireOnNewGlobalHook,
+ options));
+ ASSERT_TRUE(newGlobal);
+ JSCompartment* newCompartment = nullptr;
+ {
+ JSAutoCompartment ac(cx, newGlobal);
+ ASSERT_TRUE(JS_InitStandardClasses(cx, newGlobal));
+ newCompartment = js::GetContextCompartment(cx);
+ }
+ ASSERT_TRUE(newCompartment);
+ ASSERT_NE(newCompartment, compartment);
+
+ // Our set of target compartments is only the pre-existing compartment and
+ // does not include the new compartment.
+ JS::CompartmentSet targetCompartments;
+ ASSERT_TRUE(targetCompartments.init());
+ ASSERT_TRUE(targetCompartments.put(compartment));
+
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+
+ nodeA.compartment = compartment;
+ nodeB.compartment = nullptr;
+ nodeC.compartment = newCompartment;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeB, nodeC);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize nodeA, because it is in our target compartments.
+ ExpectWriteNode(writer, nodeA);
+
+ // Should serialize nodeB, because it doesn't belong to a compartment and is
+ // therefore assumed to be shared.
+ ExpectWriteNode(writer, nodeB);
+
+ // But we shouldn't ever serialize nodeC.
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx,
+ JS::ubi::Node(&nodeA),
+ writer,
+ /* wantNames = */ false,
+ &targetCompartments,
+ noGC));
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
new file mode 100644
index 000000000..be135dbb4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
@@ -0,0 +1,53 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that edge names get serialized correctly.
+
+#include "DevTools.h"
+
+using testing::Field;
+using testing::IsNull;
+using testing::Property;
+using testing::Return;
+
+DEF_TEST(SerializesEdgeNames, {
+ FakeNode node;
+ FakeNode referent;
+
+ const char16_t edgeName[] = u"edge name";
+ const char16_t emptyStr[] = u"";
+
+ AddEdge(node, referent, edgeName);
+ AddEdge(node, referent, emptyStr);
+ AddEdge(node, referent, nullptr);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should get the node with edges once.
+ EXPECT_CALL(
+ writer,
+ writeNode(AllOf(EdgesLength(cx, 3),
+ Edge(cx, 0, Field(&JS::ubi::Edge::name,
+ UniqueUTF16StrEq(edgeName))),
+ Edge(cx, 1, Field(&JS::ubi::Edge::name,
+ UniqueUTF16StrEq(emptyStr))),
+ Edge(cx, 2, Field(&JS::ubi::Edge::name,
+ UniqueIsNull()))),
+ _)
+ )
+ .Times(1)
+ .WillOnce(Return(true));
+
+ // Should get the referent node that doesn't have any edges once.
+ ExpectWriteNode(writer, referent);
+
+ JS::AutoCheckCannotGC noGC(cx);
+ ASSERT_TRUE(WriteHeapGraph(cx,
+ JS::ubi::Node(&node),
+ writer,
+ /* wantNames = */ true,
+ /* zones = */ nullptr,
+ noGC));
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
new file mode 100644
index 000000000..475442df8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
@@ -0,0 +1,37 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that everything in the heap graph gets serialized once, and only once.
+
+#include "DevTools.h"
+
+DEF_TEST(SerializesEverythingInHeapGraphOnce, {
+ FakeNode nodeA;
+ FakeNode nodeB;
+ FakeNode nodeC;
+ FakeNode nodeD;
+
+ AddEdge(nodeA, nodeB);
+ AddEdge(nodeB, nodeC);
+ AddEdge(nodeC, nodeD);
+ AddEdge(nodeD, nodeA);
+
+ ::testing::NiceMock<MockWriter> writer;
+
+ // Should serialize each node once.
+ ExpectWriteNode(writer, nodeA);
+ ExpectWriteNode(writer, nodeB);
+ ExpectWriteNode(writer, nodeC);
+ ExpectWriteNode(writer, nodeD);
+
+ JS::AutoCheckCannotGC noGC(cx);
+
+ ASSERT_TRUE(WriteHeapGraph(cx,
+ JS::ubi::Node(&nodeA),
+ writer,
+ /* wantNames = */ false,
+ /* zones = */ nullptr,
+ noGC));
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
new file mode 100644
index 000000000..a259c297b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Test that a ubi::Node's typeName gets properly serialized into a core dump.
+
+#include "DevTools.h"
+
+using testing::Property;
+using testing::Return;
+
+DEF_TEST(SerializesTypeNames, {
+ FakeNode node;
+
+ ::testing::NiceMock<MockWriter> writer;
+ EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName,
+ UTF16StrEq(u"FakeNode")),
+ _))
+ .Times(1)
+ .WillOnce(Return(true));
+
+ JS::AutoCheckCannotGC noGC(cx);
+ ASSERT_TRUE(WriteHeapGraph(cx,
+ JS::ubi::Node(&node),
+ writer,
+ /* wantNames = */ true,
+ /* zones = */ nullptr,
+ noGC));
+ });
diff --git a/devtools/shared/heapsnapshot/tests/gtest/moz.build b/devtools/shared/heapsnapshot/tests/gtest/moz.build
new file mode 100644
index 000000000..08c31e47c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/gtest/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library('devtoolstests')
+
+LOCAL_INCLUDES += [
+ '../..',
+]
+
+UNIFIED_SOURCES = [
+ 'DeserializedNodeUbiNodes.cpp',
+ 'DeserializedStackFrameUbiStackFrames.cpp',
+ 'DoesCrossCompartmentBoundaries.cpp',
+ 'DoesntCrossCompartmentBoundaries.cpp',
+ 'SerializesEdgeNames.cpp',
+ 'SerializesEverythingInHeapGraphOnce.cpp',
+ 'SerializesTypeNames.cpp',
+]
+
+if CONFIG['GNU_CXX']:
+ CXXFLAGS += ['-Wno-error=shadow']
+
+# THE MOCK_METHOD2 macro from gtest triggers this clang warning and it's hard
+# to work around, so we just ignore it.
+if CONFIG['CLANG_CXX']:
+ CXXFLAGS += ['-Wno-inconsistent-missing-override']
+
+FINAL_LIBRARY = 'xul-gtest'
diff --git a/devtools/shared/heapsnapshot/tests/mochitest/chrome.ini b/devtools/shared/heapsnapshot/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..497b6fe37
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/mochitest/chrome.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools devtools-memory
+skip-if = os == 'android'
+support-files =
+
+[test_DominatorTree_01.html]
+[test_SaveHeapSnapshot.html]
+
diff --git a/devtools/shared/heapsnapshot/tests/mochitest/mochitest.ini b/devtools/shared/heapsnapshot/tests/mochitest/mochitest.ini
new file mode 100644
index 000000000..5e7aa8d10
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/mochitest/mochitest.ini
@@ -0,0 +1,6 @@
+[DEFAULT]
+tags = devtools devtools-memory
+support-files =
+
+[test_saveHeapSnapshot_e10s_01.html]
+
diff --git a/devtools/shared/heapsnapshot/tests/mochitest/test_DominatorTree_01.html b/devtools/shared/heapsnapshot/tests/mochitest/test_DominatorTree_01.html
new file mode 100644
index 000000000..1f9d8c080
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/mochitest/test_DominatorTree_01.html
@@ -0,0 +1,37 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Sanity test that we can compute dominator trees from a heap snapshot in a web window.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>ChromeUtils.saveHeapSnapshot test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(dominatorTree instanceof DominatorTree);
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/shared/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html b/devtools/shared/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
new file mode 100644
index 000000000..f150a99c7
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/mochitest/test_SaveHeapSnapshot.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1024774 - Sanity test that we can take a heap snapshot in a web window.
+-->
+<head>
+ <meta charset="utf-8">
+ <title>ChromeUtils.saveHeapSnapshot test</title>
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<pre id="test">
+<script>
+SimpleTest.waitForExplicitFinish();
+window.onload = function() {
+ ok(ChromeUtils, "The ChromeUtils interface should be exposed in chrome windows.");
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should save a heap snapshot and shouldn't throw.");
+ SimpleTest.finish();
+};
+</script>
+</pre>
+</body>
+</html>
diff --git a/devtools/shared/heapsnapshot/tests/mochitest/test_saveHeapSnapshot_e10s_01.html b/devtools/shared/heapsnapshot/tests/mochitest/test_saveHeapSnapshot_e10s_01.html
new file mode 100644
index 000000000..15f88f8e0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/mochitest/test_saveHeapSnapshot_e10s_01.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<!--
+Bug 1201597 - Sanity test that we can take a heap snapshot in an e10s child process.
+-->
+<html>
+<head>
+ <title>saveHeapSnapshot in e10s child processes</title>
+ <script type="application/javascript"
+ src="/tests/SimpleTest/SimpleTest.js">
+ </script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+ <script type="application/javascript">
+ window.onerror = function (msg, url, line, col, err) {
+ ok(false, "@" + url + ":" + line + ":" + col + ": " + msg + "\n" + err.stack);
+ };
+
+ SimpleTest.waitForExplicitFinish();
+
+ var childFrameURL = "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
+
+ // This function is stringified and loaded in the child process as a frame
+ // script.
+ function childFrameScript() {
+ try {
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ } catch (err) {
+ sendAsyncMessage("testSaveHeapSnapshot:error",
+ { error: err.toString() });
+ return;
+ }
+
+ sendAsyncMessage("testSaveHeapSnapshot:done", {});
+ }
+
+ // Kick everything off on load.
+ window.onload = function () {
+ info("window.onload fired");
+ SpecialPowers.addPermission("browser", true, document);
+ SpecialPowers.pushPrefEnv({
+ "set": [
+ ["dom.ipc.browser_frames.oop_by_default", true],
+ ["dom.mozBrowserFramesEnabled", true],
+ ["browser.pagethumbnails.capturing_disabled", true]
+ ]
+ }, function () {
+ var iframe = document.createElement("iframe");
+ SpecialPowers.wrap(iframe).mozbrowser = true;
+ iframe.id = "iframe";
+ iframe.src = childFrameURL;
+
+
+ iframe.addEventListener("mozbrowserloadend", function onLoadEnd() {
+ iframe.removeEventListener("mozbrowserloadend", onLoadEnd);
+ info("iframe done loading");
+
+ var mm = SpecialPowers.getBrowserFrameMessageManager(iframe);
+
+ function onError(e) {
+ ok(false, e.data.error);
+ }
+ mm.addMessageListener("testSaveHeapSnapshot:error", onError);
+
+ mm.addMessageListener("testSaveHeapSnapshot:done", function onMsg() {
+ mm.removeMessageListener("testSaveHeapSnapshot:done", onMsg);
+ mm.removeMessageListener("testSaveHeapSnapshot:error", onError);
+ ok(true, "Saved heap snapshot in child process");
+ SimpleTest.finish();
+ });
+
+ info("Loading frame script to save heap snapshot");
+ mm.loadFrameScript("data:,(" + encodeURI(childFrameScript.toString()) + ")();",
+ false);
+ });
+
+ info("Loading iframe");
+ document.body.appendChild(iframe);
+ });
+ };
+ </script>
+</window>
diff --git a/devtools/shared/heapsnapshot/tests/unit/.eslintrc.js b/devtools/shared/heapsnapshot/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/heapsnapshot/tests/unit/Census.jsm b/devtools/shared/heapsnapshot/tests/unit/Census.jsm
new file mode 100644
index 000000000..f8fb1ce44
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/Census.jsm
@@ -0,0 +1,165 @@
+// Functions for checking results returned by
+// Debugger.Memory.prototype.takeCensus and
+// HeapSnapshot.prototype.takeCensus. Adapted from js/src/jit-test/lib/census.js.
+
+this.EXPORTED_SYMBOLS = ["Census"];
+
+this.Census = (function () {
+ const Census = {};
+
+ function dumpn(msg) {
+ dump("DBG-TEST: Census.jsm: " + msg + "\n");
+ }
+
+ // Census.walkCensus(subject, name, walker)
+ //
+ // Use |walker| to check |subject|, a census object of the sort returned by
+ // Debugger.Memory.prototype.takeCensus: a tree of objects with integers at the
+ // leaves. Use |name| as the name for |subject| in diagnostic messages. Return
+ // the number of leaves of |subject| we visited.
+ //
+ // A walker is an object with three methods:
+ //
+ // - enter(prop): Return the walker we should use to check the property of the
+ // subject census named |prop|. This is for recursing into the subobjects of
+ // the subject.
+ //
+ // - done(): Called after we have called 'enter' on every property of the
+ // subject.
+ //
+ // - check(value): Check |value|, a leaf in the subject.
+ //
+ // Walker methods are expected to simply throw if a node we visit doesn't look
+ // right.
+ Census.walkCensus = (subject, name, walker) => walk(subject, name, walker, 0);
+ function walk(subject, name, walker, count) {
+ if (typeof subject === "object") {
+ dumpn(name);
+ for (let prop in subject) {
+ count = walk(subject[prop],
+ name + "[" + uneval(prop) + "]",
+ walker.enter(prop),
+ count);
+ }
+ walker.done();
+ } else {
+ dumpn(name + " = " + uneval(subject));
+ walker.check(subject);
+ count++;
+ }
+
+ return count;
+ }
+
+ // A walker that doesn't check anything.
+ Census.walkAnything = {
+ enter: () => Census.walkAnything,
+ done: () => undefined,
+ check: () => undefined
+ };
+
+ // A walker that requires all leaves to be zeros.
+ Census.assertAllZeros = {
+ enter: () => Census.assertAllZeros,
+ done: () => undefined,
+ check: elt => { if (elt !== 0) throw new Error("Census mismatch: expected zero, found " + elt); }
+ };
+
+ function expectedObject() {
+ throw new Error("Census mismatch: subject has leaf where basis has nested object");
+ }
+
+ function expectedLeaf() {
+ throw new Error("Census mismatch: subject has nested object where basis has leaf");
+ }
+
+ // Return a function that, given a 'basis' census, returns a census walker that
+ // compares the subject census against the basis. The returned walker calls the
+ // given |compare|, |missing|, and |extra| functions as follows:
+ //
+ // - compare(subjectLeaf, basisLeaf): Check a leaf of the subject against the
+ // corresponding leaf of the basis.
+ //
+ // - missing(prop, value): Called when the subject is missing a property named
+ // |prop| which is present in the basis with value |value|.
+ //
+ // - extra(prop): Called when the subject has a property named |prop|, but the
+ // basis has no such property. This should return a walker that can check
+ // the subject's value.
+ function makeBasisChecker({compare, missing, extra}) {
+ return function makeWalker(basis) {
+ if (typeof basis === "object") {
+ var unvisited = new Set(Object.getOwnPropertyNames(basis));
+ return {
+ enter: prop => {
+ unvisited.delete(prop);
+ if (prop in basis) {
+ return makeWalker(basis[prop]);
+ } else {
+ return extra(prop);
+ }
+ },
+
+ done: () => unvisited.forEach(prop => missing(prop, basis[prop])),
+ check: expectedObject
+ };
+ } else {
+ return {
+ enter: expectedLeaf,
+ done: expectedLeaf,
+ check: elt => compare(elt, basis)
+ };
+ }
+ };
+ }
+
+ function missingProp(prop) {
+ throw new Error("Census mismatch: subject lacks property present in basis: " + prop);
+ }
+
+ function extraProp(prop) {
+ throw new Error("Census mismatch: subject has property not present in basis: " + prop);
+ }
+
+ // Return a walker that checks that the subject census has counts all equal to
+ // |basis|.
+ Census.assertAllEqual = makeBasisChecker({
+ compare: (a, b) => { if (a !== b) throw new Error("Census mismatch: expected " + a + " got " + b);},
+ missing: missingProp,
+ extra: extraProp
+ });
+
+ function ok(val) {
+ if (!val) {
+ throw new Error("Census mismatch: expected truthy, got " + val);
+ }
+ }
+
+ // Return a walker that checks that the subject census has at least as many
+ // items of each category as |basis|.
+ Census.assertAllNotLessThan = makeBasisChecker({
+ compare: (subject, basis) => ok(subject >= basis),
+ missing: missingProp,
+ extra: () => Census.walkAnything
+ });
+
+ // Return a walker that checks that the subject census has at most as many
+ // items of each category as |basis|.
+ Census.assertAllNotMoreThan = makeBasisChecker({
+ compare: (subject, basis) => ok(subject <= basis),
+ missing: missingProp,
+ extra: () => Census.walkAnything
+ });
+
+ // Return a walker that checks that the subject census has within |fudge|
+ // items of each category of the count in |basis|.
+ Census.assertAllWithin = function (fudge, basis) {
+ return makeBasisChecker({
+ compare: (subject, basis) => ok(Math.abs(subject - basis) <= fudge),
+ missing: missingProp,
+ extra: () => Census.walkAnything
+ })(basis);
+ };
+
+ return Census;
+}());
diff --git a/devtools/shared/heapsnapshot/tests/unit/Match.jsm b/devtools/shared/heapsnapshot/tests/unit/Match.jsm
new file mode 100644
index 000000000..c29e6484e
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/Match.jsm
@@ -0,0 +1,190 @@
+// A little pattern-matching library.
+//
+// Ported from js/src/tests/js1_8_5/reflect-parse/Match.js for use with devtools
+// server xpcshell tests.
+
+this.EXPORTED_SYMBOLS = ["Match"];
+
+this.Match = (function() {
+
+ function Pattern(template) {
+ // act like a constructor even as a function
+ if (!(this instanceof Pattern))
+ return new Pattern(template);
+
+ this.template = template;
+ }
+
+ Pattern.prototype = {
+ match: function(act) {
+ return match(act, this.template);
+ },
+
+ matches: function(act) {
+ try {
+ return this.match(act);
+ }
+ catch (e if e instanceof MatchError) {
+ return false;
+ }
+ },
+
+ assert: function(act, message) {
+ try {
+ return this.match(act);
+ }
+ catch (e if e instanceof MatchError) {
+ throw new Error((message || "failed match") + ": " + e.message);
+ }
+ },
+
+ toString: () => "[object Pattern]"
+ };
+
+ Pattern.ANY = new Pattern;
+ Pattern.ANY.template = Pattern.ANY;
+
+ Pattern.NUMBER = new Pattern;
+ Pattern.NUMBER.match = function (act) {
+ if (typeof act !== 'number') {
+ throw new MatchError("Expected number, got: " + quote(act));
+ }
+ }
+
+ Pattern.NATURAL = new Pattern
+ Pattern.NATURAL.match = function (act) {
+ if (typeof act !== 'number' || act !== Math.floor(act) || act < 0) {
+ throw new MatchError("Expected natural number, got: " + quote(act));
+ }
+ }
+
+ var quote = uneval;
+
+ function MatchError(msg) {
+ this.message = msg;
+ }
+
+ MatchError.prototype = {
+ toString: function() {
+ return "match error: " + this.message;
+ }
+ };
+
+ function isAtom(x) {
+ return (typeof x === "number") ||
+ (typeof x === "string") ||
+ (typeof x === "boolean") ||
+ (x === null) ||
+ (typeof x === "object" && x instanceof RegExp);
+ }
+
+ function isObject(x) {
+ return (x !== null) && (typeof x === "object");
+ }
+
+ function isFunction(x) {
+ return typeof x === "function";
+ }
+
+ function isArrayLike(x) {
+ return isObject(x) && ("length" in x);
+ }
+
+ function matchAtom(act, exp) {
+ if ((typeof exp) === "number" && isNaN(exp)) {
+ if ((typeof act) !== "number" || !isNaN(act))
+ throw new MatchError("expected NaN, got: " + quote(act));
+ return true;
+ }
+
+ if (exp === null) {
+ if (act !== null)
+ throw new MatchError("expected null, got: " + quote(act));
+ return true;
+ }
+
+ if (exp instanceof RegExp) {
+ if (!(act instanceof RegExp) || exp.source !== act.source)
+ throw new MatchError("expected " + quote(exp) + ", got: " + quote(act));
+ return true;
+ }
+
+ switch (typeof exp) {
+ case "string":
+ if (act !== exp)
+ throw new MatchError("expected " + quote(exp) + ", got " + quote(act));
+ return true;
+ case "boolean":
+ case "number":
+ if (exp !== act)
+ throw new MatchError("expected " + exp + ", got " + quote(act));
+ return true;
+ }
+
+ throw new Error("bad pattern: " + exp.toSource());
+ }
+
+ function matchObject(act, exp) {
+ if (!isObject(act))
+ throw new MatchError("expected object, got " + quote(act));
+
+ for (var key in exp) {
+ if (!(key in act))
+ throw new MatchError("expected property " + quote(key) + " not found in " + quote(act));
+ match(act[key], exp[key]);
+ }
+
+ return true;
+ }
+
+ function matchFunction(act, exp) {
+ if (!isFunction(act))
+ throw new MatchError("expected function, got " + quote(act));
+
+ if (act !== exp)
+ throw new MatchError("expected function: " + exp +
+ "\nbut got different function: " + act);
+ }
+
+ function matchArray(act, exp) {
+ if (!isObject(act) || !("length" in act))
+ throw new MatchError("expected array-like object, got " + quote(act));
+
+ var length = exp.length;
+ if (act.length !== exp.length)
+ throw new MatchError("expected array-like object of length " + length + ", got " + quote(act));
+
+ for (var i = 0; i < length; i++) {
+ if (i in exp) {
+ if (!(i in act))
+ throw new MatchError("expected array property " + i + " not found in " + quote(act));
+ match(act[i], exp[i]);
+ }
+ }
+
+ return true;
+ }
+
+ function match(act, exp) {
+ if (exp === Pattern.ANY)
+ return true;
+
+ if (exp instanceof Pattern)
+ return exp.match(act);
+
+ if (isAtom(exp))
+ return matchAtom(act, exp);
+
+ if (isArrayLike(exp))
+ return matchArray(act, exp);
+
+ if (isFunction(exp))
+ return matchFunction(act, exp);
+
+ return matchObject(act, exp);
+ }
+
+ return { Pattern: Pattern,
+ MatchError: MatchError };
+
+})();
diff --git a/devtools/shared/heapsnapshot/tests/unit/dominator-tree-worker.js b/devtools/shared/heapsnapshot/tests/unit/dominator-tree-worker.js
new file mode 100644
index 000000000..1f49ca841
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/dominator-tree-worker.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("Initializing worker.");
+
+self.onmessage = e => {
+ console.log("Starting test.");
+ try {
+ const path = ThreadSafeChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(dominatorTree instanceof DominatorTree);
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+ } catch (e) {
+ ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
+ } finally {
+ done();
+ }
+};
+
+// Proxy assertions to the main thread.
+function ok(val, msg) {
+ console.log("ok(" + !!val + ", \"" + msg + "\")");
+ self.postMessage({
+ type: "assertion",
+ passed: !!val,
+ msg,
+ stack: Error().stack
+ });
+}
+
+// Tell the main thread we are done with the tests.
+function done() {
+ console.log("done()");
+ self.postMessage({
+ type: "done"
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
new file mode 100644
index 000000000..3171c8a6f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -0,0 +1,448 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { Match } = Cu.import("resource://test/Match.jsm", {});
+const { Census } = Cu.import("resource://test/Census.jsm", {});
+const { addDebuggerToGlobal } =
+ Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+const { Task } = require("devtools/shared/task");
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
+const HeapAnalysesClient =
+ require("devtools/shared/heapsnapshot/HeapAnalysesClient");
+const Services = require("Services");
+const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
+const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
+const DominatorTreeNode = require("devtools/shared/heapsnapshot/DominatorTreeNode");
+const { deduplicatePaths } = require("devtools/shared/heapsnapshot/shortest-paths");
+const { LabelAndShallowSizeVisitor } = DominatorTreeNode;
+
+
+// Always log packets when running tests. runxpcshelltests.py will throw
+// the output away anyway, unless you give it the --verbose flag.
+if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
+ Services.prefs.setBoolPref("devtools.debugger.log", true);
+}
+flags.wantLogging = true;
+
+const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+
+function dumpn(msg) {
+ dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
+}
+
+function addTestingFunctionsToGlobal(global) {
+ global.eval(
+ `
+ const testingFunctions = Components.utils.getJSTestingFunctions();
+ for (let k in testingFunctions) {
+ this[k] = testingFunctions[k];
+ }
+ `
+ );
+ if (!global.print) {
+ global.print = do_print;
+ }
+ if (!global.newGlobal) {
+ global.newGlobal = newGlobal;
+ }
+ if (!global.Debugger) {
+ addDebuggerToGlobal(global);
+ }
+}
+
+addTestingFunctionsToGlobal(this);
+
+/**
+ * Create a new global, with all the JS shell testing functions. Similar to the
+ * newGlobal function exposed to JS shells, and useful for porting JS shell
+ * tests to xpcshell tests.
+ */
+function newGlobal() {
+ const global = new Cu.Sandbox(SYSTEM_PRINCIPAL, { freshZone: true });
+ addTestingFunctionsToGlobal(global);
+ return global;
+}
+
+function assertThrowsValue(f, val, msg) {
+ var fullmsg;
+ try {
+ f();
+ } catch (exc) {
+ if ((exc === val) === (val === val) && (val !== 0 || 1 / exc === 1 / val))
+ return;
+ fullmsg = "Assertion failed: expected exception " + val + ", got " + exc;
+ }
+ if (fullmsg === undefined)
+ fullmsg = "Assertion failed: expected exception " + val + ", no exception thrown";
+ if (msg !== undefined)
+ fullmsg += " - " + msg;
+ throw new Error(fullmsg);
+}
+
+/**
+ * Returns the full path of the file with the specified name in a
+ * platform-independent and URL-like form.
+ */
+function getFilePath(aName, aAllowMissing = false, aUsePlatformPathSeparator = false)
+{
+ let file = do_get_file(aName, aAllowMissing);
+ let path = Services.io.newFileURI(file).spec;
+ let filePrePath = "file://";
+ if ("nsILocalFileWin" in Ci &&
+ file instanceof Ci.nsILocalFileWin) {
+ filePrePath += "/";
+ }
+
+ path = path.slice(filePrePath.length);
+
+ if (aUsePlatformPathSeparator && path.match(/^\w:/)) {
+ path = path.replace(/\//g, "\\");
+ }
+
+ return path;
+}
+
+function saveNewHeapSnapshot(opts = { runtime: true }) {
+ const filePath = ChromeUtils.saveHeapSnapshot(opts);
+ ok(filePath, "Should get a file path to save the core dump to.");
+ ok(true, "Saved a heap snapshot to " + filePath);
+ return filePath;
+}
+
+function readHeapSnapshot(filePath) {
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should have read a heap snapshot back from " + filePath);
+ ok(snapshot instanceof HeapSnapshot, "snapshot should be an instance of HeapSnapshot");
+ return snapshot;
+}
+
+/**
+ * Save a heap snapshot to the file with the given name in the current
+ * directory, read it back as a HeapSnapshot instance, and then take a census of
+ * the heap snapshot's serialized heap graph with the provided census options.
+ *
+ * @param {Object|undefined} censusOptions
+ * Options that should be passed through to the takeCensus method. See
+ * js/src/doc/Debugger/Debugger.Memory.md for details.
+ *
+ * @param {Debugger|null} dbg
+ * If a Debugger object is given, only serialize the subgraph covered by
+ * the Debugger's debuggees. If null, serialize the whole heap graph.
+ *
+ * @param {String} fileName
+ * The file name to save the heap snapshot's core dump file to, within
+ * the current directory.
+ *
+ * @returns Census
+ */
+function saveHeapSnapshotAndTakeCensus(dbg = null, censusOptions = undefined) {
+ const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
+ const filePath = saveNewHeapSnapshot(snapshotOptions);
+ const snapshot = readHeapSnapshot(filePath);
+
+ equal(typeof snapshot.takeCensus, "function", "snapshot should have a takeCensus method");
+
+ return snapshot.takeCensus(censusOptions);
+}
+
+/**
+ * Save a heap snapshot to disk, read it back as a HeapSnapshot instance, and
+ * then compute its dominator tree.
+ *
+ * @param {Debugger|null} dbg
+ * If a Debugger object is given, only serialize the subgraph covered by
+ * the Debugger's debuggees. If null, serialize the whole heap graph.
+ *
+ * @returns {DominatorTree}
+ */
+function saveHeapSnapshotAndComputeDominatorTree(dbg = null) {
+ const snapshotOptions = dbg ? { debugger: dbg } : { runtime: true };
+ const filePath = saveNewHeapSnapshot(snapshotOptions);
+ const snapshot = readHeapSnapshot(filePath);
+
+ equal(typeof snapshot.computeDominatorTree, "function",
+ "snapshot should have a `computeDominatorTree` method");
+
+ const dominatorTree = snapshot.computeDominatorTree();
+
+ ok(dominatorTree, "Should be able to compute a dominator tree");
+ ok(dominatorTree instanceof DominatorTree, "Should be an instance of DominatorTree");
+
+ return dominatorTree;
+}
+
+function isSavedFrame(obj) {
+ return Object.prototype.toString.call(obj) === "[object SavedFrame]";
+}
+
+function savedFrameReplacer(key, val) {
+ if (isSavedFrame(val)) {
+ return `<SavedFrame '${val.toString().split(/\n/g).shift()}'>`;
+ } else {
+ return val;
+ }
+}
+
+/**
+ * Assert that creating a CensusTreeNode from the given `report` with the
+ * specified `breakdown` creates the given `expected` CensusTreeNode.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} report
+ * The census report.
+ *
+ * @param {Object} expected
+ * The expected CensusTreeNode result.
+ *
+ * @param {Object} options
+ * The options to pass through to `censusReportToCensusTreeNode`.
+ */
+function compareCensusViewData(breakdown, report, expected, options) {
+ dumpn("Generating CensusTreeNode from report:");
+ dumpn("breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("report: " + JSON.stringify(report, null, 4));
+ dumpn("expected: " + JSON.stringify(expected, savedFrameReplacer, 4));
+
+ const actual = censusReportToCensusTreeNode(breakdown, report, options);
+ dumpn("actual: " + JSON.stringify(actual, savedFrameReplacer, 4));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+// Deep structural equivalence that can handle Map objects in addition to plain
+// objects.
+function assertStructurallyEquivalent(actual, expected, path = "root") {
+ if (actual === expected) {
+ equal(actual, expected, "actual and expected are the same");
+ return;
+ }
+
+ equal(typeof actual, typeof expected, `${path}: typeof should be the same`);
+
+ if (actual && typeof actual === "object") {
+ const actualProtoString = Object.prototype.toString.call(actual);
+ const expectedProtoString = Object.prototype.toString.call(expected);
+ equal(actualProtoString, expectedProtoString,
+ `${path}: Object.prototype.toString.call() should be the same`);
+
+ if (actualProtoString === "[object Map]") {
+ const expectedKeys = new Set([...expected.keys()]);
+
+ for (let key of actual.keys()) {
+ ok(expectedKeys.has(key),
+ `${path}: every key in actual should exist in expected: ${String(key).slice(0, 10)}`);
+ expectedKeys.delete(key);
+
+ assertStructurallyEquivalent(actual.get(key), expected.get(key),
+ path + ".get(" + String(key).slice(0, 20) + ")");
+ }
+
+ equal(expectedKeys.size, 0,
+ `${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
+ } else if (actualProtoString === "[object Set]") {
+ const expectedItems = new Set([...expected]);
+
+ for (let item of actual) {
+ ok(expectedItems.has(item),
+ `${path}: every set item in actual should exist in expected: ${item}`);
+ expectedItems.delete(item);
+ }
+
+ equal(expectedItems.size, 0,
+ `${path}: every set item in expected should also exist in actual, did not see ${[...expectedItems]}`);
+ } else {
+ const expectedKeys = new Set(Object.keys(expected));
+
+ for (let key of Object.keys(actual)) {
+ ok(expectedKeys.has(key),
+ `${path}: every key in actual should exist in expected: ${key}`);
+ expectedKeys.delete(key);
+
+ assertStructurallyEquivalent(actual[key], expected[key], path + "." + key);
+ }
+
+ equal(expectedKeys.size, 0,
+ `${path}: every key in expected should also exist in actual, did not see ${[...expectedKeys]}`);
+ }
+ } else {
+ equal(actual, expected, `${path}: primitives should be equal`);
+ }
+}
+
+/**
+ * Assert that creating a diff of the `first` and `second` census reports
+ * creates the `expected` delta-report.
+ *
+ * @param {Object} breakdown
+ * The census breakdown.
+ *
+ * @param {Object} first
+ * The first census report.
+ *
+ * @param {Object} second
+ * The second census report.
+ *
+ * @param {Object} expected
+ * The expected delta-report.
+ */
+function assertDiff(breakdown, first, second, expected) {
+ dumpn("Diffing census reports:");
+ dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("First census report: " + JSON.stringify(first, null, 4));
+ dumpn("Second census report: " + JSON.stringify(second, null, 4));
+ dumpn("Expected delta-report: " + JSON.stringify(expected, null, 4));
+
+ const actual = CensusUtils.diff(breakdown, first, second);
+ dumpn("Actual delta-report: " + JSON.stringify(actual, null, 4));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+/**
+ * Assert that creating a label and getting a shallow size from the given node
+ * description with the specified breakdown is as expected.
+ *
+ * @param {Object} breakdown
+ * @param {Object} givenDescription
+ * @param {Number} expectedShallowSize
+ * @param {Object} expectedLabel
+ */
+function assertLabelAndShallowSize(breakdown, givenDescription, expectedShallowSize, expectedLabel) {
+ dumpn("Computing label and shallow size from node description:");
+ dumpn("Breakdown: " + JSON.stringify(breakdown, null, 4));
+ dumpn("Given description: " + JSON.stringify(givenDescription, null, 4));
+
+ const visitor = new LabelAndShallowSizeVisitor();
+ CensusUtils.walk(breakdown, description, visitor);
+
+ dumpn("Expected shallow size: " + expectedShallowSize);
+ dumpn("Actual shallow size: " + visitor.shallowSize());
+ equal(visitor.shallowSize(), expectedShallowSize, "Shallow size should be correct");
+
+ dumpn("Expected label: " + JSON.stringify(expectedLabel, null, 4));
+ dumpn("Actual label: " + JSON.stringify(visitor.label(), null, 4));
+ assertStructurallyEquivalent(visitor.label(), expectedLabel);
+}
+
+// Counter for mock DominatorTreeNode ids.
+let TEST_NODE_ID_COUNTER = 0;
+
+/**
+ * Create a mock DominatorTreeNode for testing, with sane defaults. Override any
+ * property by providing it on `opts`. Optionally pass child nodes as well.
+ *
+ * @param {Object} opts
+ * @param {Array<DominatorTreeNode>?} children
+ *
+ * @returns {DominatorTreeNode}
+ */
+function makeTestDominatorTreeNode(opts, children) {
+ const nodeId = TEST_NODE_ID_COUNTER++;
+
+ const node = Object.assign({
+ nodeId,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: (children || []).reduce((size, c) => size + c.retainedSize, 1),
+ parentId: undefined,
+ children,
+ moreChildrenAvailable: true,
+ }, opts);
+
+ if (children && children.length) {
+ children.map(c => c.parentId = node.nodeId);
+ }
+
+ return node;
+}
+
+/**
+ * Insert `newChildren` into the given dominator `tree` as specified by the
+ * `path` from the root to the node the `newChildren` should be inserted
+ * beneath. Assert that the resulting tree matches `expected`.
+ */
+function assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected) {
+ dumpn("Inserting new children into a dominator tree:");
+ dumpn("Dominator tree: " + JSON.stringify(tree, null, 2));
+ dumpn("Path: " + JSON.stringify(path, null, 2));
+ dumpn("New children: " + JSON.stringify(newChildren, null, 2));
+ dumpn("Expected resulting tree: " + JSON.stringify(expected, null, 2));
+
+ const actual = DominatorTreeNode.insert(tree, path, newChildren, moreChildrenAvailable);
+ dumpn("Actual resulting tree: " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+function assertDeduplicatedPaths({ target, paths, expectedNodes, expectedEdges }) {
+ dumpn("Deduplicating paths:");
+ dumpn("target = " + target);
+ dumpn("paths = " + JSON.stringify(paths, null, 2));
+ dumpn("expectedNodes = " + expectedNodes);
+ dumpn("expectedEdges = " + JSON.stringify(expectedEdges, null, 2));
+
+ const { nodes, edges } = deduplicatePaths(target, paths);
+
+ dumpn("Actual nodes = " + nodes);
+ dumpn("Actual edges = " + JSON.stringify(edges, null, 2));
+
+ equal(nodes.length, expectedNodes.length,
+ "actual number of nodes is equal to the expected number of nodes");
+
+ equal(edges.length, expectedEdges.length,
+ "actual number of edges is equal to the expected number of edges");
+
+ const expectedNodeSet = new Set(expectedNodes);
+ const nodeSet = new Set(nodes);
+ ok(nodeSet.size === nodes.length,
+ "each returned node should be unique");
+
+ for (let node of nodes) {
+ ok(expectedNodeSet.has(node), `the ${node} node was expected`);
+ }
+
+ for (let expectedEdge of expectedEdges) {
+ let count = 0;
+ for (let edge of edges) {
+ if (edge.from === expectedEdge.from &&
+ edge.to === expectedEdge.to &&
+ edge.name === expectedEdge.name) {
+ count++;
+ }
+ }
+ equal(count, 1,
+ "should have exactly one matching edge for the expected edge = " + JSON.stringify(edge));
+ }
+}
+
+function assertCountToBucketBreakdown(breakdown, expected) {
+ dumpn("count => bucket breakdown");
+ dumpn("Initial breakdown = ", JSON.stringify(breakdown, null, 2));
+ dumpn("Expected results = ", JSON.stringify(expected, null, 2));
+
+ const actual = CensusUtils.countToBucketBreakdown(breakdown);
+ dumpn("Actual results = ", JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(actual, expected);
+}
+
+/**
+ * Create a mock path entry for the given predecessor and edge.
+ */
+function pathEntry(predecessor, edge) {
+ return { predecessor, edge };
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/heap-snapshot-worker.js b/devtools/shared/heapsnapshot/tests/unit/heap-snapshot-worker.js
new file mode 100644
index 000000000..10ee70cec
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/heap-snapshot-worker.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+console.log("Initializing worker.");
+
+self.onmessage = e => {
+ console.log("Starting test.");
+ try {
+ ok(typeof ChromeUtils === "undefined",
+ "Should not have access to ChromeUtils in a worker.");
+ ok(ThreadSafeChromeUtils,
+ "Should have access to ThreadSafeChromeUtils in a worker.");
+ ok(HeapSnapshot,
+ "Should have access to HeapSnapshot in a worker.");
+
+ const filePath = ThreadSafeChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ThreadSafeChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
+ } catch (e) {
+ ok(false, "Unexpected error inside worker:\n" + e.toString() + "\n" + e.stack);
+ } finally {
+ done();
+ }
+};
+
+// Proxy assertions to the main thread.
+function ok(val, msg) {
+ console.log("ok(" + !!val + ", \"" + msg + "\")");
+ self.postMessage({
+ type: "assertion",
+ passed: !!val,
+ msg,
+ stack: Error().stack
+ });
+}
+
+// Tell the main thread we are done with the tests.
+function done() {
+ console.log("done()");
+ self.postMessage({
+ type: "done"
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js
new file mode 100644
index 000000000..845a0d263
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_01.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ Function: { count: 1, bytes: 32 },
+ other: { count: 0, bytes: 0 }
+ },
+ strings: {},
+ scripts: {},
+ other: {}
+};
+
+const expected = [
+ "objects",
+ "Function"
+];
+
+const shallowSize = 32;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js
new file mode 100644
index 000000000..e1f32de58
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_02.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ other: { count: 1, bytes: 10 }
+ },
+ strings: {},
+ scripts: {},
+ other: {}
+};
+
+const expected = [
+ "objects",
+ "other"
+];
+
+const shallowSize = 10;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js
new file mode 100644
index 000000000..ad35dcec1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_03.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const description = {
+ objects: {
+ other: { count: 0, bytes: 0 }
+ },
+ strings: {
+ "JSString": { count: 1, bytes: 42 },
+ },
+ scripts: {},
+ other: {}
+};
+
+const expected = [
+ "strings",
+ "JSString"
+];
+
+const shallowSize = 42;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js
new file mode 100644
index 000000000..566ad0dab
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_LabelAndShallowSize_04.js
@@ -0,0 +1,53 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can generate label structures from node description reports.
+
+const breakdown = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+ },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+const stack = saveStack();
+
+const description = {
+ objects: {
+ Array: new Map([[stack, { count: 1, bytes: 512 }]]),
+ other: { count: 0, bytes: 0 }
+ },
+ strings: {},
+ scripts: {},
+ other: {}
+};
+
+const expected = [
+ "objects",
+ "Array",
+ stack
+];
+
+const shallowSize = 512;
+
+function run_test() {
+ assertLabelAndShallowSize(breakdown, description, shallowSize, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_attachShortestPaths_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_attachShortestPaths_01.js
new file mode 100644
index 000000000..24e8e2eb5
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_attachShortestPaths_01.js
@@ -0,0 +1,132 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that the DominatorTreeNode.attachShortestPaths function can correctly
+// attach the deduplicated shortest retaining paths for each node it is given.
+
+const startNodeId = 9999;
+const maxNumPaths = 2;
+
+// Mock data mapping node id to shortest paths to that node id.
+const shortestPaths = new Map([
+ [1000, [
+ [pathEntry(1100, "a"), pathEntry(1200, "b")],
+ [pathEntry(1100, "c"), pathEntry(1300, "d")],
+ ]],
+ [2000, [
+ [pathEntry(2100, "e"), pathEntry(2200, "f"), pathEntry(2300, "g")]
+ ]],
+ [3000, [
+ [pathEntry(3100, "h")],
+ [pathEntry(3100, "i")],
+ [pathEntry(3100, "j")],
+ [pathEntry(3200, "k")],
+ [pathEntry(3300, "l")],
+ [pathEntry(3400, "m")],
+ ]],
+]);
+
+const actual = [
+ makeTestDominatorTreeNode({ nodeId: 1000 }),
+ makeTestDominatorTreeNode({ nodeId: 2000 }),
+ makeTestDominatorTreeNode({ nodeId: 3000 }),
+];
+
+const expected = [
+ makeTestDominatorTreeNode({
+ nodeId: 1000,
+ shortestPaths: {
+ nodes: [
+ { id: 1000, label: ["SomeType-1000"] },
+ { id: 1100, label: ["SomeType-1100"] },
+ { id: 1200, label: ["SomeType-1200"] },
+ { id: 1300, label: ["SomeType-1300"] },
+ ],
+ edges: [
+ { from: 1100, to: 1200, name: "a" },
+ { from: 1100, to: 1300, name: "c" },
+ { from: 1200, to: 1000, name: "b" },
+ { from: 1300, to: 1000, name: "d" },
+ ]
+ }
+ }),
+
+ makeTestDominatorTreeNode({
+ nodeId: 2000,
+ shortestPaths: {
+ nodes: [
+ { id: 2000, label: ["SomeType-2000"] },
+ { id: 2100, label: ["SomeType-2100"] },
+ { id: 2200, label: ["SomeType-2200"] },
+ { id: 2300, label: ["SomeType-2300"] },
+ ],
+ edges: [
+ { from: 2100, to: 2200, name: "e" },
+ { from: 2200, to: 2300, name: "f" },
+ { from: 2300, to: 2000, name: "g" },
+ ]
+ }
+ }),
+
+ makeTestDominatorTreeNode({ nodeId: 3000,
+ shortestPaths: {
+ nodes: [
+ { id: 3000, label: ["SomeType-3000"] },
+ { id: 3100, label: ["SomeType-3100"] },
+ { id: 3200, label: ["SomeType-3200"] },
+ { id: 3300, label: ["SomeType-3300"] },
+ { id: 3400, label: ["SomeType-3400"] },
+ ],
+ edges: [
+ { from: 3100, to: 3000, name: "h" },
+ { from: 3100, to: 3000, name: "i" },
+ { from: 3100, to: 3000, name: "j" },
+ { from: 3200, to: 3000, name: "k" },
+ { from: 3300, to: 3000, name: "l" },
+ { from: 3400, to: 3000, name: "m" },
+ ]
+ }
+ }),
+];
+
+const breakdown = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+};
+
+const mockSnapshot = {
+ computeShortestPaths: (start, nodeIds, max) => {
+ equal(start, startNodeId);
+ equal(max, maxNumPaths);
+
+ return new Map(nodeIds.map(nodeId => {
+ const paths = shortestPaths.get(nodeId);
+ ok(paths, "Expected computeShortestPaths call for node id = " + nodeId);
+ return [nodeId, paths];
+ }));
+ },
+
+ describeNode: (bd, nodeId) => {
+ equal(bd, breakdown);
+ return {
+ ["SomeType-" + nodeId]: {
+ count: 1,
+ bytes: 10,
+ }
+ };
+ },
+};
+
+function run_test() {
+ DominatorTreeNode.attachShortestPaths(mockSnapshot,
+ breakdown,
+ startNodeId,
+ actual,
+ maxNumPaths);
+
+ dumpn("Expected = " + JSON.stringify(expected, null, 2));
+ dumpn("Actual = " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(expected, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_getNodeByIdAlongPath_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_getNodeByIdAlongPath_01.js
new file mode 100644
index 000000000..de2907809
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_getNodeByIdAlongPath_01.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can find the node with the given id along the specified path.
+
+const node3000 = makeTestDominatorTreeNode({ nodeId: 3000 });
+
+const node2000 = makeTestDominatorTreeNode({ nodeId: 2000 }, [
+ makeTestDominatorTreeNode({}),
+ node3000,
+ makeTestDominatorTreeNode({}),
+]);
+
+const node1000 = makeTestDominatorTreeNode({ nodeId: 1000 }, [
+ makeTestDominatorTreeNode({}),
+ node2000,
+ makeTestDominatorTreeNode({}),
+]);
+
+const tree = node1000;
+
+const path = [1000, 2000, 3000];
+
+const tests = [
+ { id: 1000, expected: node1000 },
+ { id: 2000, expected: node2000 },
+ { id: 3000, expected: node3000 },
+];
+
+function run_test() {
+ for (let { id, expected } of tests) {
+ const actual = DominatorTreeNode.getNodeByIdAlongPath(id, tree, path);
+ equal(actual, expected, `We should have got the node with id = ${id}`);
+ }
+
+ equal(null,
+ DominatorTreeNode.getNodeByIdAlongPath(999999999999, tree, path),
+ "null is returned for nodes that are not even in the tree");
+
+ const lastNodeId = tree.children[tree.children.length - 1].nodeId;
+ equal(null,
+ DominatorTreeNode.getNodeByIdAlongPath(lastNodeId, tree, path),
+ "null is returned for nodes that are not along the path");
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_01.js
new file mode 100644
index 000000000..979232ff4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_01.js
@@ -0,0 +1,112 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can insert new children into an existing DominatorTreeNode tree.
+
+const tree = makeTestDominatorTreeNode({ nodeId: 1000 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({ nodeId: 2000 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({ nodeId: 3000 }),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [1000, 2000, 3000];
+
+const newChildren = [
+ makeTestDominatorTreeNode({ parentId: 3000 }),
+ makeTestDominatorTreeNode({ parentId: 3000 }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = {
+ nodeId: 1000,
+ parentId: undefined,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 7,
+ children: [
+ {
+ nodeId: 0,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 1000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 2000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 4,
+ parentId: 1000,
+ children: [
+ {
+ nodeId: 1,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ children: [
+ {
+ nodeId: 7,
+ parentId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ {
+ nodeId: 8,
+ parentId: 3000,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: false
+ },
+ {
+ nodeId: 3,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 2000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ },
+ ],
+ moreChildrenAvailable: true
+ },
+ {
+ nodeId: 5,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 1000,
+ moreChildrenAvailable: true,
+ children: undefined,
+ }
+ ],
+ moreChildrenAvailable: true
+};
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_02.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_02.js
new file mode 100644
index 000000000..9a8d11d0b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_02.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test attempting to insert new children into an existing DominatorTreeNode
+// tree with a bad path.
+
+const tree = makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [111111, 222222, 333333];
+
+const newChildren = [
+ makeTestDominatorTreeNode({ parentId: 333333 }),
+ makeTestDominatorTreeNode({ parentId: 333333 }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = tree;
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_03.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_03.js
new file mode 100644
index 000000000..f8cb5eec3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_insert_03.js
@@ -0,0 +1,117 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test inserting new children into an existing DominatorTreeNode at the root.
+
+const tree = makeTestDominatorTreeNode({ nodeId: 666 }, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}, [
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ makeTestDominatorTreeNode({}),
+ ]),
+ makeTestDominatorTreeNode({}),
+]);
+
+const path = [666];
+
+const newChildren = [
+ makeTestDominatorTreeNode({
+ nodeId: 777,
+ parentId: 666
+ }),
+ makeTestDominatorTreeNode({
+ nodeId: 888,
+ parentId: 666
+ }),
+];
+
+const moreChildrenAvailable = false;
+
+const expected = {
+ nodeId: 666,
+ label: undefined,
+ parentId: undefined,
+ shallowSize: 1,
+ retainedSize: 7,
+ children: [
+ {
+ nodeId: 0,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined
+ },
+ {
+ nodeId: 4,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 4,
+ parentId: 666,
+ children: [
+ {
+ nodeId: 1,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined
+ },
+ {
+ nodeId: 2,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined
+ },
+ {
+ nodeId: 3,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 4,
+ moreChildrenAvailable: true,
+ children: undefined
+ }
+ ],
+ moreChildrenAvailable: true
+ },
+ {
+ nodeId: 5,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined
+ },
+ {
+ nodeId: 777,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined
+ },
+ {
+ nodeId: 888,
+ label: undefined,
+ shallowSize: 1,
+ retainedSize: 1,
+ parentId: 666,
+ moreChildrenAvailable: true,
+ children: undefined
+ }
+ ],
+ moreChildrenAvailable: false
+};
+
+function run_test() {
+ assertDominatorTreeNodeInsertion(tree, path, newChildren, moreChildrenAvailable, expected);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js
new file mode 100644
index 000000000..78ec47b64
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTreeNode_partialTraversal_01.js
@@ -0,0 +1,164 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we correctly set `moreChildrenAvailable` when doing a partial
+// traversal of a dominator tree to create the initial incrementally loaded
+// `DominatorTreeNode` tree.
+
+// `tree` maps parent to children:
+//
+// 100
+// |- 200
+// | |- 500
+// | |- 600
+// | `- 700
+// |- 300
+// | |- 800
+// | |- 900
+// `- 400
+// |- 1000
+// |- 1100
+// `- 1200
+const tree = new Map([
+ [100, [200, 300, 400]],
+ [200, [500, 600, 700]],
+ [300, [800, 900]],
+ [400, [1000, 1100, 1200]]
+]);
+
+const mockDominatorTree = {
+ root: 100,
+ getRetainedSize: _ => 10,
+ getImmediatelyDominated: id => (tree.get(id) || []).slice()
+};
+
+const mockSnapshot = {
+ describeNode: _ => ({
+ objects: { count: 0, bytes: 0 },
+ strings: { count: 0, bytes: 0 },
+ scripts: { count: 0, bytes: 0 },
+ other: { SomeType: { count: 1, bytes: 10 } }
+ })
+};
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+ },
+};
+
+const expected = {
+ nodeId: 100,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 200,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 100,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 500,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 200,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined
+ },
+ {
+ nodeId: 600,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 200,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined
+ }
+ ],
+ moreChildrenAvailable: true
+ },
+ {
+ nodeId: 300,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 100,
+ shortestPaths: undefined,
+ children: [
+ {
+ nodeId: 800,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 300,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined
+ },
+ {
+ nodeId: 900,
+ label: [
+ "other",
+ "SomeType"
+ ],
+ shallowSize: 10,
+ retainedSize: 10,
+ parentId: 300,
+ moreChildrenAvailable: false,
+ shortestPaths: undefined,
+ children: undefined
+ }
+ ],
+ moreChildrenAvailable: false
+ }
+ ],
+ moreChildrenAvailable: true,
+ parentId: undefined,
+};
+
+function run_test() {
+ // Traverse the whole depth of the test tree, but one short of the number of
+ // siblings. This will exercise the moreChildrenAvailable handling for
+ // siblings.
+ const actual = DominatorTreeNode.partialTraversal(mockDominatorTree,
+ mockSnapshot,
+ breakdown,
+ /* maxDepth = */ 4,
+ /* siblings = */ 2);
+
+ dumpn("Expected = " + JSON.stringify(expected, null, 2));
+ dumpn("Actual = " + JSON.stringify(actual, null, 2));
+
+ assertStructurallyEquivalent(expected, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_01.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_01.js
new file mode 100644
index 000000000..e8145f658
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_01.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Sanity test that we can compute dominator trees.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ ok(dominatorTree);
+ ok(dominatorTree instanceof DominatorTree);
+
+ let threw = false;
+ try {
+ new DominatorTree();
+ } catch (e) {
+ threw = true;
+ }
+ ok(threw, "Constructor shouldn't be usable");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_02.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_02.js
new file mode 100644
index 000000000..a518f8a27
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_02.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can compute dominator trees from a snapshot in a worker.
+
+add_task(function* () {
+ const worker = new ChromeWorker("resource://test/dominator-tree-worker.js");
+ worker.postMessage({});
+
+ let assertionCount = 0;
+ worker.onmessage = e => {
+ if (e.data.type !== "assertion") {
+ return;
+ }
+
+ ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
+ assertionCount++;
+ };
+
+ yield waitForDone(worker);
+
+ ok(assertionCount > 0);
+ worker.terminate();
+});
+
+function waitForDone(w) {
+ return new Promise((resolve, reject) => {
+ w.onerror = e => {
+ reject();
+ ok(false, "Error in worker: " + e);
+ };
+
+ w.addEventListener("message", function listener(e) {
+ if (e.data.type === "done") {
+ w.removeEventListener("message", listener, false);
+ resolve();
+ }
+ }, false);
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js
new file mode 100644
index 000000000..0a14ce53d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_03.js
@@ -0,0 +1,13 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can get the root of dominator trees.
+
+function run_test() {
+ const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+ equal(typeof dominatorTree.root, "number", "root should be a number");
+ equal(Math.floor(dominatorTree.root), dominatorTree.root, "root should be an integer");
+ ok(dominatorTree.root >= 0, "root should be positive");
+ ok(dominatorTree.root <= Math.pow(2, 48), "root should be less than or equal to 2^48");
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_04.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_04.js
new file mode 100644
index 000000000..e5aef3fec
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_04.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can get the retained sizes of dominator trees.
+
+function run_test() {
+ const dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+ equal(typeof dominatorTree.getRetainedSize, "function",
+ "getRetainedSize should be a function");
+
+ const size = dominatorTree.getRetainedSize(dominatorTree.root);
+ ok(size, "should get a size for the root");
+ equal(typeof size, "number", "retained sizes should be a number");
+ equal(Math.floor(size), size, "size should be an integer");
+ ok(size > 0, "size should be positive");
+ ok(size <= Math.pow(2, 64), "size should be less than or equal to 2^64");
+
+ const bad = dominatorTree.getRetainedSize(1);
+ equal(bad, null, "null is returned for unknown node ids");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_05.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_05.js
new file mode 100644
index 000000000..c07cee994
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_05.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can get the set of immediately dominated nodes for any given
+// node and that this forms a tree.
+
+function run_test() {
+ var dominatorTree = saveHeapSnapshotAndComputeDominatorTree();
+ equal(typeof dominatorTree.getImmediatelyDominated, "function",
+ "getImmediatelyDominated should be a function");
+
+ // Do a traversal of the dominator tree.
+ //
+ // Note that we don't assert directly, only if we get an unexpected
+ // value. There are just way too many nodes in the heap graph to assert for
+ // every one. This test would constantly time out and assertion messages would
+ // overflow the log size.
+
+ var root = dominatorTree.root;
+ equal(dominatorTree.getImmediateDominator(root), null,
+ "The root should not have a parent");
+
+ var seen = new Set();
+ var stack = [root];
+ while (stack.length > 0) {
+ var top = stack.pop();
+
+ if (seen.has(top)) {
+ ok(false,
+ "This is a tree, not a graph: we shouldn't have multiple edges to the same node");
+ }
+ seen.add(top);
+ if (seen.size % 1000 === 0) {
+ dumpn("Progress update: seen size = " + seen.size);
+ }
+
+ var newNodes = dominatorTree.getImmediatelyDominated(top);
+ if (Object.prototype.toString.call(newNodes) !== "[object Array]") {
+ ok(false, "getImmediatelyDominated should return an array for known node ids");
+ }
+
+ var topSize = dominatorTree.getRetainedSize(top);
+
+ var lastSize = Infinity;
+ for (var i = 0; i < newNodes.length; i++) {
+ if (typeof newNodes[i] !== "number") {
+ ok(false, "Every dominated id should be a number");
+ }
+
+ if (dominatorTree.getImmediateDominator(newNodes[i]) !== top) {
+ ok(false, "child's parent should be the expected parent");
+ }
+
+ var thisSize = dominatorTree.getRetainedSize(newNodes[i]);
+
+ if (thisSize >= topSize) {
+ ok(false, "the size of children in the dominator tree should always be less than that of their parent");
+ }
+
+ if (thisSize > lastSize) {
+ ok(false,
+ "children should be sorted by greatest to least retained size, "
+ + "lastSize = " + lastSize + ", thisSize = " + thisSize);
+ }
+
+ lastSize = thisSize;
+ stack.push(newNodes[i]);
+ }
+ }
+
+ ok(true, "Successfully walked the tree");
+ dumpn("Walked " + seen.size + " nodes");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_06.js b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_06.js
new file mode 100644
index 000000000..680478623
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_DominatorTree_06.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the retained size of a node is the sum of its children retained
+// sizes plus its shallow size.
+
+// Note that we don't assert directly, only if we get an unexpected
+// value. There are just way too many nodes in the heap graph to assert for
+// every one. This test would constantly time out and assertion messages would
+// overflow the log size.
+function fastAssert(cond, msg) {
+ if (!cond) {
+ ok(false, msg);
+ }
+}
+
+var COUNT = { by: "count", count: false, bytes: true };
+
+function run_test() {
+ var path = saveNewHeapSnapshot();
+ var snapshot = ChromeUtils.readHeapSnapshot(path);
+ var dominatorTree = snapshot.computeDominatorTree();
+
+ // Do a traversal of the dominator tree and assert the relationship between
+ // retained size, shallow size, and children's retained sizes.
+
+ var root = dominatorTree.root;
+ var stack = [root];
+ while (stack.length > 0) {
+ var top = stack.pop();
+
+ var children = dominatorTree.getImmediatelyDominated(top);
+
+ var topRetainedSize = dominatorTree.getRetainedSize(top);
+ var topShallowSize = snapshot.describeNode(COUNT, top).bytes;
+ fastAssert(topShallowSize <= topRetainedSize,
+ "The shallow size should be less than or equal to the " +
+ "retained size");
+
+ var sumOfChildrensRetainedSizes = 0;
+ for (var i = 0; i < children.length; i++) {
+ sumOfChildrensRetainedSizes += dominatorTree.getRetainedSize(children[i]);
+ stack.push(children[i]);
+ }
+
+ fastAssert(sumOfChildrensRetainedSizes <= topRetainedSize,
+ "The sum of the children's retained sizes should be less than " +
+ "or equal to the retained size");
+ fastAssert(sumOfChildrensRetainedSizes + topShallowSize === topRetainedSize,
+ "The sum of the children's retained sizes plus the shallow " +
+ "size should be equal to the retained size");
+ }
+
+ ok(true, "Successfully walked the tree");
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_01.js
new file mode 100644
index 000000000..0114e0b69
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_01.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+ equal(typeof dominatorTreeId, "number",
+ "should get a dominator tree id, and it should be a number");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_02.js
new file mode 100644
index 000000000..6e3f5b257
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_computeDominatorTree_02.js
@@ -0,0 +1,23 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "computeDominatorTree" request with bad
+// file paths.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ yield client.computeDominatorTree("/etc/passwd");
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "should throw when given a bad path");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_01.js
new file mode 100644
index 000000000..7708de93c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_01.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can delete heap snapshots.
+
+function run_test() {
+ run_next_test();
+}
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ let dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+ ok(true, "Should have computed the dominator tree");
+
+ yield client.deleteHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have deleted the snapshot");
+
+ let threw = false;
+ try {
+ yield client.getDominatorTree({
+ dominatorTreeId: dominatorTreeId,
+ breakdown
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getDominatorTree on deleted tree should throw an error");
+
+ threw = false;
+ try {
+ yield client.computeDominatorTree(snapshotFilePath);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "computeDominatorTree on deleted snapshot should throw an error");
+
+ threw = false;
+ try {
+ yield client.takeCensus(snapshotFilePath);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "takeCensus on deleted tree should throw an error");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_02.js
new file mode 100644
index 000000000..3e25ddac4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_02.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test deleteHeapSnapshot is a noop if the provided path matches no snapshot
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ yield client.deleteHeapSnapshot("path-does-not-exist");
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "deleteHeapSnapshot on non-existant path should throw an error");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js
new file mode 100644
index 000000000..e648c9407
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_deleteHeapSnapshot_03.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test other dominatorTrees can still be retrieved after deleting a snapshot
+
+function run_test() {
+ run_next_test();
+}
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+function* createSnapshotAndDominatorTree(client) {
+ let snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ let dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+ return { dominatorTreeId, snapshotFilePath };
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ let savedSnapshots = [
+ yield createSnapshotAndDominatorTree(client),
+ yield createSnapshotAndDominatorTree(client),
+ yield createSnapshotAndDominatorTree(client)
+ ];
+ ok(true, "Create 3 snapshots and dominator trees");
+
+ yield client.deleteHeapSnapshot(savedSnapshots[1].snapshotFilePath);
+ ok(true, "Snapshot deleted");
+
+ let tree = yield client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[0].dominatorTreeId,
+ breakdown
+ });
+ ok(tree, "Should get a valid tree for first snapshot");
+
+ let threw = false;
+ try {
+ yield client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[1].dominatorTreeId,
+ breakdown
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getDominatorTree on a deleted snapshot should throw an error");
+
+ tree = yield client.getDominatorTree({
+ dominatorTreeId: savedSnapshots[2].dominatorTreeId,
+ breakdown
+ });
+ ok(tree, "Should get a valid tree for third snapshot");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
new file mode 100644
index 000000000..b63ad4230
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCensusIndividuals_01.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can get census individuals.
+
+function run_test() {
+ run_next_test();
+}
+
+const COUNT = { by: "count", count: true, bytes: true };
+
+const CENSUS_BREAKDOWN = {
+ by: "coarseType",
+ objects: COUNT,
+ strings: COUNT,
+ scripts: COUNT,
+ other: COUNT,
+};
+
+const LABEL_BREAKDOWN = {
+ by: "internalType",
+ then: COUNT,
+};
+
+const MAX_INDIVIDUALS = 10;
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+ ok(true, "Should have computed dominator tree");
+
+ const { report } = yield client.takeCensus(snapshotFilePath,
+ { breakdown: CENSUS_BREAKDOWN },
+ { asTreeNode: true });
+ ok(report, "Should get a report");
+
+ let nodesWithLeafIndicesFound = 0;
+
+ yield* (function* assertCanGetIndividuals(censusNode) {
+ if (censusNode.reportLeafIndex !== undefined) {
+ nodesWithLeafIndicesFound++;
+
+ const response = yield client.getCensusIndividuals({
+ dominatorTreeId,
+ indices: DevToolsUtils.isSet(censusNode.reportLeafIndex)
+ ? censusNode.reportLeafIndex
+ : new Set([censusNode.reportLeafIndex]),
+ censusBreakdown: CENSUS_BREAKDOWN,
+ labelBreakdown: LABEL_BREAKDOWN,
+ maxRetainingPaths: 1,
+ maxIndividuals: MAX_INDIVIDUALS,
+ });
+
+ dumpn(`response = ${JSON.stringify(response, null, 4)}`);
+
+ equal(response.nodes.length, Math.min(MAX_INDIVIDUALS, censusNode.count),
+ "response.nodes.length === Math.min(MAX_INDIVIDUALS, censusNode.count)");
+
+ let lastRetainedSize = Infinity;
+ for (let individual of response.nodes) {
+ equal(typeof individual.nodeId, "number",
+ "individual.nodeId should be a number");
+ ok(individual.retainedSize <= lastRetainedSize,
+ "individual.retainedSize <= lastRetainedSize");
+ lastRetainedSize = individual.retainedSize;
+ ok(individual.shallowSize, "individual.shallowSize should exist and be non-zero");
+ ok(individual.shortestPaths, "individual.shortestPaths should exist");
+ ok(individual.shortestPaths.nodes, "individual.shortestPaths.nodes should exist");
+ ok(individual.shortestPaths.edges, "individual.shortestPaths.edges should exist");
+ ok(individual.label, "individual.label should exist");
+ }
+ }
+
+ if (censusNode.children) {
+ for (let child of censusNode.children) {
+ yield* assertCanGetIndividuals(child);
+ }
+ }
+ }(report));
+
+ equal(nodesWithLeafIndicesFound, 4, "Should have found a leaf for each coarse type");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCreationTime_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCreationTime_01.js
new file mode 100644
index 000000000..5df79de7a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getCreationTime_01.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can get a HeapSnapshot's
+// creation time.
+
+function waitForThirtyMilliseconds() {
+ const start = Date.now();
+ while (Date.now() - start < 30) ;
+}
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+ const start = Date.now() * 1000;
+
+ // Because Date.now() is less precise than the snapshot's time stamp, give it
+ // a little bit of head room. Additionally, WinXP's timers have a granularity
+ // of only +/-15 ms.
+ waitForThirtyMilliseconds();
+ const snapshotFilePath = saveNewHeapSnapshot();
+ waitForThirtyMilliseconds();
+ const end = Date.now() * 1000;
+
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ let threw = false;
+ try {
+ yield client.getCreationTime("/not/a/real/path", {
+ breakdown: BREAKDOWN
+ });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "getCreationTime should throw when snapshot does not exist");
+
+ let time = yield client.getCreationTime(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ });
+
+ dumpn("Start = " + start);
+ dumpn("End = " + end);
+ dumpn("Time = " + time);
+
+ ok(time >= start, "creation time occurred after start");
+ ok(time <= end, "creation time occurred before end");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js
new file mode 100644
index 000000000..cedea5375
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_01.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request.
+
+function run_test() {
+ run_next_test();
+}
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+ equal(typeof dominatorTreeId, "number",
+ "should get a dominator tree id, and it should be a number");
+
+ const partialTree = yield client.getDominatorTree({
+ dominatorTreeId,
+ breakdown
+ });
+ ok(partialTree, "Should get a partial tree");
+ equal(typeof partialTree, "object",
+ "partialTree should be an object");
+
+ function checkTree(node) {
+ equal(typeof node.nodeId, "number", "each node should have an id");
+
+ if (node === partialTree) {
+ equal(node.parentId, undefined, "the root has no parent");
+ } else {
+ equal(typeof node.parentId, "number", "each node should have a parent id");
+ }
+
+ equal(typeof node.retainedSize, "number",
+ "each node should have a retained size");
+
+ ok(node.children === undefined || Array.isArray(node.children),
+ "each node either has a list of children, or undefined meaning no children loaded");
+ equal(typeof node.moreChildrenAvailable, "boolean",
+ "each node should indicate if there are more children available or not");
+
+ equal(typeof node.shortestPaths, "object",
+ "Should have shortest paths");
+ equal(typeof node.shortestPaths.nodes, "object",
+ "Should have shortest paths' nodes");
+ equal(typeof node.shortestPaths.edges, "object",
+ "Should have shortest paths' edges");
+
+ if (node.children) {
+ node.children.forEach(checkTree);
+ }
+ }
+
+ checkTree(partialTree);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_02.js
new file mode 100644
index 000000000..fd29beece
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getDominatorTree_02.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getDominatorTree" request with bad
+// dominator tree ids.
+
+function run_test() {
+ run_next_test();
+}
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ let threw = false;
+ try {
+ yield client.getDominatorTree({ dominatorTreeId: 42, breakdown });
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "should throw when given a bad id");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js
new file mode 100644
index 000000000..caf1c2056
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_getImmediatelyDominated_01.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the HeapAnalyses{Client,Worker} "getImmediatelyDominated" request.
+
+function run_test() {
+ run_next_test();
+}
+
+const breakdown = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ const dominatorTreeId = yield client.computeDominatorTree(snapshotFilePath);
+
+ const partialTree = yield client.getDominatorTree({
+ dominatorTreeId,
+ breakdown
+ });
+ ok(partialTree.children.length > 0,
+ "root should immediately dominate some nodes");
+
+ // First, test getting a subset of children available.
+ const response = yield client.getImmediatelyDominated({
+ dominatorTreeId,
+ breakdown,
+ nodeId: partialTree.nodeId,
+ startIndex: 0,
+ maxCount: partialTree.children.length - 1
+ });
+
+ ok(Array.isArray(response.nodes));
+ ok(response.nodes.every(node => node.parentId === partialTree.nodeId));
+ ok(response.moreChildrenAvailable);
+ equal(response.path.length, 1);
+ equal(response.path[0], partialTree.nodeId);
+
+ for (let node of response.nodes) {
+ equal(typeof node.shortestPaths, "object",
+ "Should have shortest paths");
+ equal(typeof node.shortestPaths.nodes, "object",
+ "Should have shortest paths' nodes");
+ equal(typeof node.shortestPaths.edges, "object",
+ "Should have shortest paths' edges");
+ }
+
+ // Next, test getting a subset of children available.
+ const secondResponse = yield client.getImmediatelyDominated({
+ dominatorTreeId,
+ breakdown,
+ nodeId: partialTree.nodeId,
+ startIndex: 0,
+ maxCount: Infinity
+ });
+
+ ok(Array.isArray(secondResponse.nodes));
+ ok(secondResponse.nodes.every(node => node.parentId === partialTree.nodeId));
+ ok(!secondResponse.moreChildrenAvailable);
+ equal(secondResponse.path.length, 1);
+ equal(secondResponse.path[0], partialTree.nodeId);
+
+ for (let node of secondResponse.nodes) {
+ equal(typeof node.shortestPaths, "object",
+ "Should have shortest paths");
+ equal(typeof node.shortestPaths.nodes, "object",
+ "Should have shortest paths' nodes");
+ equal(typeof node.shortestPaths.edges, "object",
+ "Should have shortest paths' edges");
+ }
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_readHeapSnapshot_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_readHeapSnapshot_01.js
new file mode 100644
index 000000000..0d0d58bef
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_readHeapSnapshot_01.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can read heap snapshots.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js
new file mode 100644
index 000000000..6f22cbad3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_01.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses.
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: false },
+ other: { by: "count", count: true, bytes: false },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const markers = [allocationMarker()];
+
+ const firstSnapshotFilePath = saveNewHeapSnapshot();
+
+ // Allocate and hold an additional AllocationMarker object so we can see it in
+ // the next heap snapshot.
+ markers.push(allocationMarker());
+
+ const secondSnapshotFilePath = saveNewHeapSnapshot();
+
+ yield client.readHeapSnapshot(firstSnapshotFilePath);
+ yield client.readHeapSnapshot(secondSnapshotFilePath);
+ ok(true, "Should have read both heap snapshot files");
+
+ const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN });
+
+ equal(delta.AllocationMarker.count, 1,
+ "There exists one new AllocationMarker in the second heap snapshot");
+
+ const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN },
+ { asTreeNode: true });
+
+ // Have to manually set these because symbol properties aren't structured
+ // cloned.
+ delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
+ delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
+
+ compareCensusViewData(BREAKDOWN, delta, deltaTreeNode,
+ "Returning delta-census as a tree node represents same data as the report");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
new file mode 100644
index 000000000..f1ba9ce84
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensusDiff_02.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take diffs between censuses as
+// inverted trees.
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+add_task(function* () {
+ const firstSnapshotFilePath = saveNewHeapSnapshot();
+ const secondSnapshotFilePath = saveNewHeapSnapshot();
+
+ const client = new HeapAnalysesClient();
+ yield client.readHeapSnapshot(firstSnapshotFilePath);
+ yield client.readHeapSnapshot(secondSnapshotFilePath);
+
+ ok(true, "Should have read both heap snapshot files");
+
+ const { delta } = yield client.takeCensusDiff(firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN });
+
+ const { delta: deltaTreeNode } = yield client.takeCensusDiff(firstSnapshotFilePath,
+ secondSnapshotFilePath,
+ { breakdown: BREAKDOWN },
+ { asInvertedTreeNode: true });
+
+ // Have to manually set these because symbol properties aren't structured
+ // cloned.
+ delta[CensusUtils.basisTotalBytes] = deltaTreeNode.totalBytes;
+ delta[CensusUtils.basisTotalCount] = deltaTreeNode.totalCount;
+
+ compareCensusViewData(BREAKDOWN, delta, deltaTreeNode, { invert: true });
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js
new file mode 100644
index 000000000..e26981db4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_01.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = yield client.takeCensus(snapshotFilePath);
+ ok(report, "Should get a report");
+ equal(typeof report, "object", "report should be an object");
+
+ ok(report.objects);
+ ok(report.scripts);
+ ok(report.strings);
+ ok(report.other);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js
new file mode 100644
index 000000000..34494af70
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_02.js
@@ -0,0 +1,29 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses with breakdown
+// options.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: { by: "count", count: true, bytes: true }
+ });
+
+ ok(report, "Should get a report");
+ equal(typeof report, "object", "report should be an object");
+
+ ok(report.count);
+ ok(report.bytes);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_03.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_03.js
new file mode 100644
index 000000000..486e250b5
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_03.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} bubbles errors properly when things
+// go wrong.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ // Snapshot file path to a file that doesn't exist.
+ let failed = false;
+ try {
+ yield client.readHeapSnapshot(getFilePath("foo-bar-baz" + Math.random(), true));
+ } catch (e) {
+ failed = true;
+ }
+ ok(failed, "should not read heap snapshots that do not exist");
+
+ // Snapshot file path to a file that is not a heap snapshot.
+ failed = false;
+ try {
+ yield client.readHeapSnapshot(getFilePath("test_HeapAnalyses_takeCensus_03.js"));
+ } catch (e) {
+ failed = true;
+ }
+ ok(failed, "should not be able to read a file that is not a heap snapshot as a heap snapshot");
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ // Bad census breakdown options.
+ failed = false;
+ try {
+ yield client.takeCensus(snapshotFilePath, {
+ breakdown: { by: "some classification that we do not have" }
+ });
+ } catch (e) {
+ failed = true;
+ }
+ ok(failed, "should not be able to breakdown by an unknown classification");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js
new file mode 100644
index 000000000..769a2d99d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_04.js
@@ -0,0 +1,118 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can send SavedFrame stacks from
+// by-allocation-stack reports from the worker.
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function* test() {
+ const client = new HeapAnalysesClient();
+
+ // Track some allocation stacks.
+
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+ g.eval(` // 1
+ this.log = []; // 2
+ function f() { this.log.push(allocationMarker()); } // 3
+ function g() { this.log.push(allocationMarker()); } // 4
+ function h() { this.log.push(allocationMarker()); } // 5
+ `); // 6
+
+ // Create one allocationMarker with tracking turned off,
+ // so it will have no associated stack.
+ g.f();
+
+ dbg.memory.allocationSamplingProbability = 1;
+
+ for (let [func, n] of [ [g.f, 20],
+ [g.g, 10],
+ [g.h, 5] ]) {
+ for (let i = 0; i < n; i++) {
+ dbg.memory.trackingAllocationSites = true;
+ // All allocations of allocationMarker occur with this line as the oldest
+ // stack frame.
+ func();
+ dbg.memory.trackingAllocationSites = false;
+ }
+ }
+
+ // Take a heap snapshot.
+
+ const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ // Run a census broken down by class name -> allocation stack so we can grab
+ // only the AllocationMarker objects we have complete control over.
+
+ const { report } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: { by: "objectClass",
+ then: { by: "allocationStack",
+ then: { by: "count",
+ bytes: true,
+ count: true
+ },
+ noStack: { by: "count",
+ bytes: true,
+ count: true
+ }
+ }
+ }
+ });
+
+ // Test the generated report.
+
+ ok(report, "Should get a report");
+
+ const map = report.AllocationMarker;
+ ok(map, "Should get AllocationMarkers in the report.");
+ // From a module with a different global, and therefore a different Map
+ // constructor, so we can't use instanceof.
+ equal(map.__proto__.constructor.name, "Map");
+
+ equal(map.size, 4, "Should have 4 allocation stacks (including the lack of a stack)");
+
+ // Gather the stacks we are expecting to appear as keys, and
+ // check that there are no unexpected keys.
+ let stacks = {};
+
+ map.forEach((v, k) => {
+ if (k === "noStack") {
+ // No need to save this key.
+ } else if (k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "test") {
+ stacks.f = k;
+ } else if (k.functionDisplayName === "g" &&
+ k.parent.functionDisplayName === "test") {
+ stacks.g = k;
+ } else if (k.functionDisplayName === "h" &&
+ k.parent.functionDisplayName === "test") {
+ stacks.h = k;
+ } else {
+ dumpn("Unexpected allocation stack:");
+ k.toString().split(/\n/g).forEach(s => dumpn(s));
+ ok(false);
+ }
+ });
+
+ ok(map.get("noStack"));
+ equal(map.get("noStack").count, 1);
+
+ ok(stacks.f);
+ ok(map.get(stacks.f));
+ equal(map.get(stacks.f).count, 20);
+
+ ok(stacks.g);
+ ok(map.get(stacks.g));
+ equal(map.get(stacks.g).count, 10);
+
+ ok(stacks.h);
+ ok(map.get(stacks.h));
+ equal(map.get(stacks.h).count, 5);
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js
new file mode 100644
index 000000000..7e16d9f00
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_05.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses and return
+// a CensusTreeNode.
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ });
+
+ const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ }, {
+ asTreeNode: true
+ });
+
+ ok(treeNode.children.length > 0, "treeNode has children");
+ ok(treeNode.children.every(type => {
+ return "name" in type &&
+ "bytes" in type &&
+ "count" in type;
+ }), "all of tree node's children have name, bytes, count");
+
+ compareCensusViewData(BREAKDOWN, report, treeNode,
+ "Returning census as a tree node represents same data as the report");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
new file mode 100644
index 000000000..7795a9700
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_06.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses by
+// "allocationStack" and return a CensusTreeNode.
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true }
+ },
+ other: { by: "count", count: true, bytes: true }
+};
+
+add_task(function* () {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ // 5 allocation markers with no stack.
+ g.eval(`
+ this.markers = [];
+ for (var i = 0; i < 5; i++) {
+ markers.push(allocationMarker());
+ }
+ `);
+
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+
+ // 5 allocation markers at 5 stacks.
+ g.eval(`
+ (function shouldHaveCountOfOne() {
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ markers.push(allocationMarker());
+ }());
+ `);
+
+ // 5 allocation markers at 1 stack.
+ g.eval(`
+ (function shouldHaveCountOfFive() {
+ for (var i = 0; i < 5; i++) {
+ markers.push(allocationMarker());
+ }
+ }());
+ `);
+
+ const snapshotFilePath = saveNewHeapSnapshot({ debugger: dbg });
+
+ const client = new HeapAnalysesClient();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ });
+
+ const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ }, {
+ asTreeNode: true
+ });
+
+ const markers = treeNode.children.find(c => c.name === "AllocationMarker");
+ ok(markers);
+
+ const noStack = markers.children.find(c => c.name === "noStack");
+ equal(noStack.count, 5);
+
+ let numShouldHaveFiveFound = 0;
+ let numShouldHaveOneFound = 0;
+
+ function walk(node) {
+ if (node.children) {
+ node.children.forEach(walk);
+ }
+
+ if (!isSavedFrame(node.name)) {
+ return;
+ }
+
+ if (node.name.functionDisplayName === "shouldHaveCountOfFive") {
+ equal(node.count, 5, "shouldHaveCountOfFive should have count of five");
+ numShouldHaveFiveFound++;
+ }
+
+ if (node.name.functionDisplayName === "shouldHaveCountOfOne") {
+ equal(node.count, 1, "shouldHaveCountOfOne should have count of one");
+ numShouldHaveOneFound++;
+ }
+ }
+ markers.children.forEach(walk);
+
+ equal(numShouldHaveFiveFound, 1);
+ equal(numShouldHaveOneFound, 5);
+
+ compareCensusViewData(BREAKDOWN, report, treeNode,
+ "Returning census as a tree node represents same data as the report");
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
new file mode 100644
index 000000000..986b3aaa8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapAnalyses_takeCensus_07.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that the HeapAnalyses{Client,Worker} can take censuses and return
+// an inverted CensusTreeNode.
+
+function run_test() {
+ run_next_test();
+}
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+};
+
+add_task(function* () {
+ const client = new HeapAnalysesClient();
+
+ const snapshotFilePath = saveNewHeapSnapshot();
+ yield client.readHeapSnapshot(snapshotFilePath);
+ ok(true, "Should have read the heap snapshot");
+
+ const { report } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ });
+
+ const { report: treeNode } = yield client.takeCensus(snapshotFilePath, {
+ breakdown: BREAKDOWN
+ }, {
+ asInvertedTreeNode: true
+ });
+
+ compareCensusViewData(BREAKDOWN, report, treeNode, { invert: true });
+
+ client.destroy();
+});
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_01.js
new file mode 100644
index 000000000..2ec577bd0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_01.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Sanity test that we can compute shortest paths.
+//
+// Because the actual heap graph is too unpredictable and likely to drastically
+// change as various implementation bits change, we don't test exact paths
+// here. See js/src/jsapi-tests/testUbiNode.cpp for such tests, where we can
+// control the specific graph shape and structure and so testing exact paths is
+// reliable.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ const dominatedByRoot = dominatorTree.getImmediatelyDominated(dominatorTree.root)
+ .slice(0, 10);
+ ok(dominatedByRoot);
+ ok(dominatedByRoot.length);
+
+ const targetSet = new Set(dominatedByRoot);
+
+ const shortestPaths = snapshot.computeShortestPaths(dominatorTree.root, dominatedByRoot, 2);
+ ok(shortestPaths);
+ ok(shortestPaths instanceof Map);
+ ok(shortestPaths.size === targetSet.size);
+
+ for (let [target, paths] of shortestPaths) {
+ ok(targetSet.has(target),
+ "We should only get paths for our targets");
+ targetSet.delete(target);
+
+ ok(paths.length > 0,
+ "We must have at least one path, since the target is dominated by the root");
+ ok(paths.length <= 2,
+ "Should not have recorded more paths than the max requested");
+
+ dumpn("---------------------");
+ dumpn("Shortest paths for 0x" + target.toString(16) + ":");
+ for (let path of paths) {
+ dumpn(" path =");
+ for (let part of path) {
+ dumpn(" predecessor: 0x" + part.predecessor.toString(16) +
+ "; edge: " + part.edge);
+ }
+ }
+ dumpn("---------------------");
+
+ for (let path of paths) {
+ ok(path.length > 0, "Cannot have zero length paths");
+ ok(path[0].predecessor === dominatorTree.root,
+ "The first predecessor is always our start node");
+
+ for (let part of path) {
+ ok(part.predecessor, "Each part of a path has a predecessor");
+ ok(!!snapshot.describeNode({ by: "count", count: true, bytes: true},
+ part.predecessor),
+ "The predecessor is in the heap snapshot");
+ ok("edge" in part, "Each part has an (potentially null) edge property");
+ }
+ }
+ }
+
+ ok(targetSet.size === 0,
+ "We found paths for all of our targets");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_02.js
new file mode 100644
index 000000000..04fe58733
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_computeShortestPaths_02.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test computing shortest paths with invalid arguments.
+
+function run_test() {
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+
+ const dominatorTree = snapshot.computeDominatorTree();
+ const target = dominatorTree.getImmediatelyDominated(dominatorTree.root).pop();
+ ok(target);
+
+ let threw = false;
+ try {
+ snapshot.computeShortestPaths(0, [target], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "invalid start node should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [0], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "invalid target nodes should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [], 2);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "empty target nodes should throw");
+
+ threw = false;
+ try {
+ snapshot.computeShortestPaths(dominatorTree.root, [target], 0);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "0 max paths should throw");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_creationTime_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_creationTime_01.js
new file mode 100644
index 000000000..0d08fea16
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_creationTime_01.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.creationTime returns the expected time.
+
+function waitForThirtyMilliseconds() {
+ const start = Date.now();
+ while (Date.now() - start < 30) ;
+}
+
+function run_test() {
+ const start = Date.now() * 1000;
+ do_print("start = " + start);
+
+ // Because Date.now() is less precise than the snapshot's time stamp, give it
+ // a little bit of head room. Additionally, WinXP's timer only has granularity
+ // of +/- 15ms.
+ waitForThirtyMilliseconds();
+ const path = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ waitForThirtyMilliseconds();
+
+ const end = Date.now() * 1000;
+ do_print("end = " + end);
+
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+ do_print("snapshot.creationTime = " + snapshot.creationTime);
+
+ ok(snapshot.creationTime >= start);
+ ok(snapshot.creationTime <= end);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_deepStack_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_deepStack_01.js
new file mode 100644
index 000000000..9eb11d9af
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_deepStack_01.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can save a core dump with very deep allocation stacks and read
+// it back into a HeapSnapshot.
+
+function stackDepth(stack) {
+ return stack ? 1 + stackDepth(stack.parent) : 0;
+}
+
+function run_test() {
+ // Create a Debugger observing a debuggee's allocations.
+ const debuggee = new Cu.Sandbox(null);
+ const dbg = new Debugger(debuggee);
+ dbg.memory.trackingAllocationSites = true;
+
+ // Allocate some objects in the debuggee that will have their allocation
+ // stacks recorded by the Debugger.
+
+ debuggee.eval("this.objects = []");
+ debuggee.eval(
+ (function recursiveAllocate(n) {
+ if (n <= 0)
+ return;
+
+ // Make sure to recurse before pushing the object so that when TCO is
+ // implemented sometime in the future, it doesn't invalidate this test.
+ recursiveAllocate(n - 1);
+ this.objects.push({});
+ }).toString()
+ );
+ debuggee.eval("recursiveAllocate = recursiveAllocate.bind(this);");
+ debuggee.eval("recursiveAllocate(200);");
+
+ // Now save a snapshot that will include the allocation stacks and read it
+ // back again.
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
+
+ const report = snapshot.takeCensus({
+ breakdown: { by: "allocationStack",
+ then: { by: "count", bytes: true, count: true },
+ noStack: { by: "count", bytes: true, count: true }
+ }
+ });
+
+ // Keep this synchronized with `HeapSnapshot::MAX_STACK_DEPTH`!
+ const MAX_STACK_DEPTH = 60;
+
+ let foundStacks = false;
+ report.forEach((v, k) => {
+ if (k === "noStack") {
+ return;
+ }
+
+ foundStacks = true;
+ const depth = stackDepth(k);
+ dumpn("Stack depth is " + depth);
+ ok(depth <= MAX_STACK_DEPTH,
+ "Every stack should have depth less than or equal to the maximum stack depth");
+ });
+ ok(foundStacks);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_describeNode_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_describeNode_01.js
new file mode 100644
index 000000000..d79cb5a7b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_describeNode_01.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can describe nodes with a breakdown.
+
+function run_test() {
+ const path = saveNewHeapSnapshot();
+ const snapshot = ChromeUtils.readHeapSnapshot(path);
+ ok(snapshot.describeNode);
+ equal(typeof snapshot.describeNode, "function");
+
+ const dt = snapshot.computeDominatorTree();
+
+ let threw = false;
+ try {
+ snapshot.describeNode(undefined, dt.root);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "Should require a breakdown");
+
+ const breakdown = {
+ by: "coarseType",
+ objects: { by: "objectClass" },
+ scripts: { by: "internalType" },
+ strings: { by: "internalType" },
+ other: { by: "internalType" }
+ };
+
+ threw = false;
+ try {
+ snapshot.describeNode(breakdown, 0);
+ } catch (_) {
+ threw = true;
+ }
+ ok(threw, "Should throw when given an invalid node id");
+
+ const description = snapshot.describeNode(breakdown, dt.root);
+ ok(description);
+ ok(description.other);
+ ok(description.other["JS::ubi::RootList"]);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_01.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_01.js
new file mode 100644
index 000000000..f3b3090b0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_01.js
@@ -0,0 +1,31 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus returns a value of an appropriate
+// shape. Ported from js/src/jit-tests/debug/Memory-takeCensus-01.js
+
+function run_test() {
+ var dbg = new Debugger;
+
+ function checkProperties(census) {
+ equal(typeof census, "object");
+ for (prop of Object.getOwnPropertyNames(census)) {
+ var desc = Object.getOwnPropertyDescriptor(census, prop);
+ equal(desc.enumerable, true);
+ equal(desc.configurable, true);
+ equal(desc.writable, true);
+ if (typeof desc.value === "object")
+ checkProperties(desc.value);
+ else
+ equal(typeof desc.value, "number");
+ }
+ }
+
+ checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
+
+ var g = newGlobal();
+ dbg.addDebuggee(g);
+ checkProperties(saveHeapSnapshotAndTakeCensus(dbg));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_02.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_02.js
new file mode 100644
index 000000000..680ac9b58
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_02.js
@@ -0,0 +1,57 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus behaves plausibly as we allocate objects.
+//
+// Exact object counts vary in ways we can't predict. For example,
+// BaselineScripts can hold onto "template objects", which exist only to hold
+// the shape and type for newly created objects. When BaselineScripts are
+// discarded, these template objects go with them.
+//
+// So instead of expecting precise counts, we expect counts that are at least as
+// many as we would expect given the object graph we've built.
+//
+// Ported from js/src/jit-tests/debug/Memory-takeCensus-02.js
+
+function run_test() {
+ // A Debugger with no debuggees had better not find anything.
+ var dbg = new Debugger;
+ var census0 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+ function newGlobalWithDefs() {
+ var g = newGlobal();
+ g.eval(`
+ function times(n, fn) {
+ var a=[];
+ for (var i = 0; i<n; i++)
+ a.push(fn());
+ return a;
+ }
+ `);
+ return g;
+ }
+
+ // Allocate a large number of various types of objects, and check that census
+ // finds them.
+ var g = newGlobalWithDefs();
+ dbg.addDebuggee(g);
+
+ g.eval("var objs = times(100, () => ({}));");
+ g.eval("var rxs = times(200, () => /foo/);");
+ g.eval("var ars = times(400, () => []);");
+ g.eval("var fns = times(800, () => () => {});");
+
+ var census1 = dbg.memory.takeCensus(dbg);
+ Census.walkCensus(census1, "census1",
+ Census.assertAllNotLessThan(
+ { "objects":
+ { "Object": { count: 100 },
+ "RegExp": { count: 200 },
+ "Array": { count: 400 },
+ "Function": { count: 800 }
+ }
+ }));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_03.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_03.js
new file mode 100644
index 000000000..25f2c3791
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_03.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus behaves plausibly as we add and remove
+// debuggees.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-03.js
+
+function run_test() {
+ var dbg = new Debugger;
+
+ var census0 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census0, "census0", Census.assertAllZeros);
+
+ var g1 = newGlobal();
+ dbg.addDebuggee(g1);
+ var census1 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census1, "census1", Census.assertAllNotLessThan(census0));
+
+ var g2 = newGlobal();
+ dbg.addDebuggee(g2);
+ var census2 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census2, "census2", Census.assertAllNotLessThan(census1));
+
+ dbg.removeDebuggee(g2);
+ var census3 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census3, "census3", Census.assertAllEqual(census1));
+
+ dbg.removeDebuggee(g1);
+ var census4 = saveHeapSnapshotAndTakeCensus(dbg);
+ Census.walkCensus(census4, "census4", Census.assertAllEqual(census0));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_04.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_04.js
new file mode 100644
index 000000000..799844cde
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_04.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that HeapSnapshot.prototype.takeCensus finds GC roots that are on the
+// stack.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-04.js
+
+function run_test() {
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ g.eval(`
+function withAllocationMarkerOnStack(f) {
+ (function () {
+ var onStack = allocationMarker();
+ f();
+ }());
+}
+`);
+
+ equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false,
+ "There shouldn't exist any allocation markers in the census.");
+
+ var allocationMarkerCount;
+ g.withAllocationMarkerOnStack(() => {
+ const census = saveHeapSnapshotAndTakeCensus(dbg);
+ allocationMarkerCount = census.objects.AllocationMarker.count;
+ });
+
+ equal(allocationMarkerCount, 1,
+ "Should have one allocation marker in the census, because there " +
+ "was one on the stack.");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_05.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_05.js
new file mode 100644
index 000000000..da6067624
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_05.js
@@ -0,0 +1,24 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that HeapSnapshot.prototype.takeCensus finds cross compartment
+// wrapper GC roots.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-05.js
+
+function run_test() {
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ equal("AllocationMarker" in saveHeapSnapshotAndTakeCensus(dbg).objects, false,
+ "No allocation markers should exist in the census.");
+
+ this.ccw = g.allocationMarker();
+
+ const census = saveHeapSnapshotAndTakeCensus(dbg);
+ equal(census.objects.AllocationMarker.count, 1,
+ "Should have one allocation marker in the census, because there " +
+ "is one cross-compartment wrapper referring to it.");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_06.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_06.js
new file mode 100644
index 000000000..0412410c0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_06.js
@@ -0,0 +1,125 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check HeapSnapshot.prototype.takeCensus handling of 'breakdown' argument.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-06.js
+
+function run_test() {
+ var Pattern = Match.Pattern;
+
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ Pattern({ count: Pattern.NATURAL,
+ bytes: Pattern.NATURAL })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count" } }));
+
+ let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: false, bytes: false } });
+ equal("count" in census, false);
+ equal("bytes" in census, false);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: true, bytes: false } });
+ equal("count" in census, true);
+ equal("bytes" in census, false);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: false, bytes: true } });
+ equal("count" in census, false);
+ equal("bytes" in census, true);
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count", count: true, bytes: true } });
+ equal("count" in census, true);
+ equal("bytes" in census, true);
+
+
+ // Pattern doesn't mind objects with extra properties, so we'll restrict this
+ // list to the object classes we're pretty sure are going to stick around for
+ // the forseeable future.
+ Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ Debugger: { count: Pattern.NATURAL },
+ Sandbox: { count: Pattern.NATURAL },
+
+ // The below are all Debugger prototype objects.
+ Source: { count: Pattern.NATURAL },
+ Environment: { count: Pattern.NATURAL },
+ Script: { count: Pattern.NATURAL },
+ Memory: { count: Pattern.NATURAL },
+ Frame: { count: Pattern.NATURAL }
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass" } }));
+
+ Pattern({
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL }
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "coarseType" } }));
+
+ // As for { by: 'objectClass' }, restrict our pattern to the types
+ // we predict will stick around for a long time.
+ Pattern({
+ JSString: { count: Pattern.NATURAL },
+ "js::Shape": { count: Pattern.NATURAL },
+ JSObject: { count: Pattern.NATURAL },
+ JSScript: { count: Pattern.NATURAL }
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "internalType" } }));
+
+
+ // Nested breakdowns.
+
+ let coarseTypePattern = {
+ objects: { count: Pattern.NATURAL },
+ scripts: { count: Pattern.NATURAL },
+ strings: { count: Pattern.NATURAL },
+ other: { count: Pattern.NATURAL }
+ };
+
+ Pattern({
+ JSString: coarseTypePattern,
+ "js::Shape": coarseTypePattern,
+ JSObject: coarseTypePattern,
+ JSScript: coarseTypePattern,
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "internalType",
+ then: { by: "coarseType" }
+ }
+ }));
+
+ Pattern({
+ Function: { count: Pattern.NATURAL },
+ Object: { count: Pattern.NATURAL },
+ Debugger: { count: Pattern.NATURAL },
+ Sandbox: { count: Pattern.NATURAL },
+ other: coarseTypePattern
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "objectClass",
+ then: { by: "count" },
+ other: { by: "coarseType" }
+ }
+ }));
+
+ Pattern({
+ objects: { count: Pattern.NATURAL, label: "object" },
+ scripts: { count: Pattern.NATURAL, label: "scripts" },
+ strings: { count: Pattern.NATURAL, label: "strings" },
+ other: { count: Pattern.NATURAL, label: "other" }
+ })
+ .assert(saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: {
+ by: "coarseType",
+ objects: { by: "count", label: "object" },
+ scripts: { by: "count", label: "scripts" },
+ strings: { by: "count", label: "strings" },
+ other: { by: "count", label: "other" }
+ }
+ }));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_07.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_07.js
new file mode 100644
index 000000000..f5c36056f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_07.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus breakdown: check error handling on property
+// gets.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-07.js
+
+function run_test() {
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { get by() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", get count() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "count", get bytes() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "objectClass", get then() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "objectClass", get other() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "coarseType", get objects() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "coarseType", get scripts() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "coarseType", get strings() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "coarseType", get other() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+
+
+ assertThrowsValue(() => {
+ saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "internalType", get then() { throw "ಠ_ಠ"; } }
+ });
+ }, "ಠ_ಠ");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_08.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_08.js
new file mode 100644
index 000000000..5934aa919
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_08.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus: test by: 'count' breakdown
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-08.js
+
+function run_test() {
+ let g = newGlobal();
+ let dbg = new Debugger(g);
+
+ g.eval(`
+ var stuff = [];
+ function add(n, c) {
+ for (let i = 0; i < n; i++)
+ stuff.push(c());
+ }
+
+ let count = 0;
+
+ function obj() { return { count: count++ }; }
+ obj.factor = 1;
+
+ // This creates a closure (a function JSObject) that has captured
+ // a Call object. So each call creates two items.
+ function fun() { let v = count; return () => { return v; } }
+ fun.factor = 2;
+
+ function str() { return 'perambulator' + count++; }
+ str.factor = 1;
+
+ // Eval a fresh text each time, allocating:
+ // - a fresh ScriptSourceObject
+ // - a new JSScripts, not an eval cache hits
+ // - a fresh prototype object
+ // - a fresh Call object, since the eval makes 'ev' heavyweight
+ // - the new function itself
+ function ev() {
+ return eval(\`(function () { return \${ count++ } })\`);
+ }
+ ev.factor = 5;
+
+ // A new object (1) with a new shape (2) with a new atom (3)
+ function shape() { return { [ 'theobroma' + count++ ]: count }; }
+ shape.factor = 3;
+ `);
+
+ let baseline = 0;
+ function countIncreasedByAtLeast(n) {
+ let oldBaseline = baseline;
+
+ // Since a census counts only reachable objects, one might assume that calling
+ // GC here would have no effect on the census results. But GC also throws away
+ // JIT code and any objects it might be holding (template objects, say);
+ // takeCensus reaches those. Shake everything loose that we can, to make the
+ // census approximate reachability a bit more closely, and make our results a
+ // bit more predictable.
+ gc(g, "shrinking");
+
+ baseline = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "count" } }).count;
+ return baseline >= oldBaseline + n;
+ }
+
+ countIncreasedByAtLeast(0);
+
+ g.add(100, g.obj);
+ ok(countIncreasedByAtLeast(g.obj.factor * 100));
+
+ g.add(100, g.fun);
+ ok(countIncreasedByAtLeast(g.fun.factor * 100));
+
+ g.add(100, g.str);
+ ok(countIncreasedByAtLeast(g.str.factor * 100));
+
+ g.add(100, g.ev);
+ ok(countIncreasedByAtLeast(g.ev.factor * 100));
+
+ g.add(100, g.shape);
+ ok(countIncreasedByAtLeast(g.shape.factor * 100));
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_09.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_09.js
new file mode 100644
index 000000000..bbacccc8d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_09.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// HeapSnapshot.prototype.takeCensus: by: allocationStack breakdown
+//
+// Ported from js/src/jit-test/tests/debug/Memory-takeCensus-09.js
+
+function run_test() {
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ g.eval(` // 1
+ var log = []; // 2
+ function f() { log.push(allocationMarker()); } // 3
+ function g() { f(); } // 4
+ function h() { f(); } // 5
+ `); // 6
+
+ // Create one allocationMarker with tracking turned off,
+ // so it will have no associated stack.
+ g.f();
+
+ dbg.memory.allocationSamplingProbability = 1;
+
+ for ([func, n] of [[g.f, 20], [g.g, 10], [g.h, 5]]) {
+ for (let i = 0; i < n; i++) {
+ dbg.memory.trackingAllocationSites = true;
+ // All allocations of allocationMarker occur with this line as the oldest
+ // stack frame.
+ func();
+ dbg.memory.trackingAllocationSites = false;
+ }
+ }
+
+ let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass",
+ then: { by: "allocationStack",
+ then: { by: "count",
+ label: "haz stack"
+ },
+ noStack: { by: "count",
+ label: "no haz stack"
+ }
+ }
+ }
+ });
+
+ let map = census.AllocationMarker;
+ ok(map instanceof Map, "Should be a Map instance");
+ equal(map.size, 4, "Should have 4 allocation stacks (including the lack of a stack)");
+
+ // Gather the stacks we are expecting to appear as keys, and
+ // check that there are no unexpected keys.
+ let stacks = { };
+
+ map.forEach((v, k) => {
+ if (k === "noStack") {
+ // No need to save this key.
+ } else if (k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "run_test") {
+ stacks.f = k;
+ } else if (k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "g" &&
+ k.parent.parent.functionDisplayName === "run_test") {
+ stacks.fg = k;
+ } else if (k.functionDisplayName === "f" &&
+ k.parent.functionDisplayName === "h" &&
+ k.parent.parent.functionDisplayName === "run_test") {
+ stacks.fh = k;
+ } else {
+ dumpn("Unexpected allocation stack:");
+ k.toString().split(/\n/g).forEach(s => dumpn(s));
+ ok(false);
+ }
+ });
+
+ equal(map.get("noStack").label, "no haz stack");
+ equal(map.get("noStack").count, 1);
+
+ ok(stacks.f);
+ equal(map.get(stacks.f).label, "haz stack");
+ equal(map.get(stacks.f).count, 20);
+
+ ok(stacks.fg);
+ equal(map.get(stacks.fg).label, "haz stack");
+ equal(map.get(stacks.fg).count, 10);
+
+ ok(stacks.fh);
+ equal(map.get(stacks.fh).label, "haz stack");
+ equal(map.get(stacks.fh).count, 5);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_10.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_10.js
new file mode 100644
index 000000000..a7f987f5a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_10.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Check byte counts produced by takeCensus.
+//
+// Ported from js/src/jit-test/tests/debug/Memory-take Census-10.js
+
+function run_test() {
+ let g = newGlobal();
+ let dbg = new Debugger(g);
+
+ let sizeOfAM = byteSize(allocationMarker());
+
+ // Allocate a single allocation marker, and check that we can find it.
+ g.eval("var hold = allocationMarker();");
+ let census = saveHeapSnapshotAndTakeCensus(dbg, { breakdown: { by: "objectClass" } });
+ equal(census.AllocationMarker.count, 1);
+ equal(census.AllocationMarker.bytes, sizeOfAM);
+ g.hold = null;
+
+ g.eval(` // 1
+ var objs = []; // 2
+ function fnerd() { // 3
+ objs.push(allocationMarker()); // 4
+ for (let i = 0; i < 10; i++) // 5
+ objs.push(allocationMarker()); // 6
+ } // 7
+ `); // 8
+
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+ g.fnerd();
+ dbg.memory.trackingAllocationSites = false;
+
+ census = saveHeapSnapshotAndTakeCensus(dbg, {
+ breakdown: { by: "objectClass",
+ then: { by: "allocationStack" }
+ }
+ });
+
+ let seen = 0;
+ census.AllocationMarker.forEach((v, k) => {
+ equal(k.functionDisplayName, "fnerd");
+ switch (k.line) {
+ case 4:
+ equal(v.count, 1);
+ equal(v.bytes, sizeOfAM);
+ seen++;
+ break;
+
+ case 6:
+ equal(v.count, 10);
+ equal(v.bytes, 10 * sizeOfAM);
+ seen++;
+ break;
+
+ default:
+ dumpn("Unexpected stack:");
+ k.toString().split(/\n/g).forEach(s => dumpn(s));
+ ok(false);
+ break;
+ }
+ });
+
+ equal(seen, 2);
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_11.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_11.js
new file mode 100644
index 000000000..3d898b2d1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_11.js
@@ -0,0 +1,116 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that Debugger.Memory.prototype.takeCensus and
+// HeapSnapshot.prototype.takeCensus return the same data for the same heap
+// graph.
+
+function doLiveAndOfflineCensus(g, dbg, opts) {
+ dbg.memory.allocationSamplingProbability = 1;
+ dbg.memory.trackingAllocationSites = true;
+ g.eval(` // 1
+ (function unsafeAtAnySpeed() { // 2
+ for (var i = 0; i < 100; i++) { // 3
+ this.markers.push(allocationMarker()); // 4
+ } // 5
+ }()); // 6
+ `); // 7
+ dbg.memory.trackingAllocationSites = false;
+
+ return {
+ live: dbg.memory.takeCensus(opts),
+ offline: saveHeapSnapshotAndTakeCensus(dbg, opts)
+ };
+}
+
+function run_test() {
+ var g = newGlobal();
+ var dbg = new Debugger(g);
+
+ g.eval("this.markers = []");
+ const markerSize = byteSize(allocationMarker());
+
+ // First, test that we get the same counts and sizes as we allocate and retain
+ // more things.
+
+ let prevCount = 0;
+ let prevBytes = 0;
+
+ for (var i = 0; i < 10; i++) {
+ const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
+ breakdown: { by: "objectClass",
+ then: { by: "count"} }
+ });
+
+ equal(live.AllocationMarker.count, offline.AllocationMarker.count);
+ equal(live.AllocationMarker.bytes, offline.AllocationMarker.bytes);
+ equal(live.AllocationMarker.count, prevCount + 100);
+ equal(live.AllocationMarker.bytes, prevBytes + 100 * markerSize);
+
+ prevCount = live.AllocationMarker.count;
+ prevBytes = live.AllocationMarker.bytes;
+ }
+
+ // Second, test that the reported allocation stacks and counts and sizes at
+ // those allocation stacks match up.
+
+ const { live, offline } = doLiveAndOfflineCensus(g, dbg, {
+ breakdown: { by: "objectClass",
+ then: { by: "allocationStack"} }
+ });
+
+ equal(live.AllocationMarker.size, offline.AllocationMarker.size);
+ // One stack with the loop further above, and another stack featuring the call
+ // right above.
+ equal(live.AllocationMarker.size, 2);
+
+ // Note that because SavedFrame stacks reconstructed from an offline heap
+ // snapshot don't have the same principals as SavedFrame stacks captured from
+ // a live stack, the live and offline allocation stacks won't be identity
+ // equal, but should be structurally the same.
+
+ const liveEntries = [];
+ live.AllocationMarker.forEach((v, k) => {
+ dumpn("Allocation stack:");
+ k.toString().split(/\n/g).forEach(s => dumpn(s));
+
+ equal(k.functionDisplayName, "unsafeAtAnySpeed");
+ equal(k.line, 4);
+
+ liveEntries.push([k.toString(), v]);
+ });
+
+ const offlineEntries = [];
+ offline.AllocationMarker.forEach((v, k) => {
+ dumpn("Allocation stack:");
+ k.toString().split(/\n/g).forEach(s => dumpn(s));
+
+ equal(k.functionDisplayName, "unsafeAtAnySpeed");
+ equal(k.line, 4);
+
+ offlineEntries.push([k.toString(), v]);
+ });
+
+ const sortEntries = (a, b) => {
+ if (a[0] < b[0]) {
+ return -1;
+ } else if (a[0] > b[0]) {
+ return 1;
+ } else {
+ return 0;
+ }
+ };
+ liveEntries.sort(sortEntries);
+ offlineEntries.sort(sortEntries);
+
+ equal(liveEntries.length, live.AllocationMarker.size);
+ equal(liveEntries.length, offlineEntries.length);
+
+ for (let i = 0; i < liveEntries.length; i++) {
+ equal(liveEntries[i][0], offlineEntries[i][0]);
+ equal(liveEntries[i][1].count, offlineEntries[i][1].count);
+ equal(liveEntries[i][1].bytes, offlineEntries[i][1].bytes);
+ }
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_12.js b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_12.js
new file mode 100644
index 000000000..f10dd5b03
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_HeapSnapshot_takeCensus_12.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that when we take a census and get a bucket list of ids that matched the
+// given category, that the returned ids are all in the snapshot and their
+// reported category.
+
+function run_test() {
+ const g = newGlobal();
+ const dbg = new Debugger(g);
+
+ const path = saveNewHeapSnapshot({ debugger: dbg });
+ const snapshot = readHeapSnapshot(path);
+
+ const bucket = { by: "bucket" };
+ const count = { by: "count", count: true, bytes: false };
+ const objectClassCount = { by: "objectClass", then: count, other: count };
+
+ const byClassName = snapshot.takeCensus({
+ breakdown: {
+ by: "objectClass",
+ then: bucket,
+ other: bucket,
+ }
+ });
+
+ const byClassNameCount = snapshot.takeCensus({
+ breakdown: objectClassCount
+ });
+
+ const keys = new Set(Object.keys(byClassName));
+ equal(keys.size, Object.keys(byClassNameCount).length,
+ "Should have the same number of keys.");
+ for (let k of Object.keys(byClassNameCount)) {
+ ok(keys.has(k), "Should not have any unexpected class names");
+ }
+
+ for (let key of Object.keys(byClassName)) {
+ equal(byClassNameCount[key].count, byClassName[key].length,
+ "Length of the bucket and count should be equal");
+
+ for (let id of byClassName[key]) {
+ const desc = snapshot.describeNode(objectClassCount, id);
+ equal(desc[key].count, 1,
+ "Describing the bucketed node confirms that it belongs to the category");
+ }
+ }
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
new file mode 100644
index 000000000..dde139ffd
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can read core dumps into HeapSnapshot instances.
+
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+ addDebuggerToGlobal(this);
+}
+
+function run_test() {
+ const filePath = ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
new file mode 100644
index 000000000..d91f36f56
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_with_allocations.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can save a core dump with allocation stacks and read it back
+// into a HeapSnapshot.
+
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+ addDebuggerToGlobal(this);
+}
+
+function run_test() {
+ // Create a Debugger observing a debuggee's allocations.
+ const debuggee = new Cu.Sandbox(null);
+ const dbg = new Debugger(debuggee);
+ dbg.memory.trackingAllocationSites = true;
+
+ // Allocate some objects in the debuggee that will have their allocation
+ // stacks recorded by the Debugger.
+ debuggee.eval("this.objects = []");
+ for (let i = 0; i < 100; i++) {
+ debuggee.eval("this.objects.push({})");
+ }
+
+ // Now save a snapshot that will include the allocation stacks and read it
+ // back again.
+
+ const filePath = ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot.");
+
+ const snapshot = ChromeUtils.readHeapSnapshot(filePath);
+ ok(snapshot, "Should be able to read a heap snapshot");
+ ok(snapshot instanceof HeapSnapshot, "Should be an instanceof HeapSnapshot");
+
+ do_test_finished();
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
new file mode 100644
index 000000000..76461b694
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_ReadHeapSnapshot_worker.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that we can read core dumps into HeapSnapshot instances in a worker.
+
+add_task(function* () {
+ const worker = new ChromeWorker("resource://test/heap-snapshot-worker.js");
+ worker.postMessage({});
+
+ let assertionCount = 0;
+ worker.onmessage = e => {
+ if (e.data.type !== "assertion") {
+ return;
+ }
+
+ ok(e.data.passed, e.data.msg + "\n" + e.data.stack);
+ assertionCount++;
+ };
+
+ yield waitForDone(worker);
+
+ ok(assertionCount > 0);
+ worker.terminate();
+});
+
+function waitForDone(w) {
+ return new Promise((resolve, reject) => {
+ w.onerror = e => {
+ reject();
+ ok(false, "Error in worker: " + e);
+ };
+
+ w.addEventListener("message", function listener(e) {
+ if (e.data.type === "done") {
+ w.removeEventListener("message", listener, false);
+ resolve();
+ }
+ }, false);
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js b/devtools/shared/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
new file mode 100644
index 000000000..affd8d1e4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_SaveHeapSnapshot.js
@@ -0,0 +1,82 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the ChromeUtils interface.
+
+if (typeof Debugger != "function") {
+ const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+ addDebuggerToGlobal(this);
+}
+
+function run_test() {
+ ok(ChromeUtils, "Should be able to get the ChromeUtils interface");
+
+ testBadParameters();
+ testGoodParameters();
+
+ do_test_finished();
+}
+
+function testBadParameters() {
+ throws(() => ChromeUtils.saveHeapSnapshot(),
+ "Should throw if arguments aren't passed in.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot(null),
+ "Should throw if boundaries isn't an object.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({}),
+ "Should throw if the boundaries object doesn't have any properties.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ runtime: true,
+ globals: [this] }),
+ "Should throw if the boundaries object has more than one property.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ debugger: {} }),
+ "Should throw if the debuggees object is not a Debugger object");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ globals: [{}] }),
+ "Should throw if the globals array contains non-global objects.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ runtime: false }),
+ "Should throw if runtime is supplied and is not true.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ globals: null }),
+ "Should throw if globals is not an object.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ globals: {} }),
+ "Should throw if globals is not an array.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ debugger: Debugger.prototype }),
+ "Should throw if debugger is the Debugger.prototype object.");
+
+ throws(() => ChromeUtils.saveHeapSnapshot({ get globals() { return [this]; } }),
+ "Should throw if boundaries property is a getter.");
+}
+
+const makeNewSandbox = () =>
+ Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")());
+
+function testGoodParameters() {
+ let sandbox = makeNewSandbox();
+ let dbg = new Debugger(sandbox);
+
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot for a debuggee global.");
+
+ dbg = new Debugger;
+ let sandboxes = Array(10).fill(null).map(makeNewSandbox);
+ sandboxes.forEach(sb => dbg.addDebuggee(sb));
+
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot for many debuggee globals.");
+
+ dbg = new Debugger;
+ ChromeUtils.saveHeapSnapshot({ debugger: dbg });
+ ok(true, "Should be able to save a snapshot with no debuggee globals.");
+
+ ChromeUtils.saveHeapSnapshot({ globals: [this] });
+ ok(true, "Should be able to save a snapshot for a specific global.");
+
+ ChromeUtils.saveHeapSnapshot({ runtime: true });
+ ok(true, "Should be able to save a snapshot of the full runtime.");
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
new file mode 100644
index 000000000..16038c5c4
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-01.js
@@ -0,0 +1,76 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests CensusTreeNode with `internalType` breakdown.
+ */
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+};
+
+const REPORT = {
+ "JSObject": {
+ "bytes": 100,
+ "count": 10,
+ },
+ "js::Shape": {
+ "bytes": 500,
+ "count": 50,
+ },
+ "JSString": {
+ "bytes": 10,
+ "count": 1,
+ },
+};
+
+const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 610,
+ count: 0,
+ totalCount: 61,
+ children: [
+ {
+ name: "js::Shape",
+ bytes: 500,
+ totalBytes: 500,
+ count: 50,
+ totalCount: 50,
+ children: undefined,
+ id: 3,
+ parent: 1,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "JSObject",
+ bytes: 100,
+ totalBytes: 100,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 2,
+ parent: 1,
+ reportLeafIndex: 1,
+ },
+ {
+ name: "JSString",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 4,
+ parent: 1,
+ reportLeafIndex: 3,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
new file mode 100644
index 000000000..37d039954
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-02.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests CensusTreeNode with `coarseType` breakdown.
+ */
+
+const countBreakdown = { by: "count", count: true, bytes: true };
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: countBreakdown },
+ strings: countBreakdown,
+ scripts: countBreakdown,
+ other: { by: "internalType", then: countBreakdown },
+};
+
+const REPORT = {
+ "objects": {
+ "Function": { bytes: 10, count: 1 },
+ "Array": { bytes: 20, count: 2 },
+ },
+ "strings": { bytes: 10, count: 1 },
+ "scripts": { bytes: 1, count: 1 },
+ "other": {
+ "js::Shape": { bytes: 30, count: 3 },
+ "js::Shape2": { bytes: 40, count: 4 }
+ },
+};
+
+const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 111,
+ count: 0,
+ totalCount: 12,
+ children: [
+ {
+ name: "other",
+ count: 0,
+ totalCount: 7,
+ bytes: 0,
+ totalBytes: 70,
+ children: [
+ {
+ name: "js::Shape2",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 9,
+ parent: 7,
+ reportLeafIndex: 8,
+ },
+ {
+ name: "js::Shape",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 8,
+ parent: 7,
+ reportLeafIndex: 7,
+ },
+ ],
+ id: 7,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "objects",
+ count: 0,
+ totalCount: 3,
+ bytes: 0,
+ totalBytes: 30,
+ children: [
+ {
+ name: "Array",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 4,
+ parent: 2,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Function",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 3,
+ parent: 2,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "strings",
+ count: 1,
+ totalCount: 1,
+ bytes: 10,
+ totalBytes: 10,
+ children: undefined,
+ id: 6,
+ parent: 1,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "scripts",
+ count: 1,
+ totalCount: 1,
+ bytes: 1,
+ totalBytes: 1,
+ children: undefined,
+ id: 5,
+ parent: 1,
+ reportLeafIndex: 4,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js
new file mode 100644
index 000000000..bdf932099
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-03.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests CensusTreeNode with `objectClass` breakdown.
+ */
+
+const countBreakdown = { by: "count", count: true, bytes: true };
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: countBreakdown,
+ other: { by: "internalType", then: countBreakdown }
+};
+
+const REPORT = {
+ "Function": { bytes: 10, count: 10 },
+ "Array": { bytes: 100, count: 1 },
+ "other": {
+ "JIT::CODE::NOW!!!": { bytes: 20, count: 2 },
+ "JIT::CODE::LATER!!!": { bytes: 40, count: 4 }
+ }
+};
+
+const EXPECTED = {
+ name: null,
+ count: 0,
+ totalCount: 17,
+ bytes: 0,
+ totalBytes: 170,
+ children: [
+ {
+ name: "Array",
+ bytes: 100,
+ totalBytes: 100,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 3,
+ parent: 1,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "other",
+ count: 0,
+ totalCount: 6,
+ bytes: 0,
+ totalBytes: 60,
+ children: [
+ {
+ name: "JIT::CODE::LATER!!!",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 6,
+ parent: 4,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "JIT::CODE::NOW!!!",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 4,
+ },
+ ],
+ id: 4,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "Function",
+ bytes: 10,
+ totalBytes: 10,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 2,
+ parent: 1,
+ reportLeafIndex: 1,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+};
+
+function run_test() {
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
new file mode 100644
index 000000000..cc0c3bac0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-04.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests CensusTreeNode with `allocationStack` breakdown.
+ */
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ let stack1, stack2, stack3, stack4, stack5;
+
+ (function a() {
+ (function b() {
+ (function c() {
+ stack1 = saveStack(3);
+ }());
+ (function d() {
+ stack2 = saveStack(3);
+ stack3 = saveStack(3);
+ }());
+ stack4 = saveStack(2);
+ }());
+ }());
+
+ stack5 = saveStack(1);
+
+ const REPORT = new Map([
+ [stack1, { bytes: 10, count: 1 }],
+ [stack2, { bytes: 20, count: 2 }],
+ [stack3, { bytes: 30, count: 3 }],
+ [stack4, { bytes: 40, count: 4 }],
+ [stack5, { bytes: 50, count: 5 }],
+ ["noStack", { bytes: 60, count: 6 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 210,
+ count: 0,
+ totalCount: 21,
+ children: [
+ {
+ name: stack4.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack3.parent,
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: stack3,
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 7,
+ parent: 5,
+ reportLeafIndex: 3,
+ },
+ {
+ name: stack2,
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 6,
+ parent: 5,
+ reportLeafIndex: 2,
+ }
+ ],
+ id: 5,
+ parent: 2,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: stack4,
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 8,
+ parent: 2,
+ reportLeafIndex: 4,
+ },
+ {
+ name: stack1.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: stack1,
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 4,
+ parent: 3,
+ reportLeafIndex: 1,
+ },
+ ],
+ id: 3,
+ parent: 2,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "noStack",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ children: undefined,
+ id: 10,
+ parent: 1,
+ reportLeafIndex: 6,
+ },
+ {
+ name: stack5,
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 9,
+ parent: 1,
+ reportLeafIndex: 5
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
new file mode 100644
index 000000000..20fb76bd2
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-05.js
@@ -0,0 +1,145 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests CensusTreeNode with `allocationStack` => `objectClass` breakdown.
+ */
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: {
+ by: "objectClass",
+ then: countBreakdown,
+ other: countBreakdown
+ },
+ noStack: countBreakdown,
+ };
+
+ let stack;
+
+ (function a() {
+ (function b() {
+ (function c() {
+ stack = saveStack(3);
+ }());
+ }());
+ }());
+
+ const REPORT = new Map([
+ [stack, { Foo: { bytes: 10, count: 1 },
+ Bar: { bytes: 20, count: 2 },
+ Baz: { bytes: 30, count: 3 },
+ other: { bytes: 40, count: 4 }
+ }],
+ ["noStack", { bytes: 50, count: 5 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 150,
+ count: 0,
+ totalCount: 15,
+ children: [
+ {
+ name: stack.parent.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack.parent,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: stack,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "other",
+ bytes: 40,
+ totalBytes: 40,
+ count: 4,
+ totalCount: 4,
+ children: undefined,
+ id: 8,
+ parent: 4,
+ reportLeafIndex: 5,
+ },
+ {
+ name: "Baz",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 7,
+ parent: 4,
+ reportLeafIndex: 4,
+ },
+ {
+ name: "Bar",
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 6,
+ parent: 4,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Foo",
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 2,
+ },
+ ],
+ id: 4,
+ parent: 3,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 3,
+ parent: 2,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "noStack",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 9,
+ parent: 1,
+ reportLeafIndex: 6,
+ },
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js
new file mode 100644
index 000000000..eb1801207
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-06.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test inverting CensusTreeNode with a by alloaction stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+ };
+
+ let stack1, stack2, stack3, stack4;
+
+ function a(n) {
+ return b(n);
+ }
+ function b(n) {
+ return c(n);
+ }
+ function c(n) {
+ return saveStack(n);
+ }
+ function d(n) {
+ return b(n);
+ }
+ function e(n) {
+ return c(n);
+ }
+
+ const abc_Stack = a(3);
+ const bc_Stack = b(2);
+ const c_Stack = c(1);
+ const dbc_Stack = d(3);
+ const ec_Stack = e(2);
+
+ const REPORT = new Map([
+ [abc_Stack, { bytes: 10, count: 1 }],
+ [ bc_Stack, { bytes: 10, count: 1 }],
+ [ c_Stack, { bytes: 10, count: 1 }],
+ [dbc_Stack, { bytes: 10, count: 1 }],
+ [ ec_Stack, { bytes: 10, count: 1 }],
+ ["noStack", { bytes: 50, count: 5 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "noStack",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 16,
+ parent: 15,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 15,
+ parent: 14,
+ reportLeafIndex: 6,
+ },
+ {
+ name: abc_Stack,
+ bytes: 50,
+ totalBytes: 10,
+ count: 5,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 18,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: abc_Stack.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 22,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: abc_Stack.parent.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 21,
+ parent: 20,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 20,
+ parent: 19,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: dbc_Stack.parent.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 24,
+ parent: 23,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 23,
+ parent: 19,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 19,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: ec_Stack.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: undefined,
+ id: 26,
+ parent: 25,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 25,
+ parent: 17,
+ reportLeafIndex: undefined,
+ },
+ ],
+ id: 17,
+ parent: 14,
+ reportLeafIndex: new Set([1, 2, 3, 4, 5]),
+ }
+ ],
+ id: 14,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
new file mode 100644
index 000000000..6bc085257
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-07.js
@@ -0,0 +1,200 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test inverting CensusTreeNode with a non-allocation stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other:{
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ }
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: [
+ {
+ name: "js::Shape",
+ bytes: 80,
+ totalBytes: 80,
+ count: 8,
+ totalCount: 8,
+ children: [
+ {
+ name: "other",
+ bytes: 0,
+ totalBytes: 80,
+ count: 0,
+ totalCount: 8,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 14,
+ parent: 13,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 13,
+ parent: 12,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 12,
+ parent: 11,
+ reportLeafIndex: 9,
+ },
+ {
+ name: "JSAtom",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ children: [
+ {
+ name: "strings",
+ bytes: 0,
+ totalBytes: 60,
+ count: 0,
+ totalCount: 6,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 17,
+ parent: 16,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 16,
+ parent: 15,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 15,
+ parent: 11,
+ reportLeafIndex: 7,
+ },
+ {
+ name: "Array",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 20,
+ parent: 19,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 19,
+ parent: 18,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 18,
+ parent: 11,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "js::jit::JitScript",
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: [
+ {
+ name: "scripts",
+ bytes: 0,
+ totalBytes: 30,
+ count: 0,
+ totalCount: 3,
+ children: [
+ {
+ name: null,
+ bytes: 0,
+ totalBytes: 220,
+ count: 0,
+ totalCount: 22,
+ children: undefined,
+ id: 23,
+ parent: 22,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 22,
+ parent: 21,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 21,
+ parent: 11,
+ reportLeafIndex: 5,
+ },
+ ],
+ id: 11,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { invert: true });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
new file mode 100644
index 000000000..1c686c810
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-08.js
@@ -0,0 +1,142 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test inverting CensusTreeNode with a non-allocation stack breakdown.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+ },
+ };
+
+ const REPORT = {
+ "http://example.com/app.js": {
+ JSScript: { count: 10, bytes: 100 }
+ },
+ "http://example.com/ads.js": {
+ "js::LazyScript": { count: 20, bytes: 200 }
+ },
+ "http://example.com/trackers.js": {
+ JSScript: { count: 30, bytes: 300 }
+ },
+ noFilename: {
+ "js::jit::JitCode": { count: 40, bytes: 400 }
+ }
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 1000,
+ count: 0,
+ totalCount: 100,
+ children: [
+ {
+ name: "noFilename",
+ bytes: 0,
+ totalBytes: 400,
+ count: 0,
+ totalCount: 40,
+ children: [
+ {
+ name: "js::jit::JitCode",
+ bytes: 400,
+ totalBytes: 400,
+ count: 40,
+ totalCount: 40,
+ children: undefined,
+ id: 9,
+ parent: 8,
+ reportLeafIndex: 8,
+ }
+ ],
+ id: 8,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/trackers.js",
+ bytes: 0,
+ totalBytes: 300,
+ count: 0,
+ totalCount: 30,
+ children: [
+ {
+ name: "JSScript",
+ bytes: 300,
+ totalBytes: 300,
+ count: 30,
+ totalCount: 30,
+ children: undefined,
+ id: 7,
+ parent: 6,
+ reportLeafIndex: 6,
+ }
+ ],
+ id: 6,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/ads.js",
+ bytes: 0,
+ totalBytes: 200,
+ count: 0,
+ totalCount: 20,
+ children: [
+ {
+ name: "js::LazyScript",
+ bytes: 200,
+ totalBytes: 200,
+ count: 20,
+ totalCount: 20,
+ children: undefined,
+ id: 5,
+ parent: 4,
+ reportLeafIndex: 4,
+ }
+ ],
+ id: 4,
+ parent: 1,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: "http://example.com/app.js",
+ bytes: 0,
+ totalBytes: 100,
+ count: 0,
+ totalCount: 10,
+ children: [
+ {
+ name: "JSScript",
+ bytes: 100,
+ totalBytes: 100,
+ count: 10,
+ totalCount: 10,
+ children: undefined,
+ id: 3,
+ parent: 2,
+ reportLeafIndex: 2,
+ }
+ ],
+ id: 2,
+ parent: 1,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 1,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-09.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-09.js
new file mode 100644
index 000000000..3efed04b0
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-09.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test that repeatedly converting the same census report to a CensusTreeNode
+ * tree results in the same CensusTreeNode tree.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+ },
+ };
+
+ const REPORT = {
+ "http://example.com/app.js": {
+ JSScript: { count: 10, bytes: 100 }
+ },
+ "http://example.com/ads.js": {
+ "js::LazyScript": { count: 20, bytes: 200 }
+ },
+ "http://example.com/trackers.js": {
+ JSScript: { count: 30, bytes: 300 }
+ },
+ noFilename: {
+ "js::jit::JitCode": { count: 40, bytes: 400 }
+ }
+ };
+
+ const first = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ const second = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ const third = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+
+ assertStructurallyEquivalent(first, second);
+ assertStructurallyEquivalent(second, third);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
new file mode 100644
index 000000000..b7798f23f
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census-tree-node-10.js
@@ -0,0 +1,43 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test when multiple leaves in the census report map to the same node in an
+ * inverted CensusReportTree.
+ */
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { count: 1, bytes: 10 },
+ },
+ other: {
+ Array: { count: 1, bytes: 10 },
+ },
+ strings: { count: 0, bytes: 0 },
+ scripts: { count: 0, bytes: 0 },
+ };
+
+ const node = censusReportToCensusTreeNode(BREAKDOWN, REPORT, { invert: true });
+
+ equal(node.children[0].name, "Array");
+ equal(node.children[0].reportLeafIndex.size, 2);
+ dumpn(`node.children[0].reportLeafIndex = ${[...node.children[0].reportLeafIndex]}`);
+ ok(node.children[0].reportLeafIndex.has(2));
+ ok(node.children[0].reportLeafIndex.has(6));
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_01.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_01.js
new file mode 100644
index 000000000..75977bccb
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_01.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of breakdown by "internalType".
+
+const BREAKDOWN = {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true }
+};
+
+const REPORT1 = {
+ "JSObject": {
+ "count": 10,
+ "bytes": 100,
+ },
+ "js::Shape": {
+ "count": 50,
+ "bytes": 500,
+ },
+ "JSString": {
+ "count": 0,
+ "bytes": 0,
+ },
+ "js::LazyScript": {
+ "count": 1,
+ "bytes": 10,
+ },
+};
+
+const REPORT2 = {
+ "JSObject": {
+ "count": 11,
+ "bytes": 110,
+ },
+ "js::Shape": {
+ "count": 51,
+ "bytes": 510,
+ },
+ "JSString": {
+ "count": 1,
+ "bytes": 1,
+ },
+ "js::BaseShape": {
+ "count": 1,
+ "bytes": 42,
+ },
+};
+
+const EXPECTED = {
+ "JSObject": {
+ "count": 1,
+ "bytes": 10,
+ },
+ "js::Shape": {
+ "count": 1,
+ "bytes": 10,
+ },
+ "JSString": {
+ "count": 1,
+ "bytes": 1,
+ },
+ "js::LazyScript": {
+ "count": -1,
+ "bytes": -10,
+ },
+ "js::BaseShape": {
+ "count": 1,
+ "bytes": 42,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_02.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_02.js
new file mode 100644
index 000000000..169e3f036
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_02.js
@@ -0,0 +1,25 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of breakdown by "count".
+
+const BREAKDOWN = { by: "count", count: true, bytes: true };
+
+const REPORT1 = {
+ "count": 10,
+ "bytes": 100,
+};
+
+const REPORT2 = {
+ "count": 11,
+ "bytes": 110,
+};
+
+const EXPECTED = {
+ "count": 1,
+ "bytes": 10,
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js
new file mode 100644
index 000000000..6dbca3e40
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_03.js
@@ -0,0 +1,73 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of breakdown by "coarseType".
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "count", count: true, bytes: true },
+ scripts: { by: "count", count: true, bytes: true },
+ strings: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+const REPORT1 = {
+ objects: {
+ count: 1,
+ bytes: 10,
+ },
+ scripts: {
+ count: 1,
+ bytes: 10,
+ },
+ strings: {
+ count: 1,
+ bytes: 10,
+ },
+ other: {
+ count: 3,
+ bytes: 30,
+ },
+};
+
+const REPORT2 = {
+ objects: {
+ count: 1,
+ bytes: 10,
+ },
+ scripts: {
+ count: 0,
+ bytes: 0,
+ },
+ strings: {
+ count: 2,
+ bytes: 20,
+ },
+ other: {
+ count: 4,
+ bytes: 40,
+ },
+};
+
+const EXPECTED = {
+ objects: {
+ count: 0,
+ bytes: 0,
+ },
+ scripts: {
+ count: -1,
+ bytes: -10,
+ },
+ strings: {
+ count: 1,
+ bytes: 10,
+ },
+ other: {
+ count: 1,
+ bytes: 10,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_04.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_04.js
new file mode 100644
index 000000000..a10097945
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_04.js
@@ -0,0 +1,63 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of breakdown by "objectClass".
+
+const BREAKDOWN = {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+};
+
+const REPORT1 = {
+ "Array": {
+ count: 1,
+ bytes: 100,
+ },
+ "Function": {
+ count: 10,
+ bytes: 10,
+ },
+ "other": {
+ count: 10,
+ bytes: 100,
+ }
+};
+
+const REPORT2 = {
+ "Object": {
+ count: 1,
+ bytes: 100,
+ },
+ "Function": {
+ count: 20,
+ bytes: 20,
+ },
+ "other": {
+ count: 10,
+ bytes: 100,
+ }
+};
+
+const EXPECTED = {
+ "Array": {
+ count: -1,
+ bytes: -100,
+ },
+ "Function": {
+ count: 10,
+ bytes: 10,
+ },
+ "other": {
+ count: 0,
+ bytes: 0,
+ },
+ "Object": {
+ count: 1,
+ bytes: 100,
+ },
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_05.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_05.js
new file mode 100644
index 000000000..b6d99f823
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_05.js
@@ -0,0 +1,34 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of breakdown by "allocationStack".
+
+const BREAKDOWN = {
+ by: "allocationStack",
+ then: { by: "count", count: true, bytes: true },
+ noStack: { by: "count", count: true, bytes: true },
+};
+
+const stack1 = saveStack();
+const stack2 = saveStack();
+const stack3 = saveStack();
+
+const REPORT1 = new Map([
+ [stack1, { "count": 10, "bytes": 100 }],
+ [stack2, { "count": 1, "bytes": 10 }],
+]);
+
+const REPORT2 = new Map([
+ [stack2, { "count": 10, "bytes": 100 }],
+ [stack3, { "count": 1, "bytes": 10 }],
+]);
+
+const EXPECTED = new Map([
+ [stack1, { "count": -10, "bytes": -100 }],
+ [stack2, { "count": 9, "bytes": 90 }],
+ [stack3, { "count": 1, "bytes": 10 }],
+]);
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
new file mode 100644
index 000000000..430ff8c9c
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_diff_06.js
@@ -0,0 +1,137 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test diffing census reports of a "complex" and "realistic" breakdown.
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "allocationStack",
+ then: {
+ by: "objectClass",
+ then: { by: "count", count: false, bytes: true },
+ other: { by: "count", count: false, bytes: true }
+ },
+ noStack: {
+ by: "objectClass",
+ then: { by: "count", count: false, bytes: true },
+ other: { by: "count", count: false, bytes: true }
+ }
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true }
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true }
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: false, bytes: true }
+ },
+};
+
+const stack1 = saveStack();
+const stack2 = saveStack();
+const stack3 = saveStack();
+
+const REPORT1 = {
+ objects: new Map([
+ [stack1, { Function: { bytes: 1 },
+ Object: { bytes: 2 },
+ other: { bytes: 0 },
+ }],
+ [stack2, { Array: { bytes: 3 },
+ Date: { bytes: 4 },
+ other: { bytes: 0 },
+ }],
+ ["noStack", { Object: { bytes: 3 }}],
+ ]),
+ strings: {
+ JSAtom: { bytes: 10 },
+ JSLinearString: { bytes: 5 },
+ },
+ scripts: {
+ JSScript: { bytes: 1 },
+ "js::jit::JitCode": { bytes: 2 },
+ },
+ other: {
+ "mozilla::dom::Thing": { bytes: 1 },
+ }
+};
+
+const REPORT2 = {
+ objects: new Map([
+ [stack2, { Array: { bytes: 1 },
+ Date: { bytes: 2 },
+ other: { bytes: 3 },
+ }],
+ [stack3, { Function: { bytes: 1 },
+ Object: { bytes: 2 },
+ other: { bytes: 0 },
+ }],
+ ["noStack", { Object: { bytes: 3 }}],
+ ]),
+ strings: {
+ JSAtom: { bytes: 5 },
+ JSLinearString: { bytes: 10 },
+ },
+ scripts: {
+ JSScript: { bytes: 2 },
+ "js::LazyScript": { bytes: 42 },
+ "js::jit::JitCode": { bytes: 1 },
+ },
+ other: {
+ "mozilla::dom::OtherThing": { bytes: 1 },
+ }
+};
+
+const EXPECTED = {
+ "objects": new Map([
+ [stack1, { Function: { bytes: -1 },
+ Object: { bytes: -2 },
+ other: { bytes: 0 },
+ }],
+ [stack2, { Array: { bytes: -2 },
+ Date: { bytes: -2 },
+ other: { bytes: 3 },
+ }],
+ [stack3, { Function: { bytes: 1 },
+ Object: { bytes: 2 },
+ other: { bytes: 0 },
+ }],
+ ["noStack", { Object: { bytes: 0 }}],
+ ]),
+ "scripts": {
+ "JSScript": {
+ "bytes": 1
+ },
+ "js::jit::JitCode": {
+ "bytes": -1
+ },
+ "js::LazyScript": {
+ "bytes": 42
+ }
+ },
+ "strings": {
+ "JSAtom": {
+ "bytes": -5
+ },
+ "JSLinearString": {
+ "bytes": 5
+ }
+ },
+ "other": {
+ "mozilla::dom::Thing": {
+ "bytes": -1
+ },
+ "mozilla::dom::OtherThing": {
+ "bytes": 1
+ }
+ }
+};
+
+function run_test() {
+ assertDiff(BREAKDOWN, REPORT1, REPORT2, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
new file mode 100644
index 000000000..57724d7c1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_01.js
@@ -0,0 +1,105 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test filtering basic CensusTreeNode trees.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other:{
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ UInt8Array: { bytes: 80, count: 8 },
+ Int32Array: { bytes: 320, count: 32 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ }
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 620,
+ count: 0,
+ totalCount: 62,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 450,
+ count: 0,
+ totalCount: 45,
+ children: [
+ {
+ name: "Int32Array",
+ bytes: 320,
+ totalBytes: 320,
+ count: 32,
+ totalCount: 32,
+ children: undefined,
+ id: 15,
+ parent: 14,
+ reportLeafIndex: 4,
+ },
+ {
+ name: "UInt8Array",
+ bytes: 80,
+ totalBytes: 80,
+ count: 8,
+ totalCount: 8,
+ children: undefined,
+ id: 16,
+ parent: 14,
+ reportLeafIndex: 3,
+ },
+ {
+ name: "Array",
+ bytes: 50,
+ totalBytes: 50,
+ count: 5,
+ totalCount: 5,
+ children: undefined,
+ id: 17,
+ parent: 14,
+ reportLeafIndex: 2,
+ }
+ ],
+ id: 14,
+ parent: 13,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 13,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "Array" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js
new file mode 100644
index 000000000..0a57ce66d
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_02.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test filtering CensusTreeNode trees with an `allocationStack` breakdown.
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ let stack1, stack2, stack3, stack4, stack5;
+
+ (function foo() {
+ (function bar() {
+ (function baz() {
+ stack1 = saveStack(3);
+ }());
+ (function quux() {
+ stack2 = saveStack(3);
+ stack3 = saveStack(3);
+ }());
+ }());
+ stack4 = saveStack(2);
+ }());
+
+ stack5 = saveStack(1);
+
+ const REPORT = new Map([
+ [stack1, { bytes: 10, count: 1 }],
+ [stack2, { bytes: 20, count: 2 }],
+ [stack3, { bytes: 30, count: 3 }],
+ [stack4, { bytes: 40, count: 4 }],
+ [stack5, { bytes: 50, count: 5 }],
+ ["noStack", { bytes: 60, count: 6 }],
+ ]);
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 210,
+ count: 0,
+ totalCount: 21,
+ children: [
+ {
+ name: stack1.parent.parent,
+ bytes: 0,
+ totalBytes: 60,
+ count: 0,
+ totalCount: 6,
+ children: [
+ {
+ name: stack2.parent,
+ bytes: 0,
+ totalBytes: 50,
+ count: 0,
+ totalCount: 5,
+ children: [
+ {
+ name: stack3,
+ bytes: 30,
+ totalBytes: 30,
+ count: 3,
+ totalCount: 3,
+ children: undefined,
+ id: 15,
+ parent: 14,
+ reportLeafIndex: 3,
+ },
+ {
+ name: stack2,
+ bytes: 20,
+ totalBytes: 20,
+ count: 2,
+ totalCount: 2,
+ children: undefined,
+ id: 16,
+ parent: 14,
+ reportLeafIndex: 2,
+ }
+ ],
+ id: 14,
+ parent: 13,
+ reportLeafIndex: undefined,
+ },
+ {
+ name: stack1.parent,
+ bytes: 0,
+ totalBytes: 10,
+ count: 0,
+ totalCount: 1,
+ children: [
+ {
+ name: stack1,
+ bytes: 10,
+ totalBytes: 10,
+ count: 1,
+ totalCount: 1,
+ children: undefined,
+ id: 18,
+ parent: 17,
+ reportLeafIndex: 1,
+ }
+ ],
+ id: 17,
+ parent: 13,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 13,
+ parent: 12,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 12,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "bar" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
new file mode 100644
index 000000000..2c69a14b8
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_03.js
@@ -0,0 +1,59 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test filtering with no matches.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ scripts: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ strings: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ other:{
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { bytes: 50, count: 5 },
+ UInt8Array: { bytes: 80, count: 8 },
+ Int32Array: { bytes: 320, count: 32 },
+ other: { bytes: 0, count: 0 },
+ },
+ scripts: {
+ "js::jit::JitScript": { bytes: 30, count: 3 },
+ },
+ strings: {
+ JSAtom: { bytes: 60, count: 6 },
+ },
+ other: {
+ "js::Shape": { bytes: 80, count: 8 },
+ }
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 620,
+ count: 0,
+ totalCount: 62,
+ children: undefined,
+ id: 13,
+ parent: undefined,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "zzzzzzzzzzzzzzzzzzzz" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
new file mode 100644
index 000000000..c9871436b
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_04.js
@@ -0,0 +1,102 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test the filtered nodes' counts and bytes are the same as they were when
+// unfiltered.
+
+function run_test() {
+ const COUNT = { by: "count", count: true, bytes: true };
+ const INTERNAL_TYPE = { by: "internalType", then: COUNT };
+
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: COUNT, other: COUNT },
+ strings: COUNT,
+ scripts: {
+ by: "filename",
+ then: INTERNAL_TYPE,
+ noFilename: INTERNAL_TYPE
+ },
+ other: INTERNAL_TYPE,
+ };
+
+ const REPORT = {
+ objects: {
+ Function: {
+ count: 7,
+ bytes: 70
+ },
+ Array: {
+ count: 6,
+ bytes: 60
+ }
+ },
+ scripts: {
+ "http://mozilla.github.io/pdf.js/build/pdf.js": {
+ "js::LazyScript": {
+ count: 4,
+ bytes: 40
+ },
+ }
+ },
+ strings: {
+ count: 2,
+ bytes: 20
+ },
+ other: {
+ "js::Shape": {
+ count: 1,
+ bytes: 10
+ }
+ }
+ };
+
+ const EXPECTED = {
+ name: null,
+ bytes: 0,
+ totalBytes: 200,
+ count: 0,
+ totalCount: 20,
+ parent: undefined,
+ children: [
+ {
+ name: "objects",
+ bytes: 0,
+ totalBytes: 130,
+ count: 0,
+ totalCount: 13,
+ children: [
+ {
+ name: "Function",
+ bytes: 70,
+ totalBytes: 70,
+ count: 7,
+ totalCount: 7,
+ id: 13,
+ parent: 12,
+ children: undefined,
+ reportLeafIndex: 2,
+ },
+ {
+ name: "Array",
+ bytes: 60,
+ totalBytes: 60,
+ count: 6,
+ totalCount: 6,
+ id: 14,
+ parent: 12,
+ children: undefined,
+ reportLeafIndex: 3,
+ },
+ ],
+ id: 12,
+ parent: 11,
+ reportLeafIndex: undefined,
+ }
+ ],
+ id: 11,
+ reportLeafIndex: undefined,
+ };
+
+ compareCensusViewData(BREAKDOWN, REPORT, EXPECTED, { filter: "objects" });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_05.js b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_05.js
new file mode 100644
index 000000000..1d1f4fa55
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_census_filtering_05.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test that filtered and inverted allocation stack census trees are sorted
+// properly.
+
+function run_test() {
+ const countBreakdown = { by: "count", count: true, bytes: true };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: countBreakdown,
+ noStack: countBreakdown,
+ };
+
+ const stacks = [];
+
+ function foo(depth = 1) {
+ stacks.push(saveStack(depth));
+ bar(depth + 1);
+ baz(depth + 1);
+ stacks.push(saveStack(depth));
+ }
+
+ function bar(depth = 1) {
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ }
+
+ function baz(depth = 1) {
+ stacks.push(saveStack(depth));
+ bang(depth + 1);
+ stacks.push(saveStack(depth));
+ }
+
+ function bang(depth = 1) {
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ stacks.push(saveStack(depth));
+ }
+
+ foo();
+ bar();
+ baz();
+ bang();
+
+ const REPORT = new Map(stacks.map((s, i) => {
+ return [s, {
+ count: i + 1,
+ bytes: (i + 1) * 10
+ }];
+ }));
+
+ const tree = censusReportToCensusTreeNode(BREAKDOWN, REPORT, {
+ filter: "baz",
+ invert: true
+ });
+
+ dumpn("tree = " + JSON.stringify(tree, savedFrameReplacer, 4));
+
+ (function assertSortedBySelf(node) {
+ if (node.children) {
+ let lastSelfBytes = Infinity;
+ for (let child of node.children) {
+ ok(child.bytes <= lastSelfBytes, `${child.bytes} <= ${lastSelfBytes}`);
+ lastSelfBytes = child.bytes;
+ assertSortedBySelf(child);
+ }
+ }
+ }(tree));
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_countToBucketBreakdown_01.js b/devtools/shared/heapsnapshot/tests/unit/test_countToBucketBreakdown_01.js
new file mode 100644
index 000000000..e89048c33
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_countToBucketBreakdown_01.js
@@ -0,0 +1,37 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that we can turn a breakdown with { by: "count" } leaves into a
+// breakdown with { by: "bucket" } leaves.
+
+const COUNT = { by: "count", count: true, bytes: true };
+const BUCKET = { by: "bucket" };
+
+const BREAKDOWN = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: COUNT, other: COUNT },
+ strings: COUNT,
+ scripts: {
+ by: "filename",
+ then: { by: "internalType", then: COUNT },
+ noFilename: { by: "internalType", then: COUNT },
+ },
+ other: { by: "internalType", then: COUNT },
+};
+
+const EXPECTED = {
+ by: "coarseType",
+ objects: { by: "objectClass", then: BUCKET, other: BUCKET },
+ strings: BUCKET,
+ scripts: {
+ by: "filename",
+ then: { by: "internalType", then: BUCKET },
+ noFilename: { by: "internalType", then: BUCKET },
+ },
+ other: { by: "internalType", then: BUCKET },
+};
+
+function run_test() {
+ assertCountToBucketBreakdown(BREAKDOWN, EXPECTED);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_deduplicatePaths_01.js b/devtools/shared/heapsnapshot/tests/unit/test_deduplicatePaths_01.js
new file mode 100644
index 000000000..418b49db3
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_deduplicatePaths_01.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test the behavior of the deduplicatePaths utility function.
+
+function edge(from, to, name) {
+ return { from, to, name };
+}
+
+function run_test() {
+ const a = 1;
+ const b = 2;
+ const c = 3;
+ const d = 4;
+ const e = 5;
+ const f = 6;
+ const g = 7;
+
+ dumpn("Single long path");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [
+ pathEntry(a, "e1"),
+ pathEntry(b, "e2"),
+ pathEntry(c, "e3"),
+ pathEntry(d, "e4"),
+ pathEntry(e, "e5"),
+ pathEntry(f, "e6"),
+ ]
+ ],
+ expectedNodes: [a, b, c, d, e, f, g],
+ expectedEdges: [
+ edge(a, b, "e1"),
+ edge(b, c, "e2"),
+ edge(c, d, "e3"),
+ edge(d, e, "e4"),
+ edge(e, f, "e5"),
+ edge(f, g, "e6"),
+ ]
+ });
+
+ dumpn("Multiple edges from and to the same nodes");
+ assertDeduplicatedPaths({
+ target: a,
+ paths: [
+ [pathEntry(b, "x")],
+ [pathEntry(b, "y")],
+ [pathEntry(b, "z")],
+ ],
+ expectedNodes: [a, b],
+ expectedEdges: [
+ edge(b, a, "x"),
+ edge(b, a, "y"),
+ edge(b, a, "z"),
+ ]
+ });
+
+ dumpn("Multiple paths sharing some nodes and edges");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->c"),
+ pathEntry(c, "foo"),
+ ],
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->d"),
+ pathEntry(d, "bar"),
+ ],
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->e"),
+ pathEntry(e, "baz"),
+ ],
+ ],
+ expectedNodes: [a, b, c, d, e, g],
+ expectedEdges: [
+ edge(a, b, "a->b"),
+ edge(b, c, "b->c"),
+ edge(b, d, "b->d"),
+ edge(b, e, "b->e"),
+ edge(c, g, "foo"),
+ edge(d, g, "bar"),
+ edge(e, g, "baz"),
+ ]
+ });
+
+ dumpn("Second shortest path contains target itself");
+ assertDeduplicatedPaths({
+ target: g,
+ paths: [
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->g"),
+ ],
+ [
+ pathEntry(a, "a->b"),
+ pathEntry(b, "b->g"),
+ pathEntry(g, "g->f"),
+ pathEntry(f, "f->g"),
+ ],
+ ],
+ expectedNodes: [a, b, g],
+ expectedEdges: [
+ edge(a, b, "a->b"),
+ edge(b, g, "b->g"),
+ ]
+ });
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js b/devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js
new file mode 100644
index 000000000..9c4f60991
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_getCensusIndividuals_01.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test basic functionality of `CensusUtils.getCensusIndividuals`.
+
+function run_test() {
+ const stack1 = saveStack(1);
+ const stack2 = saveStack(1);
+ const stack3 = saveStack(1);
+
+ const COUNT = { by: "count", count: true, bytes: true };
+ const INTERNAL_TYPE = { by: "internalType", then: COUNT };
+
+ const BREAKDOWN = {
+ by: "allocationStack",
+ then: INTERNAL_TYPE,
+ noStack: INTERNAL_TYPE,
+ };
+
+ const MOCK_SNAPSHOT = {
+ takeCensus: ({ breakdown }) => {
+ assertStructurallyEquivalent(
+ breakdown,
+ CensusUtils.countToBucketBreakdown(BREAKDOWN));
+
+ // DFS Index
+ return new Map([ // 0
+ [stack1, { // 1
+ JSObject: [101, 102, 103], // 2
+ JSString: [111, 112, 113], // 3
+ }],
+ [stack2, { // 4
+ JSObject: [201, 202, 203], // 5
+ JSString: [211, 212, 213], // 6
+ }],
+ [stack3, { // 7
+ JSObject: [301, 302, 303], // 8
+ JSString: [311, 312, 313], // 9
+ }],
+ ["noStack", { // 10
+ JSObject: [401, 402, 403], // 11
+ JSString: [411, 412, 413], // 12
+ }],
+ ]);
+ }
+ };
+
+ const INDICES = new Set([3, 5, 9]);
+
+ const EXPECTED = new Set([111, 112, 113,
+ 201, 202, 203,
+ 311, 312, 313]);
+
+ const actual = new Set(CensusUtils.getCensusIndividuals(INDICES,
+ BREAKDOWN,
+ MOCK_SNAPSHOT));
+
+ assertStructurallyEquivalent(EXPECTED, actual);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js b/devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js
new file mode 100644
index 000000000..4c4298b6a
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_getReportLeaves_01.js
@@ -0,0 +1,114 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test basic functionality of `CensusUtils.getReportLeaves`.
+
+function run_test() {
+ const BREAKDOWN = {
+ by: "coarseType",
+ objects: {
+ by: "objectClass",
+ then: { by: "count", count: true, bytes: true },
+ other: { by: "count", count: true, bytes: true },
+ },
+ strings: { by: "count", count: true, bytes: true },
+ scripts: {
+ by: "filename",
+ then: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ noFilename: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ },
+ other: {
+ by: "internalType",
+ then: { by: "count", count: true, bytes: true },
+ },
+ };
+
+ const REPORT = {
+ objects: {
+ Array: { count: 6, bytes: 60 },
+ Function: { count: 1, bytes: 10 },
+ Object: { count: 1, bytes: 10 },
+ RegExp: { count: 1, bytes: 10 },
+ other: { count: 0, bytes: 0 },
+ },
+ strings: { count: 1, bytes: 10 },
+ scripts: {
+ "foo.js": {
+ JSScript: { count: 1, bytes: 10 },
+ "js::jit::IonScript": { count: 1, bytes: 10 },
+ },
+ noFilename: {
+ JSScript: { count: 1, bytes: 10 },
+ "js::jit::IonScript": { count: 1, bytes: 10 },
+ },
+ },
+ other: {
+ "js::Shape": { count: 7, bytes: 70 },
+ "js::BaseShape": { count: 1, bytes: 10 },
+ },
+ };
+
+ const root = censusReportToCensusTreeNode(BREAKDOWN, REPORT);
+ dumpn("CensusTreeNode tree = " + JSON.stringify(root, null, 4));
+
+ (function assertEveryNodeCanFindItsLeaf(node) {
+ if (node.reportLeafIndex) {
+ const [ leaf ] = CensusUtils.getReportLeaves(new Set([node.reportLeafIndex]),
+ BREAKDOWN,
+ REPORT);
+ ok(leaf, "Should be able to find leaf for a node with a reportLeafIndex = " + node.reportLeafIndex);
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ assertEveryNodeCanFindItsLeaf(child);
+ }
+ }
+ }(root));
+
+ // Test finding multiple leaves at a time.
+
+ function find(name, node) {
+ if (node.name === name) {
+ return node;
+ }
+
+ if (node.children) {
+ for (let child of node.children) {
+ const found = find(name, child);
+ if (found) {
+ return found;
+ }
+ }
+ }
+ }
+
+ const arrayNode = find("Array", root);
+ ok(arrayNode);
+ equal(typeof arrayNode.reportLeafIndex, "number");
+
+ const shapeNode = find("js::Shape", root);
+ ok(shapeNode);
+ equal(typeof shapeNode.reportLeafIndex, "number");
+
+ const indices = new Set([arrayNode.reportLeafIndex, shapeNode.reportLeafIndex]);
+ const leaves = CensusUtils.getReportLeaves(indices, BREAKDOWN, REPORT);
+ equal(leaves.length, 2);
+
+ // `getReportLeaves` does not guarantee order of the results, so handle both
+ // cases.
+ ok(leaves.some(l => l === REPORT.objects.Array));
+ ok(leaves.some(l => l === REPORT.other["js::Shape"]));
+
+ // Test that bad indices do not yield results.
+
+ const none = CensusUtils.getReportLeaves(new Set([999999999999]), BREAKDOWN, REPORT);
+ equal(none.length, 0);
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/test_saveHeapSnapshot_e10s_01.js b/devtools/shared/heapsnapshot/tests/unit/test_saveHeapSnapshot_e10s_01.js
new file mode 100644
index 000000000..067b9effb
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/test_saveHeapSnapshot_e10s_01.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test saving a heap snapshot in the sandboxed e10s child process.
+
+function run_test() {
+ run_test_in_child("../unit/test_SaveHeapSnapshot.js");
+}
diff --git a/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..f84b282d1
--- /dev/null
+++ b/devtools/shared/heapsnapshot/tests/unit/xpcshell.ini
@@ -0,0 +1,98 @@
+[DEFAULT]
+tags = devtools heapsnapshot devtools-memory
+head = head_heapsnapshot.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+support-files =
+ Census.jsm
+ dominator-tree-worker.js
+ heap-snapshot-worker.js
+ Match.jsm
+
+[test_census_diff_01.js]
+[test_census_diff_02.js]
+[test_census_diff_03.js]
+[test_census_diff_04.js]
+[test_census_diff_05.js]
+[test_census_diff_06.js]
+[test_census_filtering_01.js]
+[test_census_filtering_02.js]
+[test_census_filtering_03.js]
+[test_census_filtering_04.js]
+[test_census_filtering_05.js]
+[test_census-tree-node-01.js]
+[test_census-tree-node-02.js]
+[test_census-tree-node-03.js]
+[test_census-tree-node-04.js]
+[test_census-tree-node-05.js]
+[test_census-tree-node-06.js]
+[test_census-tree-node-07.js]
+[test_census-tree-node-08.js]
+[test_census-tree-node-09.js]
+[test_census-tree-node-10.js]
+[test_countToBucketBreakdown_01.js]
+[test_deduplicatePaths_01.js]
+[test_DominatorTree_01.js]
+[test_DominatorTree_02.js]
+[test_DominatorTree_03.js]
+[test_DominatorTree_04.js]
+[test_DominatorTree_05.js]
+[test_DominatorTree_06.js]
+[test_DominatorTreeNode_attachShortestPaths_01.js]
+[test_DominatorTreeNode_getNodeByIdAlongPath_01.js]
+[test_DominatorTreeNode_insert_01.js]
+[test_DominatorTreeNode_insert_02.js]
+[test_DominatorTreeNode_insert_03.js]
+[test_DominatorTreeNode_LabelAndShallowSize_01.js]
+[test_DominatorTreeNode_LabelAndShallowSize_02.js]
+[test_DominatorTreeNode_LabelAndShallowSize_03.js]
+[test_DominatorTreeNode_LabelAndShallowSize_04.js]
+[test_DominatorTreeNode_partialTraversal_01.js]
+[test_getCensusIndividuals_01.js]
+[test_getReportLeaves_01.js]
+[test_HeapAnalyses_computeDominatorTree_01.js]
+[test_HeapAnalyses_computeDominatorTree_02.js]
+[test_HeapAnalyses_deleteHeapSnapshot_01.js]
+[test_HeapAnalyses_deleteHeapSnapshot_02.js]
+[test_HeapAnalyses_deleteHeapSnapshot_03.js]
+[test_HeapAnalyses_getCensusIndividuals_01.js]
+[test_HeapAnalyses_getCreationTime_01.js]
+[test_HeapAnalyses_getDominatorTree_01.js]
+[test_HeapAnalyses_getDominatorTree_02.js]
+[test_HeapAnalyses_getImmediatelyDominated_01.js]
+[test_HeapAnalyses_readHeapSnapshot_01.js]
+[test_HeapAnalyses_takeCensusDiff_01.js]
+[test_HeapAnalyses_takeCensusDiff_02.js]
+[test_HeapAnalyses_takeCensus_01.js]
+[test_HeapAnalyses_takeCensus_02.js]
+[test_HeapAnalyses_takeCensus_03.js]
+[test_HeapAnalyses_takeCensus_04.js]
+[test_HeapAnalyses_takeCensus_05.js]
+[test_HeapAnalyses_takeCensus_06.js]
+[test_HeapAnalyses_takeCensus_07.js]
+[test_HeapSnapshot_creationTime_01.js]
+[test_HeapSnapshot_deepStack_01.js]
+[test_HeapSnapshot_describeNode_01.js]
+[test_HeapSnapshot_computeShortestPaths_01.js]
+[test_HeapSnapshot_computeShortestPaths_02.js]
+[test_HeapSnapshot_takeCensus_01.js]
+[test_HeapSnapshot_takeCensus_02.js]
+[test_HeapSnapshot_takeCensus_03.js]
+[test_HeapSnapshot_takeCensus_04.js]
+[test_HeapSnapshot_takeCensus_05.js]
+[test_HeapSnapshot_takeCensus_06.js]
+[test_HeapSnapshot_takeCensus_07.js]
+[test_HeapSnapshot_takeCensus_08.js]
+[test_HeapSnapshot_takeCensus_09.js]
+[test_HeapSnapshot_takeCensus_10.js]
+[test_HeapSnapshot_takeCensus_11.js]
+[test_HeapSnapshot_takeCensus_12.js]
+[test_ReadHeapSnapshot.js]
+[test_ReadHeapSnapshot_with_allocations.js]
+skip-if = os == 'linux' # Bug 1176173
+[test_ReadHeapSnapshot_worker.js]
+skip-if = os == 'linux' # Bug 1176173
+[test_SaveHeapSnapshot.js]
+[test_saveHeapSnapshot_e10s_01.js]
diff --git a/devtools/shared/indentation.js b/devtools/shared/indentation.js
new file mode 100644
index 000000000..9d3cabcb7
--- /dev/null
+++ b/devtools/shared/indentation.js
@@ -0,0 +1,160 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
+/* vim:set ts=2 sw=2 sts=2 et tw=80:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const Services = require("Services");
+
+const EXPAND_TAB = "devtools.editor.expandtab";
+const TAB_SIZE = "devtools.editor.tabsize";
+const DETECT_INDENT = "devtools.editor.detectindentation";
+const DETECT_INDENT_MAX_LINES = 500;
+
+/**
+ * Get the indentation to use in an editor, or return false if the user has
+ * asked for the indentation to be guessed from some text.
+ *
+ * @return {false | Object}
+ * Returns false if the "detect indentation" pref is set.
+ * an object of the form {indentUnit, indentWithTabs}.
+ * |indentUnit| is the number of indentation units to use
+ * to indent a "block".
+ * |indentWithTabs| is a boolean which is true if indentation
+ * should be done using tabs.
+ */
+function getIndentationFromPrefs() {
+ let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
+ if (shouldDetect) {
+ return false;
+ }
+
+ let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
+ let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
+ return {indentUnit, indentWithTabs};
+}
+
+/**
+ * Given a function that can iterate over some text, compute the indentation to
+ * use. This consults various prefs to arrive at a decision.
+ *
+ * @param {Function} iterFunc A function of three arguments:
+ * (start, end, callback); where |start| and |end| describe
+ * the range of text lines to examine, and |callback| is a function
+ * to be called with the text of each line.
+ *
+ * @return {Object} an object of the form {indentUnit, indentWithTabs}.
+ * |indentUnit| is the number of indentation units to use
+ * to indent a "block".
+ * |indentWithTabs| is a boolean which is true if indentation
+ * should be done using tabs.
+ */
+function getIndentationFromIteration(iterFunc) {
+ let indentWithTabs = !Services.prefs.getBoolPref(EXPAND_TAB);
+ let indentUnit = Services.prefs.getIntPref(TAB_SIZE);
+ let shouldDetect = Services.prefs.getBoolPref(DETECT_INDENT);
+
+ if (shouldDetect) {
+ let indent = detectIndentation(iterFunc);
+ if (indent != null) {
+ indentWithTabs = indent.tabs;
+ indentUnit = indent.spaces ? indent.spaces : indentUnit;
+ }
+ }
+
+ return {indentUnit, indentWithTabs};
+}
+
+/**
+ * A wrapper for @see getIndentationFromIteration which computes the
+ * indentation of a given string.
+ *
+ * @param {String} string the input text
+ * @return {Object} an object of the same form as returned by
+ * getIndentationFromIteration
+ */
+function getIndentationFromString(string) {
+ let iteratorFn = function (start, end, callback) {
+ let split = string.split(/\r\n|\r|\n|\f/);
+ split.slice(start, end).forEach(callback);
+ };
+ return getIndentationFromIteration(iteratorFn);
+}
+
+/**
+ * Detect the indentation used in an editor. Returns an object
+ * with 'tabs' - whether this is tab-indented and 'spaces' - the
+ * width of one indent in spaces. Or `null` if it's inconclusive.
+ */
+function detectIndentation(textIteratorFn) {
+ // # spaces indent -> # lines with that indent
+ let spaces = {};
+ // indentation width of the last line we saw
+ let last = 0;
+ // # of lines that start with a tab
+ let tabs = 0;
+ // # of indented lines (non-zero indent)
+ let total = 0;
+
+ textIteratorFn(0, DETECT_INDENT_MAX_LINES, (text) => {
+ if (text.startsWith("\t")) {
+ tabs++;
+ total++;
+ return;
+ }
+ let width = 0;
+ while (text[width] === " ") {
+ width++;
+ }
+ // don't count lines that are all spaces
+ if (width == text.length) {
+ last = 0;
+ return;
+ }
+ if (width > 1) {
+ total++;
+ }
+
+ // see how much this line is offset from the line above it
+ let indent = Math.abs(width - last);
+ if (indent > 1 && indent <= 8) {
+ spaces[indent] = (spaces[indent] || 0) + 1;
+ }
+ last = width;
+ });
+
+ // this file is not indented at all
+ if (total == 0) {
+ return null;
+ }
+
+ // mark as tabs if they start more than half the lines
+ if (tabs >= total / 2) {
+ return { tabs: true };
+ }
+
+ // find most frequent non-zero width difference between adjacent lines
+ let freqIndent = null, max = 1;
+ for (let width in spaces) {
+ width = parseInt(width, 10);
+ let tally = spaces[width];
+ if (tally > max) {
+ max = tally;
+ freqIndent = width;
+ }
+ }
+ if (!freqIndent) {
+ return null;
+ }
+
+ return { tabs: false, spaces: freqIndent };
+}
+
+exports.EXPAND_TAB = EXPAND_TAB;
+exports.TAB_SIZE = TAB_SIZE;
+exports.DETECT_INDENT = DETECT_INDENT;
+exports.getIndentationFromPrefs = getIndentationFromPrefs;
+exports.getIndentationFromIteration = getIndentationFromIteration;
+exports.getIndentationFromString = getIndentationFromString;
diff --git a/devtools/shared/inspector/css-logic.js b/devtools/shared/inspector/css-logic.js
new file mode 100644
index 000000000..c8cdd2fdb
--- /dev/null
+++ b/devtools/shared/inspector/css-logic.js
@@ -0,0 +1,325 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * About the objects defined in this file:
+ * - CssLogic contains style information about a view context. It provides
+ * access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
+ * information that does not change when the selected element changes while
+ * Css[Property|Selector]Info provide information that is dependent on the
+ * selected element.
+ * Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
+ * It also contains a number of static methods for l10n, naming, etc
+ *
+ * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
+ * including shortSource and href.
+ * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
+ * of CssSelectors that the rule provides properties for
+ * - CssSelector A single selector - i.e. not a selector group. In other words
+ * a CssSelector does not contain ','. This terminology is different from the
+ * standard DOM API, but more inline with the definition in the spec.
+ *
+ * - CssPropertyInfo contains style information for a single property for the
+ * highlighted element.
+ * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
+ * reference to the selected element.
+ */
+
+"use strict";
+
+/**
+ * Provide access to the style information in a page.
+ * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
+ * styling information in the page, and present this to the user in a way that
+ * helps them understand:
+ * - why their expectations may not have been fulfilled
+ * - how browsers process CSS
+ * @constructor
+ */
+
+const Services = require("Services");
+const CSSLexer = require("devtools/shared/css/lexer");
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const styleInspectorL10N =
+ new LocalizationHelper("devtools/shared/locales/styleinspector.properties");
+
+/**
+ * Special values for filter, in addition to an href these values can be used
+ */
+exports.FILTER = {
+ // show properties for all user style sheets.
+ USER: "user",
+ // USER, plus user-agent (i.e. browser) style sheets
+ UA: "ua",
+};
+
+/**
+ * Each rule has a status, the bigger the number, the better placed it is to
+ * provide styling information.
+ *
+ * These statuses are localized inside the styleinspector.properties
+ * string bundle.
+ * @see csshtmltree.js RuleView._cacheStatusNames()
+ */
+exports.STATUS = {
+ BEST: 3,
+ MATCHED: 2,
+ PARENT_MATCH: 1,
+ UNMATCHED: 0,
+ UNKNOWN: -1,
+};
+
+/**
+ * Lookup a l10n string in the shared styleinspector string bundle.
+ *
+ * @param {String} name
+ * The key to lookup.
+ * @returns {String} A localized version of the given key.
+ */
+exports.l10n = name => styleInspectorL10N.getStr(name);
+
+/**
+ * Is the given property sheet a content stylesheet?
+ *
+ * @param {CSSStyleSheet} sheet a stylesheet
+ * @return {boolean} true if the given stylesheet is a content stylesheet,
+ * false otherwise.
+ */
+exports.isContentStylesheet = function (sheet) {
+ return sheet.parsingMode !== "agent";
+};
+
+/**
+ * Return a shortened version of a style sheet's source.
+ *
+ * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
+ */
+exports.shortSource = function (sheet) {
+ // Use a string like "inline" if there is no source href
+ if (!sheet || !sheet.href) {
+ return exports.l10n("rule.sourceInline");
+ }
+
+ // We try, in turn, the filename, filePath, query string, whole thing
+ let url = {};
+ try {
+ url = new URL(sheet.href);
+ } catch (ex) {
+ // Some UA-provided stylesheets are not valid URLs.
+ }
+
+ if (url.pathname) {
+ let index = url.pathname.lastIndexOf("/");
+ if (index !== -1 && index < url.pathname.length) {
+ return url.pathname.slice(index + 1);
+ }
+ return url.pathname;
+ }
+
+ if (url.query) {
+ return url.query;
+ }
+
+ let dataUrl = sheet.href.match(/^(data:[^,]*),/);
+ return dataUrl ? dataUrl[1] : sheet.href;
+};
+
+const TAB_CHARS = "\t";
+
+/**
+ * Prettify minified CSS text.
+ * This prettifies CSS code where there is no indentation in usual places while
+ * keeping original indentation as-is elsewhere.
+ * @param string text The CSS source to prettify.
+ * @return string Prettified CSS source
+ */
+function prettifyCSS(text, ruleCount) {
+ if (prettifyCSS.LINE_SEPARATOR == null) {
+ let os = Services.appinfo.OS;
+ prettifyCSS.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n");
+ }
+
+ // remove initial and terminating HTML comments and surrounding whitespace
+ text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, "");
+ let originalText = text;
+ text = text.trim();
+
+ // don't attempt to prettify if there's more than one line per rule.
+ let lineCount = text.split("\n").length - 1;
+ if (ruleCount !== null && lineCount >= ruleCount) {
+ return originalText;
+ }
+
+ // We reformat the text using a simple state machine. The
+ // reformatting preserves most of the input text, changing only
+ // whitespace. The rules are:
+ //
+ // * After a "{" or ";" symbol, ensure there is a newline and
+ // indentation before the next non-comment, non-whitespace token.
+ // * Additionally after a "{" symbol, increase the indentation.
+ // * A "}" symbol ensures there is a preceding newline, and
+ // decreases the indentation level.
+ // * Ensure there is whitespace before a "{".
+ //
+ // This approach can be confused sometimes, but should do ok on a
+ // minified file.
+ let indent = "";
+ let indentLevel = 0;
+ let tokens = CSSLexer.getCSSLexer(text);
+ let result = "";
+ let pushbackToken = undefined;
+
+ // A helper function that reads tokens, looking for the next
+ // non-comment, non-whitespace token. Comment and whitespace tokens
+ // are appended to |result|. If this encounters EOF, it returns
+ // null. Otherwise it returns the last whitespace token that was
+ // seen. This function also updates |pushbackToken|.
+ let readUntilSignificantToken = () => {
+ while (true) {
+ let token = tokens.nextToken();
+ if (!token || token.tokenType !== "whitespace") {
+ pushbackToken = token;
+ return token;
+ }
+ // Saw whitespace. Before committing to it, check the next
+ // token.
+ let nextToken = tokens.nextToken();
+ if (!nextToken || nextToken.tokenType !== "comment") {
+ pushbackToken = nextToken;
+ return token;
+ }
+ // Saw whitespace + comment. Update the result and continue.
+ result = result + text.substring(token.startOffset, nextToken.endOffset);
+ }
+ };
+
+ // State variables for readUntilNewlineNeeded.
+ //
+ // Starting index of the accumulated tokens.
+ let startIndex;
+ // Ending index of the accumulated tokens.
+ let endIndex;
+ // True if any non-whitespace token was seen.
+ let anyNonWS;
+ // True if the terminating token is "}".
+ let isCloseBrace;
+ // True if the token just before the terminating token was
+ // whitespace.
+ let lastWasWS;
+
+ // A helper function that reads tokens until there is a reason to
+ // insert a newline. This updates the state variables as needed.
+ // If this encounters EOF, it returns null. Otherwise it returns
+ // the final token read. Note that if the returned token is "{",
+ // then it will not be included in the computed start/end token
+ // range. This is used to handle whitespace insertion before a "{".
+ let readUntilNewlineNeeded = () => {
+ let token;
+ while (true) {
+ if (pushbackToken) {
+ token = pushbackToken;
+ pushbackToken = undefined;
+ } else {
+ token = tokens.nextToken();
+ }
+ if (!token) {
+ endIndex = text.length;
+ break;
+ }
+
+ // A "}" symbol must be inserted later, to deal with indentation
+ // and newline.
+ if (token.tokenType === "symbol" && token.text === "}") {
+ isCloseBrace = true;
+ break;
+ } else if (token.tokenType === "symbol" && token.text === "{") {
+ break;
+ }
+
+ if (token.tokenType !== "whitespace") {
+ anyNonWS = true;
+ }
+
+ if (startIndex === undefined) {
+ startIndex = token.startOffset;
+ }
+ endIndex = token.endOffset;
+
+ if (token.tokenType === "symbol" && token.text === ";") {
+ break;
+ }
+
+ lastWasWS = token.tokenType === "whitespace";
+ }
+ return token;
+ };
+
+ while (true) {
+ // Set the initial state.
+ startIndex = undefined;
+ endIndex = undefined;
+ anyNonWS = false;
+ isCloseBrace = false;
+ lastWasWS = false;
+
+ // Read tokens until we see a reason to insert a newline.
+ let token = readUntilNewlineNeeded();
+
+ // Append any saved up text to the result, applying indentation.
+ if (startIndex !== undefined) {
+ if (isCloseBrace && !anyNonWS) {
+ // If we saw only whitespace followed by a "}", then we don't
+ // need anything here.
+ } else {
+ result = result + indent + text.substring(startIndex, endIndex);
+ if (isCloseBrace) {
+ result += prettifyCSS.LINE_SEPARATOR;
+ }
+ }
+ }
+
+ if (isCloseBrace) {
+ indent = TAB_CHARS.repeat(--indentLevel);
+ result = result + indent + "}";
+ }
+
+ if (!token) {
+ break;
+ }
+
+ if (token.tokenType === "symbol" && token.text === "{") {
+ if (!lastWasWS) {
+ result += " ";
+ }
+ result += "{";
+ indent = TAB_CHARS.repeat(++indentLevel);
+ }
+
+ // Now it is time to insert a newline. However first we want to
+ // deal with any trailing comments.
+ token = readUntilSignificantToken();
+
+ // "Early" bail-out if the text does not appear to be minified.
+ // Here we ignore the case where whitespace appears at the end of
+ // the text.
+ if (pushbackToken && token && token.tokenType === "whitespace" &&
+ /\n/g.test(text.substring(token.startOffset, token.endOffset))) {
+ return originalText;
+ }
+
+ // Finally time for that newline.
+ result = result + prettifyCSS.LINE_SEPARATOR;
+
+ // Maybe we hit EOF.
+ if (!pushbackToken) {
+ break;
+ }
+ }
+
+ return result;
+}
+
+exports.prettifyCSS = prettifyCSS;
diff --git a/devtools/shared/inspector/moz.build b/devtools/shared/inspector/moz.build
new file mode 100644
index 000000000..e2c143fc3
--- /dev/null
+++ b/devtools/shared/inspector/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'css-logic.js'
+)
diff --git a/devtools/shared/jar.mn b/devtools/shared/jar.mn
new file mode 100644
index 000000000..4d0823550
--- /dev/null
+++ b/devtools/shared/jar.mn
@@ -0,0 +1,10 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+devtools.jar:
+% resource devtools %modules/devtools/
+# The typical approach would be to list all the resource files in this manifest
+# for installation. Instead of doing this, use the DevToolsModules syntax via
+# moz.build files to do the installation so that we can enforce correct paths
+# based on source tree location.
diff --git a/devtools/shared/jsbeautify/UPGRADING.md b/devtools/shared/jsbeautify/UPGRADING.md
new file mode 100644
index 000000000..dc6e0fe43
--- /dev/null
+++ b/devtools/shared/jsbeautify/UPGRADING.md
@@ -0,0 +1,37 @@
+# UPGRADING
+
+1. `git clone https://github.com/beautify-web/js-beautify.git`
+
+2. Copy `js/lib/beautify.js` to `devtools/shared/jsbeautify/src/beautify-js.js`
+
+3. Remove the acorn section from the file and add the following to the top:
+
+ ```
+ const acorn = require("acorn/acorn");
+ ```
+
+4. Just above `function Beautifier(js_source_text, options) {` add:
+
+ ```
+ exports.jsBeautify = js_beautify;
+ ```
+
+5. Copy `beautify-html.js` to `devtools/shared/jsbeautify/src/beautify-html.js`
+
+6. Replace the require blocks at the bottom of the file with:
+
+ ```
+ var beautify = require('devtools/shared/jsbeautify/beautify');
+
+ exports.htmlBeautify = function(html_source, options) {
+ return style_html(html_source, options, beautify.js, beautify.css);
+ };
+ ```
+
+7. Copy `beautify-css.js` to `devtools/shared/jsbeautify/src/beautify-css.js`
+
+8. Replace the global define block at the bottom of the file with:
+ ```
+ exports.cssBeautify = css_beautify;
+ ```
+9. Copy `js/test/beautify-tests.js` to `devtools/shared/jsbeautify/src/beautify-tests.js`
diff --git a/devtools/shared/jsbeautify/beautify.js b/devtools/shared/jsbeautify/beautify.js
new file mode 100644
index 000000000..df2e31eff
--- /dev/null
+++ b/devtools/shared/jsbeautify/beautify.js
@@ -0,0 +1,7 @@
+var { cssBeautify } = require("devtools/shared/jsbeautify/src/beautify-css");
+var { htmlBeautify } = require("devtools/shared/jsbeautify/src/beautify-html");
+var { jsBeautify } = require("devtools/shared/jsbeautify/src/beautify-js");
+
+exports.css = cssBeautify;
+exports.html = htmlBeautify;
+exports.js = jsBeautify;
diff --git a/devtools/shared/jsbeautify/lib/moz.build b/devtools/shared/jsbeautify/lib/moz.build
new file mode 100644
index 000000000..8fc89a102
--- /dev/null
+++ b/devtools/shared/jsbeautify/lib/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'sanitytest.js',
+ 'urlencode_unpacker.js',
+)
diff --git a/devtools/shared/jsbeautify/lib/sanitytest.js b/devtools/shared/jsbeautify/lib/sanitytest.js
new file mode 100644
index 000000000..f072a74b1
--- /dev/null
+++ b/devtools/shared/jsbeautify/lib/sanitytest.js
@@ -0,0 +1,137 @@
+//
+// simple testing interface
+// written by Einar Lielmanis, einar@jsbeautifier.org
+//
+// usage:
+//
+// var t = new SanityTest(function (x) { return x; }, 'my function');
+// t.expect('input', 'output');
+// t.expect('a', 'a');
+// output_somewhere(t.results()); // good for <pre>, html safe-ish
+// alert(t.results_raw()); // html unescaped
+
+
+function SanityTest (func, name_of_test) {
+
+ var test_func = func || function (x) {
+ return x;
+ };
+
+ var test_name = name_of_test || '';
+
+ var n_failed = 0;
+ var n_succeeded = 0;
+
+ this.failures = [];
+ this.successes = [];
+
+ this.test_function = function(func, name) {
+ test_func = func;
+ test_name = name || '';
+ };
+
+ this.get_exitcode = function() {
+ return n_succeeded === 0 || n_failed !== 0 ? 1 : 0;
+ };
+
+ this.expect = function(parameters, expected_value) {
+ // multi-parameter calls not supported (I don't need them now).
+ var result = test_func(parameters);
+ // proper array checking is a pain. i'll maybe do it later, compare strings representations instead
+ if ((result === expected_value) || (expected_value instanceof Array && result.join(', ') == expected_value.join(', '))) {
+ n_succeeded += 1;
+ this.successes.push([test_name, parameters, expected_value, result]);
+ } else {
+ n_failed += 1;
+ this.failures.push([test_name, parameters, expected_value, result]);
+ }
+ };
+
+
+ this.results_raw = function() {
+ var results = '';
+ if (n_failed === 0) {
+ if (n_succeeded === 0) {
+ results = 'No tests run.';
+ } else {
+ results = 'All ' + n_succeeded + ' tests passed.';
+ }
+ } else {
+ for (var i = 0 ; i < this.failures.length; i++) {
+ var f = this.failures[i];
+ if (f[0]) {
+ f[0] = f[0] + ' ';
+ }
+ results += '---- ' + f[0] + 'input -------\n' + this.prettyprint(f[1]) + '\n';
+ results += '---- ' + f[0] + 'expected ----\n' + this.prettyprint(f[2]) + '\n';
+ results += '---- ' + f[0] + 'output ------\n' + this.prettyprint(f[3]) + '\n\n';
+
+ }
+ results += n_failed + ' tests failed.\n';
+ }
+ return results;
+ };
+
+
+ this.results = function() {
+ return this.lazy_escape(this.results_raw());
+ };
+
+
+ this.prettyprint = function(something, quote_strings) {
+ var type = typeof something;
+ switch(type.toLowerCase()) {
+ case 'string':
+ if (quote_strings) {
+ return "'" + something.replace("'", "\\'") + "'";
+ } else {
+ return something;
+ }
+ case 'number':
+ return '' + something;
+ case 'boolean':
+ return something ? 'true' : 'false';
+ case 'undefined':
+ return 'undefined';
+ case 'object':
+ if (something instanceof Array) {
+ var x = [];
+ var expected_index = 0;
+ for (var k in something) {
+ if (k == expected_index) {
+ x.push(this.prettyprint(something[k], true));
+ expected_index += 1;
+ } else {
+ x.push('\n' + k + ': ' + this.prettyprint(something[k], true));
+ }
+ }
+ return '[' + x.join(', ') + ']';
+ } else {
+ return 'object: ' + something;
+ }
+ default:
+ return type + ': ' + something;
+ }
+ };
+
+
+ this.lazy_escape = function (str) {
+ return str.replace(/</g, '&lt;').replace(/\>/g, '&gt;').replace(/\n/g, '<br />');
+ };
+
+
+ this.log = function () {
+ if (window.console) {
+ if (console.firebug) {
+ console.log.apply(console, Array.prototype.slice.call(arguments));
+ } else {
+ console.log.call(console, Array.prototype.slice.call(arguments));
+ }
+ }
+ };
+
+}
+
+if (typeof module !== 'undefined' && module.exports) {
+ module.exports = SanityTest;
+}
diff --git a/devtools/shared/jsbeautify/lib/urlencode_unpacker.js b/devtools/shared/jsbeautify/lib/urlencode_unpacker.js
new file mode 100644
index 000000000..b45cd15c0
--- /dev/null
+++ b/devtools/shared/jsbeautify/lib/urlencode_unpacker.js
@@ -0,0 +1,73 @@
+/*global unescape */
+/*jshint curly: false, scripturl: true */
+//
+// trivial bookmarklet/escaped script detector for the javascript beautifier
+// written by Einar Lielmanis <einar@jsbeautifier.org>
+//
+// usage:
+//
+// if (Urlencoded.detect(some_string)) {
+// var unpacked = Urlencoded.unpack(some_string);
+// }
+//
+//
+
+var isNode = (typeof module !== 'undefined' && module.exports);
+if (isNode) {
+ var SanityTest = require("devtools/shared/jsbeautify/lib/sanitytest");
+}
+
+var Urlencoded = {
+ detect: function (str) {
+ // the fact that script doesn't contain any space, but has %20 instead
+ // should be sufficient check for now.
+ if (str.indexOf(' ') == -1) {
+ if (str.indexOf('%2') != -1) return true;
+ if (str.replace(/[^%]+/g, '').length > 3) return true;
+ }
+ return false;
+ },
+
+ unpack: function (str) {
+ if (Urlencoded.detect(str)) {
+ if (str.indexOf('%2B') != -1 || str.indexOf('%2b') != -1) {
+ // "+" escaped as "%2B"
+ return unescape(str.replace(/\+/g, '%20'));
+ } else {
+ return unescape(str);
+ }
+ }
+ return str;
+ },
+
+
+
+ run_tests: function (sanity_test) {
+ var t = sanity_test || new SanityTest();
+ t.test_function(Urlencoded.detect, "Urlencoded.detect");
+ t.expect('', false);
+ t.expect('var a = b', false);
+ t.expect('var%20a+=+b', true);
+ t.expect('var%20a=b', true);
+ t.expect('var%20%21%22', true);
+ t.expect('javascript:(function(){var%20whatever={init:function(){alert(%22a%22+%22b%22)}};whatever.init()})();', true);
+ t.test_function(Urlencoded.unpack, 'Urlencoded.unpack');
+
+ t.expect('javascript:(function(){var%20whatever={init:function(){alert(%22a%22+%22b%22)}};whatever.init()})();',
+ 'javascript:(function(){var whatever={init:function(){alert("a"+"b")}};whatever.init()})();'
+ );
+ t.expect('', '');
+ t.expect('abcd', 'abcd');
+ t.expect('var a = b', 'var a = b');
+ t.expect('var%20a=b', 'var a=b');
+ t.expect('var%20a=b+1', 'var a=b+1');
+ t.expect('var%20a=b%2b1', 'var a=b+1');
+ return t;
+ }
+
+
+};
+
+if (isNode) {
+ module.exports = Urlencoded;
+}
diff --git a/devtools/shared/jsbeautify/moz.build b/devtools/shared/jsbeautify/moz.build
new file mode 100644
index 000000000..939ca8943
--- /dev/null
+++ b/devtools/shared/jsbeautify/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'lib',
+ 'src',
+]
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'beautify.js',
+)
diff --git a/devtools/shared/jsbeautify/src/beautify-css.js b/devtools/shared/jsbeautify/src/beautify-css.js
new file mode 100644
index 000000000..faf9ac0f2
--- /dev/null
+++ b/devtools/shared/jsbeautify/src/beautify-css.js
@@ -0,0 +1,367 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+/*
+
+ The MIT License (MIT)
+
+ Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+
+ CSS Beautifier
+---------------
+
+ Written by Harutyun Amirjanyan, (amirjanyan@gmail.com)
+
+ Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
+ http://jsbeautifier.org/
+
+ Usage:
+ css_beautify(source_text);
+ css_beautify(source_text, options);
+
+ The options are (default in brackets):
+ indent_size (4) — indentation size,
+ indent_char (space) — character to indent with,
+ selector_separator_newline (true) - separate selectors with newline or
+ not (e.g. "a,\nbr" or "a, br")
+ end_with_newline (false) - end with a newline
+
+ e.g
+
+ css_beautify(css_source_text, {
+ 'indent_size': 1,
+ 'indent_char': '\t',
+ 'selector_separator': ' ',
+ 'end_with_newline': false,
+ });
+*/
+
+// http://www.w3.org/TR/CSS21/syndata.html#tokenization
+// http://www.w3.org/TR/css3-syntax/
+
+(function () {
+ function css_beautify(source_text, options) {
+ options = options || {};
+ var indentSize = options.indent_size || 4;
+ var indentCharacter = options.indent_char || ' ';
+ var selectorSeparatorNewline = (options.selector_separator_newline === undefined) ? true : options.selector_separator_newline;
+ var endWithNewline = (options.end_with_newline === undefined) ? false : options.end_with_newline;
+
+ // compatibility
+ if (typeof indentSize === "string") {
+ indentSize = parseInt(indentSize, 10);
+ }
+
+
+ // tokenizer
+ var whiteRe = /^\s+$/;
+ var wordRe = /[\w$\-_]/;
+
+ var pos = -1,
+ ch;
+
+ function next() {
+ ch = source_text.charAt(++pos);
+ return ch;
+ }
+
+ function peek() {
+ return source_text.charAt(pos + 1);
+ }
+
+ function eatString(endChar) {
+ var start = pos;
+ while (next()) {
+ if (ch === "\\") {
+ next();
+ next();
+ } else if (ch === endChar) {
+ break;
+ } else if (ch === "\n") {
+ break;
+ }
+ }
+ return source_text.substring(start, pos + 1);
+ }
+
+ function eatWhitespace() {
+ var start = pos;
+ while (whiteRe.test(peek())) {
+ pos++;
+ }
+ return pos !== start;
+ }
+
+ function skipWhitespace() {
+ var start = pos;
+ do {} while (whiteRe.test(next()));
+ return pos !== start + 1;
+ }
+
+ function eatComment(singleLine) {
+ var start = pos;
+ next();
+ while (next()) {
+ if (ch === "*" && peek() === "/") {
+ pos++;
+ break;
+ } else if (singleLine && ch === "\n") {
+ break;
+ }
+ }
+
+ return source_text.substring(start, pos + 1);
+ }
+
+
+ function lookBack(str) {
+ return source_text.substring(pos - str.length, pos).toLowerCase() ===
+ str;
+ }
+
+ function isCommentOnLine() {
+ var endOfLine = source_text.indexOf('\n', pos);
+ if (endOfLine === -1) {
+ return false;
+ }
+ var restOfLine = source_text.substring(pos, endOfLine);
+ return restOfLine.indexOf('//') !== -1;
+ }
+
+ // printer
+ var indentString = source_text.match(/^[\r\n]*[\t ]*/)[0];
+ var singleIndent = new Array(indentSize + 1).join(indentCharacter);
+ var indentLevel = 0;
+ var nestedLevel = 0;
+
+ function indent() {
+ indentLevel++;
+ indentString += singleIndent;
+ }
+
+ function outdent() {
+ indentLevel--;
+ indentString = indentString.slice(0, -indentSize);
+ }
+
+ var print = {};
+ print["{"] = function (ch) {
+ print.singleSpace();
+ output.push(ch);
+ print.newLine();
+ };
+ print["}"] = function (ch) {
+ print.newLine();
+ output.push(ch);
+ print.newLine();
+ };
+
+ print._lastCharWhitespace = function () {
+ return whiteRe.test(output[output.length - 1]);
+ };
+
+ print.newLine = function (keepWhitespace) {
+ if (!keepWhitespace) {
+ while (print._lastCharWhitespace()) {
+ output.pop();
+ }
+ }
+
+ if (output.length) {
+ output.push('\n');
+ }
+ if (indentString) {
+ output.push(indentString);
+ }
+ };
+ print.singleSpace = function () {
+ if (output.length && !print._lastCharWhitespace()) {
+ output.push(' ');
+ }
+ };
+ var output = [];
+ if (indentString) {
+ output.push(indentString);
+ }
+ /*_____________________--------------------_____________________*/
+
+ var insideRule = false;
+ var enteringConditionalGroup = false;
+
+ while (true) {
+ var isAfterSpace = skipWhitespace();
+
+ if (!ch) {
+ break;
+ } else if (ch === '/' && peek() === '*') { /* css comment */
+ print.newLine();
+ output.push(eatComment(), "\n", indentString);
+ var header = lookBack("");
+ if (header) {
+ print.newLine();
+ }
+ } else if (ch === '/' && peek() === '/') { // single line comment
+ output.push(eatComment(true), indentString);
+ } else if (ch === '@') {
+ // strip trailing space, if present, for hash property checks
+ var atRule = eatString(" ").replace(/ $/, '');
+
+ // pass along the space we found as a separate item
+ output.push(atRule, ch);
+
+ // might be a nesting at-rule
+ if (atRule in css_beautify.NESTED_AT_RULE) {
+ nestedLevel += 1;
+ if (atRule in css_beautify.CONDITIONAL_GROUP_RULE) {
+ enteringConditionalGroup = true;
+ }
+ }
+ } else if (ch === '{') {
+ eatWhitespace();
+ if (peek() === '}') {
+ next();
+ output.push(" {}");
+ } else {
+ indent();
+ print["{"](ch);
+ // when entering conditional groups, only rulesets are allowed
+ if (enteringConditionalGroup) {
+ enteringConditionalGroup = false;
+ insideRule = (indentLevel > nestedLevel);
+ } else {
+ // otherwise, declarations are also allowed
+ insideRule = (indentLevel >= nestedLevel);
+ }
+ }
+ } else if (ch === '}') {
+ outdent();
+ print["}"](ch);
+ insideRule = false;
+ if (nestedLevel) {
+ nestedLevel--;
+ }
+ } else if (ch === ":") {
+ eatWhitespace();
+ if (insideRule || enteringConditionalGroup) {
+ // 'property: value' delimiter
+ // which could be in a conditional group query
+ output.push(ch, " ");
+ } else {
+ if (peek() === ":") {
+ // pseudo-element
+ next();
+ output.push("::");
+ } else {
+ // pseudo-class
+ output.push(ch);
+ }
+ }
+ } else if (ch === '"' || ch === '\'') {
+ output.push(eatString(ch));
+ } else if (ch === ';') {
+ if (isCommentOnLine()) {
+ var beforeComment = eatString('/');
+ var comment = eatComment(true);
+ output.push(beforeComment, comment.substring(1, comment.length - 1), '\n', indentString);
+ } else {
+ output.push(ch, '\n', indentString);
+ }
+ } else if (ch === '(') { // may be a url
+ if (lookBack("url")) {
+ output.push(ch);
+ eatWhitespace();
+ if (next()) {
+ if (ch !== ')' && ch !== '"' && ch !== '\'') {
+ output.push(eatString(')'));
+ } else {
+ pos--;
+ }
+ }
+ } else {
+ if (isAfterSpace) {
+ print.singleSpace();
+ }
+ output.push(ch);
+ eatWhitespace();
+ }
+ } else if (ch === ')') {
+ output.push(ch);
+ } else if (ch === ',') {
+ eatWhitespace();
+ output.push(ch);
+ if (!insideRule && selectorSeparatorNewline) {
+ print.newLine();
+ } else {
+ print.singleSpace();
+ }
+ } else if (ch === ']') {
+ output.push(ch);
+ } else if (ch === '[') {
+ if (isAfterSpace) {
+ print.singleSpace();
+ }
+ output.push(ch);
+ } else if (ch === '=') { // no whitespace before or after
+ eatWhitespace();
+ output.push(ch);
+ } else {
+ if (isAfterSpace) {
+ print.singleSpace();
+ }
+
+ output.push(ch);
+ }
+ }
+
+
+ var sweetCode = output.join('').replace(/[\n ]+$/, '');
+
+ // establish end_with_newline
+ var should = endWithNewline;
+ var actually = /\n$/.test(sweetCode);
+ if (should && !actually) {
+ sweetCode += "\n";
+ } else if (!should && actually) {
+ sweetCode = sweetCode.slice(0, -1);
+ }
+
+ return sweetCode;
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/CSS/At-rule
+ css_beautify.NESTED_AT_RULE = {
+ "@page": true,
+ "@font-face": true,
+ "@keyframes": true,
+ // also in CONDITIONAL_GROUP_RULE below
+ "@media": true,
+ "@supports": true,
+ "@document": true
+ };
+ css_beautify.CONDITIONAL_GROUP_RULE = {
+ "@media": true,
+ "@supports": true,
+ "@document": true
+ };
+
+ exports.cssBeautify = css_beautify;
+}());
diff --git a/devtools/shared/jsbeautify/src/beautify-html.js b/devtools/shared/jsbeautify/src/beautify-html.js
new file mode 100644
index 000000000..4967c3e3d
--- /dev/null
+++ b/devtools/shared/jsbeautify/src/beautify-html.js
@@ -0,0 +1,822 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+/*
+
+ The MIT License (MIT)
+
+ Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+
+ Style HTML
+---------------
+
+ Written by Nochum Sossonko, (nsossonko@hotmail.com)
+
+ Based on code initially developed by: Einar Lielmanis, <einar@jsbeautifier.org>
+ http://jsbeautifier.org/
+
+ Usage:
+ style_html(html_source);
+
+ style_html(html_source, options);
+
+ The options are:
+ indent_inner_html (default false) — indent <head> and <body> sections,
+ indent_size (default 4) — indentation size,
+ indent_char (default space) — character to indent with,
+ wrap_line_length (default 250) - maximum amount of characters per line (0 = disable)
+ brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
+ 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.
+ unformatted (defaults to inline tags) - list of tags, that shouldn't be reformatted
+ indent_scripts (default normal) - "keep"|"separate"|"normal"
+ preserve_newlines (default true) - whether existing line breaks before elements should be preserved
+ Only works before elements, not inside tags or for text.
+ max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk
+ indent_handlebars (default false) - format and indent {{#foo}} and {{/foo}}
+
+ e.g.
+
+ style_html(html_source, {
+ 'indent_inner_html': false,
+ 'indent_size': 2,
+ 'indent_char': ' ',
+ 'wrap_line_length': 78,
+ 'brace_style': 'expand',
+ 'unformatted': ['a', 'sub', 'sup', 'b', 'i', 'u'],
+ 'preserve_newlines': true,
+ 'max_preserve_newlines': 5,
+ 'indent_handlebars': false
+ });
+*/
+
+(function() {
+
+ function trim(s) {
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+
+ function ltrim(s) {
+ return s.replace(/^\s+/g, '');
+ }
+
+ function style_html(html_source, options, js_beautify, css_beautify) {
+ //Wrapper function to invoke all the necessary constructors and deal with the output.
+
+ var multi_parser,
+ indent_inner_html,
+ indent_size,
+ indent_character,
+ wrap_line_length,
+ brace_style,
+ unformatted,
+ preserve_newlines,
+ max_preserve_newlines,
+ indent_handlebars;
+
+ options = options || {};
+
+ // backwards compatibility to 1.3.4
+ if ((options.wrap_line_length === undefined || parseInt(options.wrap_line_length, 10) === 0) &&
+ (options.max_char !== undefined && parseInt(options.max_char, 10) !== 0)) {
+ options.wrap_line_length = options.max_char;
+ }
+
+ indent_inner_html = (options.indent_inner_html === undefined) ? false : options.indent_inner_html;
+ indent_size = (options.indent_size === undefined) ? 4 : parseInt(options.indent_size, 10);
+ indent_character = (options.indent_char === undefined) ? ' ' : options.indent_char;
+ brace_style = (options.brace_style === undefined) ? 'collapse' : options.brace_style;
+ wrap_line_length = parseInt(options.wrap_line_length, 10) === 0 ? 32786 : parseInt(options.wrap_line_length || 250, 10);
+ unformatted = options.unformatted || ['a', 'span', 'bdo', 'em', 'strong', 'dfn', 'code', 'samp', 'kbd', 'var', 'cite', 'abbr', 'acronym', 'q', 'sub', 'sup', 'tt', 'i', 'b', 'big', 'small', 'u', 's', 'strike', 'font', 'ins', 'del', 'pre', 'address', 'dt', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'];
+ preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
+ max_preserve_newlines = preserve_newlines ?
+ (isNaN(parseInt(options.max_preserve_newlines, 10)) ? 32786 : parseInt(options.max_preserve_newlines, 10))
+ : 0;
+ indent_handlebars = (options.indent_handlebars === undefined) ? false : options.indent_handlebars;
+
+ function Parser() {
+
+ this.pos = 0; //Parser position
+ this.token = '';
+ this.current_mode = 'CONTENT'; //reflects the current Parser mode: TAG/CONTENT
+ this.tags = { //An object to hold tags, their position, and their parent-tags, initiated with default values
+ parent: 'parent1',
+ parentcount: 1,
+ parent1: ''
+ };
+ this.tag_type = '';
+ this.token_text = this.last_token = this.last_text = this.token_type = '';
+ this.newlines = 0;
+ this.indent_content = indent_inner_html;
+
+ this.Utils = { //Uilities made available to the various functions
+ whitespace: "\n\r\t ".split(''),
+ single_token: 'br,input,link,meta,!doctype,basefont,base,area,hr,wbr,param,img,isindex,?xml,embed,?php,?,?='.split(','), //all the single tags for HTML
+ extra_liners: 'head,body,/html'.split(','), //for tags that need a line of whitespace before them
+ in_array: function(what, arr) {
+ for (var i = 0; i < arr.length; i++) {
+ if (what === arr[i]) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+
+ this.traverse_whitespace = function() {
+ var input_char = '';
+
+ input_char = this.input.charAt(this.pos);
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ this.newlines = 0;
+ while (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ if (preserve_newlines && input_char === '\n' && this.newlines <= max_preserve_newlines) {
+ this.newlines += 1;
+ }
+
+ this.pos++;
+ input_char = this.input.charAt(this.pos);
+ }
+ return true;
+ }
+ return false;
+ };
+
+ this.get_content = function() { //function to capture regular content between tags
+
+ var input_char = '',
+ content = [],
+ space = false; //if a space is needed
+
+ while (this.input.charAt(this.pos) !== '<') {
+ if (this.pos >= this.input.length) {
+ return content.length ? content.join('') : ['', 'TK_EOF'];
+ }
+
+ if (this.traverse_whitespace()) {
+ if (content.length) {
+ space = true;
+ }
+ continue; //don't want to insert unnecessary space
+ }
+
+ if (indent_handlebars) {
+ // Handlebars parsing is complicated.
+ // {{#foo}} and {{/foo}} are formatted tags.
+ // {{something}} should get treated as content, except:
+ // {{else}} specifically behaves like {{#if}} and {{/if}}
+ var peek3 = this.input.substr(this.pos, 3);
+ if (peek3 === '{{#' || peek3 === '{{/') {
+ // These are tags and not content.
+ break;
+ } else if (this.input.substr(this.pos, 2) === '{{') {
+ if (this.get_tag(true) === '{{else}}') {
+ break;
+ }
+ }
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ if (space) {
+ if (this.line_char_count >= this.wrap_line_length) { //insert a line when the wrap_line_length is reached
+ this.print_newline(false, content);
+ this.print_indentation(content);
+ } else {
+ this.line_char_count++;
+ content.push(' ');
+ }
+ space = false;
+ }
+ this.line_char_count++;
+ content.push(input_char); //letter at-a-time (or string) inserted to an array
+ }
+ return content.length ? content.join('') : '';
+ };
+
+ this.get_contents_to = function(name) { //get the full content of a script or style to pass to js_beautify
+ if (this.pos === this.input.length) {
+ return ['', 'TK_EOF'];
+ }
+ var input_char = '';
+ var content = '';
+ var reg_match = new RegExp('</' + name + '\\s*>', 'igm');
+ reg_match.lastIndex = this.pos;
+ var reg_array = reg_match.exec(this.input);
+ var end_script = reg_array ? reg_array.index : this.input.length; //absolute end of script
+ if (this.pos < end_script) { //get everything in between the script tags
+ content = this.input.substring(this.pos, end_script);
+ this.pos = end_script;
+ }
+ return content;
+ };
+
+ this.record_tag = function(tag) { //function to record a tag and its parent in this.tags Object
+ if (this.tags[tag + 'count']) { //check for the existence of this tag type
+ this.tags[tag + 'count']++;
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+ } else { //otherwise initialize this tag type
+ this.tags[tag + 'count'] = 1;
+ this.tags[tag + this.tags[tag + 'count']] = this.indent_level; //and record the present indent level
+ }
+ this.tags[tag + this.tags[tag + 'count'] + 'parent'] = this.tags.parent; //set the parent (i.e. in the case of a div this.tags.div1parent)
+ this.tags.parent = tag + this.tags[tag + 'count']; //and make this the current parent (i.e. in the case of a div 'div1')
+ };
+
+ this.retrieve_tag = function(tag) { //function to retrieve the opening tag to the corresponding closer
+ if (this.tags[tag + 'count']) { //if the openener is not in the Object we ignore it
+ var temp_parent = this.tags.parent; //check to see if it's a closable tag.
+ while (temp_parent) { //till we reach '' (the initial value);
+ if (tag + this.tags[tag + 'count'] === temp_parent) { //if this is it use it
+ break;
+ }
+ temp_parent = this.tags[temp_parent + 'parent']; //otherwise keep on climbing up the DOM Tree
+ }
+ if (temp_parent) { //if we caught something
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']]; //set the indent_level accordingly
+ this.tags.parent = this.tags[temp_parent + 'parent']; //and set the current parent
+ }
+ delete this.tags[tag + this.tags[tag + 'count'] + 'parent']; //delete the closed tags parent reference...
+ delete this.tags[tag + this.tags[tag + 'count']]; //...and the tag itself
+ if (this.tags[tag + 'count'] === 1) {
+ delete this.tags[tag + 'count'];
+ } else {
+ this.tags[tag + 'count']--;
+ }
+ }
+ };
+
+ this.indent_to_tag = function(tag) {
+ // Match the indentation level to the last use of this tag, but don't remove it.
+ if (!this.tags[tag + 'count']) {
+ return;
+ }
+ var temp_parent = this.tags.parent;
+ while (temp_parent) {
+ if (tag + this.tags[tag + 'count'] === temp_parent) {
+ break;
+ }
+ temp_parent = this.tags[temp_parent + 'parent'];
+ }
+ if (temp_parent) {
+ this.indent_level = this.tags[tag + this.tags[tag + 'count']];
+ }
+ };
+
+ this.get_tag = function(peek) { //function to get a full tag and parse its type
+ var input_char = '',
+ content = [],
+ comment = '',
+ space = false,
+ tag_start, tag_end,
+ tag_start_char,
+ orig_pos = this.pos,
+ orig_line_char_count = this.line_char_count;
+
+ peek = peek !== undefined ? peek : false;
+
+ do {
+ if (this.pos >= this.input.length) {
+ if (peek) {
+ this.pos = orig_pos;
+ this.line_char_count = orig_line_char_count;
+ }
+ return content.length ? content.join('') : ['', 'TK_EOF'];
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) { //don't want to insert unnecessary space
+ space = true;
+ continue;
+ }
+
+ if (input_char === "'" || input_char === '"') {
+ input_char += this.get_unformatted(input_char);
+ space = true;
+
+ }
+
+ if (input_char === '=') { //no space before =
+ space = false;
+ }
+
+ if (content.length && content[content.length - 1] !== '=' && input_char !== '>' && space) {
+ //no space after = or before >
+ if (this.line_char_count >= this.wrap_line_length) {
+ this.print_newline(false, content);
+ this.print_indentation(content);
+ } else {
+ content.push(' ');
+ this.line_char_count++;
+ }
+ space = false;
+ }
+
+ if (indent_handlebars && tag_start_char === '<') {
+ // When inside an angle-bracket tag, put spaces around
+ // handlebars not inside of strings.
+ if ((input_char + this.input.charAt(this.pos)) === '{{') {
+ input_char += this.get_unformatted('}}');
+ if (content.length && content[content.length - 1] !== ' ' && content[content.length - 1] !== '<') {
+ input_char = ' ' + input_char;
+ }
+ space = true;
+ }
+ }
+
+ if (input_char === '<' && !tag_start_char) {
+ tag_start = this.pos - 1;
+ tag_start_char = '<';
+ }
+
+ if (indent_handlebars && !tag_start_char) {
+ if (content.length >= 2 && content[content.length - 1] === '{' && content[content.length - 2] == '{') {
+ if (input_char === '#' || input_char === '/') {
+ tag_start = this.pos - 3;
+ } else {
+ tag_start = this.pos - 2;
+ }
+ tag_start_char = '{';
+ }
+ }
+
+ this.line_char_count++;
+ content.push(input_char); //inserts character at-a-time (or string)
+
+ if (content[1] && content[1] === '!') { //if we're in a comment, do something special
+ // We treat all comments as literals, even more than preformatted tags
+ // we just look for the appropriate close tag
+ content = [this.get_comment(tag_start)];
+ break;
+ }
+
+ if (indent_handlebars && tag_start_char === '{' && content.length > 2 && content[content.length - 2] === '}' && content[content.length - 1] === '}') {
+ break;
+ }
+ } while (input_char !== '>');
+
+ var tag_complete = content.join('');
+ var tag_index;
+ var tag_offset;
+
+ if (tag_complete.indexOf(' ') !== -1) { //if there's whitespace, thats where the tag name ends
+ tag_index = tag_complete.indexOf(' ');
+ } else if (tag_complete[0] === '{') {
+ tag_index = tag_complete.indexOf('}');
+ } else { //otherwise go with the tag ending
+ tag_index = tag_complete.indexOf('>');
+ }
+ if (tag_complete[0] === '<' || !indent_handlebars) {
+ tag_offset = 1;
+ } else {
+ tag_offset = tag_complete[2] === '#' ? 3 : 2;
+ }
+ var tag_check = tag_complete.substring(tag_offset, tag_index).toLowerCase();
+ if (tag_complete.charAt(tag_complete.length - 2) === '/' ||
+ this.Utils.in_array(tag_check, this.Utils.single_token)) { //if this tag name is a single tag type (either in the list or has a closing /)
+ if (!peek) {
+ this.tag_type = 'SINGLE';
+ }
+ } else if (indent_handlebars && tag_complete[0] === '{' && tag_check === 'else') {
+ if (!peek) {
+ this.indent_to_tag('if');
+ this.tag_type = 'HANDLEBARS_ELSE';
+ this.indent_content = true;
+ this.traverse_whitespace();
+ }
+ } else if (tag_check === 'script' &&
+ (tag_complete.search('type') === -1 ||
+ (tag_complete.search('type') > -1 &&
+ tag_complete.search(/\b(text|application)\/(x-)?(javascript|ecmascript|jscript|livescript)/) > -1))) {
+ if (!peek) {
+ this.record_tag(tag_check);
+ this.tag_type = 'SCRIPT';
+ }
+ } else if (tag_check === 'style' &&
+ (tag_complete.search('type') === -1 ||
+ (tag_complete.search('type') > -1 && tag_complete.search('text/css') > -1))) {
+ if (!peek) {
+ this.record_tag(tag_check);
+ this.tag_type = 'STYLE';
+ }
+ } else if (this.is_unformatted(tag_check, unformatted)) { // do not reformat the "unformatted" tags
+ comment = this.get_unformatted('</' + tag_check + '>', tag_complete); //...delegate to get_unformatted function
+ content.push(comment);
+ // Preserve collapsed whitespace either before or after this tag.
+ if (tag_start > 0 && this.Utils.in_array(this.input.charAt(tag_start - 1), this.Utils.whitespace)) {
+ content.splice(0, 0, this.input.charAt(tag_start - 1));
+ }
+ tag_end = this.pos - 1;
+ if (this.Utils.in_array(this.input.charAt(tag_end + 1), this.Utils.whitespace)) {
+ content.push(this.input.charAt(tag_end + 1));
+ }
+ this.tag_type = 'SINGLE';
+ } else if (tag_check.charAt(0) === '!') { //peek for <! comment
+ // for comments content is already correct.
+ if (!peek) {
+ this.tag_type = 'SINGLE';
+ this.traverse_whitespace();
+ }
+ } else if (!peek) {
+ if (tag_check.charAt(0) === '/') { //this tag is a double tag so check for tag-ending
+ this.retrieve_tag(tag_check.substring(1)); //remove it and all ancestors
+ this.tag_type = 'END';
+ this.traverse_whitespace();
+ } else { //otherwise it's a start-tag
+ this.record_tag(tag_check); //push it on the tag stack
+ if (tag_check.toLowerCase() !== 'html') {
+ this.indent_content = true;
+ }
+ this.tag_type = 'START';
+
+ // Allow preserving of newlines after a start tag
+ this.traverse_whitespace();
+ }
+ if (this.Utils.in_array(tag_check, this.Utils.extra_liners)) { //check if this double needs an extra line
+ this.print_newline(false, this.output);
+ if (this.output.length && this.output[this.output.length - 2] !== '\n') {
+ this.print_newline(true, this.output);
+ }
+ }
+ }
+
+ if (peek) {
+ this.pos = orig_pos;
+ this.line_char_count = orig_line_char_count;
+ }
+
+ return content.join(''); //returns fully formatted tag
+ };
+
+ this.get_comment = function(start_pos) { //function to return comment content in its entirety
+ // this is will have very poor perf, but will work for now.
+ var comment = '',
+ delimiter = '>',
+ matched = false;
+
+ this.pos = start_pos;
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ while (this.pos <= this.input.length) {
+ comment += input_char;
+
+ // only need to check for the delimiter if the last chars match
+ if (comment[comment.length - 1] === delimiter[delimiter.length - 1] &&
+ comment.indexOf(delimiter) !== -1) {
+ break;
+ }
+
+ // only need to search for custom delimiter for the first few characters
+ if (!matched && comment.length < 10) {
+ if (comment.indexOf('<![if') === 0) { //peek for <![if conditional comment
+ delimiter = '<![endif]>';
+ matched = true;
+ } else if (comment.indexOf('<![cdata[') === 0) { //if it's a <[cdata[ comment...
+ delimiter = ']]>';
+ matched = true;
+ } else if (comment.indexOf('<![') === 0) { // some other ![ comment? ...
+ delimiter = ']>';
+ matched = true;
+ } else if (comment.indexOf('<!--') === 0) { // <!-- comment ...
+ delimiter = '-->';
+ matched = true;
+ }
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+ }
+
+ return comment;
+ };
+
+ this.get_unformatted = function(delimiter, orig_tag) { //function to return unformatted content in its entirety
+
+ if (orig_tag && orig_tag.toLowerCase().indexOf(delimiter) !== -1) {
+ return '';
+ }
+ var input_char = '';
+ var content = '';
+ var min_index = 0;
+ var space = true;
+ do {
+
+ if (this.pos >= this.input.length) {
+ return content;
+ }
+
+ input_char = this.input.charAt(this.pos);
+ this.pos++;
+
+ if (this.Utils.in_array(input_char, this.Utils.whitespace)) {
+ if (!space) {
+ this.line_char_count--;
+ continue;
+ }
+ if (input_char === '\n' || input_char === '\r') {
+ content += '\n';
+ /* Don't change tab indention for unformatted blocks. If using code for html editing, this will greatly affect <pre> tags if they are specified in the 'unformatted array'
+ for (var i=0; i<this.indent_level; i++) {
+ content += this.indent_string;
+ }
+ space = false; //...and make sure other indentation is erased
+ */
+ this.line_char_count = 0;
+ continue;
+ }
+ }
+ content += input_char;
+ this.line_char_count++;
+ space = true;
+
+ if (indent_handlebars && input_char === '{' && content.length && content[content.length - 2] === '{') {
+ // Handlebars expressions in strings should also be unformatted.
+ content += this.get_unformatted('}}');
+ // These expressions are opaque. Ignore delimiters found in them.
+ min_index = content.length;
+ }
+ } while (content.toLowerCase().indexOf(delimiter, min_index) === -1);
+ return content;
+ };
+
+ this.get_token = function() { //initial handler for token-retrieval
+ var token;
+
+ if (this.last_token === 'TK_TAG_SCRIPT' || this.last_token === 'TK_TAG_STYLE') { //check if we need to format javascript
+ var type = this.last_token.substr(7);
+ token = this.get_contents_to(type);
+ if (typeof token !== 'string') {
+ return token;
+ }
+ return [token, 'TK_' + type];
+ }
+ if (this.current_mode === 'CONTENT') {
+ token = this.get_content();
+ if (typeof token !== 'string') {
+ return token;
+ } else {
+ return [token, 'TK_CONTENT'];
+ }
+ }
+
+ if (this.current_mode === 'TAG') {
+ token = this.get_tag();
+ if (typeof token !== 'string') {
+ return token;
+ } else {
+ var tag_name_type = 'TK_TAG_' + this.tag_type;
+ return [token, tag_name_type];
+ }
+ }
+ };
+
+ this.get_full_indent = function(level) {
+ level = this.indent_level + level || 0;
+ if (level < 1) {
+ return '';
+ }
+
+ return Array(level + 1).join(this.indent_string);
+ };
+
+ this.is_unformatted = function(tag_check, unformatted) {
+ //is this an HTML5 block-level link?
+ if (!this.Utils.in_array(tag_check, unformatted)) {
+ return false;
+ }
+
+ if (tag_check.toLowerCase() !== 'a' || !this.Utils.in_array('a', unformatted)) {
+ return true;
+ }
+
+ //at this point we have an tag; is its first child something we want to remain
+ //unformatted?
+ var next_tag = this.get_tag(true /* peek. */ );
+
+ // test next_tag to see if it is just html tag (no external content)
+ var tag = (next_tag || "").match(/^\s*<\s*\/?([a-z]*)\s*[^>]*>\s*$/);
+
+ // if next_tag comes back but is not an isolated tag, then
+ // let's treat the 'a' tag as having content
+ // and respect the unformatted option
+ if (!tag || this.Utils.in_array(tag, unformatted)) {
+ return true;
+ } else {
+ return false;
+ }
+ };
+
+ this.printer = function(js_source, indent_character, indent_size, wrap_line_length, brace_style) { //handles input/output and some other printing functions
+
+ this.input = js_source || ''; //gets the input for the Parser
+ this.output = [];
+ this.indent_character = indent_character;
+ this.indent_string = '';
+ this.indent_size = indent_size;
+ this.brace_style = brace_style;
+ this.indent_level = 0;
+ this.wrap_line_length = wrap_line_length;
+ this.line_char_count = 0; //count to see if wrap_line_length was exceeded
+
+ for (var i = 0; i < this.indent_size; i++) {
+ this.indent_string += this.indent_character;
+ }
+
+ this.print_newline = function(force, arr) {
+ this.line_char_count = 0;
+ if (!arr || !arr.length) {
+ return;
+ }
+ if (force || (arr[arr.length - 1] !== '\n')) { //we might want the extra line
+ arr.push('\n');
+ }
+ };
+
+ this.print_indentation = function(arr) {
+ for (var i = 0; i < this.indent_level; i++) {
+ arr.push(this.indent_string);
+ this.line_char_count += this.indent_string.length;
+ }
+ };
+
+ this.print_token = function(text) {
+ if (text || text !== '') {
+ if (this.output.length && this.output[this.output.length - 1] === '\n') {
+ this.print_indentation(this.output);
+ text = ltrim(text);
+ }
+ }
+ this.print_token_raw(text);
+ };
+
+ this.print_token_raw = function(text) {
+ if (text && text !== '') {
+ if (text.length > 1 && text[text.length - 1] === '\n') {
+ // unformatted tags can grab newlines as their last character
+ this.output.push(text.slice(0, -1));
+ this.print_newline(false, this.output);
+ } else {
+ this.output.push(text);
+ }
+ }
+
+ for (var n = 0; n < this.newlines; n++) {
+ this.print_newline(n > 0, this.output);
+ }
+ this.newlines = 0;
+ };
+
+ this.indent = function() {
+ this.indent_level++;
+ };
+
+ this.unindent = function() {
+ if (this.indent_level > 0) {
+ this.indent_level--;
+ }
+ };
+ };
+ return this;
+ }
+
+ /*_____________________--------------------_____________________*/
+
+ multi_parser = new Parser(); //wrapping functions Parser
+ multi_parser.printer(html_source, indent_character, indent_size, wrap_line_length, brace_style); //initialize starting values
+
+ while (true) {
+ var t = multi_parser.get_token();
+ multi_parser.token_text = t[0];
+ multi_parser.token_type = t[1];
+
+ if (multi_parser.token_type === 'TK_EOF') {
+ break;
+ }
+
+ switch (multi_parser.token_type) {
+ case 'TK_TAG_START':
+ multi_parser.print_newline(false, multi_parser.output);
+ multi_parser.print_token(multi_parser.token_text);
+ if (multi_parser.indent_content) {
+ multi_parser.indent();
+ multi_parser.indent_content = false;
+ }
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_STYLE':
+ case 'TK_TAG_SCRIPT':
+ multi_parser.print_newline(false, multi_parser.output);
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_END':
+ //Print new line only if the tag has no content and has child
+ if (multi_parser.last_token === 'TK_CONTENT' && multi_parser.last_text === '') {
+ var tag_name = multi_parser.token_text.match(/\w+/)[0];
+ var tag_extracted_from_last_output = null;
+ if (multi_parser.output.length) {
+ tag_extracted_from_last_output = multi_parser.output[multi_parser.output.length - 1].match(/(?:<|{{#)\s*(\w+)/);
+ }
+ if (tag_extracted_from_last_output === null ||
+ tag_extracted_from_last_output[1] !== tag_name) {
+ multi_parser.print_newline(false, multi_parser.output);
+ }
+ }
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_SINGLE':
+ // Don't add a newline before elements that should remain unformatted.
+ var tag_check = multi_parser.token_text.match(/^\s*<([a-z]+)/i);
+ if (!tag_check || !multi_parser.Utils.in_array(tag_check[1], unformatted)) {
+ multi_parser.print_newline(false, multi_parser.output);
+ }
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_TAG_HANDLEBARS_ELSE':
+ multi_parser.print_token(multi_parser.token_text);
+ if (multi_parser.indent_content) {
+ multi_parser.indent();
+ multi_parser.indent_content = false;
+ }
+ multi_parser.current_mode = 'CONTENT';
+ break;
+ case 'TK_CONTENT':
+ multi_parser.print_token(multi_parser.token_text);
+ multi_parser.current_mode = 'TAG';
+ break;
+ case 'TK_STYLE':
+ case 'TK_SCRIPT':
+ if (multi_parser.token_text !== '') {
+ multi_parser.print_newline(false, multi_parser.output);
+ var text = multi_parser.token_text,
+ _beautifier,
+ script_indent_level = 1;
+ if (multi_parser.token_type === 'TK_SCRIPT') {
+ _beautifier = typeof js_beautify === 'function' && js_beautify;
+ } else if (multi_parser.token_type === 'TK_STYLE') {
+ _beautifier = typeof css_beautify === 'function' && css_beautify;
+ }
+
+ if (options.indent_scripts === "keep") {
+ script_indent_level = 0;
+ } else if (options.indent_scripts === "separate") {
+ script_indent_level = -multi_parser.indent_level;
+ }
+
+ var indentation = multi_parser.get_full_indent(script_indent_level);
+ if (_beautifier) {
+ // call the Beautifier if avaliable
+ text = _beautifier(text.replace(/^\s*/, indentation), options);
+ } else {
+ // simply indent the string otherwise
+ var white = text.match(/^\s*/)[0];
+ var _level = white.match(/[^\n\r]*$/)[0].split(multi_parser.indent_string).length - 1;
+ var reindent = multi_parser.get_full_indent(script_indent_level - _level);
+ text = text.replace(/^\s*/, indentation)
+ .replace(/\r\n|\r|\n/g, '\n' + reindent)
+ .replace(/\s+$/, '');
+ }
+ if (text) {
+ multi_parser.print_token_raw(indentation + trim(text));
+ multi_parser.print_newline(false, multi_parser.output);
+ }
+ }
+ multi_parser.current_mode = 'TAG';
+ break;
+ }
+ multi_parser.last_token = multi_parser.token_type;
+ multi_parser.last_text = multi_parser.token_text;
+ }
+ return multi_parser.output.join('');
+ }
+
+ var beautify = require('devtools/shared/jsbeautify/beautify');
+
+ exports.htmlBeautify = function(html_source, options) {
+ return style_html(html_source, options, beautify.js, beautify.css);
+ };
+}());
diff --git a/devtools/shared/jsbeautify/src/beautify-js.js b/devtools/shared/jsbeautify/src/beautify-js.js
new file mode 100644
index 000000000..053d973e0
--- /dev/null
+++ b/devtools/shared/jsbeautify/src/beautify-js.js
@@ -0,0 +1,1662 @@
+/*jshint curly:true, eqeqeq:true, laxbreak:true, noempty:false */
+
+"use strict";
+
+const acorn = require("acorn/acorn");
+
+/*
+
+ The MIT License (MIT)
+
+ Copyright (c) 2007-2013 Einar Lielmanis and contributors.
+
+ Permission is hereby granted, free of charge, to any person
+ obtaining a copy of this software and associated documentation files
+ (the "Software"), to deal in the Software without restriction,
+ including without limitation the rights to use, copy, modify, merge,
+ publish, distribute, sublicense, and/or sell copies of the Software,
+ and to permit persons to whom the Software is furnished to do so,
+ subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be
+ included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+
+ JS Beautifier
+---------------
+
+
+ Written by Einar Lielmanis, <einar@jsbeautifier.org>
+ http://jsbeautifier.org/
+
+ Originally converted to javascript by Vital, <vital76@gmail.com>
+ "End braces on own line" added by Chris J. Shull, <chrisjshull@gmail.com>
+ Parsing improvements for brace-less statements by Liam Newman <bitwiseman@gmail.com>
+
+
+ Usage:
+ js_beautify(js_source_text);
+ js_beautify(js_source_text, options);
+
+ The options are:
+ indent_size (default 4) - indentation size,
+ indent_char (default space) - character to indent with,
+ preserve_newlines (default true) - whether existing line breaks should be preserved,
+ max_preserve_newlines (default unlimited) - maximum number of line breaks to be preserved in one chunk,
+
+ jslint_happy (default false) - if true, then jslint-stricter mode is enforced.
+
+ jslint_happy !jslint_happy
+ ---------------------------------
+ function () function()
+
+ brace_style (default "collapse") - "collapse" | "expand" | "end-expand"
+ 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.
+
+ space_before_conditional (default true) - should the space before conditional statement be added, "if(true)" vs "if (true)",
+
+ unescape_strings (default false) - should printable characters in strings encoded in \xNN notation be unescaped, "example" vs "\x65\x78\x61\x6d\x70\x6c\x65"
+
+ wrap_line_length (default unlimited) - lines should wrap at next opportunity after this number of characters.
+ NOTE: This is not a hard limit. Lines will continue until a point where a newline would
+ be preserved if it were present.
+
+ e.g
+
+ js_beautify(js_source_text, {
+ 'indent_size': 1,
+ 'indent_char': '\t'
+ });
+
+*/
+
+var js_beautify = function js_beautify(js_source_text, options) {
+ var beautifier = new Beautifier(js_source_text, options);
+ return beautifier.beautify();
+};
+
+exports.jsBeautify = js_beautify;
+
+function Beautifier(js_source_text, options) {
+ var input, output_lines;
+ var token_text, token_type, last_type, last_last_text, indent_string;
+ var flags, previous_flags, flag_store;
+ var whitespace, wordchar, punct, parser_pos, line_starters, reserved_words, digits;
+ var prefix;
+ var input_wanted_newline;
+ var output_space_before_token;
+ var input_length, n_newlines, whitespace_before_token;
+ var handlers, MODE, opt;
+ var preindent_string = '';
+
+
+
+ whitespace = "\n\r\t ".split('');
+ wordchar = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_$'.split('');
+ digits = '0123456789'.split('');
+
+ punct = '+ - * / % & ++ -- = += -= *= /= %= == === != !== > < >= <= >> << >>> >>>= >>= <<= && &= | || ! ~ , : ? ^ ^= |= :: =>';
+ punct += ' <%= <% %> <?= <? ?>'; // try to be a good boy and try not to break the markup language identifiers
+ punct = punct.split(' ');
+
+ // words which should always start on new line.
+ line_starters = 'continue,try,throw,return,var,let,const,if,switch,case,default,for,while,break,function,yield'.split(',');
+ reserved_words = line_starters.concat(['do', 'in', 'else', 'get', 'set', 'new', 'catch', 'finally', 'typeof']);
+
+
+ MODE = {
+ BlockStatement: 'BlockStatement', // 'BLOCK'
+ Statement: 'Statement', // 'STATEMENT'
+ ObjectLiteral: 'ObjectLiteral', // 'OBJECT',
+ ArrayLiteral: 'ArrayLiteral', //'[EXPRESSION]',
+ ForInitializer: 'ForInitializer', //'(FOR-EXPRESSION)',
+ Conditional: 'Conditional', //'(COND-EXPRESSION)',
+ Expression: 'Expression' //'(EXPRESSION)'
+ };
+
+ handlers = {
+ 'TK_START_EXPR': handle_start_expr,
+ 'TK_END_EXPR': handle_end_expr,
+ 'TK_START_BLOCK': handle_start_block,
+ 'TK_END_BLOCK': handle_end_block,
+ 'TK_WORD': handle_word,
+ 'TK_RESERVED': handle_word,
+ 'TK_SEMICOLON': handle_semicolon,
+ 'TK_STRING': handle_string,
+ 'TK_EQUALS': handle_equals,
+ 'TK_OPERATOR': handle_operator,
+ 'TK_COMMA': handle_comma,
+ 'TK_BLOCK_COMMENT': handle_block_comment,
+ 'TK_INLINE_COMMENT': handle_inline_comment,
+ 'TK_COMMENT': handle_comment,
+ 'TK_DOT': handle_dot,
+ 'TK_UNKNOWN': handle_unknown
+ };
+
+ function create_flags(flags_base, mode) {
+ var next_indent_level = 0;
+ if (flags_base) {
+ next_indent_level = flags_base.indentation_level;
+ if (!just_added_newline() &&
+ flags_base.line_indent_level > next_indent_level) {
+ next_indent_level = flags_base.line_indent_level;
+ }
+ }
+
+ var next_flags = {
+ mode: mode,
+ parent: flags_base,
+ last_text: flags_base ? flags_base.last_text : '', // last token text
+ last_word: flags_base ? flags_base.last_word : '', // last 'TK_WORD' passed
+ declaration_statement: false,
+ declaration_assignment: false,
+ in_html_comment: false,
+ multiline_frame: false,
+ if_block: false,
+ else_block: false,
+ do_block: false,
+ do_while: false,
+ in_case_statement: false, // switch(..){ INSIDE HERE }
+ in_case: false, // we're on the exact line with "case 0:"
+ case_body: false, // the indented case-action block
+ indentation_level: next_indent_level,
+ line_indent_level: flags_base ? flags_base.line_indent_level : next_indent_level,
+ start_line_index: output_lines.length,
+ had_comment: false,
+ ternary_depth: 0
+ };
+ return next_flags;
+ }
+
+ // Using object instead of string to allow for later expansion of info about each line
+
+ function create_output_line() {
+ return {
+ text: []
+ };
+ }
+
+ // Some interpreters have unexpected results with foo = baz || bar;
+ options = options ? options : {};
+ opt = {};
+
+ // compatibility
+ if (options.space_after_anon_function !== undefined && options.jslint_happy === undefined) {
+ options.jslint_happy = options.space_after_anon_function;
+ }
+ if (options.braces_on_own_line !== undefined) { //graceful handling of deprecated option
+ opt.brace_style = options.braces_on_own_line ? "expand" : "collapse";
+ }
+ opt.brace_style = options.brace_style ? options.brace_style : (opt.brace_style ? opt.brace_style : "collapse");
+
+ // graceful handling of deprecated option
+ if (opt.brace_style === "expand-strict") {
+ opt.brace_style = "expand";
+ }
+
+
+ opt.indent_size = options.indent_size ? parseInt(options.indent_size, 10) : 4;
+ opt.indent_char = options.indent_char ? options.indent_char : ' ';
+ opt.preserve_newlines = (options.preserve_newlines === undefined) ? true : options.preserve_newlines;
+ opt.break_chained_methods = (options.break_chained_methods === undefined) ? false : options.break_chained_methods;
+ opt.max_preserve_newlines = (options.max_preserve_newlines === undefined) ? 0 : parseInt(options.max_preserve_newlines, 10);
+ opt.space_in_paren = (options.space_in_paren === undefined) ? false : options.space_in_paren;
+ opt.space_in_empty_paren = (options.space_in_empty_paren === undefined) ? false : options.space_in_empty_paren;
+ opt.jslint_happy = (options.jslint_happy === undefined) ? false : options.jslint_happy;
+ opt.keep_array_indentation = (options.keep_array_indentation === undefined) ? false : options.keep_array_indentation;
+ opt.space_before_conditional = (options.space_before_conditional === undefined) ? true : options.space_before_conditional;
+ opt.unescape_strings = (options.unescape_strings === undefined) ? false : options.unescape_strings;
+ opt.wrap_line_length = (options.wrap_line_length === undefined) ? 0 : parseInt(options.wrap_line_length, 10);
+ opt.e4x = (options.e4x === undefined) ? false : options.e4x;
+
+ if(options.indent_with_tabs){
+ opt.indent_char = '\t';
+ opt.indent_size = 1;
+ }
+
+ //----------------------------------
+ indent_string = '';
+ while (opt.indent_size > 0) {
+ indent_string += opt.indent_char;
+ opt.indent_size -= 1;
+ }
+
+ while (js_source_text && (js_source_text.charAt(0) === ' ' || js_source_text.charAt(0) === '\t')) {
+ preindent_string += js_source_text.charAt(0);
+ js_source_text = js_source_text.substring(1);
+ }
+ input = js_source_text;
+ // cache the source's length.
+ input_length = js_source_text.length;
+
+ last_type = 'TK_START_BLOCK'; // last token type
+ last_last_text = ''; // pre-last token text
+ output_lines = [create_output_line()];
+ output_space_before_token = false;
+ whitespace_before_token = [];
+
+ // Stack of parsing/formatting states, including MODE.
+ // We tokenize, parse, and output in an almost purely a forward-only stream of token input
+ // and formatted output. This makes the beautifier less accurate than full parsers
+ // but also far more tolerant of syntax errors.
+ //
+ // For example, the default mode is MODE.BlockStatement. If we see a '{' we push a new frame of type
+ // MODE.BlockStatement on the the stack, even though it could be object literal. If we later
+ // encounter a ":", we'll switch to to MODE.ObjectLiteral. If we then see a ";",
+ // most full parsers would die, but the beautifier gracefully falls back to
+ // MODE.BlockStatement and continues on.
+ flag_store = [];
+ set_mode(MODE.BlockStatement);
+
+ parser_pos = 0;
+
+ this.beautify = function() {
+ /*jshint onevar:true */
+ var t, i, keep_whitespace, sweet_code;
+
+ while (true) {
+ t = get_next_token();
+ token_text = t[0];
+ token_type = t[1];
+
+ if (token_type === 'TK_EOF') {
+ // Unwind any open statements
+ while (flags.mode === MODE.Statement) {
+ restore_mode();
+ }
+ break;
+ }
+
+ keep_whitespace = opt.keep_array_indentation && is_array(flags.mode);
+ input_wanted_newline = n_newlines > 0;
+
+ if (keep_whitespace) {
+ for (i = 0; i < n_newlines; i += 1) {
+ print_newline(i > 0);
+ }
+ } else {
+ if (opt.max_preserve_newlines && n_newlines > opt.max_preserve_newlines) {
+ n_newlines = opt.max_preserve_newlines;
+ }
+
+ if (opt.preserve_newlines) {
+ if (n_newlines > 1) {
+ print_newline();
+ for (i = 1; i < n_newlines; i += 1) {
+ print_newline(true);
+ }
+ }
+ }
+ }
+
+ handlers[token_type]();
+
+ // The cleanest handling of inline comments is to treat them as though they aren't there.
+ // Just continue formatting and the behavior should be logical.
+ // Also ignore unknown tokens. Again, this should result in better behavior.
+ if (token_type !== 'TK_INLINE_COMMENT' && token_type !== 'TK_COMMENT' &&
+ token_type !== 'TK_BLOCK_COMMENT' && token_type !== 'TK_UNKNOWN') {
+ last_last_text = flags.last_text;
+ last_type = token_type;
+ flags.last_text = token_text;
+ }
+ flags.had_comment = (token_type === 'TK_INLINE_COMMENT' || token_type === 'TK_COMMENT'
+ || token_type === 'TK_BLOCK_COMMENT');
+ }
+
+
+ sweet_code = output_lines[0].text.join('');
+ for (var line_index = 1; line_index < output_lines.length; line_index++) {
+ sweet_code += '\n' + output_lines[line_index].text.join('');
+ }
+ sweet_code = sweet_code.replace(/[\r\n ]+$/, '');
+ return sweet_code;
+ };
+
+ function trim_output(eat_newlines) {
+ eat_newlines = (eat_newlines === undefined) ? false : eat_newlines;
+
+ if (output_lines.length) {
+ trim_output_line(output_lines[output_lines.length - 1], eat_newlines);
+
+ while (eat_newlines && output_lines.length > 1 &&
+ output_lines[output_lines.length - 1].text.length === 0) {
+ output_lines.pop();
+ trim_output_line(output_lines[output_lines.length - 1], eat_newlines);
+ }
+ }
+ }
+
+ function trim_output_line(line) {
+ while (line.text.length &&
+ (line.text[line.text.length - 1] === ' ' ||
+ line.text[line.text.length - 1] === indent_string ||
+ line.text[line.text.length - 1] === preindent_string)) {
+ line.text.pop();
+ }
+ }
+
+ function trim(s) {
+ return s.replace(/^\s+|\s+$/g, '');
+ }
+
+ // we could use just string.split, but
+ // IE doesn't like returning empty strings
+
+ function split_newlines(s) {
+ //return s.split(/\x0d\x0a|\x0a/);
+
+ s = s.replace(/\x0d/g, '');
+ var out = [],
+ idx = s.indexOf("\n");
+ while (idx !== -1) {
+ out.push(s.substring(0, idx));
+ s = s.substring(idx + 1);
+ idx = s.indexOf("\n");
+ }
+ if (s.length) {
+ out.push(s);
+ }
+ return out;
+ }
+
+ function just_added_newline() {
+ var line = output_lines[output_lines.length - 1];
+ return line.text.length === 0;
+ }
+
+ function just_added_blankline() {
+ if (just_added_newline()) {
+ if (output_lines.length === 1) {
+ return true; // start of the file and newline = blank
+ }
+
+ var line = output_lines[output_lines.length - 2];
+ return line.text.length === 0;
+ }
+ return false;
+ }
+
+ function allow_wrap_or_preserved_newline(force_linewrap) {
+ force_linewrap = (force_linewrap === undefined) ? false : force_linewrap;
+ if (opt.wrap_line_length && !force_linewrap) {
+ var line = output_lines[output_lines.length - 1];
+ var proposed_line_length = 0;
+ // never wrap the first token of a line.
+ if (line.text.length > 0) {
+ proposed_line_length = line.text.join('').length + token_text.length +
+ (output_space_before_token ? 1 : 0);
+ if (proposed_line_length >= opt.wrap_line_length) {
+ force_linewrap = true;
+ }
+ }
+ }
+ if (((opt.preserve_newlines && input_wanted_newline) || force_linewrap) && !just_added_newline()) {
+ print_newline(false, true);
+
+ }
+ }
+
+ function print_newline(force_newline, preserve_statement_flags) {
+ output_space_before_token = false;
+
+ if (!preserve_statement_flags) {
+ if (flags.last_text !== ';' && flags.last_text !== ',' && flags.last_text !== '=' && last_type !== 'TK_OPERATOR') {
+ while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
+ restore_mode();
+ }
+ }
+ }
+
+ if (output_lines.length === 1 && just_added_newline()) {
+ return; // no newline on start of file
+ }
+
+ if (force_newline || !just_added_newline()) {
+ flags.multiline_frame = true;
+ output_lines.push(create_output_line());
+ }
+ }
+
+ function print_token_line_indentation() {
+ if (just_added_newline()) {
+ var line = output_lines[output_lines.length - 1];
+ if (opt.keep_array_indentation && is_array(flags.mode) && input_wanted_newline) {
+ // prevent removing of this whitespace as redundant
+ line.text.push('');
+ for (var i = 0; i < whitespace_before_token.length; i += 1) {
+ line.text.push(whitespace_before_token[i]);
+ }
+ } else {
+ if (preindent_string) {
+ line.text.push(preindent_string);
+ }
+
+ print_indent_string(flags.indentation_level);
+ }
+ }
+ }
+
+ function print_indent_string(level) {
+ // Never indent your first output indent at the start of the file
+ if (output_lines.length > 1) {
+ var line = output_lines[output_lines.length - 1];
+
+ flags.line_indent_level = level;
+ for (var i = 0; i < level; i += 1) {
+ line.text.push(indent_string);
+ }
+ }
+ }
+
+ function print_token_space_before() {
+ var line = output_lines[output_lines.length - 1];
+ if (output_space_before_token && line.text.length) {
+ var last_output = line.text[line.text.length - 1];
+ if (last_output !== ' ' && last_output !== indent_string) { // prevent occassional duplicate space
+ line.text.push(' ');
+ }
+ }
+ }
+
+ function print_token(printable_token) {
+ printable_token = printable_token || token_text;
+ print_token_line_indentation();
+ print_token_space_before();
+ output_space_before_token = false;
+ output_lines[output_lines.length - 1].text.push(printable_token);
+ }
+
+ function indent() {
+ flags.indentation_level += 1;
+ }
+
+ function deindent() {
+ if (flags.indentation_level > 0 &&
+ ((!flags.parent) || flags.indentation_level > flags.parent.indentation_level))
+ flags.indentation_level -= 1;
+ }
+
+ function remove_redundant_indentation(frame) {
+ // This implementation is effective but has some issues:
+ // - less than great performance due to array splicing
+ // - can cause line wrap to happen too soon due to indent removal
+ // after wrap points are calculated
+ // These issues are minor compared to ugly indentation.
+
+ if (frame.multiline_frame) return;
+
+ // remove one indent from each line inside this section
+ var index = frame.start_line_index;
+ var splice_index = 0;
+ var line;
+
+ while (index < output_lines.length) {
+ line = output_lines[index];
+ index++;
+
+ // skip empty lines
+ if (line.text.length === 0) {
+ continue;
+ }
+
+ // skip the preindent string if present
+ if (preindent_string && line.text[0] === preindent_string) {
+ splice_index = 1;
+ } else {
+ splice_index = 0;
+ }
+
+ // remove one indent, if present
+ if (line.text[splice_index] === indent_string) {
+ line.text.splice(splice_index, 1);
+ }
+ }
+ }
+
+ function set_mode(mode) {
+ if (flags) {
+ flag_store.push(flags);
+ previous_flags = flags;
+ } else {
+ previous_flags = create_flags(null, mode);
+ }
+
+ flags = create_flags(previous_flags, mode);
+ }
+
+ function is_array(mode) {
+ return mode === MODE.ArrayLiteral;
+ }
+
+ function is_expression(mode) {
+ return in_array(mode, [MODE.Expression, MODE.ForInitializer, MODE.Conditional]);
+ }
+
+ function restore_mode() {
+ if (flag_store.length > 0) {
+ previous_flags = flags;
+ flags = flag_store.pop();
+ if (previous_flags.mode === MODE.Statement) {
+ remove_redundant_indentation(previous_flags);
+ }
+ }
+ }
+
+ function start_of_object_property() {
+ return flags.parent.mode === MODE.ObjectLiteral && flags.mode === MODE.Statement && flags.last_text === ':' &&
+ flags.ternary_depth === 0;
+ }
+
+ function start_of_statement() {
+ if (
+ (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && token_type === 'TK_WORD') ||
+ (last_type === 'TK_RESERVED' && flags.last_text === 'do') ||
+ (last_type === 'TK_RESERVED' && flags.last_text === 'return' && !input_wanted_newline) ||
+ (last_type === 'TK_RESERVED' && flags.last_text === 'else' && !(token_type === 'TK_RESERVED' && token_text === 'if')) ||
+ (last_type === 'TK_END_EXPR' && (previous_flags.mode === MODE.ForInitializer || previous_flags.mode === MODE.Conditional)) ||
+ (last_type === 'TK_WORD' && flags.mode === MODE.BlockStatement
+ && !flags.in_case
+ && !(token_text === '--' || token_text === '++')
+ && token_type !== 'TK_WORD' && token_type !== 'TK_RESERVED') ||
+ (flags.mode === MODE.ObjectLiteral && flags.last_text === ':' && flags.ternary_depth === 0)
+
+ ) {
+
+ set_mode(MODE.Statement);
+ indent();
+
+ if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const']) && token_type === 'TK_WORD') {
+ flags.declaration_statement = true;
+ }
+
+ // Issue #276:
+ // If starting a new statement with [if, for, while, do], push to a new line.
+ // if (a) if (b) if(c) d(); else e(); else f();
+ if (!start_of_object_property()) {
+ allow_wrap_or_preserved_newline(
+ token_type === 'TK_RESERVED' && in_array(token_text, ['do', 'for', 'if', 'while']));
+ }
+
+ return true;
+ }
+ return false;
+ }
+
+ function all_lines_start_with(lines, c) {
+ for (var i = 0; i < lines.length; i++) {
+ var line = trim(lines[i]);
+ if (line.charAt(0) !== c) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function each_line_matches_indent(lines, indent) {
+ var i = 0,
+ len = lines.length,
+ line;
+ for (; i < len; i++) {
+ line = lines[i];
+ // allow empty lines to pass through
+ if (line && line.indexOf(indent) !== 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ function is_special_word(word) {
+ return in_array(word, ['case', 'return', 'do', 'if', 'throw', 'else']);
+ }
+
+ function in_array(what, arr) {
+ for (var i = 0; i < arr.length; i += 1) {
+ if (arr[i] === what) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ function unescape_string(s) {
+ var esc = false,
+ out = '',
+ pos = 0,
+ s_hex = '',
+ escaped = 0,
+ c;
+
+ while (esc || pos < s.length) {
+
+ c = s.charAt(pos);
+ pos++;
+
+ if (esc) {
+ esc = false;
+ if (c === 'x') {
+ // simple hex-escape \x24
+ s_hex = s.substr(pos, 2);
+ pos += 2;
+ } else if (c === 'u') {
+ // unicode-escape, \u2134
+ s_hex = s.substr(pos, 4);
+ pos += 4;
+ } else {
+ // some common escape, e.g \n
+ out += '\\' + c;
+ continue;
+ }
+ if (!s_hex.match(/^[0123456789abcdefABCDEF]+$/)) {
+ // some weird escaping, bail out,
+ // leaving whole string intact
+ return s;
+ }
+
+ escaped = parseInt(s_hex, 16);
+
+ if (escaped >= 0x00 && escaped < 0x20) {
+ // leave 0x00...0x1f escaped
+ if (c === 'x') {
+ out += '\\x' + s_hex;
+ } else {
+ out += '\\u' + s_hex;
+ }
+ continue;
+ } else if (escaped === 0x22 || escaped === 0x27 || escaped === 0x5c) {
+ // single-quote, apostrophe, backslash - escape these
+ out += '\\' + String.fromCharCode(escaped);
+ } else if (c === 'x' && escaped > 0x7e && escaped <= 0xff) {
+ // we bail out on \x7f..\xff,
+ // leaving whole string escaped,
+ // as it's probably completely binary
+ return s;
+ } else {
+ out += String.fromCharCode(escaped);
+ }
+ } else if (c === '\\') {
+ esc = true;
+ } else {
+ out += c;
+ }
+ }
+ return out;
+ }
+
+ function is_next(find) {
+ var local_pos = parser_pos;
+ var c = input.charAt(local_pos);
+ while (in_array(c, whitespace) && c !== find) {
+ local_pos++;
+ if (local_pos >= input_length) {
+ return false;
+ }
+ c = input.charAt(local_pos);
+ }
+ return c === find;
+ }
+
+ function get_next_token() {
+ var i, resulting_string;
+
+ n_newlines = 0;
+
+ if (parser_pos >= input_length) {
+ return ['', 'TK_EOF'];
+ }
+
+ input_wanted_newline = false;
+ whitespace_before_token = [];
+
+ var c = input.charAt(parser_pos);
+ parser_pos += 1;
+
+ while (in_array(c, whitespace)) {
+
+ if (c === '\n') {
+ n_newlines += 1;
+ whitespace_before_token = [];
+ } else if (n_newlines) {
+ if (c === indent_string) {
+ whitespace_before_token.push(indent_string);
+ } else if (c !== '\r') {
+ whitespace_before_token.push(' ');
+ }
+ }
+
+ if (parser_pos >= input_length) {
+ return ['', 'TK_EOF'];
+ }
+
+ c = input.charAt(parser_pos);
+ parser_pos += 1;
+ }
+
+ // NOTE: because beautifier doesn't fully parse, it doesn't use acorn.isIdentifierStart.
+ // It just treats all identifiers and numbers and such the same.
+ if (acorn.isIdentifierChar(input.charCodeAt(parser_pos-1))) {
+ if (parser_pos < input_length) {
+ while (acorn.isIdentifierChar(input.charCodeAt(parser_pos))) {
+ c += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos === input_length) {
+ break;
+ }
+ }
+ }
+
+ // small and surprisingly unugly hack for 1E-10 representation
+ if (parser_pos !== input_length && c.match(/^[0-9]+[Ee]$/) && (input.charAt(parser_pos) === '-' || input.charAt(parser_pos) === '+')) {
+
+ var sign = input.charAt(parser_pos);
+ parser_pos += 1;
+
+ var t = get_next_token();
+ c += sign + t[0];
+ return [c, 'TK_WORD'];
+ }
+
+ if (!(last_type === 'TK_DOT' ||
+ (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['set', 'get'])))
+ && in_array(c, reserved_words)) {
+ if (c === 'in') { // hack for 'in' operator
+ return [c, 'TK_OPERATOR'];
+ }
+ return [c, 'TK_RESERVED'];
+ }
+ return [c, 'TK_WORD'];
+ }
+
+ if (c === '(' || c === '[') {
+ return [c, 'TK_START_EXPR'];
+ }
+
+ if (c === ')' || c === ']') {
+ return [c, 'TK_END_EXPR'];
+ }
+
+ if (c === '{') {
+ return [c, 'TK_START_BLOCK'];
+ }
+
+ if (c === '}') {
+ return [c, 'TK_END_BLOCK'];
+ }
+
+ if (c === ';') {
+ return [c, 'TK_SEMICOLON'];
+ }
+
+ if (c === '/') {
+ var comment = '';
+ // peek for comment /* ... */
+ var inline_comment = true;
+ if (input.charAt(parser_pos) === '*') {
+ parser_pos += 1;
+ if (parser_pos < input_length) {
+ while (parser_pos < input_length && !(input.charAt(parser_pos) === '*' && input.charAt(parser_pos + 1) && input.charAt(parser_pos + 1) === '/')) {
+ c = input.charAt(parser_pos);
+ comment += c;
+ if (c === "\n" || c === "\r") {
+ inline_comment = false;
+ }
+ parser_pos += 1;
+ if (parser_pos >= input_length) {
+ break;
+ }
+ }
+ }
+ parser_pos += 2;
+ if (inline_comment && n_newlines === 0) {
+ return ['/*' + comment + '*/', 'TK_INLINE_COMMENT'];
+ } else {
+ return ['/*' + comment + '*/', 'TK_BLOCK_COMMENT'];
+ }
+ }
+ // peek for comment // ...
+ if (input.charAt(parser_pos) === '/') {
+ comment = c;
+ while (input.charAt(parser_pos) !== '\r' && input.charAt(parser_pos) !== '\n') {
+ comment += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos >= input_length) {
+ break;
+ }
+ }
+ return [comment, 'TK_COMMENT'];
+ }
+
+ }
+
+
+ if (c === '`' || c === "'" || c === '"' || // string
+ (
+ (c === '/') || // regexp
+ (opt.e4x && c === "<" && input.slice(parser_pos - 1).match(/^<([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*\/?\s*>/)) // xml
+ ) && ( // regex and xml can only appear in specific locations during parsing
+ (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) ||
+ (last_type === 'TK_END_EXPR' && in_array(previous_flags.mode, [MODE.Conditional, MODE.ForInitializer])) ||
+ (in_array(last_type, ['TK_COMMENT', 'TK_START_EXPR', 'TK_START_BLOCK',
+ 'TK_END_BLOCK', 'TK_OPERATOR', 'TK_EQUALS', 'TK_EOF', 'TK_SEMICOLON', 'TK_COMMA'
+ ]))
+ )) {
+
+ var sep = c,
+ esc = false,
+ has_char_escapes = false;
+
+ resulting_string = c;
+
+ if (parser_pos < input_length) {
+ if (sep === '/') {
+ //
+ // handle regexp
+ //
+ var in_char_class = false;
+ while (esc || in_char_class || input.charAt(parser_pos) !== sep) {
+ resulting_string += input.charAt(parser_pos);
+ if (!esc) {
+ esc = input.charAt(parser_pos) === '\\';
+ if (input.charAt(parser_pos) === '[') {
+ in_char_class = true;
+ } else if (input.charAt(parser_pos) === ']') {
+ in_char_class = false;
+ }
+ } else {
+ esc = false;
+ }
+ parser_pos += 1;
+ if (parser_pos >= input_length) {
+ // incomplete string/rexp when end-of-file reached.
+ // bail out with what had been received so far.
+ return [resulting_string, 'TK_STRING'];
+ }
+ }
+ } else if (opt.e4x && sep === '<') {
+ //
+ // handle e4x xml literals
+ //
+ var xmlRegExp = /<(\/?)([-a-zA-Z:0-9_.]+|{[^{}]*}|!\[CDATA\[[\s\S]*?\]\])\s*([-a-zA-Z:0-9_.]+=('[^']*'|"[^"]*"|{[^{}]*})\s*)*(\/?)\s*>/g;
+ var xmlStr = input.slice(parser_pos - 1);
+ var match = xmlRegExp.exec(xmlStr);
+ if (match && match.index === 0) {
+ var rootTag = match[2];
+ var depth = 0;
+ while (match) {
+ var isEndTag = !! match[1];
+ var tagName = match[2];
+ var isSingletonTag = ( !! match[match.length - 1]) || (tagName.slice(0, 8) === "![CDATA[");
+ if (tagName === rootTag && !isSingletonTag) {
+ if (isEndTag) {
+ --depth;
+ } else {
+ ++depth;
+ }
+ }
+ if (depth <= 0) {
+ break;
+ }
+ match = xmlRegExp.exec(xmlStr);
+ }
+ var xmlLength = match ? match.index + match[0].length : xmlStr.length;
+ parser_pos += xmlLength - 1;
+ return [xmlStr.slice(0, xmlLength), "TK_STRING"];
+ }
+ } else {
+ //
+ // handle string
+ //
+ while (esc || input.charAt(parser_pos) !== sep) {
+ resulting_string += input.charAt(parser_pos);
+ if (esc) {
+ if (input.charAt(parser_pos) === 'x' || input.charAt(parser_pos) === 'u') {
+ has_char_escapes = true;
+ }
+ esc = false;
+ } else {
+ esc = input.charAt(parser_pos) === '\\';
+ }
+ parser_pos += 1;
+ if (parser_pos >= input_length) {
+ // incomplete string/rexp when end-of-file reached.
+ // bail out with what had been received so far.
+ return [resulting_string, 'TK_STRING'];
+ }
+ }
+
+ }
+ }
+
+ parser_pos += 1;
+ resulting_string += sep;
+
+ if (has_char_escapes && opt.unescape_strings) {
+ resulting_string = unescape_string(resulting_string);
+ }
+
+ if (sep === '/') {
+ // regexps may have modifiers /regexp/MOD , so fetch those, too
+ while (parser_pos < input_length && in_array(input.charAt(parser_pos), wordchar)) {
+ resulting_string += input.charAt(parser_pos);
+ parser_pos += 1;
+ }
+ }
+ return [resulting_string, 'TK_STRING'];
+ }
+
+ if (c === '#') {
+
+
+ if (output_lines.length === 1 && output_lines[0].text.length === 0 &&
+ input.charAt(parser_pos) === '!') {
+ // shebang
+ resulting_string = c;
+ while (parser_pos < input_length && c !== '\n') {
+ c = input.charAt(parser_pos);
+ resulting_string += c;
+ parser_pos += 1;
+ }
+ return [trim(resulting_string) + '\n', 'TK_UNKNOWN'];
+ }
+
+
+
+ // Spidermonkey-specific sharp variables for circular references
+ // https://developer.mozilla.org/En/Sharp_variables_in_JavaScript
+ // http://dxr.mozilla.org/mozilla-central/source/js/src/jsscan.cpp around line 1935
+ var sharp = '#';
+ if (parser_pos < input_length && in_array(input.charAt(parser_pos), digits)) {
+ do {
+ c = input.charAt(parser_pos);
+ sharp += c;
+ parser_pos += 1;
+ } while (parser_pos < input_length && c !== '#' && c !== '=');
+ if (c === '#') {
+ //
+ } else if (input.charAt(parser_pos) === '[' && input.charAt(parser_pos + 1) === ']') {
+ sharp += '[]';
+ parser_pos += 2;
+ } else if (input.charAt(parser_pos) === '{' && input.charAt(parser_pos + 1) === '}') {
+ sharp += '{}';
+ parser_pos += 2;
+ }
+ return [sharp, 'TK_WORD'];
+ }
+ }
+
+ if (c === '<' && input.substring(parser_pos - 1, parser_pos + 3) === '<!--') {
+ parser_pos += 3;
+ c = '<!--';
+ while (input.charAt(parser_pos) !== '\n' && parser_pos < input_length) {
+ c += input.charAt(parser_pos);
+ parser_pos++;
+ }
+ flags.in_html_comment = true;
+ return [c, 'TK_COMMENT'];
+ }
+
+ if (c === '-' && flags.in_html_comment && input.substring(parser_pos - 1, parser_pos + 2) === '-->') {
+ flags.in_html_comment = false;
+ parser_pos += 2;
+ return ['-->', 'TK_COMMENT'];
+ }
+
+ if (c === '.') {
+ return [c, 'TK_DOT'];
+ }
+
+ if (in_array(c, punct)) {
+ while (parser_pos < input_length && in_array(c + input.charAt(parser_pos), punct)) {
+ c += input.charAt(parser_pos);
+ parser_pos += 1;
+ if (parser_pos >= input_length) {
+ break;
+ }
+ }
+
+ if (c === ',') {
+ return [c, 'TK_COMMA'];
+ } else if (c === '=') {
+ return [c, 'TK_EQUALS'];
+ } else {
+ return [c, 'TK_OPERATOR'];
+ }
+ }
+
+ return [c, 'TK_UNKNOWN'];
+ }
+
+ function handle_start_expr() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ }
+
+ var next_mode = MODE.Expression;
+ if (token_text === '[') {
+
+ if (last_type === 'TK_WORD' || flags.last_text === ')') {
+ // this is array index specifier, break immediately
+ // a[x], fn()[x]
+ if (last_type === 'TK_RESERVED' && in_array(flags.last_text, line_starters)) {
+ output_space_before_token = true;
+ }
+ set_mode(next_mode);
+ print_token();
+ indent();
+ if (opt.space_in_paren) {
+ output_space_before_token = true;
+ }
+ return;
+ }
+
+ next_mode = MODE.ArrayLiteral;
+ if (is_array(flags.mode)) {
+ if (flags.last_text === '[' ||
+ (flags.last_text === ',' && (last_last_text === ']' || last_last_text === '}'))) {
+ // ], [ goes to new line
+ // }, [ goes to new line
+ if (!opt.keep_array_indentation) {
+ print_newline();
+ }
+ }
+ }
+
+ } else {
+ if (last_type === 'TK_RESERVED' && flags.last_text === 'for') {
+ next_mode = MODE.ForInitializer;
+ } else if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['if', 'while'])) {
+ next_mode = MODE.Conditional;
+ } else {
+ // next_mode = MODE.Expression;
+ }
+ }
+
+ if (flags.last_text === ';' || last_type === 'TK_START_BLOCK') {
+ print_newline();
+ } else if (last_type === 'TK_END_EXPR' || last_type === 'TK_START_EXPR' || last_type === 'TK_END_BLOCK' || flags.last_text === '.') {
+ // TODO: Consider whether forcing this is required. Review failing tests when removed.
+ allow_wrap_or_preserved_newline(input_wanted_newline);
+ // do nothing on (( and )( and ][ and ]( and .(
+ } else if (!(last_type === 'TK_RESERVED' && token_text === '(') && last_type !== 'TK_WORD' && last_type !== 'TK_OPERATOR') {
+ output_space_before_token = true;
+ } else if ((last_type === 'TK_RESERVED' && (flags.last_word === 'function' || flags.last_word === 'typeof')) ||
+ (flags.last_text === '*' && last_last_text === 'function')) {
+ // function() vs function ()
+ if (opt.jslint_happy) {
+ output_space_before_token = true;
+ }
+ } else if (last_type === 'TK_RESERVED' && (in_array(flags.last_text, line_starters) || flags.last_text === 'catch')) {
+ if (opt.space_before_conditional) {
+ output_space_before_token = true;
+ }
+ }
+
+ // Support of this kind of newline preservation.
+ // a = (b &&
+ // (c || d));
+ if (token_text === '(') {
+ if (last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
+ if (!start_of_object_property()) {
+ allow_wrap_or_preserved_newline();
+ }
+ }
+ }
+
+ set_mode(next_mode);
+ print_token();
+ if (opt.space_in_paren) {
+ output_space_before_token = true;
+ }
+
+ // In all cases, if we newline while inside an expression it should be indented.
+ indent();
+ }
+
+ function handle_end_expr() {
+ // statements inside expressions are not valid syntax, but...
+ // statements must all be closed when their container closes
+ while (flags.mode === MODE.Statement) {
+ restore_mode();
+ }
+
+ if (flags.multiline_frame) {
+ allow_wrap_or_preserved_newline(token_text === ']' && is_array(flags.mode) && !opt.keep_array_indentation);
+ }
+
+ if (opt.space_in_paren) {
+ if (last_type === 'TK_START_EXPR' && ! opt.space_in_empty_paren) {
+ // () [] no inner space in empty parens like these, ever, ref #320
+ trim_output();
+ output_space_before_token = false;
+ } else {
+ output_space_before_token = true;
+ }
+ }
+ if (token_text === ']' && opt.keep_array_indentation) {
+ print_token();
+ restore_mode();
+ } else {
+ restore_mode();
+ print_token();
+ }
+ remove_redundant_indentation(previous_flags);
+
+ // do {} while () // no statement required after
+ if (flags.do_while && previous_flags.mode === MODE.Conditional) {
+ previous_flags.mode = MODE.Expression;
+ flags.do_block = false;
+ flags.do_while = false;
+
+ }
+ }
+
+ function handle_start_block() {
+ set_mode(MODE.BlockStatement);
+
+ var empty_braces = is_next('}');
+ var empty_anonymous_function = empty_braces && flags.last_word === 'function' &&
+ last_type === 'TK_END_EXPR';
+
+ if (opt.brace_style === "expand") {
+ if (last_type !== 'TK_OPERATOR' &&
+ (empty_anonymous_function ||
+ last_type === 'TK_EQUALS' ||
+ (last_type === 'TK_RESERVED' && is_special_word(flags.last_text) && flags.last_text !== 'else'))) {
+ output_space_before_token = true;
+ } else {
+ print_newline(false, true);
+ }
+ } else { // collapse
+ if (last_type !== 'TK_OPERATOR' && last_type !== 'TK_START_EXPR') {
+ if (last_type === 'TK_START_BLOCK') {
+ print_newline();
+ } else {
+ output_space_before_token = true;
+ }
+ } else {
+ // if TK_OPERATOR or TK_START_EXPR
+ if (is_array(previous_flags.mode) && flags.last_text === ',') {
+ if (last_last_text === '}') {
+ // }, { in array context
+ output_space_before_token = true;
+ } else {
+ print_newline(); // [a, b, c, {
+ }
+ }
+ }
+ }
+ print_token();
+ indent();
+ }
+
+ function handle_end_block() {
+ // statements must all be closed when their container closes
+ while (flags.mode === MODE.Statement) {
+ restore_mode();
+ }
+ var empty_braces = last_type === 'TK_START_BLOCK';
+
+ if (opt.brace_style === "expand") {
+ if (!empty_braces) {
+ print_newline();
+ }
+ } else {
+ // skip {}
+ if (!empty_braces) {
+ if (is_array(flags.mode) && opt.keep_array_indentation) {
+ // we REALLY need a newline here, but newliner would skip that
+ opt.keep_array_indentation = false;
+ print_newline();
+ opt.keep_array_indentation = true;
+
+ } else {
+ print_newline();
+ }
+ }
+ }
+ restore_mode();
+ print_token();
+ }
+
+ function handle_word() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ } else if (input_wanted_newline && !is_expression(flags.mode) &&
+ (last_type !== 'TK_OPERATOR' || (flags.last_text === '--' || flags.last_text === '++')) &&
+ last_type !== 'TK_EQUALS' &&
+ (opt.preserve_newlines || !(last_type === 'TK_RESERVED' && in_array(flags.last_text, ['var', 'let', 'const', 'set', 'get'])))) {
+
+ print_newline();
+ }
+
+ if (flags.do_block && !flags.do_while) {
+ if (token_type === 'TK_RESERVED' && token_text === 'while') {
+ // do {} ## while ()
+ output_space_before_token = true;
+ print_token();
+ output_space_before_token = true;
+ flags.do_while = true;
+ return;
+ } else {
+ // do {} should always have while as the next word.
+ // if we don't see the expected while, recover
+ print_newline();
+ flags.do_block = false;
+ }
+ }
+
+ // if may be followed by else, or not
+ // Bare/inline ifs are tricky
+ // Need to unwind the modes correctly: if (a) if (b) c(); else d(); else e();
+ if (flags.if_block) {
+ if (!flags.else_block && (token_type === 'TK_RESERVED' && token_text === 'else')) {
+ flags.else_block = true;
+ } else {
+ while (flags.mode === MODE.Statement) {
+ restore_mode();
+ }
+ flags.if_block = false;
+ flags.else_block = false;
+ }
+ }
+
+ if (token_type === 'TK_RESERVED' && (token_text === 'case' || (token_text === 'default' && flags.in_case_statement))) {
+ print_newline();
+ if (flags.case_body || opt.jslint_happy) {
+ // switch cases following one another
+ deindent();
+ flags.case_body = false;
+ }
+ print_token();
+ flags.in_case = true;
+ flags.in_case_statement = true;
+ return;
+ }
+
+ if (token_type === 'TK_RESERVED' && token_text === 'function') {
+ if (in_array(flags.last_text, ['}', ';']) || (just_added_newline() && ! in_array(flags.last_text, ['{', ':', '=', ',']))) {
+ // make sure there is a nice clean space of at least one blank line
+ // before a new function definition
+ if ( ! just_added_blankline() && ! flags.had_comment) {
+ print_newline();
+ print_newline(true);
+ }
+ }
+ if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD') {
+ if (last_type === 'TK_RESERVED' && in_array(flags.last_text, ['get', 'set', 'new', 'return'])) {
+ output_space_before_token = true;
+ } else {
+ print_newline();
+ }
+ } else if (last_type === 'TK_OPERATOR' || flags.last_text === '=') {
+ // foo = function
+ output_space_before_token = true;
+ } else if (is_expression(flags.mode)) {
+ // (function
+ } else {
+ print_newline();
+ }
+ }
+
+ if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
+ if (!start_of_object_property()) {
+ allow_wrap_or_preserved_newline();
+ }
+ }
+
+ if (token_type === 'TK_RESERVED' && token_text === 'function') {
+ print_token();
+ flags.last_word = token_text;
+ return;
+ }
+
+ prefix = 'NONE';
+
+ if (last_type === 'TK_END_BLOCK') {
+ if (!(token_type === 'TK_RESERVED' && in_array(token_text, ['else', 'catch', 'finally']))) {
+ prefix = 'NEWLINE';
+ } else {
+ if (opt.brace_style === "expand" || opt.brace_style === "end-expand") {
+ prefix = 'NEWLINE';
+ } else {
+ prefix = 'SPACE';
+ output_space_before_token = true;
+ }
+ }
+ } else if (last_type === 'TK_SEMICOLON' && flags.mode === MODE.BlockStatement) {
+ // TODO: Should this be for STATEMENT as well?
+ prefix = 'NEWLINE';
+ } else if (last_type === 'TK_SEMICOLON' && is_expression(flags.mode)) {
+ prefix = 'SPACE';
+ } else if (last_type === 'TK_STRING') {
+ prefix = 'NEWLINE';
+ } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD' ||
+ (flags.last_text === '*' && last_last_text === 'function')) {
+ prefix = 'SPACE';
+ } else if (last_type === 'TK_START_BLOCK') {
+ prefix = 'NEWLINE';
+ } else if (last_type === 'TK_END_EXPR') {
+ output_space_before_token = true;
+ prefix = 'NEWLINE';
+ }
+
+ if (token_type === 'TK_RESERVED' && in_array(token_text, line_starters) && flags.last_text !== ')') {
+ if (flags.last_text === 'else') {
+ prefix = 'SPACE';
+ } else {
+ prefix = 'NEWLINE';
+ }
+
+ }
+
+ if (token_type === 'TK_RESERVED' && in_array(token_text, ['else', 'catch', 'finally'])) {
+ if (last_type !== 'TK_END_BLOCK' || opt.brace_style === "expand" || opt.brace_style === "end-expand") {
+ print_newline();
+ } else {
+ trim_output(true);
+ var line = output_lines[output_lines.length - 1];
+ // If we trimmed and there's something other than a close block before us
+ // put a newline back in. Handles '} // comment' scenario.
+ if (line.text[line.text.length - 1] !== '}') {
+ print_newline();
+ }
+ output_space_before_token = true;
+ }
+ } else if (prefix === 'NEWLINE') {
+ if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
+ // no newline between 'return nnn'
+ output_space_before_token = true;
+ } else if (last_type !== 'TK_END_EXPR') {
+ if ((last_type !== 'TK_START_EXPR' || !(token_type === 'TK_RESERVED' && in_array(token_text, ['var', 'let', 'const']))) && flags.last_text !== ':') {
+ // no need to force newline on 'var': for (var x = 0...)
+ if (token_type === 'TK_RESERVED' && token_text === 'if' && flags.last_word === 'else' && flags.last_text !== '{') {
+ // no newline for } else if {
+ output_space_before_token = true;
+ } else {
+ print_newline();
+ }
+ }
+ } else if (token_type === 'TK_RESERVED' && in_array(token_text, line_starters) && flags.last_text !== ')') {
+ print_newline();
+ }
+ } else if (is_array(flags.mode) && flags.last_text === ',' && last_last_text === '}') {
+ print_newline(); // }, in lists get a newline treatment
+ } else if (prefix === 'SPACE') {
+ output_space_before_token = true;
+ }
+ print_token();
+ flags.last_word = token_text;
+
+ if (token_type === 'TK_RESERVED' && token_text === 'do') {
+ flags.do_block = true;
+ }
+
+ if (token_type === 'TK_RESERVED' && token_text === 'if') {
+ flags.if_block = true;
+ }
+ }
+
+ function handle_semicolon() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ // Semicolon can be the start (and end) of a statement
+ output_space_before_token = false;
+ }
+ while (flags.mode === MODE.Statement && !flags.if_block && !flags.do_block) {
+ restore_mode();
+ }
+ print_token();
+ if (flags.mode === MODE.ObjectLiteral) {
+ // if we're in OBJECT mode and see a semicolon, its invalid syntax
+ // recover back to treating this as a BLOCK
+ flags.mode = MODE.BlockStatement;
+ }
+ }
+
+ function handle_string() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ // One difference - strings want at least a space before
+ output_space_before_token = true;
+ } else if (last_type === 'TK_RESERVED' || last_type === 'TK_WORD') {
+ output_space_before_token = true;
+ } else if (last_type === 'TK_COMMA' || last_type === 'TK_START_EXPR' || last_type === 'TK_EQUALS' || last_type === 'TK_OPERATOR') {
+ if (!start_of_object_property()) {
+ allow_wrap_or_preserved_newline();
+ }
+ } else {
+ print_newline();
+ }
+ print_token();
+ }
+
+ function handle_equals() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ }
+
+ if (flags.declaration_statement) {
+ // just got an '=' in a var-line, different formatting/line-breaking, etc will now be done
+ flags.declaration_assignment = true;
+ }
+ output_space_before_token = true;
+ print_token();
+ output_space_before_token = true;
+ }
+
+ function handle_comma() {
+ if (flags.declaration_statement) {
+ if (is_expression(flags.parent.mode)) {
+ // do not break on comma, for(var a = 1, b = 2)
+ flags.declaration_assignment = false;
+ }
+
+ print_token();
+
+ if (flags.declaration_assignment) {
+ flags.declaration_assignment = false;
+ print_newline(false, true);
+ } else {
+ output_space_before_token = true;
+ }
+ return;
+ }
+
+ print_token();
+ if (flags.mode === MODE.ObjectLiteral ||
+ (flags.mode === MODE.Statement && flags.parent.mode === MODE.ObjectLiteral)) {
+ if (flags.mode === MODE.Statement) {
+ restore_mode();
+ }
+ print_newline();
+ } else {
+ // EXPR or DO_BLOCK
+ output_space_before_token = true;
+ }
+
+ }
+
+ function handle_operator() {
+ // Check if this is a BlockStatement that should be treated as a ObjectLiteral
+ if (token_text === ':' && flags.mode === MODE.BlockStatement &&
+ last_last_text === '{' &&
+ (last_type === 'TK_WORD' || last_type === 'TK_RESERVED')){
+ flags.mode = MODE.ObjectLiteral;
+ }
+
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ }
+
+ var space_before = true;
+ var space_after = true;
+ if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
+ // "return" had a special handling in TK_WORD. Now we need to return the favor
+ output_space_before_token = true;
+ print_token();
+ return;
+ }
+
+ // hack for actionscript's import .*;
+ if (token_text === '*' && last_type === 'TK_DOT' && !last_last_text.match(/^\d+$/)) {
+ print_token();
+ return;
+ }
+
+ if (token_text === ':' && flags.in_case) {
+ flags.case_body = true;
+ indent();
+ print_token();
+ print_newline();
+ flags.in_case = false;
+ return;
+ }
+
+ if (token_text === '::') {
+ // no spaces around exotic namespacing syntax operator
+ print_token();
+ return;
+ }
+
+ // http://www.ecma-international.org/ecma-262/5.1/#sec-7.9.1
+ // if there is a newline between -- or ++ and anything else we should preserve it.
+ if (input_wanted_newline && (token_text === '--' || token_text === '++')) {
+ print_newline();
+ }
+
+ // Allow line wrapping between operators
+ if (last_type === 'TK_OPERATOR') {
+ allow_wrap_or_preserved_newline();
+ }
+
+ 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(flags.last_text, line_starters) || flags.last_text === ','))) {
+ // unary operators (and binary +/- pretending to be unary) special cases
+
+ space_before = false;
+ space_after = false;
+
+ if (flags.last_text === ';' && is_expression(flags.mode)) {
+ // for (;; ++i)
+ // ^^^
+ space_before = true;
+ }
+
+ if (last_type === 'TK_RESERVED') {
+ space_before = true;
+ }
+
+ if ((flags.mode === MODE.BlockStatement || flags.mode === MODE.Statement) && (flags.last_text === '{' || flags.last_text === ';')) {
+ // { foo; --i }
+ // foo(); --bar;
+ print_newline();
+ }
+ } else if (token_text === ':') {
+ if (flags.ternary_depth === 0) {
+ if (flags.mode === MODE.BlockStatement) {
+ flags.mode = MODE.ObjectLiteral;
+ }
+ space_before = false;
+ } else {
+ flags.ternary_depth -= 1;
+ }
+ } else if (token_text === '?') {
+ flags.ternary_depth += 1;
+ } else if (token_text === '*' && last_type === 'TK_RESERVED' && flags.last_text === 'function') {
+ space_before = false;
+ space_after = false;
+ }
+ output_space_before_token = output_space_before_token || space_before;
+ print_token();
+ output_space_before_token = space_after;
+ }
+
+ function handle_block_comment() {
+ var lines = split_newlines(token_text);
+ var j; // iterator for this case
+ var javadoc = false;
+ var starless = false;
+ var lastIndent = whitespace_before_token.join('');
+ var lastIndentLength = lastIndent.length;
+
+ // block comment starts with a new line
+ print_newline(false, true);
+ if (lines.length > 1) {
+ if (all_lines_start_with(lines.slice(1), '*')) {
+ javadoc = true;
+ }
+ else if (each_line_matches_indent(lines.slice(1), lastIndent)) {
+ starless = true;
+ }
+ }
+
+ // first line always indented
+ print_token(lines[0]);
+ for (j = 1; j < lines.length; j++) {
+ print_newline(false, true);
+ if (javadoc) {
+ // javadoc: reformat and re-indent
+ print_token(' ' + trim(lines[j]));
+ } else if (starless && lines[j].length > lastIndentLength) {
+ // starless: re-indent non-empty content, avoiding trim
+ print_token(lines[j].substring(lastIndentLength));
+ } else {
+ // normal comments output raw
+ output_lines[output_lines.length - 1].text.push(lines[j]);
+ }
+ }
+
+ // for comments of more than one line, make sure there's a new line after
+ print_newline(false, true);
+ }
+
+ function handle_inline_comment() {
+ output_space_before_token = true;
+ print_token();
+ output_space_before_token = true;
+ }
+
+ function handle_comment() {
+ if (input_wanted_newline) {
+ print_newline(false, true);
+ } else {
+ trim_output(true);
+ }
+
+ output_space_before_token = true;
+ print_token();
+ print_newline(false, true);
+ }
+
+ function handle_dot() {
+ if (start_of_statement()) {
+ // The conditional starts the statement if appropriate.
+ }
+
+ if (last_type === 'TK_RESERVED' && is_special_word(flags.last_text)) {
+ output_space_before_token = true;
+ } else {
+ // allow preserved newlines before dots in general
+ // force newlines on dots after close paren when break_chained - for bar().baz()
+ allow_wrap_or_preserved_newline(flags.last_text === ')' && opt.break_chained_methods);
+ }
+
+ print_token();
+ }
+
+ function handle_unknown() {
+ print_token();
+
+ if (token_text[token_text.length - 1] === '\n') {
+ print_newline();
+ }
+ }
+}
diff --git a/devtools/shared/jsbeautify/src/beautify-tests.js b/devtools/shared/jsbeautify/src/beautify-tests.js
new file mode 100644
index 000000000..254a9ca94
--- /dev/null
+++ b/devtools/shared/jsbeautify/src/beautify-tests.js
@@ -0,0 +1,2096 @@
+/*global js_beautify: true */
+
+function run_beautifier_tests(test_obj, Urlencoded, js_beautify, html_beautify, css_beautify)
+{
+
+ var opts = {
+ indent_size: 4,
+ indent_char: ' ',
+ preserve_newlines: true,
+ jslint_happy: false,
+ keep_array_indentation: false,
+ brace_style: 'collapse',
+ space_before_conditional: true,
+ break_chained_methods: false,
+ selector_separator: '\n',
+ end_with_newline: true
+ };
+
+ function test_js_beautifier(input)
+ {
+ return js_beautify(input, opts);
+ }
+
+ function test_html_beautifier(input)
+ {
+ return html_beautify(input, opts);
+ }
+
+ function test_css_beautifier(input)
+ {
+ return css_beautify(input, opts);
+ }
+
+ var sanitytest;
+
+ // test the input on beautifier with the current flag settings
+ // does not check the indentation / surroundings as bt() does
+ function test_fragment(input, expected)
+ {
+ expected = expected || input;
+ sanitytest.expect(input, expected);
+ // if the expected is different from input, run it again
+ // expected output should be unchanged when run twice.
+ if (expected != input) {
+ sanitytest.expect(expected, expected);
+ }
+ }
+
+
+
+ // test the input on beautifier with the current flag settings
+ // test both the input as well as { input } wrapping
+ function bt(input, expectation)
+ {
+ var wrapped_input, wrapped_expectation;
+
+ expectation = expectation || input;
+ sanitytest.test_function(test_js_beautifier, 'js_beautify');
+ test_fragment(input, expectation);
+
+ // test also the returned indentation
+ // e.g if input = "asdf();"
+ // then test that this remains properly formatted as well:
+ // {
+ // asdf();
+ // indent;
+ // }
+
+ if (opts.indent_size === 4 && input) {
+ wrapped_input = '{\n' + input.replace(/^(.+)$/mg, ' $1') + '\n foo = bar;\n}';
+ wrapped_expectation = '{\n' + expectation.replace(/^(.+)$/mg, ' $1') + '\n foo = bar;\n}';
+ test_fragment(wrapped_input, wrapped_expectation);
+ }
+
+ }
+
+ // test html
+ function bth(input, expectation)
+ {
+ var wrapped_input, wrapped_expectation, field_input, field_expectation;
+
+ expectation = expectation || input;
+ sanitytest.test_function(test_html_beautifier, 'html_beautify');
+ test_fragment(input, expectation);
+
+ if (opts.indent_size === 4 && input) {
+ wrapped_input = '<div>\n' + input.replace(/^(.+)$/mg, ' $1') + '\n <span>inline</span>\n</div>';
+ wrapped_expectation = '<div>\n' + expectation.replace(/^(.+)$/mg, ' $1') + '\n <span>inline</span>\n</div>';
+ test_fragment(wrapped_input, wrapped_expectation);
+ }
+
+ // Test that handlebars non-block {{}} tags act as content and do not
+ // get any spacing or line breaks.
+ if (input.indexOf('content') != -1) {
+ // Just {{field}}
+ field_input = input.replace(/content/g, '{{field}}');
+ field_expectation = expectation.replace(/content/g, '{{field}}');
+ test_fragment(field_input, field_expectation);
+
+ // handlebars comment
+ field_input = input.replace(/content/g, '{{! comment}}');
+ field_expectation = expectation.replace(/content/g, '{{! comment}}');
+ test_fragment(field_input, field_expectation);
+
+ // mixed {{field}} and content
+ field_input = input.replace(/content/g, 'pre{{field1}} {{field2}} {{field3}}post');
+ field_expectation = expectation.replace(/content/g, 'pre{{field1}} {{field2}} {{field3}}post');
+ test_fragment(field_input, field_expectation);
+ }
+ }
+
+ // test css
+ function btc(input, expectation)
+ {
+ var wrapped_input, wrapped_expectation;
+
+ expectation = expectation || input;
+ sanitytest.test_function(test_css_beautifier, 'css_beautify');
+ test_fragment(input, expectation);
+ }
+
+ // test the input on beautifier with the current flag settings,
+ // but dont't
+ function bt_braces(input, expectation)
+ {
+ var braces_ex = opts.brace_style;
+ opts.brace_style = 'expand';
+ bt(input, expectation);
+ opts.brace_style = braces_ex;
+ }
+
+ function beautifier_tests()
+ {
+ sanitytest = test_obj;
+
+ opts.indent_size = 4;
+ opts.indent_char = ' ';
+ opts.preserve_newlines = true;
+ opts.jslint_happy = false;
+ opts.keep_array_indentation = false;
+ opts.brace_style = "collapse";
+
+
+ // unicode support
+ bt('var ' + String.fromCharCode(3232) + '_' + String.fromCharCode(3232) + ' = "hi";');
+ bt('var ' + String.fromCharCode(228) + 'x = {\n ' + String.fromCharCode(228) + 'rgerlich: true\n};');
+
+ bt('');
+ bt('return .5');
+ test_fragment(' return .5');
+ test_fragment(' return .5;\n a();');
+ bt('a = 1', 'a = 1');
+ bt('a=1', 'a = 1');
+ bt("a();\n\nb();", "a();\n\nb();");
+ bt('var a = 1 var b = 2', "var a = 1\nvar b = 2");
+ bt('var a=1, b=c[d], e=6;', 'var a = 1,\n b = c[d],\n e = 6;');
+ bt('var a,\n b,\n c;');
+ bt('let a = 1 let b = 2', "let a = 1\nlet b = 2");
+ bt('let a=1, b=c[d], e=6;', 'let a = 1,\n b = c[d],\n e = 6;');
+ bt('let a,\n b,\n c;');
+ bt('const a = 1 const b = 2', "const a = 1\nconst b = 2");
+ bt('const a=1, b=c[d], e=6;', 'const a = 1,\n b = c[d],\n e = 6;');
+ bt('const a,\n b,\n c;');
+ bt('a = " 12345 "');
+ bt("a = ' 12345 '");
+ bt('if (a == 1) b = 2;', "if (a == 1) b = 2;");
+ bt('if(1){2}else{3}', "if (1) {\n 2\n} else {\n 3\n}");
+ bt('if(1||2);', 'if (1 || 2);');
+ bt('(a==1)||(b==2)', '(a == 1) || (b == 2)');
+ bt('var a = 1 if (2) 3;', "var a = 1\nif (2) 3;");
+ bt('a = a + 1');
+ bt('a = a == 1');
+ bt('/12345[^678]*9+/.match(a)');
+ bt('a /= 5');
+ bt('a = 0.5 * 3');
+ bt('a *= 10.55');
+ bt('a < .5');
+ bt('a <= .5');
+ bt('a<.5', 'a < .5');
+ bt('a<=.5', 'a <= .5');
+ bt('a = 0xff;');
+ bt('a=0xff+4', 'a = 0xff + 4');
+ bt('a = [1, 2, 3, 4]');
+ bt('F*(g/=f)*g+b', 'F * (g /= f) * g + b');
+ bt('a.b({c:d})', 'a.b({\n c: d\n})');
+ bt('a.b\n(\n{\nc:\nd\n}\n)', 'a.b({\n c: d\n})');
+ bt('a.b({c:"d"})', 'a.b({\n c: "d"\n})');
+ bt('a.b\n(\n{\nc:\n"d"\n}\n)', 'a.b({\n c: "d"\n})');
+ bt('a=!b', 'a = !b');
+ bt('a=!!b', 'a = !!b');
+ bt('a?b:c', 'a ? b : c');
+ bt('a?1:2', 'a ? 1 : 2');
+ bt('a?(b):c', 'a ? (b) : c');
+ bt('x={a:1,b:w=="foo"?x:y,c:z}', 'x = {\n a: 1,\n b: w == "foo" ? x : y,\n c: z\n}');
+ bt('x=a?b?c?d:e:f:g;', 'x = a ? b ? c ? d : e : f : g;');
+ bt('x=a?b?c?d:{e1:1,e2:2}:f:g;', 'x = a ? b ? c ? d : {\n e1: 1,\n e2: 2\n} : f : g;');
+ bt('function void(void) {}');
+ bt('if(!a)foo();', 'if (!a) foo();');
+ bt('a=~a', 'a = ~a');
+ bt('a;/*comment*/b;', "a; /*comment*/\nb;");
+ bt('a;/* comment */b;', "a; /* comment */\nb;");
+ test_fragment('a;/*\ncomment\n*/b;', "a;\n/*\ncomment\n*/\nb;"); // simple comments don't get touched at all
+ bt('a;/**\n* javadoc\n*/b;', "a;\n/**\n * javadoc\n */\nb;");
+ test_fragment('a;/**\n\nno javadoc\n*/b;', "a;\n/**\n\nno javadoc\n*/\nb;");
+ bt('a;/*\n* javadoc\n*/b;', "a;\n/*\n * javadoc\n */\nb;"); // comment blocks detected and reindented even w/o javadoc starter
+ bt('if(a)break;', "if (a) break;");
+ bt('if(a){break}', "if (a) {\n break\n}");
+ bt('if((a))foo();', 'if ((a)) foo();');
+ bt('for(var i=0;;) a', 'for (var i = 0;;) a');
+ bt('for(var i=0;;)\na', 'for (var i = 0;;)\n a');
+ bt('a++;', 'a++;');
+ bt('for(;;i++)a()', 'for (;; i++) a()');
+ bt('for(;;i++)\na()', 'for (;; i++)\n a()');
+ bt('for(;;++i)a', 'for (;; ++i) a');
+ bt('return(1)', 'return (1)');
+ bt('try{a();}catch(b){c();}finally{d();}', "try {\n a();\n} catch (b) {\n c();\n} finally {\n d();\n}");
+ bt('(xx)()'); // magic function call
+ bt('a[1]()'); // another magic function call
+ bt('if(a){b();}else if(c) foo();', "if (a) {\n b();\n} else if (c) foo();");
+ bt('switch(x) {case 0: case 1: a(); break; default: break}', "switch (x) {\n case 0:\n case 1:\n a();\n break;\n default:\n break\n}");
+ bt('switch(x){case -1:break;case !y:break;}', 'switch (x) {\n case -1:\n break;\n case !y:\n break;\n}');
+ bt('a !== b');
+ bt('if (a) b(); else c();', "if (a) b();\nelse c();");
+ bt("// comment\n(function something() {})"); // typical greasemonkey start
+ bt("{\n\n x();\n\n}"); // was: duplicating newlines
+ bt('if (a in b) foo();');
+ bt('var a, b;');
+ // bt('var a, b');
+ bt('{a:1, b:2}', "{\n a: 1,\n b: 2\n}");
+ bt('a={1:[-1],2:[+1]}', 'a = {\n 1: [-1],\n 2: [+1]\n}');
+ bt('var l = {\'a\':\'1\', \'b\':\'2\'}', "var l = {\n 'a': '1',\n 'b': '2'\n}");
+ bt('if (template.user[n] in bk) foo();');
+ bt('{{}/z/}', "{\n {}\n /z/\n}");
+ bt('return 45', "return 45");
+ bt('return this.prevObject ||\n\n this.constructor(null);');
+ bt('If[1]', "If[1]");
+ bt('Then[1]', "Then[1]");
+ bt('a = 1e10', "a = 1e10");
+ bt('a = 1.3e10', "a = 1.3e10");
+ bt('a = 1.3e-10', "a = 1.3e-10");
+ bt('a = -1.3e-10', "a = -1.3e-10");
+ bt('a = 1e-10', "a = 1e-10");
+ bt('a = e - 10', "a = e - 10");
+ bt('a = 11-10', "a = 11 - 10");
+ bt("a = 1;// comment", "a = 1; // comment");
+ bt("a = 1; // comment", "a = 1; // comment");
+ bt("a = 1;\n // comment", "a = 1;\n// comment");
+ bt('a = [-1, -1, -1]');
+
+ // The exact formatting these should have is open for discussion, but they are at least reasonable
+ bt('a = [ // comment\n -1, -1, -1\n]');
+ bt('var a = [ // comment\n -1, -1, -1\n]');
+ bt('a = [ // comment\n -1, // comment\n -1, -1\n]');
+ bt('var a = [ // comment\n -1, // comment\n -1, -1\n]');
+
+ bt('o = [{a:b},{c:d}]', 'o = [{\n a: b\n}, {\n c: d\n}]');
+
+ bt("if (a) {\n do();\n}"); // was: extra space appended
+
+ bt("if (a) {\n// comment\n}else{\n// comment\n}", "if (a) {\n // comment\n} else {\n // comment\n}"); // if/else statement with empty body
+ bt("if (a) {\n// comment\n// comment\n}", "if (a) {\n // comment\n // comment\n}"); // multiple comments indentation
+ bt("if (a) b() else c();", "if (a) b()\nelse c();");
+ bt("if (a) b() else if c() d();", "if (a) b()\nelse if c() d();");
+
+ bt("{}");
+ bt("{\n\n}");
+ bt("do { a(); } while ( 1 );", "do {\n a();\n} while (1);");
+ bt("do {} while (1);");
+ bt("do {\n} while (1);", "do {} while (1);");
+ bt("do {\n\n} while (1);");
+ bt("var a = x(a, b, c)");
+ bt("delete x if (a) b();", "delete x\nif (a) b();");
+ bt("delete x[x] if (a) b();", "delete x[x]\nif (a) b();");
+ bt("for(var a=1,b=2)d", "for (var a = 1, b = 2) d");
+ bt("for(var a=1,b=2,c=3) d", "for (var a = 1, b = 2, c = 3) d");
+ bt("for(var a=1,b=2,c=3;d<3;d++)\ne", "for (var a = 1, b = 2, c = 3; d < 3; d++)\n e");
+ bt("function x(){(a||b).c()}", "function x() {\n (a || b).c()\n}");
+ bt("function x(){return - 1}", "function x() {\n return -1\n}");
+ bt("function x(){return ! a}", "function x() {\n return !a\n}");
+ bt("x => x", "x => x");
+ bt("(x) => x", "(x) => x");
+ bt("x => { x }", "x => {\n x\n}");
+ bt("(x) => { x }", "(x) => {\n x\n}");
+
+ // a common snippet in jQuery plugins
+ bt("settings = $.extend({},defaults,settings);", "settings = $.extend({}, defaults, settings);");
+
+ // reserved words used as property names
+ bt("$http().then().finally().default()", "$http().then().finally().default()");
+ bt("$http()\n.then()\n.finally()\n.default()", "$http()\n .then()\n .finally()\n .default()");
+ bt("$http().when.in.new.catch().throw()", "$http().when.in.new.catch().throw()");
+ bt("$http()\n.when\n.in\n.new\n.catch()\n.throw()", "$http()\n .when\n .in\n .new\n .catch()\n .throw()");
+
+ bt('{xxx;}()', '{\n xxx;\n}()');
+
+ bt("a = 'a'\nb = 'b'");
+ bt("a = /reg/exp");
+ bt("a = /reg/");
+ bt('/abc/.test()');
+ bt('/abc/i.test()');
+ bt("{/abc/i.test()}", "{\n /abc/i.test()\n}");
+ bt('var x=(a)/a;', 'var x = (a) / a;');
+
+ bt('x != -1', 'x != -1');
+
+ bt('for (; s-->0;)t', 'for (; s-- > 0;) t');
+ bt('for (; s++>0;)u', 'for (; s++ > 0;) u');
+ bt('a = s++>s--;', 'a = s++ > s--;');
+ bt('a = s++>--s;', 'a = s++ > --s;');
+
+ bt('{x=#1=[]}', '{\n x = #1=[]\n}');
+ bt('{a:#1={}}', '{\n a: #1={}\n}');
+ bt('{a:#1#}', '{\n a: #1#\n}');
+
+ test_fragment('"incomplete-string');
+ test_fragment("'incomplete-string");
+ test_fragment('/incomplete-regex');
+ test_fragment('`incomplete-template-string');
+
+ test_fragment('{a:1},{a:2}', '{\n a: 1\n}, {\n a: 2\n}');
+ test_fragment('var ary=[{a:1}, {a:2}];', 'var ary = [{\n a: 1\n}, {\n a: 2\n}];');
+
+ test_fragment('{a:#1', '{\n a: #1'); // incomplete
+ test_fragment('{a:#', '{\n a: #'); // incomplete
+
+ test_fragment('}}}', '}\n}\n}'); // incomplete
+
+ test_fragment('<!--\nvoid();\n// -->', '<!--\nvoid();\n// -->');
+
+ test_fragment('a=/regexp', 'a = /regexp'); // incomplete regexp
+
+ bt('{a:#1=[],b:#1#,c:#999999#}', '{\n a: #1=[],\n b: #1#,\n c: #999999#\n}');
+
+ bt("a = 1e+2");
+ bt("a = 1e-2");
+ bt("do{x()}while(a>1)", "do {\n x()\n} while (a > 1)");
+
+ bt("x(); /reg/exp.match(something)", "x();\n/reg/exp.match(something)");
+
+ test_fragment("something();(", "something();\n(");
+ test_fragment("#!she/bangs, she bangs\nf=1", "#!she/bangs, she bangs\n\nf = 1");
+ test_fragment("#!she/bangs, she bangs\n\nf=1", "#!she/bangs, she bangs\n\nf = 1");
+ test_fragment("#!she/bangs, she bangs\n\n/* comment */", "#!she/bangs, she bangs\n\n/* comment */");
+ test_fragment("#!she/bangs, she bangs\n\n\n/* comment */", "#!she/bangs, she bangs\n\n\n/* comment */");
+ test_fragment("#", "#");
+ test_fragment("#!", "#!");
+
+ bt("function namespace::something()");
+
+ test_fragment("<!--\nsomething();\n-->", "<!--\nsomething();\n-->");
+ test_fragment("<!--\nif(i<0){bla();}\n-->", "<!--\nif (i < 0) {\n bla();\n}\n-->");
+
+ bt('{foo();--bar;}', '{\n foo();\n --bar;\n}');
+ bt('{foo();++bar;}', '{\n foo();\n ++bar;\n}');
+ bt('{--bar;}', '{\n --bar;\n}');
+ bt('{++bar;}', '{\n ++bar;\n}');
+
+ // Handling of newlines around unary ++ and -- operators
+ bt('{foo\n++bar;}', '{\n foo\n ++bar;\n}');
+ bt('{foo++\nbar;}', '{\n foo++\n bar;\n}');
+
+ // This is invalid, but harder to guard against. Issue #203.
+ bt('{foo\n++\nbar;}', '{\n foo\n ++\n bar;\n}');
+
+
+ // regexps
+ bt('a(/abc\\/\\/def/);b()', "a(/abc\\/\\/def/);\nb()");
+ bt('a(/a[b\\[\\]c]d/);b()', "a(/a[b\\[\\]c]d/);\nb()");
+ test_fragment('a(/a[b\\[', "a(/a[b\\["); // incomplete char class
+ // allow unescaped / in char classes
+ bt('a(/[a/b]/);b()', "a(/[a/b]/);\nb()");
+
+ bt('function foo() {\n return [\n "one",\n "two"\n ];\n}');
+ bt('a=[[1,2],[4,5],[7,8]]', "a = [\n [1, 2],\n [4, 5],\n [7, 8]\n]");
+ bt('a=[[1,2],[4,5],function(){},[7,8]]',
+ "a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]");
+ bt('a=[[1,2],[4,5],function(){},function(){},[7,8]]',
+ "a = [\n [1, 2],\n [4, 5],\n function() {},\n function() {},\n [7, 8]\n]");
+ bt('a=[[1,2],[4,5],function(){},[7,8]]',
+ "a = [\n [1, 2],\n [4, 5],\n function() {},\n [7, 8]\n]");
+ bt('a=[b,c,function(){},function(){},d]',
+ "a = [b, c,\n function() {},\n function() {},\n d\n]");
+ bt('a=[a[1],b[4],c[d[7]]]', "a = [a[1], b[4], c[d[7]]]");
+ bt('[1,2,[3,4,[5,6],7],8]', "[1, 2, [3, 4, [5, 6], 7], 8]");
+
+ bt('[[["1","2"],["3","4"]],[["5","6","7"],["8","9","0"]],[["1","2","3"],["4","5","6","7"],["8","9","0"]]]',
+ '[\n [\n ["1", "2"],\n ["3", "4"]\n ],\n [\n ["5", "6", "7"],\n ["8", "9", "0"]\n ],\n [\n ["1", "2", "3"],\n ["4", "5", "6", "7"],\n ["8", "9", "0"]\n ]\n]');
+
+ bt('{[x()[0]];indent;}', '{\n [x()[0]];\n indent;\n}');
+
+ bt('return ++i', 'return ++i');
+ bt('return !!x', 'return !!x');
+ bt('return !x', 'return !x');
+ bt('return [1,2]', 'return [1, 2]');
+ bt('return;', 'return;');
+ bt('return\nfunc', 'return\nfunc');
+ bt('catch(e)', 'catch (e)');
+ bt('yield [1, 2]');
+
+ bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},c=4;',
+ 'var a = 1,\n b = {\n foo: 2,\n bar: 3\n },\n {\n baz: 4,\n wham: 5\n }, c = 4;');
+ bt('var a=1,b={foo:2,bar:3},{baz:4,wham:5},\nc=4;',
+ 'var a = 1,\n b = {\n foo: 2,\n bar: 3\n },\n {\n baz: 4,\n wham: 5\n },\n c = 4;');
+
+
+ // inline comment
+ bt('function x(/*int*/ start, /*string*/ foo)', 'function x( /*int*/ start, /*string*/ foo)');
+
+ // javadoc comment
+ bt('/**\n* foo\n*/', '/**\n * foo\n */');
+ bt('{\n/**\n* foo\n*/\n}', '{\n /**\n * foo\n */\n}');
+
+ // starless block comment
+ bt('/**\nfoo\n*/');
+ bt('/**\nfoo\n**/');
+ bt('/**\nfoo\nbar\n**/');
+ bt('/**\nfoo\n\nbar\n**/');
+ bt('/**\nfoo\n bar\n**/');
+ bt('{\n/**\nfoo\n*/\n}', '{\n /**\n foo\n */\n}');
+ bt('{\n/**\nfoo\n**/\n}', '{\n /**\n foo\n **/\n}');
+ bt('{\n/**\nfoo\nbar\n**/\n}', '{\n /**\n foo\n bar\n **/\n}');
+ bt('{\n/**\nfoo\n\nbar\n**/\n}', '{\n /**\n foo\n\n bar\n **/\n}');
+ bt('{\n/**\nfoo\n bar\n**/\n}', '{\n /**\n foo\n bar\n **/\n}');
+ bt('{\n /**\n foo\nbar\n **/\n}');
+
+ bt('var a,b,c=1,d,e,f=2;', 'var a, b, c = 1,\n d, e, f = 2;');
+ bt('var a,b,c=[],d,e,f=2;', 'var a, b, c = [],\n d, e, f = 2;');
+ bt('function() {\n var a, b, c, d, e = [],\n f;\n}');
+
+ bt('do/regexp/;\nwhile(1);', 'do /regexp/;\nwhile (1);'); // hmmm
+
+ bt('var a = a,\na;\nb = {\nb\n}', 'var a = a,\n a;\nb = {\n b\n}');
+
+ bt('var a = a,\n /* c */\n b;');
+ bt('var a = a,\n // c\n b;');
+
+ bt('foo.("bar");'); // weird element referencing
+
+
+ bt('if (a) a()\nelse b()\nnewline()');
+ bt('if (a) a()\nnewline()');
+ bt('a=typeof(x)', 'a = typeof(x)');
+
+ bt('var a = function() {\n return null;\n },\n b = false;');
+
+ bt('var a = function() {\n func1()\n}');
+ bt('var a = function() {\n func1()\n}\nvar b = function() {\n func2()\n}');
+
+ // code with and without semicolons
+ bt( 'var whatever = require("whatever");\nfunction() {\n a = 6;\n}',
+ 'var whatever = require("whatever");\n\nfunction() {\n a = 6;\n}');
+ bt( 'var whatever = require("whatever")\nfunction() {\n a = 6\n}',
+ 'var whatever = require("whatever")\n\nfunction() {\n a = 6\n}');
+
+
+ opts.jslint_happy = true;
+
+ bt('a=typeof(x)', 'a = typeof (x)');
+ bt('x();\n\nfunction(){}', 'x();\n\nfunction () {}');
+ bt('function () {\n var a, b, c, d, e = [],\n f;\n}');
+ bt('switch(x) {case 0: case 1: a(); break; default: break}',
+ "switch (x) {\ncase 0:\ncase 1:\n a();\n break;\ndefault:\n break\n}");
+ bt('switch(x){case -1:break;case !y:break;}',
+ 'switch (x) {\ncase -1:\n break;\ncase !y:\n break;\n}');
+ test_fragment("// comment 1\n(function()", "// comment 1\n(function ()"); // typical greasemonkey start
+ bt('var o1=$.extend(a);function(){alert(x);}', 'var o1 = $.extend(a);\n\nfunction () {\n alert(x);\n}');
+ bt('function* () {\n yield 1;\n}');
+
+ opts.jslint_happy = false;
+
+ bt('switch(x) {case 0: case 1: a(); break; default: break}',
+ "switch (x) {\n case 0:\n case 1:\n a();\n break;\n default:\n break\n}");
+ bt('switch(x){case -1:break;case !y:break;}',
+ 'switch (x) {\n case -1:\n break;\n case !y:\n break;\n}');
+ test_fragment("// comment 2\n(function()", "// comment 2\n(function()"); // typical greasemonkey start
+ bt("var a2, b2, c2, d2 = 0, c = function() {}, d = '';", "var a2, b2, c2, d2 = 0,\n c = function() {},\n d = '';");
+ bt("var a2, b2, c2, d2 = 0, c = function() {},\nd = '';", "var a2, b2, c2, d2 = 0,\n c = function() {},\n d = '';");
+ bt('var o2=$.extend(a);function(){alert(x);}', 'var o2 = $.extend(a);\n\nfunction() {\n alert(x);\n}');
+ bt('function*() {\n yield 1;\n}');
+
+ bt('function* x() {\n yield 1;\n}');
+
+ bt('{"x":[{"a":1,"b":3},7,8,8,8,8,{"b":99},{"a":11}]}', '{\n "x": [{\n "a": 1,\n "b": 3\n },\n 7, 8, 8, 8, 8, {\n "b": 99\n }, {\n "a": 11\n }\n ]\n}');
+
+ bt('{"1":{"1a":"1b"},"2"}', '{\n "1": {\n "1a": "1b"\n },\n "2"\n}');
+ bt('{a:{a:b},c}', '{\n a: {\n a: b\n },\n c\n}');
+
+ bt('{[y[a]];keep_indent;}', '{\n [y[a]];\n keep_indent;\n}');
+
+ bt('if (x) {y} else { if (x) {y}}', 'if (x) {\n y\n} else {\n if (x) {\n y\n }\n}');
+
+ bt('if (foo) one()\ntwo()\nthree()');
+ bt('if (1 + foo() && bar(baz()) / 2) one()\ntwo()\nthree()');
+ bt('if (1 + foo() && bar(baz()) / 2) one();\ntwo();\nthree();');
+
+ opts.indent_size = 1;
+ opts.indent_char = ' ';
+ bt('{ one_char() }', "{\n one_char()\n}");
+
+ bt('var a,b=1,c=2', 'var a, b = 1,\n c = 2');
+
+ opts.indent_size = 4;
+ opts.indent_char = ' ';
+ bt('{ one_char() }', "{\n one_char()\n}");
+
+ opts.indent_size = 1;
+ opts.indent_char = "\t";
+ bt('{ one_char() }', "{\n\tone_char()\n}");
+ bt('x = a ? b : c; x;', 'x = a ? b : c;\nx;');
+
+ //set to something else than it should change to, but with tabs on, should override
+ opts.indent_size = 5;
+ opts.indent_char = ' ';
+ opts.indent_with_tabs = true;
+
+ bt('{ one_char() }', "{\n\tone_char()\n}");
+ bt('x = a ? b : c; x;', 'x = a ? b : c;\nx;');
+
+ opts.indent_size = 4;
+ opts.indent_char = ' ';
+ opts.indent_with_tabs = false;
+
+ opts.preserve_newlines = false;
+
+ bt('var\na=dont_preserve_newlines;', 'var a = dont_preserve_newlines;');
+
+ // make sure the blank line between function definitions stays
+ // even when preserve_newlines = false
+ bt('function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}');
+ bt('function foo() {\n return 1;\n}\nfunction foo() {\n return 1;\n}',
+ 'function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}'
+ );
+ bt('function foo() {\n return 1;\n}\n\n\nfunction foo() {\n return 1;\n}',
+ 'function foo() {\n return 1;\n}\n\nfunction foo() {\n return 1;\n}'
+ );
+
+ opts.preserve_newlines = true;
+ bt('var\na=do_preserve_newlines;', 'var\n a = do_preserve_newlines;');
+ bt('// a\n// b\n\n// c\n// d');
+ bt('if (foo) // comment\n{\n bar();\n}');
+
+
+ opts.keep_array_indentation = false;
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f']",
+ "a = ['a', 'b', 'c',\n 'd', 'e', 'f'\n]");
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']",
+ "a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i'\n]");
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']",
+ "a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i'\n]");
+ bt('var x = [{}\n]', 'var x = [{}]');
+ bt('var x = [{foo:bar}\n]', 'var x = [{\n foo: bar\n}]');
+ bt("a = ['something',\n 'completely',\n 'different'];\nif (x);",
+ "a = ['something',\n 'completely',\n 'different'\n];\nif (x);");
+ bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
+
+ bt("a = ['a', 'b','c']", "a = ['a', 'b', 'c']");
+ bt("x = [{'a':0}]",
+ "x = [{\n 'a': 0\n}]");
+ bt('{a([[a1]], {b;});}',
+ '{\n a([\n [a1]\n ], {\n b;\n });\n}');
+ bt("a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();",
+ "a();\n[\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n].toString();");
+ bt("a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();",
+ "a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n].toString();");
+ bt("function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}",
+ "function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}");
+ bt('function foo() {\n return [\n "one",\n "two"\n ];\n}');
+ // 4 spaces per indent input, processed with 4-spaces per indent
+ bt( "function foo() {\n" +
+ " return [\n" +
+ " {\n" +
+ " one: 'x',\n" +
+ " two: [\n" +
+ " {\n" +
+ " id: 'a',\n" +
+ " name: 'apple'\n" +
+ " }, {\n" +
+ " id: 'b',\n" +
+ " name: 'banana'\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ];\n" +
+ "}",
+ "function foo() {\n" +
+ " return [{\n" +
+ " one: 'x',\n" +
+ " two: [{\n" +
+ " id: 'a',\n" +
+ " name: 'apple'\n" +
+ " }, {\n" +
+ " id: 'b',\n" +
+ " name: 'banana'\n" +
+ " }]\n" +
+ " }];\n" +
+ "}");
+ // 3 spaces per indent input, processed with 4-spaces per indent
+ bt( "function foo() {\n" +
+ " return [\n" +
+ " {\n" +
+ " one: 'x',\n" +
+ " two: [\n" +
+ " {\n" +
+ " id: 'a',\n" +
+ " name: 'apple'\n" +
+ " }, {\n" +
+ " id: 'b',\n" +
+ " name: 'banana'\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ];\n" +
+ "}",
+ "function foo() {\n" +
+ " return [{\n" +
+ " one: 'x',\n" +
+ " two: [{\n" +
+ " id: 'a',\n" +
+ " name: 'apple'\n" +
+ " }, {\n" +
+ " id: 'b',\n" +
+ " name: 'banana'\n" +
+ " }]\n" +
+ " }];\n" +
+ "}");
+
+ opts.keep_array_indentation = true;
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f']");
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']");
+ bt("a = ['a', 'b', 'c',\n 'd', 'e', 'f',\n 'g', 'h', 'i']");
+ bt('var x = [{}\n]', 'var x = [{}\n]');
+ bt('var x = [{foo:bar}\n]', 'var x = [{\n foo: bar\n }\n]');
+ bt("a = ['something',\n 'completely',\n 'different'];\nif (x);");
+ bt("a = ['a','b','c']", "a = ['a', 'b', 'c']");
+ bt("a = ['a', 'b','c']", "a = ['a', 'b', 'c']");
+ bt("x = [{'a':0}]",
+ "x = [{\n 'a': 0\n}]");
+ bt('{a([[a1]], {b;});}',
+ '{\n a([[a1]], {\n b;\n });\n}');
+ bt("a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();",
+ "a();\n [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();");
+ bt("a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();",
+ "a();\na = [\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ].toString();");
+ bt("function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}",
+ "function() {\n Foo([\n ['sdfsdfsd'],\n ['sdfsdfsdf']\n ]);\n}");
+ bt('function foo() {\n return [\n "one",\n "two"\n ];\n}');
+ // 4 spaces per indent input, processed with 4-spaces per indent
+ bt( "function foo() {\n" +
+ " return [\n" +
+ " {\n" +
+ " one: 'x',\n" +
+ " two: [\n" +
+ " {\n" +
+ " id: 'a',\n" +
+ " name: 'apple'\n" +
+ " }, {\n" +
+ " id: 'b',\n" +
+ " name: 'banana'\n" +
+ " }\n" +
+ " ]\n" +
+ " }\n" +
+ " ];\n" +
+ "}");
+ // 3 spaces per indent input, processed with 4-spaces per indent
+ // Should be unchanged, but is not - #445
+// bt( "function foo() {\n" +
+// " return [\n" +
+// " {\n" +
+// " one: 'x',\n" +
+// " two: [\n" +
+// " {\n" +
+// " id: 'a',\n" +
+// " name: 'apple'\n" +
+// " }, {\n" +
+// " id: 'b',\n" +
+// " name: 'banana'\n" +
+// " }\n" +
+// " ]\n" +
+// " }\n" +
+// " ];\n" +
+// "}");
+
+
+ opts.keep_array_indentation = false;
+
+
+ bt('a = //comment\n /regex/;');
+
+ test_fragment('/*\n * X\n */');
+ test_fragment('/*\r\n * X\r\n */', '/*\n * X\n */');
+
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}', 'if (a) {\n b;\n} else {\n c;\n}');
+
+
+ opts.brace_style = 'expand';
+
+ bt('//case 1\nif (a == 1)\n{}\n//case 2\nelse if (a == 2)\n{}');
+ bt('if(1){2}else{3}', "if (1)\n{\n 2\n}\nelse\n{\n 3\n}");
+ bt('try{a();}catch(b){c();}catch(d){}finally{e();}',
+ "try\n{\n a();\n}\ncatch (b)\n{\n c();\n}\ncatch (d)\n{}\nfinally\n{\n e();\n}");
+ bt('if(a){b();}else if(c) foo();',
+ "if (a)\n{\n b();\n}\nelse if (c) foo();");
+ bt('if(X)if(Y)a();else b();else c();',
+ "if (X)\n if (Y) a();\n else b();\nelse c();");
+ bt("if (a) {\n// comment\n}else{\n// comment\n}",
+ "if (a)\n{\n // comment\n}\nelse\n{\n // comment\n}"); // if/else statement with empty body
+ bt('if (x) {y} else { if (x) {y}}',
+ 'if (x)\n{\n y\n}\nelse\n{\n if (x)\n {\n y\n }\n}');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}',
+ 'if (a)\n{\n b;\n}\nelse\n{\n c;\n}');
+ test_fragment(' /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}',
+ ' /*\n * xx\n */\n // xx\n if (foo)\n {\n bar();\n }');
+ bt('if (foo)\n{}\nelse /regex/.test();');
+ bt('if (foo) /regex/.test();');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}', 'if (a)\n{\n b;\n}\nelse\n{\n c;\n}');
+ test_fragment('if (foo) {', 'if (foo)\n{');
+ test_fragment('foo {', 'foo\n{');
+ test_fragment('return {', 'return {'); // return needs the brace.
+ test_fragment('return /* inline */ {', 'return /* inline */ {');
+ // test_fragment('return\n{', 'return\n{'); // can't support this?, but that's an improbable and extreme case anyway.
+ test_fragment('return;\n{', 'return;\n{');
+ bt("throw {}");
+ bt("throw {\n foo;\n}");
+ bt('var foo = {}');
+ bt('if (foo) bar();\nelse break');
+ bt('function x() {\n foo();\n}zzz', 'function x()\n{\n foo();\n}\nzzz');
+ bt('a: do {} while (); xxx', 'a: do {} while ();\nxxx');
+ bt('var a = new function();');
+ bt('var a = new function() {};');
+ bt('var a = new function()\n{};', 'var a = new function() {};');
+ bt('var a = new function a()\n{};');
+ bt('var a = new function a()\n {},\n b = new function b()\n {};');
+ test_fragment('new function');
+ bt("foo({\n 'a': 1\n},\n10);",
+ "foo(\n {\n 'a': 1\n },\n 10);");
+ bt('(["foo","bar"]).each(function(i) {return i;});',
+ '(["foo", "bar"]).each(function(i)\n{\n return i;\n});');
+ bt('(function(i) {return i;})();',
+ '(function(i)\n{\n return i;\n})();');
+ bt( "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/\n" +
+ " {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test(\n" +
+ "/*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "},\n" +
+ "/*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test(\n" +
+ " /*Argument 1*/\n" +
+ " {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test( /*Argument 1*/\n" +
+ "{\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */\n" +
+ "{\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/\n" +
+ " {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+
+ opts.brace_style = 'collapse';
+
+ bt('//case 1\nif (a == 1) {}\n//case 2\nelse if (a == 2) {}');
+ bt('if(1){2}else{3}', "if (1) {\n 2\n} else {\n 3\n}");
+ bt('try{a();}catch(b){c();}catch(d){}finally{e();}',
+ "try {\n a();\n} catch (b) {\n c();\n} catch (d) {} finally {\n e();\n}");
+ bt('if(a){b();}else if(c) foo();',
+ "if (a) {\n b();\n} else if (c) foo();");
+ bt("if (a) {\n// comment\n}else{\n// comment\n}",
+ "if (a) {\n // comment\n} else {\n // comment\n}"); // if/else statement with empty body
+ bt('if (x) {y} else { if (x) {y}}',
+ 'if (x) {\n y\n} else {\n if (x) {\n y\n }\n}');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}',
+ 'if (a) {\n b;\n} else {\n c;\n}');
+ test_fragment(' /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}',
+ ' /*\n * xx\n */\n // xx\n if (foo) {\n bar();\n }');
+ bt('if (foo) {} else /regex/.test();');
+ bt('if (foo) /regex/.test();');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}', 'if (a) {\n b;\n} else {\n c;\n}');
+ test_fragment('if (foo) {', 'if (foo) {');
+ test_fragment('foo {', 'foo {');
+ test_fragment('return {', 'return {'); // return needs the brace.
+ test_fragment('return /* inline */ {', 'return /* inline */ {');
+ // test_fragment('return\n{', 'return\n{'); // can't support this?, but that's an improbable and extreme case anyway.
+ test_fragment('return;\n{', 'return; {');
+ bt("throw {}");
+ bt("throw {\n foo;\n}");
+ bt('var foo = {}');
+ bt('if (foo) bar();\nelse break');
+ bt('function x() {\n foo();\n}zzz', 'function x() {\n foo();\n}\nzzz');
+ bt('a: do {} while (); xxx', 'a: do {} while ();\nxxx');
+ bt('var a = new function();');
+ bt('var a = new function() {};');
+ bt('var a = new function a() {};');
+ test_fragment('new function');
+ bt("foo({\n 'a': 1\n},\n10);",
+ "foo({\n 'a': 1\n },\n 10);");
+ bt('(["foo","bar"]).each(function(i) {return i;});',
+ '(["foo", "bar"]).each(function(i) {\n return i;\n});');
+ bt('(function(i) {return i;})();',
+ '(function(i) {\n return i;\n})();');
+ bt( "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test(\n" +
+ "/*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "},\n" +
+ "/*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test(\n" +
+ " /*Argument 1*/\n" +
+ " {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test( /*Argument 1*/\n" +
+ "{\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */\n" +
+ "{\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+
+ opts.brace_style = "end-expand";
+
+ bt('//case 1\nif (a == 1) {}\n//case 2\nelse if (a == 2) {}');
+ bt('if(1){2}else{3}', "if (1) {\n 2\n}\nelse {\n 3\n}");
+ bt('try{a();}catch(b){c();}catch(d){}finally{e();}',
+ "try {\n a();\n}\ncatch (b) {\n c();\n}\ncatch (d) {}\nfinally {\n e();\n}");
+ bt('if(a){b();}else if(c) foo();',
+ "if (a) {\n b();\n}\nelse if (c) foo();");
+ bt("if (a) {\n// comment\n}else{\n// comment\n}",
+ "if (a) {\n // comment\n}\nelse {\n // comment\n}"); // if/else statement with empty body
+ bt('if (x) {y} else { if (x) {y}}',
+ 'if (x) {\n y\n}\nelse {\n if (x) {\n y\n }\n}');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}',
+ 'if (a) {\n b;\n}\nelse {\n c;\n}');
+ test_fragment(' /*\n* xx\n*/\n// xx\nif (foo) {\n bar();\n}',
+ ' /*\n * xx\n */\n // xx\n if (foo) {\n bar();\n }');
+ bt('if (foo) {}\nelse /regex/.test();');
+ bt('if (foo) /regex/.test();');
+ bt('if (a)\n{\nb;\n}\nelse\n{\nc;\n}', 'if (a) {\n b;\n}\nelse {\n c;\n}');
+ test_fragment('if (foo) {', 'if (foo) {');
+ test_fragment('foo {', 'foo {');
+ test_fragment('return {', 'return {'); // return needs the brace.
+ test_fragment('return /* inline */ {', 'return /* inline */ {');
+ // test_fragment('return\n{', 'return\n{'); // can't support this?, but that's an improbable and extreme case anyway.
+ test_fragment('return;\n{', 'return; {');
+ bt("throw {}");
+ bt("throw {\n foo;\n}");
+ bt('var foo = {}');
+ bt('if (foo) bar();\nelse break');
+ bt('function x() {\n foo();\n}zzz', 'function x() {\n foo();\n}\nzzz');
+ bt('a: do {} while (); xxx', 'a: do {} while ();\nxxx');
+ bt('var a = new function();');
+ bt('var a = new function() {};');
+ bt('var a = new function a() {};');
+ test_fragment('new function');
+ bt("foo({\n 'a': 1\n},\n10);",
+ "foo({\n 'a': 1\n },\n 10);");
+ bt('(["foo","bar"]).each(function(i) {return i;});',
+ '(["foo", "bar"]).each(function(i) {\n return i;\n});');
+ bt('(function(i) {return i;})();',
+ '(function(i) {\n return i;\n})();');
+ bt( "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test(\n" +
+ "/*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ "},\n" +
+ "/*Argument 2\n" +
+ " */ {\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test(\n" +
+ " /*Argument 1*/\n" +
+ " {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+ bt( "test( /*Argument 1*/\n" +
+ "{\n" +
+ " 'Value1': '1'\n" +
+ "}, /*Argument 2\n" +
+ " */\n" +
+ "{\n" +
+ " 'Value2': '2'\n" +
+ "});",
+ // expected
+ "test( /*Argument 1*/ {\n" +
+ " 'Value1': '1'\n" +
+ " },\n" +
+ " /*Argument 2\n" +
+ " */\n" +
+ " {\n" +
+ " 'Value2': '2'\n" +
+ " });");
+
+ opts.brace_style = 'collapse';
+
+
+ bt('a = <?= external() ?> ;'); // not the most perfect thing in the world, but you're the weirdo beaufifying php mix-ins with javascript beautifier
+ bt('a = <%= external() %> ;');
+
+ bt('// func-comment\n\nfunction foo() {}\n\n// end-func-comment');
+
+ test_fragment('roo = {\n /*\n ****\n FOO\n ****\n */\n BAR: 0\n};');
+
+ bt('"foo""bar""baz"', '"foo"\n"bar"\n"baz"');
+ bt("'foo''bar''baz'", "'foo'\n'bar'\n'baz'");
+
+
+ test_fragment("if (zz) {\n // ....\n}\n(function");
+
+ bt("{\n get foo() {}\n}");
+ bt("{\n var a = get\n foo();\n}");
+ bt("{\n set foo() {}\n}");
+ bt("{\n var a = set\n foo();\n}");
+ bt("var x = {\n get function()\n}");
+ bt("var x = {\n set function()\n}");
+ bt("var x = set\n\na() {}", "var x = set\n\n a() {}");
+ bt("var x = set\n\nfunction() {}", "var x = set\n\n function() {}");
+
+ bt('<!-- foo\nbar();\n-->');
+ bt('<!-- dont crash');
+ bt('for () /abc/.test()');
+ bt('if (k) /aaa/m.test(v) && l();');
+ bt('switch (true) {\n case /swf/i.test(foo):\n bar();\n}');
+ bt('createdAt = {\n type: Date,\n default: Date.now\n}');
+ bt('switch (createdAt) {\n case a:\n Date,\n default:\n Date.now\n}');
+ opts.space_before_conditional = false;
+ bt('if(a) b()');
+ opts.space_before_conditional = true;
+
+
+ opts.preserve_newlines = true;
+ bt('var a = 42; // foo\n\nvar b;');
+ bt('var a = 42; // foo\n\n\nvar b;');
+ bt("var a = 'foo' +\n 'bar';");
+ bt("var a = \"foo\" +\n \"bar\";");
+ bt('this.oa = new OAuth(\n' +
+ ' _requestToken,\n' +
+ ' _accessToken,\n' +
+ ' consumer_key\n' +
+ ');');
+
+
+ opts.unescape_strings = false;
+ test_fragment('"\\x22\\x27", \'\\x22\\x27\', "\\x5c", \'\\x5c\', "\\xff and \\xzz", "unicode \\u0000 \\u0022 \\u0027 \\u005c \\uffff \\uzzzz"');
+ opts.unescape_strings = true;
+ test_fragment('"\\x20\\x40\\x4a"', '" @J"');
+ test_fragment('"\\xff\\x40\\x4a"');
+ test_fragment('"\\u0072\\u016B\\u0137\\u012B\\u0074\\u0069\\u0073"', '"rūķītis"');
+ test_fragment('"Google Chrome est\\u00E1 actualizado."', '"Google Chrome está actualizado."');
+ /*
+ bt('"\\x22\\x27",\'\\x22\\x27\',"\\x5c",\'\\x5c\',"\\xff and \\xzz","unicode \\u0000 \\u0022 \\u0027 \\u005c \\uffff \\uzzzz"',
+ '"\\"\'", \'"\\\'\', "\\\\", \'\\\\\', "\\xff and \\xzz", "unicode \\u0000 \\" \' \\\\ \\uffff \\uzzzz"');
+ */
+ opts.unescape_strings = false;
+
+ bt('return function();');
+ bt('var a = function();');
+ bt('var a = 5 + function();');
+
+ bt('3.*7;', '3. * 7;');
+ bt('import foo.*;', 'import foo.*;'); // actionscript's import
+ test_fragment('function f(a: a, b: b)'); // actionscript
+
+ bt('{\n foo // something\n ,\n bar // something\n baz\n}');
+ bt('function a(a) {} function b(b) {} function c(c) {}', 'function a(a) {}\n\nfunction b(b) {}\n\nfunction c(c) {}');
+ bt('foo(a, function() {})');
+
+ bt('foo(a, /regex/)');
+
+ bt('/* foo */\n"x"');
+
+ opts.break_chained_methods = false;
+ opts.preserve_newlines = false;
+ bt('foo\n.bar()\n.baz().cucumber(fat)', 'foo.bar().baz().cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat); foo.bar().baz().cucumber(fat)', 'foo.bar().baz().cucumber(fat);\nfoo.bar().baz().cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat)\n foo.bar().baz().cucumber(fat)', 'foo.bar().baz().cucumber(fat)\nfoo.bar().baz().cucumber(fat)');
+ bt('this\n.something = foo.bar()\n.baz().cucumber(fat)', 'this.something = foo.bar().baz().cucumber(fat)');
+ bt('this.something.xxx = foo.moo.bar()');
+ bt('this\n.something\n.xxx = foo.moo\n.bar()', 'this.something.xxx = foo.moo.bar()');
+
+ opts.break_chained_methods = false;
+ opts.preserve_newlines = true;
+ bt('foo\n.bar()\n.baz().cucumber(fat)', 'foo\n .bar()\n .baz().cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat); foo.bar().baz().cucumber(fat)', 'foo\n .bar()\n .baz().cucumber(fat);\nfoo.bar().baz().cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat)\n foo.bar().baz().cucumber(fat)', 'foo\n .bar()\n .baz().cucumber(fat)\nfoo.bar().baz().cucumber(fat)');
+ bt('this\n.something = foo.bar()\n.baz().cucumber(fat)', 'this\n .something = foo.bar()\n .baz().cucumber(fat)');
+ bt('this.something.xxx = foo.moo.bar()');
+ bt('this\n.something\n.xxx = foo.moo\n.bar()', 'this\n .something\n .xxx = foo.moo\n .bar()');
+
+ opts.break_chained_methods = true;
+ opts.preserve_newlines = false;
+ bt('foo\n.bar()\n.baz().cucumber(fat)', 'foo.bar()\n .baz()\n .cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat); foo.bar().baz().cucumber(fat)', 'foo.bar()\n .baz()\n .cucumber(fat);\nfoo.bar()\n .baz()\n .cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat)\n foo.bar().baz().cucumber(fat)', 'foo.bar()\n .baz()\n .cucumber(fat)\nfoo.bar()\n .baz()\n .cucumber(fat)');
+ bt('this\n.something = foo.bar()\n.baz().cucumber(fat)', 'this.something = foo.bar()\n .baz()\n .cucumber(fat)');
+ bt('this.something.xxx = foo.moo.bar()');
+ bt('this\n.something\n.xxx = foo.moo\n.bar()', 'this.something.xxx = foo.moo.bar()');
+
+ opts.break_chained_methods = true;
+ opts.preserve_newlines = true;
+ bt('foo\n.bar()\n.baz().cucumber(fat)', 'foo\n .bar()\n .baz()\n .cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat); foo.bar().baz().cucumber(fat)', 'foo\n .bar()\n .baz()\n .cucumber(fat);\nfoo.bar()\n .baz()\n .cucumber(fat)');
+ bt('foo\n.bar()\n.baz().cucumber(fat)\n foo.bar().baz().cucumber(fat)', 'foo\n .bar()\n .baz()\n .cucumber(fat)\nfoo.bar()\n .baz()\n .cucumber(fat)');
+ bt('this\n.something = foo.bar()\n.baz().cucumber(fat)', 'this\n .something = foo.bar()\n .baz()\n .cucumber(fat)');
+ bt('this.something.xxx = foo.moo.bar()');
+ bt('this\n.something\n.xxx = foo.moo\n.bar()', 'this\n .something\n .xxx = foo.moo\n .bar()');
+
+ opts.break_chained_methods = false;
+
+ // Line wrap test intputs
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ wrap_input_1=('foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' +
+ 'if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ '}');
+
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ wrap_input_2=('{\n' +
+ ' foo.bar().baz().cucumber((fat && "sassy") || (leans\n&& mean));\n' +
+ ' Test_very_long_variable_name_this_should_never_wrap\n.but_this_can\n' +
+ ' if (wraps_can_occur && inside_an_if_block) that_is_\n.okay();\n' +
+ ' object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ ' }' +
+ '}');
+
+ opts.preserve_newlines = false;
+ opts.wrap_line_length = 0;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap.but_this_can\n' +
+ 'if (wraps_can_occur && inside_an_if_block) that_is_.okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 70;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap.but_this_can\n' +
+ 'if (wraps_can_occur && inside_an_if_block) that_is_.okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 40;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat &&\n' +
+ ' "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_.okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 41;
+ // NOTE: wrap is only best effort - line continues until next wrap point is found.
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") ||\n' +
+ ' (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_.okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 45;
+ // NOTE: wrap is only best effort - line continues until next wrap point is found.
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_2,
+ /* expected */
+ '{\n' +
+ ' foo.bar().baz().cucumber((fat && "sassy") ||\n' +
+ ' (leans && mean));\n' +
+ ' Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ ' if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_.okay();\n' +
+ ' object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ ' }\n'+
+ '}');
+
+ opts.preserve_newlines = true;
+ opts.wrap_line_length = 0;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur && inside_an_if_block) that_is_\n' +
+ ' .okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 70;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur && inside_an_if_block) that_is_\n' +
+ ' .okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap + but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap + !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" + "but_this_can"\n' +
+ '}');
+
+
+ opts.wrap_line_length = 40;
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat &&\n' +
+ ' "sassy") || (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_\n' +
+ ' .okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 41;
+ // NOTE: wrap is only best effort - line continues until next wrap point is found.
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_1,
+ /* expected */
+ 'foo.bar().baz().cucumber((fat && "sassy") ||\n' +
+ ' (leans && mean));\n' +
+ 'Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ 'if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_\n' +
+ ' .okay();\n' +
+ 'object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ '}');
+
+ opts.wrap_line_length = 45;
+ // NOTE: wrap is only best effort - line continues until next wrap point is found.
+ //.............---------1---------2---------3---------4---------5---------6---------7
+ //.............1234567890123456789012345678901234567890123456789012345678901234567890
+ test_fragment(wrap_input_2,
+ /* expected */
+ '{\n' +
+ ' foo.bar().baz().cucumber((fat && "sassy") ||\n' +
+ ' (leans && mean));\n' +
+ ' Test_very_long_variable_name_this_should_never_wrap\n' +
+ ' .but_this_can\n' +
+ ' if (wraps_can_occur &&\n' +
+ ' inside_an_if_block) that_is_\n' +
+ ' .okay();\n' +
+ ' object_literal = {\n' +
+ ' property: first_token_should_never_wrap +\n' +
+ ' but_this_can,\n' +
+ ' propertz: first_token_should_never_wrap +\n' +
+ ' !but_this_can,\n' +
+ ' proper: "first_token_should_never_wrap" +\n' +
+ ' "but_this_can"\n' +
+ ' }\n'+
+ '}');
+
+ opts.wrap_line_length = 0;
+
+ opts.preserve_newlines = false;
+ bt('if (foo) // comment\n bar();');
+ bt('if (foo) // comment\n (bar());');
+ bt('if (foo) // comment\n (bar());');
+ bt('if (foo) // comment\n /asdf/;');
+ bt('this.oa = new OAuth(\n' +
+ ' _requestToken,\n' +
+ ' _accessToken,\n' +
+ ' consumer_key\n' +
+ ');',
+ 'this.oa = new OAuth(_requestToken, _accessToken, consumer_key);');
+ bt('foo = {\n x: y, // #44\n w: z // #44\n}');
+ bt('switch (x) {\n case "a":\n // comment on newline\n break;\n case "b": // comment on same line\n break;\n}');
+ bt('this.type =\n this.options =\n // comment\n this.enabled null;',
+ 'this.type = this.options =\n // comment\n this.enabled null;');
+ bt('someObj\n .someFunc1()\n // This comment should not break the indent\n .someFunc2();',
+ 'someObj.someFunc1()\n // This comment should not break the indent\n .someFunc2();');
+
+ bt('if (true ||\n!true) return;', 'if (true || !true) return;');
+
+ // these aren't ready yet.
+ //bt('if (foo) // comment\n bar() /*i*/ + baz() /*j\n*/ + asdf();');
+ bt('if\n(foo)\nif\n(bar)\nif\n(baz)\nwhee();\na();',
+ 'if (foo)\n if (bar)\n if (baz) whee();\na();');
+ bt('if\n(foo)\nif\n(bar)\nif\n(baz)\nwhee();\nelse\na();',
+ 'if (foo)\n if (bar)\n if (baz) whee();\n else a();');
+ bt('if (foo)\nbar();\nelse\ncar();',
+ 'if (foo) bar();\nelse car();');
+
+ bt('if (foo) if (bar) if (baz);\na();',
+ 'if (foo)\n if (bar)\n if (baz);\na();');
+ bt('if (foo) if (bar) if (baz) whee();\na();',
+ 'if (foo)\n if (bar)\n if (baz) whee();\na();');
+ bt('if (foo) a()\nif (bar) if (baz) whee();\na();',
+ 'if (foo) a()\nif (bar)\n if (baz) whee();\na();');
+ bt('if (foo);\nif (bar) if (baz) whee();\na();',
+ 'if (foo);\nif (bar)\n if (baz) whee();\na();');
+ bt('if (options)\n' +
+ ' for (var p in options)\n' +
+ ' this[p] = options[p];',
+ 'if (options)\n'+
+ ' for (var p in options) this[p] = options[p];');
+ bt('if (options) for (var p in options) this[p] = options[p];',
+ 'if (options)\n for (var p in options) this[p] = options[p];');
+
+ bt('if (options) do q(); while (b());',
+ 'if (options)\n do q(); while (b());');
+ bt('if (options) while (b()) q();',
+ 'if (options)\n while (b()) q();');
+ bt('if (options) do while (b()) q(); while (a());',
+ 'if (options)\n do\n while (b()) q(); while (a());');
+
+ bt('function f(a, b, c,\nd, e) {}',
+ 'function f(a, b, c, d, e) {}');
+
+ bt('function f(a,b) {if(a) b()}function g(a,b) {if(!a) b()}',
+ 'function f(a, b) {\n if (a) b()\n}\n\nfunction g(a, b) {\n if (!a) b()\n}');
+ bt('function f(a,b) {if(a) b()}\n\n\n\nfunction g(a,b) {if(!a) b()}',
+ 'function f(a, b) {\n if (a) b()\n}\n\nfunction g(a, b) {\n if (!a) b()\n}');
+
+ // This is not valid syntax, but still want to behave reasonably and not side-effect
+ bt('(if(a) b())(if(a) b())',
+ '(\n if (a) b())(\n if (a) b())');
+ bt('(if(a) b())\n\n\n(if(a) b())',
+ '(\n if (a) b())\n(\n if (a) b())');
+
+
+
+ bt("if\n(a)\nb();", "if (a) b();");
+ bt('var a =\nfoo', 'var a = foo');
+ bt('var a = {\n"a":1,\n"b":2}', "var a = {\n \"a\": 1,\n \"b\": 2\n}");
+ bt("var a = {\n'a':1,\n'b':2}", "var a = {\n 'a': 1,\n 'b': 2\n}");
+ bt('var a = /*i*/ "b";');
+ bt('var a = /*i*/\n"b";', 'var a = /*i*/ "b";');
+ bt('var a = /*i*/\nb;', 'var a = /*i*/ b;');
+ bt('{\n\n\n"x"\n}', '{\n "x"\n}');
+ bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a && b || c || d && e) e = f');
+ bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a && (b || c || d) && e) e = f');
+ test_fragment('\n\n"x"', '"x"');
+ bt('a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;',
+ 'a = 1;\nb = 2;');
+
+ opts.preserve_newlines = true;
+ bt('if (foo) // comment\n bar();');
+ bt('if (foo) // comment\n (bar());');
+ bt('if (foo) // comment\n (bar());');
+ bt('if (foo) // comment\n /asdf/;');
+ bt('foo = {\n x: y, // #44\n w: z // #44\n}');
+ bt('switch (x) {\n case "a":\n // comment on newline\n break;\n case "b": // comment on same line\n break;\n}');
+ bt('this.type =\n this.options =\n // comment\n this.enabled null;');
+ bt('someObj\n .someFunc1()\n // This comment should not break the indent\n .someFunc2();');
+
+ bt('if (true ||\n!true) return;', 'if (true ||\n !true) return;');
+
+ // these aren't ready yet.
+ // bt('if (foo) // comment\n bar() /*i*/ + baz() /*j\n*/ + asdf();');
+ bt('if\n(foo)\nif\n(bar)\nif\n(baz)\nwhee();\na();',
+ 'if (foo)\n if (bar)\n if (baz)\n whee();\na();');
+ bt('if\n(foo)\nif\n(bar)\nif\n(baz)\nwhee();\nelse\na();',
+ 'if (foo)\n if (bar)\n if (baz)\n whee();\n else\n a();');
+ bt('if (foo) bar();\nelse\ncar();',
+ 'if (foo) bar();\nelse\n car();');
+
+ bt('if (foo) if (bar) if (baz);\na();',
+ 'if (foo)\n if (bar)\n if (baz);\na();');
+ bt('if (foo) if (bar) if (baz) whee();\na();',
+ 'if (foo)\n if (bar)\n if (baz) whee();\na();');
+ bt('if (foo) a()\nif (bar) if (baz) whee();\na();',
+ 'if (foo) a()\nif (bar)\n if (baz) whee();\na();');
+ bt('if (foo);\nif (bar) if (baz) whee();\na();',
+ 'if (foo);\nif (bar)\n if (baz) whee();\na();');
+ bt('if (options)\n' +
+ ' for (var p in options)\n' +
+ ' this[p] = options[p];');
+ bt('if (options) for (var p in options) this[p] = options[p];',
+ 'if (options)\n for (var p in options) this[p] = options[p];');
+
+ bt('if (options) do q(); while (b());',
+ 'if (options)\n do q(); while (b());');
+ bt('if (options) do; while (b());',
+ 'if (options)\n do; while (b());');
+ bt('if (options) while (b()) q();',
+ 'if (options)\n while (b()) q();');
+ bt('if (options) do while (b()) q(); while (a());',
+ 'if (options)\n do\n while (b()) q(); while (a());');
+
+ bt('function f(a, b, c,\nd, e) {}',
+ 'function f(a, b, c,\n d, e) {}');
+
+ bt('function f(a,b) {if(a) b()}function g(a,b) {if(!a) b()}',
+ 'function f(a, b) {\n if (a) b()\n}\n\nfunction g(a, b) {\n if (!a) b()\n}');
+ bt('function f(a,b) {if(a) b()}\n\n\n\nfunction g(a,b) {if(!a) b()}',
+ 'function f(a, b) {\n if (a) b()\n}\n\n\n\nfunction g(a, b) {\n if (!a) b()\n}');
+ // This is not valid syntax, but still want to behave reasonably and not side-effect
+ bt('(if(a) b())(if(a) b())',
+ '(\n if (a) b())(\n if (a) b())');
+ bt('(if(a) b())\n\n\n(if(a) b())',
+ '(\n if (a) b())\n\n\n(\n if (a) b())');
+
+ // space between functions
+ bt('/*\n * foo\n */\nfunction foo() {}');
+ bt('// a nice function\nfunction foo() {}');
+ bt('function foo() {}\nfunction foo() {}',
+ 'function foo() {}\n\nfunction foo() {}'
+ );
+
+
+
+ bt("if\n(a)\nb();", "if (a)\n b();");
+ bt('var a =\nfoo', 'var a =\n foo');
+ bt('var a = {\n"a":1,\n"b":2}', "var a = {\n \"a\": 1,\n \"b\": 2\n}");
+ bt("var a = {\n'a':1,\n'b':2}", "var a = {\n 'a': 1,\n 'b': 2\n}");
+ bt('var a = /*i*/ "b";');
+ bt('var a = /*i*/\n"b";', 'var a = /*i*/\n "b";');
+ bt('var a = /*i*/\nb;', 'var a = /*i*/\n b;');
+ bt('{\n\n\n"x"\n}', '{\n\n\n "x"\n}');
+ bt('if(a &&\nb\n||\nc\n||d\n&&\ne) e = f', 'if (a &&\n b ||\n c || d &&\n e) e = f');
+ bt('if(a &&\n(b\n||\nc\n||d)\n&&\ne) e = f', 'if (a &&\n (b ||\n c || d) &&\n e) e = f');
+ test_fragment('\n\n"x"', '"x"');
+
+ // this beavior differs between js and python, defaults to unlimited in js, 10 in python
+ bt('a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;',
+ 'a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;');
+ opts.max_preserve_newlines = 8;
+ bt('a = 1;\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\nb = 2;',
+ 'a = 1;\n\n\n\n\n\n\n\nb = 2;');
+
+ // Test the option to have spaces within parens
+ opts.space_in_paren = false;
+ bt('if(p) foo(a,b)', 'if (p) foo(a, b)');
+ bt('try{while(true){willThrow()}}catch(result)switch(result){case 1:++result }',
+ 'try {\n while (true) {\n willThrow()\n }\n} catch (result) switch (result) {\n case 1:\n ++result\n}');
+ bt('((e/((a+(b)*c)-d))^2)*5;', '((e / ((a + (b) * c) - d)) ^ 2) * 5;');
+ bt('function f(a,b) {if(a) b()}function g(a,b) {if(!a) b()}',
+ 'function f(a, b) {\n if (a) b()\n}\n\nfunction g(a, b) {\n if (!a) b()\n}');
+ bt('a=[];',
+ 'a = [];');
+ bt('a=[b,c,d];',
+ 'a = [b, c, d];');
+ bt('a= f[b];',
+ 'a = f[b];');
+ opts.space_in_paren = true;
+ bt('if(p) foo(a,b)', 'if ( p ) foo( a, b )');
+ bt('try{while(true){willThrow()}}catch(result)switch(result){case 1:++result }',
+ 'try {\n while ( true ) {\n willThrow()\n }\n} catch ( result ) switch ( result ) {\n case 1:\n ++result\n}');
+ bt('((e/((a+(b)*c)-d))^2)*5;', '( ( e / ( ( a + ( b ) * c ) - d ) ) ^ 2 ) * 5;');
+ bt('function f(a,b) {if(a) b()}function g(a,b) {if(!a) b()}',
+ 'function f( a, b ) {\n if ( a ) b()\n}\n\nfunction g( a, b ) {\n if ( !a ) b()\n}');
+ bt('a=[];',
+ 'a = [];');
+ bt('a=[b,c,d];',
+ 'a = [ b, c, d ];');
+ bt('a= f[b];',
+ 'a = f[ b ];');
+ opts.space_in_empty_paren = true;
+ bt('if(p) foo(a,b)', 'if ( p ) foo( a, b )');
+ bt('try{while(true){willThrow()}}catch(result)switch(result){case 1:++result }',
+ 'try {\n while ( true ) {\n willThrow( )\n }\n} catch ( result ) switch ( result ) {\n case 1:\n ++result\n}');
+ bt('((e/((a+(b)*c)-d))^2)*5;', '( ( e / ( ( a + ( b ) * c ) - d ) ) ^ 2 ) * 5;');
+ bt('function f(a,b) {if(a) b()}function g(a,b) {if(!a) b()}',
+ 'function f( a, b ) {\n if ( a ) b( )\n}\n\nfunction g( a, b ) {\n if ( !a ) b( )\n}');
+ bt('a=[];',
+ 'a = [ ];');
+ bt('a=[b,c,d];',
+ 'a = [ b, c, d ];');
+ bt('a= f[b];',
+ 'a = f[ b ];');
+ opts.space_in_empty_paren = false;
+ opts.space_in_paren = false;
+
+ // Test template strings
+ bt('`This is a ${template} string.`', '`This is a ${template} string.`');
+ bt('`This\n is\n a\n ${template}\n string.`', '`This\n is\n a\n ${template}\n string.`');
+
+ // Test that e4x literals passed through when e4x-option is enabled
+ bt('xml=<a b="c"><d/><e>\n foo</e>x</a>;', 'xml = < a b = "c" > < d / > < e >\n foo < /e>x</a > ;');
+ opts.e4x = true;
+ bt('xml=<a b="c"><d/><e>\n foo</e>x</a>;', 'xml = <a b="c"><d/><e>\n foo</e>x</a>;');
+ bt('<a b=\'This is a quoted "c".\'/>', '<a b=\'This is a quoted "c".\'/>');
+ bt('<a b="This is a quoted \'c\'."/>', '<a b="This is a quoted \'c\'."/>');
+ bt('<a b="A quote \' inside string."/>', '<a b="A quote \' inside string."/>');
+ bt('<a b=\'A quote " inside string.\'/>', '<a b=\'A quote " inside string.\'/>');
+ bt('<a b=\'Some """ quotes "" inside string.\'/>', '<a b=\'Some """ quotes "" inside string.\'/>');
+ // Handles inline expressions
+ bt('xml=<{a} b="c"><d/><e v={z}>\n foo</e>x</{a}>;', 'xml = <{a} b="c"><d/><e v={z}>\n foo</e>x</{a}>;');
+ // xml literals with special characters in elem names
+ // see http://www.w3.org/TR/REC-xml/#NT-NameChar
+ bt('xml = <_:.valid.xml- _:.valid.xml-="123"/>;', 'xml = <_:.valid.xml- _:.valid.xml-="123"/>;');
+ // Handles CDATA
+ bt('xml=<![CDATA[ b="c"><d/><e v={z}>\n foo</e>x/]]>;', 'xml = <![CDATA[ b="c"><d/><e v={z}>\n foo</e>x/]]>;');
+ bt('xml=<![CDATA[]]>;', 'xml = <![CDATA[]]>;');
+ bt('xml=<a b="c"><![CDATA[d/></a></{}]]></a>;', 'xml = <a b="c"><![CDATA[d/></a></{}]]></a>;');
+
+ // Handles messed up tags, as long as it isn't the same name
+ // as the root tag. Also handles tags of same name as root tag
+ // as long as nesting matches.
+ bt('xml=<a x="jn"><c></b></f><a><d jnj="jnn"><f></a ></nj></a>;',
+ 'xml = <a x="jn"><c></b></f><a><d jnj="jnn"><f></a ></nj></a>;');
+ // If xml is not terminated, the remainder of the file is treated
+ // as part of the xml-literal (passed through unaltered)
+ test_fragment('xml=<a></b>\nc<b;', 'xml = <a></b>\nc<b;');
+ opts.e4x = false;
+
+ // START tests for issue 241
+ bt('obj\n' +
+ ' .last({\n' +
+ ' foo: 1,\n' +
+ ' bar: 2\n' +
+ ' });\n' +
+ 'var test = 1;');
+
+ bt('obj\n' +
+ ' .last(a, function() {\n' +
+ ' var test;\n' +
+ ' });\n' +
+ 'var test = 1;');
+
+ bt('obj.first()\n' +
+ ' .second()\n' +
+ ' .last(function(err, response) {\n' +
+ ' console.log(err);\n' +
+ ' });');
+
+ // END tests for issue 241
+
+
+ // START tests for issue 268 and 275
+ bt('obj.last(a, function() {\n' +
+ ' var test;\n' +
+ '});\n' +
+ 'var test = 1;');
+ bt('obj.last(a,\n' +
+ ' function() {\n' +
+ ' var test;\n' +
+ ' });\n' +
+ 'var test = 1;');
+
+ bt('(function() {if (!window.FOO) window.FOO || (window.FOO = function() {var b = {bar: "zort"};});})();',
+ '(function() {\n' +
+ ' if (!window.FOO) window.FOO || (window.FOO = function() {\n' +
+ ' var b = {\n' +
+ ' bar: "zort"\n' +
+ ' };\n' +
+ ' });\n' +
+ '})();');
+ // END tests for issue 268 and 275
+
+ // START tests for issue 281
+ bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+ ' "dojo/_base/lang", "dojo/Deferred"\n' +
+ '], function(declare, Employee, Button, lang, Deferred) {\n' +
+ ' return declare(Employee, {\n' +
+ ' constructor: function() {\n' +
+ ' new Button({\n' +
+ ' onClick: lang.hitch(this, function() {\n' +
+ ' new Deferred().then(lang.hitch(this, function() {\n' +
+ ' this.salary * 0.25;\n' +
+ ' }));\n' +
+ ' })\n' +
+ ' });\n' +
+ ' }\n' +
+ ' });\n' +
+ '});');
+
+ bt('define(["dojo/_base/declare", "my/Employee", "dijit/form/Button",\n' +
+ ' "dojo/_base/lang", "dojo/Deferred"\n' +
+ ' ],\n' +
+ ' function(declare, Employee, Button, lang, Deferred) {\n' +
+ ' return declare(Employee, {\n' +
+ ' constructor: function() {\n' +
+ ' new Button({\n' +
+ ' onClick: lang.hitch(this, function() {\n' +
+ ' new Deferred().then(lang.hitch(this, function() {\n' +
+ ' this.salary * 0.25;\n' +
+ ' }));\n' +
+ ' })\n' +
+ ' });\n' +
+ ' }\n' +
+ ' });\n' +
+ ' });');
+ // END tests for issue 281
+
+ // START tests for issue 459
+ bt( '(function() {\n' +
+ ' return {\n' +
+ ' foo: function() {\n' +
+ ' return "bar";\n' +
+ ' },\n' +
+ ' bar: ["bar"]\n' +
+ ' };\n' +
+ '}());');
+ // END tests for issue 459
+
+ bt('var a=1,b={bang:2},c=3;',
+ 'var a = 1,\n b = {\n bang: 2\n },\n c = 3;');
+ bt('var a={bing:1},b=2,c=3;',
+ 'var a = {\n bing: 1\n },\n b = 2,\n c = 3;');
+ Urlencoded.run_tests(sanitytest);
+
+ bth('');
+ bth('<div></div>');
+ bth('<div>content</div>');
+ bth('<div><div></div></div>',
+ '<div>\n' +
+ ' <div></div>\n' +
+ '</div>');
+ bth('<div><div>content</div></div>',
+ '<div>\n' +
+ ' <div>content</div>\n' +
+ '</div>');
+ bth('<div>\n' +
+ ' <span>content</span>\n' +
+ '</div>');
+ bth('<div>\n' +
+ '</div>');
+ bth('<div>\n' +
+ ' content\n' +
+ '</div>');
+ bth('<div>\n' +
+ ' </div>',
+ '<div>\n' +
+ '</div>');
+ bth(' <div>\n' +
+ ' </div>',
+ '<div>\n' +
+ '</div>');
+ bth(' <div>\n' +
+ '</div>',
+ '<div>\n' +
+ '</div>');
+ bth('<div >content</div>',
+ '<div>content</div>');
+ bth('<div thinger="preserve space here" ></div >',
+ '<div thinger="preserve space here"></div>');
+ bth('content\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ 'content',
+ 'content\n' +
+ '<div>\n' +
+ '</div>\n' +
+ 'content');
+ bth('<li>\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '</li>');
+ bth('<li>\n' +
+ '<div>\n' +
+ '</div>\n' +
+ '</li>',
+ '<li>\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '</li>');
+ bth('<li>\n' +
+ ' content\n' +
+ '</li>\n' +
+ '<li>\n' +
+ ' content\n' +
+ '</li>');
+
+ // START tests for issue 453
+ bth('<script type="text/unknown"><div></div></script>',
+ '<script type="text/unknown">\n' +
+ ' <div></div>\n' +
+ '</script>');
+ bth('<script type="text/javascript"><div></div></script>',
+ '<script type="text/javascript">\n' +
+ ' < div > < /div>\n' +
+ '</script>');
+ bth('<script><div></div></script>',
+ '<script>\n' +
+ ' < div > < /div>\n' +
+ '</script>');
+ bth('<script type="text/javascript">var foo = "bar";</script>',
+ '<script type="text/javascript">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script type="application/javascript">var foo = "bar";</script>',
+ '<script type="application/javascript">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script type="application/javascript;version=1.8">var foo = "bar";</script>',
+ '<script type="application/javascript;version=1.8">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script type="application/x-javascript">var foo = "bar";</script>',
+ '<script type="application/x-javascript">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script type="application/ecmascript">var foo = "bar";</script>',
+ '<script type="application/ecmascript">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script type="text/javascript1.5">var foo = "bar";</script>',
+ '<script type="text/javascript1.5">\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+ bth('<script>var foo = "bar";</script>',
+ '<script>\n' +
+ ' var foo = "bar";\n' +
+ '</script>');
+
+ bth('<style type="text/unknown"><tag></tag></style>',
+ '<style type="text/unknown">\n' +
+ ' <tag></tag>\n' +
+ '</style>');
+ bth('<style type="text/css"><tag></tag></style>',
+ '<style type="text/css">\n' +
+ ' <tag></tag>\n' +
+ '</style>');
+ bth('<style><tag></tag></style>',
+ '<style>\n' +
+ ' <tag></tag>\n' +
+ '</style>');
+ bth('<style type="text/css">.selector {font-size:12px;}</style>',
+ '<style type="text/css">\n' +
+ ' .selector {\n' +
+ ' font-size: 12px;\n' +
+ ' }\n'+
+ '</style>');
+ bth('<style>.selector {font-size:12px;}</style>',
+ '<style>\n' +
+ ' .selector {\n' +
+ ' font-size: 12px;\n' +
+ ' }\n'+
+ '</style>');
+ // END tests for issue 453
+
+ // Tests that don't pass, but probably should.
+ // bth('<div><span>content</span></div>');
+
+ // Handlebars tests
+ // Without the indent option on, handlebars are treated as content.
+ opts.indent_handlebars = false;
+ bth('{{#if 0}}\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '{{/if}}',
+ '{{#if 0}}\n' +
+ '<div>\n' +
+ '</div>\n' +
+ '{{/if}}');
+ bth('<div>\n' +
+ '{{#each thing}}\n' +
+ ' {{name}}\n' +
+ '{{/each}}\n' +
+ '</div>',
+ '<div>\n' +
+ ' {{#each thing}} {{name}} {{/each}}\n' +
+ '</div>');
+
+ opts.indent_handlebars = true;
+ bth('{{#if 0}}{{/if}}');
+ bth('{{#if 0}}content{{/if}}');
+ bth('{{#if 0}}\n' +
+ '{{/if}}');
+ bth('{{#if words}}{{/if}}',
+ '{{#if words}}{{/if}}');
+ bth('{{#if words}}content{{/if}}',
+ '{{#if words}}content{{/if}}');
+ bth('{{#if words}}content{{/if}}',
+ '{{#if words}}content{{/if}}');
+ bth('{{#if 1}}\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '{{/if}}');
+ bth('{{#if 1}}\n' +
+ '<div>\n' +
+ '</div>\n' +
+ '{{/if}}',
+ '{{#if 1}}\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '{{/if}}');
+ bth('<div>\n' +
+ ' {{#if 1}}\n' +
+ ' {{/if}}\n' +
+ '</div>');
+ bth('<div>\n' +
+ '{{#if 1}}\n' +
+ '{{/if}}\n' +
+ '</div>',
+ '<div>\n' +
+ ' {{#if 1}}\n' +
+ ' {{/if}}\n' +
+ '</div>');
+ bth('{{#if}}\n' +
+ '{{#each}}\n' +
+ '{{#if}}\n' +
+ 'content\n' +
+ '{{/if}}\n' +
+ '{{#if}}\n' +
+ 'content\n' +
+ '{{/if}}\n' +
+ '{{/each}}\n' +
+ '{{/if}}',
+ '{{#if}}\n' +
+ ' {{#each}}\n' +
+ ' {{#if}}\n' +
+ ' content\n' +
+ ' {{/if}}\n' +
+ ' {{#if}}\n' +
+ ' content\n' +
+ ' {{/if}}\n' +
+ ' {{/each}}\n' +
+ '{{/if}}');
+ bth('{{#if 1}}\n' +
+ ' <div>\n' +
+ ' </div>\n' +
+ '{{/if}}');
+
+ // Test {{else}} aligned with {{#if}} and {{/if}}
+ bth('{{#if 1}}\n' +
+ ' content\n' +
+ ' {{else}}\n' +
+ ' content\n' +
+ '{{/if}}',
+ '{{#if 1}}\n' +
+ ' content\n' +
+ '{{else}}\n' +
+ ' content\n' +
+ '{{/if}}');
+ bth('{{#if 1}}\n' +
+ ' {{else}}\n' +
+ ' {{/if}}',
+ '{{#if 1}}\n' +
+ '{{else}}\n' +
+ '{{/if}}');
+ bth('{{#if thing}}\n' +
+ '{{#if otherthing}}\n' +
+ ' content\n' +
+ ' {{else}}\n' +
+ 'content\n' +
+ ' {{/if}}\n' +
+ ' {{else}}\n'+
+ 'content\n' +
+ '{{/if}}',
+ '{{#if thing}}\n' +
+ ' {{#if otherthing}}\n' +
+ ' content\n' +
+ ' {{else}}\n' +
+ ' content\n' +
+ ' {{/if}}\n' +
+ '{{else}}\n'+
+ ' content\n' +
+ '{{/if}}');
+
+ // Test {{}} inside of <> tags, which should be separated by spaces
+ // for readability, unless they are inside a string.
+ bth('<div{{somestyle}}></div>',
+ '<div {{somestyle}}></div>');
+ bth('<div{{#if test}}class="foo"{{/if}}>content</div>',
+ '<div {{#if test}} class="foo" {{/if}}>content</div>');
+ bth('<div{{#if thing}}{{somestyle}}class="{{class}}"{{else}}class="{{class2}}"{{/if}}>content</div>',
+ '<div {{#if thing}} {{somestyle}} class="{{class}}" {{else}} class="{{class2}}" {{/if}}>content</div>');
+ bth('<span{{#if condition}}class="foo"{{/if}}>content</span>',
+ '<span {{#if condition}} class="foo" {{/if}}>content</span>');
+ bth('<div unformatted="{{#if}}content{{/if}}">content</div>');
+ bth('<div unformatted="{{#if }} content{{/if}}">content</div>');
+
+ // Quotes found inside of Handlebars expressions inside of quoted
+ // strings themselves should not be considered string delimiters.
+ bth('<div class="{{#if thingIs "value"}}content{{/if}}"></div>');
+ bth('<div class="{{#if thingIs \'value\'}}content{{/if}}"></div>');
+ bth('<div class=\'{{#if thingIs "value"}}content{{/if}}\'></div>');
+ bth('<div class=\'{{#if thingIs \'value\'}}content{{/if}}\'></div>');
+
+ opts.wrap_line_length = 0;
+ //...---------1---------2---------3---------4---------5---------6---------7
+ //...1234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some text that should not wrap at all.</div>',
+ /* expected */
+ '<div>Some text that should not wrap at all.</div>');
+
+ // A value of 0 means no max line length, and should not wrap.
+ //...---------1---------2---------3---------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
+ //...12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all.</div>',
+ /* expected */
+ '<div>Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all.</div>');
+
+ opts.wrap_line_length = "0";
+ //...---------1---------2---------3---------4---------5---------6---------7
+ //...1234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some text that should not wrap at all.</div>',
+ /* expected */
+ '<div>Some text that should not wrap at all.</div>');
+
+ // A value of "0" means no max line length, and should not wrap
+ //...---------1---------2---------3---------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
+ //...12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all.</div>',
+ /* expected */
+ '<div>Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all. Some text that should not wrap at all.</div>');
+
+ //BUGBUG: This should wrap before 40 not after.
+ opts.wrap_line_length = 40;
+ //...---------1---------2---------3---------4---------5---------6---------7
+ //...1234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some test text that should wrap_inside_this section here.</div>',
+ /* expected */
+ '<div>Some test text that should wrap_inside_this\n' +
+ ' section here.</div>');
+
+ opts.wrap_line_length = "40";
+ //...---------1---------2---------3---------4---------5---------6---------7
+ //...1234567890123456789012345678901234567890123456789012345678901234567890
+ bth('<div>Some test text that should wrap_inside_this section here.</div>',
+ /* expected */
+ '<div>Some test text that should wrap_inside_this\n' +
+ ' section here.</div>');
+
+ opts.indent_size = 1;
+ opts.indent_char = '\t';
+ opts.preserve_newlines = false;
+ bth('<div>\n\tfoo\n</div>', '<div>foo</div>');
+
+ opts.preserve_newlines = true;
+ bth('<div>\n\tfoo\n</div>');
+
+
+
+ // test preserve_newlines and max_preserve_newlines
+ opts.preserve_newlines = false;
+ test_fragment('<div>Should not</div>\n\n\n' +
+ '<div>preserve newlines</div>',
+ '<div>Should not</div>\n' +
+ '<div>preserve newlines</div>');
+
+ opts.preserve_newlines = true;
+ opts.max_preserve_newlines = 0;
+ test_fragment('<div>Should</div>\n\n\n' +
+ '<div>preserve zero newlines</div>',
+ '<div>Should</div>\n' +
+ '<div>preserve zero newlines</div>');
+
+ opts.max_preserve_newlines = 1;
+ test_fragment('<div>Should</div>\n\n\n' +
+ '<div>preserve one newline</div>',
+ '<div>Should</div>\n\n' +
+ '<div>preserve one newline</div>');
+
+ opts.max_preserve_newlines = null;
+ test_fragment('<div>Should</div>\n\n\n' +
+ '<div>preserve one newline</div>',
+ '<div>Should</div>\n\n\n' +
+ '<div>preserve one newline</div>');
+ // css beautifier
+ opts.indent_size = 1;
+ opts.indent_char = '\t';
+ opts.selector_separator_newline = true;
+ opts.end_with_newline = true;
+
+ // test basic css beautifier
+ btc('', '\n');
+ btc(".tabs{}", ".tabs {}\n");
+ btc(".tabs{color:red;}", ".tabs {\n\tcolor: red;\n}\n");
+ btc(".tabs{color:rgb(255, 255, 0)}", ".tabs {\n\tcolor: rgb(255, 255, 0)\n}\n");
+ btc(".tabs{background:url('back.jpg')}", ".tabs {\n\tbackground: url('back.jpg')\n}\n");
+ btc("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n");
+ btc("@media print {.tab{}}", "@media print {\n\t.tab {}\n}\n");
+ btc("@media print {.tab{background-image:url(foo@2x.png)}}", "@media print {\n\t.tab {\n\t\tbackground-image: url(foo@2x.png)\n\t}\n}\n");
+
+ // comments
+ btc("/* test */", "/* test */\n");
+ btc(".tabs{/* test */}", ".tabs {\n\t/* test */\n}\n");
+ btc("/* header */.tabs {}", "/* header */\n\n.tabs {}\n");
+
+ //single line comment support (less/sass)
+ btc(".tabs{\n// comment\nwidth:10px;\n}", ".tabs {\n\t// comment\n\twidth: 10px;\n}\n");
+ btc(".tabs{// comment\nwidth:10px;\n}", ".tabs {\n\t// comment\n\twidth: 10px;\n}\n");
+ btc("//comment\n.tabs{width:10px;}", "//comment\n.tabs {\n\twidth: 10px;\n}\n");
+ btc(".tabs{//comment\n//2nd single line comment\nwidth:10px;}", ".tabs {\n\t//comment\n\t//2nd single line comment\n\twidth: 10px;\n}\n");
+ btc(".tabs{width:10px;//end of line comment\n}", ".tabs {\n\twidth: 10px;//end of line comment\n}\n");
+ btc(".tabs{width:10px;//end of line comment\nheight:10px;}", ".tabs {\n\twidth: 10px;//end of line comment\n\theight: 10px;\n}\n");
+ btc(".tabs{width:10px;//end of line comment\nheight:10px;//another\n}", ".tabs {\n\twidth: 10px;//end of line comment\n\theight: 10px;//another\n}\n");
+
+ // separate selectors
+ btc("#bla, #foo{color:red}", "#bla,\n#foo {\n\tcolor: red\n}\n");
+ btc("a, img {padding: 0.2px}", "a,\nimg {\n\tpadding: 0.2px\n}\n");
+
+ // block nesting
+ btc("#foo {\n\tbackground-image: url(foo@2x.png);\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}\n");
+ btc("@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo@2x.png);\n\t}\n\t@font-face {\n\t\tfont-family: 'Bitstream Vera Serif Bold';\n\t\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n\t}\n}\n");
+/*
+@font-face {
+ font-family: 'Bitstream Vera Serif Bold';
+ src: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');
+}
+@media screen {
+ #foo:hover {
+ background-image: url(foo.png);
+ }
+ @media screen and (min-device-pixel-ratio: 2) {
+ @font-face {
+ font-family: 'Helvetica Neue'
+ }
+ #foo:hover {
+ background-image: url(foo@2x.png);
+ }
+ }
+}
+*/
+ btc("@font-face {\n\tfont-family: 'Bitstream Vera Serif Bold';\n\tsrc: url('http://developer.mozilla.org/@api/deki/files/2934/=VeraSeBd.ttf');\n}\n@media screen {\n\t#foo:hover {\n\t\tbackground-image: url(foo.png);\n\t}\n\t@media screen and (min-device-pixel-ratio: 2) {\n\t\t@font-face {\n\t\t\tfont-family: 'Helvetica Neue'\n\t\t}\n\t\t#foo:hover {\n\t\t\tbackground-image: url(foo@2x.png);\n\t\t}\n\t}\n}\n");
+
+ // test options
+ opts.indent_size = 2;
+ opts.indent_char = ' ';
+ opts.selector_separator_newline = false;
+
+ btc("#bla, #foo{color:green}", "#bla, #foo {\n color: green\n}\n");
+ btc("@media print {.tab{}}", "@media print {\n .tab {}\n}\n");
+ btc("@media print {.tab,.bat{}}", "@media print {\n .tab, .bat {}\n}\n");
+ btc("#bla, #foo{color:black}", "#bla, #foo {\n color: black\n}\n");
+
+ // pseudo-classes and pseudo-elements
+ btc("#foo:hover {\n background-image: url(foo@2x.png)\n}\n");
+ btc("#foo *:hover {\n color: purple\n}\n");
+ btc("::selection {\n color: #ff0000;\n}\n");
+
+ // TODO: don't break nested pseduo-classes
+ btc("@media screen {.tab,.bat:hover {color:red}}", "@media screen {\n .tab, .bat:hover {\n color: red\n }\n}\n");
+
+ // particular edge case with braces and semicolons inside tags that allows custom text
+ btc("a:not(\"foobar\\\";{}omg\"){\ncontent: 'example\\';{} text';\ncontent: \"example\\\";{} text\";}",
+ "a:not(\"foobar\\\";{}omg\") {\n content: 'example\\';{} text';\n content: \"example\\\";{} text\";\n}\n");
+
+ btc('html.js [data-custom="123"] {\n opacity: 1.00;\n}\n'); // may not eat the space before "["
+ btc('html.js *[data-custom="123"] {\n opacity: 1.00;\n}\n');
+
+ return sanitytest;
+ }
+
+ return beautifier_tests();
+}
+
+if (typeof exports !== "undefined") {
+ exports.run_beautifier_tests = run_beautifier_tests;
+}
diff --git a/devtools/shared/jsbeautify/src/moz.build b/devtools/shared/jsbeautify/src/moz.build
new file mode 100644
index 000000000..ef16a2bb2
--- /dev/null
+++ b/devtools/shared/jsbeautify/src/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'beautify-css.js',
+ 'beautify-html.js',
+ 'beautify-js.js',
+ 'beautify-tests.js'
+)
diff --git a/devtools/shared/jsbeautify/tests/unit/head_jsbeautify.js b/devtools/shared/jsbeautify/tests/unit/head_jsbeautify.js
new file mode 100644
index 000000000..1abf07664
--- /dev/null
+++ b/devtools/shared/jsbeautify/tests/unit/head_jsbeautify.js
@@ -0,0 +1,17 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+var beautify = require("devtools/shared/jsbeautify/beautify");
+var SanityTest = require('devtools/shared/jsbeautify/lib/sanitytest');
+var Urlencoded = require('devtools/shared/jsbeautify/lib/urlencode_unpacker');
+var {run_beautifier_tests} = require('devtools/shared/jsbeautify/src/beautify-tests');
diff --git a/devtools/shared/jsbeautify/tests/unit/test.js b/devtools/shared/jsbeautify/tests/unit/test.js
new file mode 100644
index 000000000..9b507624c
--- /dev/null
+++ b/devtools/shared/jsbeautify/tests/unit/test.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ var sanityTest = new SanityTest();
+ var results = run_beautifier_tests(sanityTest,
+ Urlencoded,
+ beautify.js,
+ beautify.html,
+ beautify.css);
+
+ for (let [test_name, parameters, expected_value, result] of sanityTest.successes) {
+ equal(result, expected_value, "actual result matches expected");
+ }
+
+ for (let [test_name, parameters, expected_value, result] of sanityTest.failures) {
+ equal(result, expected_value, "actual result matches expected");
+ }
+}
diff --git a/devtools/shared/jsbeautify/tests/unit/xpcshell.ini b/devtools/shared/jsbeautify/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..3d89527c6
--- /dev/null
+++ b/devtools/shared/jsbeautify/tests/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools
+head = head_jsbeautify.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test.js]
diff --git a/devtools/shared/l10n.js b/devtools/shared/l10n.js
new file mode 100644
index 000000000..8e91ed168
--- /dev/null
+++ b/devtools/shared/l10n.js
@@ -0,0 +1,253 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const parsePropertiesFile = require("devtools/shared/node-properties/node-properties");
+const { sprintf } = require("devtools/shared/sprintfjs/sprintf");
+
+const propertiesMap = {};
+
+// We need some special treatment here for webpack.
+//
+// Webpack doesn't always handle dynamic requires in the best way. In
+// particular if it sees an unrestricted dynamic require, it will try
+// to put all the files it can find into the generated pack. (It can
+// also try a bit to parse the expression passed to require, but in
+// our case this doesn't work, because our call below doesn't provide
+// enough information.)
+//
+// Webpack also provides a way around this: require.context. The idea
+// here is to tell webpack some constraints so that it can include
+// fewer files in the pack.
+//
+// Here we introduce new require contexts for each possible locale
+// directory. Then we use the correct context to load the property
+// file. In the webpack case this results in just the locale property
+// files being included in the pack; and in the devtools case this is
+// a wordy no-op.
+const reqShared = require.context("raw!devtools/shared/locales/",
+ true, /^.*\.properties$/);
+const reqClient = require.context("raw!devtools/client/locales/",
+ true, /^.*\.properties$/);
+const reqGlobal = require.context("raw!toolkit/locales/",
+ true, /^.*\.properties$/);
+
+/**
+ * Memoized getter for properties files that ensures a given url is only required and
+ * parsed once.
+ *
+ * @param {String} url
+ * The URL of the properties file to parse.
+ * @return {Object} parsed properties mapped in an object.
+ */
+function getProperties(url) {
+ if (!propertiesMap[url]) {
+ // See the comment above about webpack and require contexts. Here
+ // we take an input like "devtools/shared/locales/debugger.properties"
+ // and decide which context require function to use. Despite the
+ // string processing here, in the end a string identical to |url|
+ // ends up being passed to "require".
+ let index = url.lastIndexOf("/");
+ // Turn "mumble/locales/resource.properties" => "./resource.properties".
+ let baseName = "." + url.substr(index);
+ let reqFn;
+ if (/^toolkit/.test(url)) {
+ reqFn = reqGlobal;
+ } else if (/^devtools\/shared/.test(url)) {
+ reqFn = reqShared;
+ } else {
+ reqFn = reqClient;
+ }
+ propertiesMap[url] = parsePropertiesFile(reqFn(baseName));
+ }
+
+ return propertiesMap[url];
+}
+
+/**
+ * Localization convenience methods.
+ *
+ * @param string stringBundleName
+ * The desired string bundle's name.
+ */
+function LocalizationHelper(stringBundleName) {
+ this.stringBundleName = stringBundleName;
+}
+
+LocalizationHelper.prototype = {
+ /**
+ * L10N shortcut function.
+ *
+ * @param string name
+ * @return string
+ */
+ getStr: function (name) {
+ let properties = getProperties(this.stringBundleName);
+ if (name in properties) {
+ return properties[name];
+ }
+
+ throw new Error("No localization found for [" + name + "]");
+ },
+
+ /**
+ * L10N shortcut function.
+ *
+ * @param string name
+ * @param array args
+ * @return string
+ */
+ getFormatStr: function (name, ...args) {
+ return sprintf(this.getStr(name), ...args);
+ },
+
+ /**
+ * L10N shortcut function for numeric arguments that need to be formatted.
+ * All numeric arguments will be fixed to 2 decimals and given a localized
+ * decimal separator. Other arguments will be left alone.
+ *
+ * @param string name
+ * @param array args
+ * @return string
+ */
+ getFormatStrWithNumbers: function (name, ...args) {
+ let newArgs = args.map(x => {
+ return typeof x == "number" ? this.numberWithDecimals(x, 2) : x;
+ });
+
+ return this.getFormatStr(name, ...newArgs);
+ },
+
+ /**
+ * Converts a number to a locale-aware string format and keeps a certain
+ * number of decimals.
+ *
+ * @param number number
+ * The number to convert.
+ * @param number decimals [optional]
+ * Total decimals to keep.
+ * @return string
+ * The localized number as a string.
+ */
+ numberWithDecimals: function (number, decimals = 0) {
+ // If this is an integer, don't do anything special.
+ if (number === (number|0)) {
+ return number;
+ }
+ // If this isn't a number (and yes, `isNaN(null)` is false), return zero.
+ if (isNaN(number) || number === null) {
+ return "0";
+ }
+
+ let localized = number.toLocaleString();
+
+ // If no grouping or decimal separators are available, bail out, because
+ // padding with zeros at the end of the string won't make sense anymore.
+ if (!localized.match(/[^\d]/)) {
+ return localized;
+ }
+
+ return number.toLocaleString(undefined, {
+ maximumFractionDigits: decimals,
+ minimumFractionDigits: decimals
+ });
+ }
+};
+
+function getPropertiesForNode(node) {
+ let bundleEl = node.closest("[data-localization-bundle]");
+ if (!bundleEl) {
+ return null;
+ }
+
+ let propertiesUrl = bundleEl.getAttribute("data-localization-bundle");
+ return getProperties(propertiesUrl);
+}
+
+/**
+ * Translate existing markup annotated with data-localization attributes.
+ *
+ * How to use data-localization in markup:
+ *
+ * <div data-localization="content=myContent;title=myTitle"/>
+ *
+ * The data-localization attribute identifies an element as being localizable.
+ * The content of the attribute is semi-colon separated list of descriptors.
+ * - "title=myTitle" means the "title" attribute should be replaced with the localized
+ * string corresponding to the key "myTitle".
+ * - "content=myContent" means the text content of the node should be replaced by the
+ * string corresponding to "myContent"
+ *
+ * How to define the localization bundle in markup:
+ *
+ * <div data-localization-bundle="url/to/my.properties">
+ * [...]
+ * <div data-localization="content=myContent;title=myTitle"/>
+ *
+ * Set the data-localization-bundle on an ancestor of the nodes that should be localized.
+ *
+ * @param {Element} root
+ * The root node to use for the localization
+ */
+function localizeMarkup(root) {
+ let elements = root.querySelectorAll("[data-localization]");
+ for (let element of elements) {
+ let properties = getPropertiesForNode(element);
+ if (!properties) {
+ continue;
+ }
+
+ let attributes = element.getAttribute("data-localization").split(";");
+ for (let attribute of attributes) {
+ let [name, value] = attribute.trim().split("=");
+ if (name === "content") {
+ element.textContent = properties[value];
+ } else {
+ element.setAttribute(name, properties[value]);
+ }
+ }
+
+ element.removeAttribute("data-localization");
+ }
+}
+
+const sharedL10N = new LocalizationHelper("devtools/shared/locales/shared.properties");
+
+/**
+ * A helper for having the same interface as LocalizationHelper, but for more
+ * than one file. Useful for abstracting l10n string locations.
+ */
+function MultiLocalizationHelper(...stringBundleNames) {
+ let instances = stringBundleNames.map(bundle => {
+ return new LocalizationHelper(bundle);
+ });
+
+ // Get all function members of the LocalizationHelper class, making sure we're
+ // not executing any potential getters while doing so, and wrap all the
+ // methods we've found to work on all given string bundles.
+ Object.getOwnPropertyNames(LocalizationHelper.prototype)
+ .map(name => ({
+ name: name,
+ descriptor: Object.getOwnPropertyDescriptor(LocalizationHelper.prototype,
+ name)
+ }))
+ .filter(({ descriptor }) => descriptor.value instanceof Function)
+ .forEach(method => {
+ this[method.name] = (...args) => {
+ for (let l10n of instances) {
+ try {
+ return method.descriptor.value.apply(l10n, args);
+ } catch (e) {
+ // Do nothing
+ }
+ }
+ return null;
+ };
+ });
+}
+
+exports.LocalizationHelper = LocalizationHelper;
+exports.localizeMarkup = localizeMarkup;
+exports.MultiLocalizationHelper = MultiLocalizationHelper;
+Object.defineProperty(exports, "ELLIPSIS", { get: () => sharedL10N.getStr("ellipsis") });
diff --git a/devtools/shared/layout/moz.build b/devtools/shared/layout/moz.build
new file mode 100644
index 000000000..62d5d2cb0
--- /dev/null
+++ b/devtools/shared/layout/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'utils.js'
+)
diff --git a/devtools/shared/layout/utils.js b/devtools/shared/layout/utils.js
new file mode 100644
index 000000000..1e6ab5075
--- /dev/null
+++ b/devtools/shared/layout/utils.js
@@ -0,0 +1,649 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Ci, Cc } = require("chrome");
+const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
+
+loader.lazyRequireGetter(this, "setIgnoreLayoutChanges", "devtools/server/actors/reflow", true);
+exports.setIgnoreLayoutChanges = (...args) =>
+ this.setIgnoreLayoutChanges(...args);
+
+/**
+ * Returns the `DOMWindowUtils` for the window given.
+ *
+ * @param {DOMWindow} win
+ * @returns {DOMWindowUtils}
+ */
+const utilsCache = new WeakMap();
+function utilsFor(win) {
+ if (!utilsCache.has(win)) {
+ utilsCache.set(win, win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils));
+ }
+ return utilsCache.get(win);
+}
+
+/**
+ * like win.top, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {DOMWindow}
+ */
+function getTopWindow(win) {
+ let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+
+ if (!docShell.isMozBrowserOrApp) {
+ return win.top;
+ }
+
+ let topDocShell =
+ docShell.getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries();
+
+ return topDocShell
+ ? topDocShell.contentViewer.DOMDocument.defaultView
+ : null;
+}
+
+exports.getTopWindow = getTopWindow;
+
+/**
+ * Returns `true` is the window given is a top level window.
+ * like win.top === win, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {Boolean}
+ */
+const isTopWindow = win => win && getTopWindow(win) === win;
+exports.isTopWindow = isTopWindow;
+
+/**
+ * Check a window is part of the boundary window given.
+ *
+ * @param {DOMWindow} boundaryWindow
+ * @param {DOMWindow} win
+ * @return {Boolean}
+ */
+function isWindowIncluded(boundaryWindow, win) {
+ if (win === boundaryWindow) {
+ return true;
+ }
+
+ let parent = getParentWindow(win);
+
+ if (!parent || parent === win) {
+ return false;
+ }
+
+ return isWindowIncluded(boundaryWindow, parent);
+}
+exports.isWindowIncluded = isWindowIncluded;
+
+/**
+ * like win.parent, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * @return {DOMWindow}
+ */
+function getParentWindow(win) {
+ if (isTopWindow(win)) {
+ return null;
+ }
+
+ let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIDocShell);
+
+ if (!docShell.isMozBrowserOrApp) {
+ return win.parent;
+ }
+
+ let parentDocShell =
+ docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
+
+ return parentDocShell
+ ? parentDocShell.contentViewer.DOMDocument.defaultView
+ : null;
+}
+
+exports.getParentWindow = getParentWindow;
+
+/**
+ * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
+ *
+ * @param {DOMWindow} win
+ * The window to get the frame for
+ * @return {DOMNode}
+ * The element in which the window is embedded.
+ */
+const getFrameElement = (win) =>
+ isTopWindow(win) ? null : utilsFor(win).containerElement;
+exports.getFrameElement = getFrameElement;
+
+/**
+ * Get the x/y offsets for of all the parent frames of a given node, limited to
+ * the boundary window given.
+ *
+ * @param {DOMWindow} boundaryWindow
+ * The window where to stop to iterate. If `null` is given, the top
+ * window is used.
+ * @param {DOMNode} node
+ * The node for which we are to get the offset
+ * @return {Array}
+ * The frame offset [x, y]
+ */
+function getFrameOffsets(boundaryWindow, node) {
+ let xOffset = 0;
+ let yOffset = 0;
+
+ let frameWin = getWindowFor(node);
+ let scale = getCurrentZoom(node);
+
+ if (boundaryWindow === null) {
+ boundaryWindow = getTopWindow(frameWin);
+ } else if (typeof boundaryWindow === "undefined") {
+ throw new Error("No boundaryWindow given. Use null for the default one.");
+ }
+
+ while (frameWin !== boundaryWindow) {
+ let frameElement = getFrameElement(frameWin);
+ if (!frameElement) {
+ break;
+ }
+
+ // We are in an iframe.
+ // We take into account the parent iframe position and its
+ // offset (borders and padding).
+ let frameRect = frameElement.getBoundingClientRect();
+
+ let [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
+
+ xOffset += frameRect.left + offsetLeft;
+ yOffset += frameRect.top + offsetTop;
+
+ frameWin = getParentWindow(frameWin);
+ }
+
+ return [xOffset * scale, yOffset * scale];
+}
+exports.getFrameOffsets = getFrameOffsets;
+
+/**
+ * Get box quads adjusted for iframes and zoom level.
+ *
+ * @param {DOMWindow} boundaryWindow
+ * The window where to stop to iterate. If `null` is given, the top
+ * window is used.
+ * @param {DOMNode} node
+ * The node for which we are to get the box model region
+ * quads.
+ * @param {String} region
+ * The box model region to return: "content", "padding", "border" or
+ * "margin".
+ * @return {Array}
+ * An array of objects that have the same structure as quads returned by
+ * getBoxQuads. An empty array if the node has no quads or is invalid.
+ */
+function getAdjustedQuads(boundaryWindow, node, region) {
+ if (!node || !node.getBoxQuads) {
+ return [];
+ }
+
+ let quads = node.getBoxQuads({
+ box: region
+ });
+
+ if (!quads.length) {
+ return [];
+ }
+
+ let [xOffset, yOffset] = getFrameOffsets(boundaryWindow, node);
+ let scale = getCurrentZoom(node);
+
+ let adjustedQuads = [];
+ for (let quad of quads) {
+ adjustedQuads.push({
+ p1: {
+ w: quad.p1.w * scale,
+ x: quad.p1.x * scale + xOffset,
+ y: quad.p1.y * scale + yOffset,
+ z: quad.p1.z * scale
+ },
+ p2: {
+ w: quad.p2.w * scale,
+ x: quad.p2.x * scale + xOffset,
+ y: quad.p2.y * scale + yOffset,
+ z: quad.p2.z * scale
+ },
+ p3: {
+ w: quad.p3.w * scale,
+ x: quad.p3.x * scale + xOffset,
+ y: quad.p3.y * scale + yOffset,
+ z: quad.p3.z * scale
+ },
+ p4: {
+ w: quad.p4.w * scale,
+ x: quad.p4.x * scale + xOffset,
+ y: quad.p4.y * scale + yOffset,
+ z: quad.p4.z * scale
+ },
+ bounds: {
+ bottom: quad.bounds.bottom * scale + yOffset,
+ height: quad.bounds.height * scale,
+ left: quad.bounds.left * scale + xOffset,
+ right: quad.bounds.right * scale + xOffset,
+ top: quad.bounds.top * scale + yOffset,
+ width: quad.bounds.width * scale,
+ x: quad.bounds.x * scale + xOffset,
+ y: quad.bounds.y * scale + yOffset
+ }
+ });
+ }
+
+ return adjustedQuads;
+}
+exports.getAdjustedQuads = getAdjustedQuads;
+
+/**
+ * Compute the absolute position and the dimensions of a node, relativalely
+ * to the root window.
+
+ * @param {DOMWindow} boundaryWindow
+ * The window where to stop to iterate. If `null` is given, the top
+ * window is used.
+ * @param {DOMNode} node
+ * a DOM element to get the bounds for
+ * @param {DOMWindow} contentWindow
+ * the content window holding the node
+ * @return {Object}
+ * A rect object with the {top, left, width, height} properties
+ */
+function getRect(boundaryWindow, node, contentWindow) {
+ let frameWin = node.ownerDocument.defaultView;
+ let clientRect = node.getBoundingClientRect();
+
+ if (boundaryWindow === null) {
+ boundaryWindow = getTopWindow(frameWin);
+ } else if (typeof boundaryWindow === "undefined") {
+ throw new Error("No boundaryWindow given. Use null for the default one.");
+ }
+
+ // Go up in the tree of frames to determine the correct rectangle.
+ // clientRect is read-only, we need to be able to change properties.
+ let rect = {
+ top: clientRect.top + contentWindow.pageYOffset,
+ left: clientRect.left + contentWindow.pageXOffset,
+ width: clientRect.width,
+ height: clientRect.height
+ };
+
+ // We iterate through all the parent windows.
+ while (frameWin !== boundaryWindow) {
+ let frameElement = getFrameElement(frameWin);
+ if (!frameElement) {
+ break;
+ }
+
+ // We are in an iframe.
+ // We take into account the parent iframe position and its
+ // offset (borders and padding).
+ let frameRect = frameElement.getBoundingClientRect();
+
+ let [offsetTop, offsetLeft] = getFrameContentOffset(frameElement);
+
+ rect.top += frameRect.top + offsetTop;
+ rect.left += frameRect.left + offsetLeft;
+
+ frameWin = getParentWindow(frameWin);
+ }
+
+ return rect;
+}
+exports.getRect = getRect;
+
+/**
+ * Get the 4 bounding points for a node taking iframes into account.
+ * Note that for transformed nodes, this will return the untransformed bound.
+ *
+ * @param {DOMWindow} boundaryWindow
+ * The window where to stop to iterate. If `null` is given, the top
+ * window is used.
+ * @param {DOMNode} node
+ * @return {Object}
+ * An object with p1,p2,p3,p4 properties being {x,y} objects
+ */
+function getNodeBounds(boundaryWindow, node) {
+ if (!node) {
+ return null;
+ }
+
+ let scale = getCurrentZoom(node);
+
+ // Find out the offset of the node in its current frame
+ let offsetLeft = 0;
+ let offsetTop = 0;
+ let el = node;
+ while (el && el.parentNode) {
+ offsetLeft += el.offsetLeft;
+ offsetTop += el.offsetTop;
+ el = el.offsetParent;
+ }
+
+ // Also take scrolled containers into account
+ el = node;
+ while (el && el.parentNode) {
+ if (el.scrollTop) {
+ offsetTop -= el.scrollTop;
+ }
+ if (el.scrollLeft) {
+ offsetLeft -= el.scrollLeft;
+ }
+ el = el.parentNode;
+ }
+
+ // And add the potential frame offset if the node is nested
+ let [xOffset, yOffset] = getFrameOffsets(boundaryWindow, node);
+ xOffset += offsetLeft;
+ yOffset += offsetTop;
+
+ xOffset *= scale;
+ yOffset *= scale;
+
+ // Get the width and height
+ let width = node.offsetWidth * scale;
+ let height = node.offsetHeight * scale;
+
+ return {
+ p1: {x: xOffset, y: yOffset},
+ p2: {x: xOffset + width, y: yOffset},
+ p3: {x: xOffset + width, y: yOffset + height},
+ p4: {x: xOffset, y: yOffset + height}
+ };
+}
+exports.getNodeBounds = getNodeBounds;
+
+/**
+ * Same as doing iframe.contentWindow but works with all types of container
+ * elements that act like frames (e.g. <embed>), where 'contentWindow' isn't a
+ * property that can be accessed.
+ * This uses the inIDeepTreeWalker instead.
+ * @param {DOMNode} frame
+ * @return {Window}
+ */
+function safelyGetContentWindow(frame) {
+ if (frame.contentWindow) {
+ return frame.contentWindow;
+ }
+
+ let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"]
+ .createInstance(Ci.inIDeepTreeWalker);
+ walker.showSubDocuments = true;
+ walker.showDocumentsAsNodes = true;
+ walker.init(frame, nodeFilterConstants.SHOW_ALL);
+ walker.currentNode = frame;
+
+ let document = walker.nextNode();
+ if (!document || !document.defaultView) {
+ throw new Error("Couldn't get the content window inside frame " + frame);
+ }
+
+ return document.defaultView;
+}
+
+/**
+ * Returns a frame's content offset (frame border + padding).
+ * Note: this function shouldn't need to exist, had the platform provided a
+ * suitable API for determining the offset between the frame's content and
+ * its bounding client rect. Bug 626359 should provide us with such an API.
+ *
+ * @param {DOMNode} frame
+ * The frame.
+ * @return {Array} [offsetTop, offsetLeft]
+ * offsetTop is the distance from the top of the frame and the top of
+ * the content document.
+ * offsetLeft is the distance from the left of the frame and the left
+ * of the content document.
+ */
+function getFrameContentOffset(frame) {
+ let style = safelyGetContentWindow(frame).getComputedStyle(frame, null);
+
+ // In some cases, the computed style is null
+ if (!style) {
+ return [0, 0];
+ }
+
+ let paddingTop = parseInt(style.getPropertyValue("padding-top"), 10);
+ let paddingLeft = parseInt(style.getPropertyValue("padding-left"), 10);
+
+ let borderTop = parseInt(style.getPropertyValue("border-top-width"), 10);
+ let borderLeft = parseInt(style.getPropertyValue("border-left-width"), 10);
+
+ return [borderTop + paddingTop, borderLeft + paddingLeft];
+}
+
+/**
+ * Find an element from the given coordinates. This method descends through
+ * frames to find the element the user clicked inside frames.
+ *
+ * @param {DOMDocument} document
+ * The document to look into.
+ * @param {Number} x
+ * @param {Number} y
+ * @return {DOMNode}
+ * the element node found at the given coordinates, or null if no node
+ * was found
+ */
+function getElementFromPoint(document, x, y) {
+ let node = document.elementFromPoint(x, y);
+ if (node && node.contentDocument) {
+ if (node instanceof Ci.nsIDOMHTMLIFrameElement) {
+ let rect = node.getBoundingClientRect();
+
+ // Gap between the frame and its content window.
+ let [offsetTop, offsetLeft] = getFrameContentOffset(node);
+
+ x -= rect.left + offsetLeft;
+ y -= rect.top + offsetTop;
+
+ if (x < 0 || y < 0) {
+ // Didn't reach the content document, still over the frame.
+ return node;
+ }
+ }
+ if (node instanceof Ci.nsIDOMHTMLIFrameElement ||
+ node instanceof Ci.nsIDOMHTMLFrameElement) {
+ let subnode = getElementFromPoint(node.contentDocument, x, y);
+ if (subnode) {
+ node = subnode;
+ }
+ }
+ }
+ return node;
+}
+exports.getElementFromPoint = getElementFromPoint;
+
+/**
+ * Check if a node and its document are still alive
+ * and attached to the window.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isNodeConnected(node) {
+ if (!node.ownerDocument || !node.ownerDocument.defaultView) {
+ return false;
+ }
+
+ try {
+ return !(node.compareDocumentPosition(node.ownerDocument.documentElement) &
+ node.DOCUMENT_POSITION_DISCONNECTED);
+ } catch (e) {
+ // "can't access dead object" error
+ return false;
+ }
+}
+exports.isNodeConnected = isNodeConnected;
+
+/**
+ * Traverse getBindingParent until arriving upon the bound element
+ * responsible for the generation of the specified node.
+ * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/DOM_Interfaces#getBindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {DOMNode}
+ * If node is not anonymous, this will return node. Otherwise,
+ * it will return the bound element
+ *
+ */
+function getRootBindingParent(node) {
+ let parent;
+ let doc = node.ownerDocument;
+ if (!doc) {
+ return node;
+ }
+ while ((parent = doc.getBindingParent(node))) {
+ node = parent;
+ }
+ return node;
+}
+exports.getRootBindingParent = getRootBindingParent;
+
+function getBindingParent(node) {
+ let doc = node.ownerDocument;
+ if (!doc) {
+ return null;
+ }
+
+ // If there is no binding parent then it is not anonymous.
+ let parent = doc.getBindingParent(node);
+ if (!parent) {
+ return null;
+ }
+
+ return parent;
+}
+exports.getBindingParent = getBindingParent;
+
+/**
+ * Determine whether a node is anonymous by determining if there
+ * is a bindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const isAnonymous = (node) => getRootBindingParent(node) !== node;
+exports.isAnonymous = isAnonymous;
+
+/**
+ * Determine whether a node has a bindingParent.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const hasBindingParent = (node) => !!getBindingParent(node);
+
+/**
+ * Determine whether a node is native anonymous content (as opposed
+ * to XBL anonymous or shadow DOM).
+ * Native anonymous content includes elements like internals to form
+ * controls and ::before/::after.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+const isNativeAnonymous = (node) =>
+ hasBindingParent(node) && !(isXBLAnonymous(node) || isShadowAnonymous(node));
+
+exports.isNativeAnonymous = isNativeAnonymous;
+
+/**
+ * Determine whether a node is XBL anonymous content (as opposed
+ * to native anonymous or shadow DOM).
+ * See https://developer.mozilla.org/en-US/docs/XBL/XBL_1.0_Reference/Anonymous_Content.
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ *
+ */
+function isXBLAnonymous(node) {
+ let parent = getBindingParent(node);
+ if (!parent) {
+ return false;
+ }
+
+ // Shadow nodes also show up in getAnonymousNodes, so return false.
+ if (parent.shadowRoot && parent.shadowRoot.contains(node)) {
+ return false;
+ }
+
+ let anonNodes = [...node.ownerDocument.getAnonymousNodes(parent) || []];
+ return anonNodes.indexOf(node) > -1;
+}
+exports.isXBLAnonymous = isXBLAnonymous;
+
+/**
+ * Determine whether a node is a child of a shadow root.
+ * See https://w3c.github.io/webcomponents/spec/shadow/
+ *
+ * @param {DOMNode} node
+ * @return {Boolean}
+ */
+function isShadowAnonymous(node) {
+ let parent = getBindingParent(node);
+ if (!parent) {
+ return false;
+ }
+
+ // If there is a shadowRoot and this is part of it then this
+ // is not native anonymous
+ return parent.shadowRoot && parent.shadowRoot.contains(node);
+}
+exports.isShadowAnonymous = isShadowAnonymous;
+
+/**
+ * Get the current zoom factor applied to the container window of a given node.
+ * Container windows are used as a weakmap key to store the corresponding
+ * nsIDOMWindowUtils instance to avoid querying it every time.
+ *
+ * @param {DOMNode|DOMWindow}
+ * The node for which the zoom factor should be calculated, or its
+ * owner window.
+ * @return {Number}
+ */
+function getCurrentZoom(node) {
+ let win = getWindowFor(node);
+
+ if (!win) {
+ throw new Error("Unable to get the zoom from the given argument.");
+ }
+
+ return utilsFor(win).fullZoom;
+}
+exports.getCurrentZoom = getCurrentZoom;
+
+/**
+ * Return the default view for a given node, where node can be:
+ * - a DOM node
+ * - the document node
+ * - the window itself
+ * @param {DOMNode|DOMWindow|DOMDocument} node The node to get the window for.
+ * @return {DOMWindow}
+ */
+function getWindowFor(node) {
+ if (node instanceof Ci.nsIDOMNode) {
+ if (node.nodeType === node.DOCUMENT_NODE) {
+ return node.defaultView;
+ }
+ return node.ownerDocument.defaultView;
+ } else if (node instanceof Ci.nsIDOMWindow) {
+ return node;
+ }
+ return null;
+}
diff --git a/devtools/shared/loader-plugin-raw.jsm b/devtools/shared/loader-plugin-raw.jsm
new file mode 100644
index 000000000..f719ac054
--- /dev/null
+++ b/devtools/shared/loader-plugin-raw.jsm
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { utils: Cu } = Components;
+const { NetUtil } = Cu.import("resource://gre/modules/NetUtil.jsm", {});
+
+/**
+ * A function that can be used as part of a require hook for a
+ * loader.js Loader. This function only handles webpack-style "raw!"
+ * requires; other requires should not be passed to this. See
+ * https://github.com/webpack/raw-loader.
+ */
+this.requireRawId = function (id, require) {
+ let uri = require.resolve(id.slice(4));
+ // If the original string did not end with ".js", then
+ // require.resolve might have added the suffix. We don't want to
+ // add a suffix for a raw load (if needed the caller can specify it
+ // manually), so remove it here.
+ if (!id.endsWith(".js") && uri.endsWith(".js")) {
+ uri = uri.slice(0, -3);
+ }
+
+ let stream = NetUtil.newChannel({
+ uri: NetUtil.newURI(uri, "UTF-8"),
+ loadUsingSystemPrincipal: true
+ }).open2();
+
+ let count = stream.available();
+ let data = NetUtil.readInputStreamToString(stream, count, {
+ charset: "UTF-8"
+ });
+ stream.close();
+
+ // For the time being it doesn't seem worthwhile to cache the
+ // result here.
+ return data;
+};
+
+this.EXPORTED_SYMBOLS = ["requireRawId"];
diff --git a/devtools/shared/locales/en-US/csscoverage.dtd b/devtools/shared/locales/en-US/csscoverage.dtd
new file mode 100644
index 000000000..b306387ad
--- /dev/null
+++ b/devtools/shared/locales/en-US/csscoverage.dtd
@@ -0,0 +1,47 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+ - License, v. 2.0. If a copy of the MPL was not distributed with this
+ - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!-- LOCALIZATION NOTE : FILE This file contains the CSS Coverage Report
+ - strings. See the 'csscoverage' command for more information, and
+ - devtools/client/styleeditor/styleeditor.xul for context -->
+
+<!-- LOCALIZATION NOTE : FILE The correct localization of this file might be to
+ - keep it in English, or another language commonly spoken among web developers.
+ - You want to make that choice consistent across the developer tools.
+ - A good criteria is the language in which you'd find the best
+ - documentation on web development on the web. -->
+
+<!-- LOCALIZATION NOTE (csscoverage.backButton):
+ - Text on the button to go back to the main style editor -->
+<!ENTITY csscoverage.backButton "Back">
+
+<!-- LOCALIZATION NOTE (csscoverage.unused, csscoverage.noMatches):
+ - This is the heading and body text for the CSS usage part of the report -->
+<!ENTITY csscoverage.unused "Unused Rules">
+<!ENTITY csscoverage.noMatches "No matches found for the following rules:">
+
+<!-- LOCALIZATION NOTE (csscoverage.optimize.header):
+ - This is the heading for the CSS optimization part of the report -->
+<!ENTITY csscoverage.optimize.header "Optimizable Pages">
+
+<!-- LOCALIZATION NOTE (csscoverage.preload1, csscoverage.preload2,
+ - csscoverage.preload3): These 3 are part of a paragraph with 1 and 2
+ - separated by a styled <link> tag and 2 and 3 separated by a styled
+ - <style> tag -->
+<!ENTITY csscoverage.optimize.body1 "You can sometimes speed up loading by moving">
+<!ENTITY csscoverage.optimize.body2 "tags to the bottom of the page and creating a new inline">
+<!ENTITY csscoverage.optimize.body3 "element with the styles needed before the ‘load’ event to the top. Here are the style blocks you need:">
+
+<!-- LOCALIZATION NOTE (csscoverage.optimize.bodyX):
+ - This is what we say when we have no optimization suggestions -->
+<!ENTITY csscoverage.optimize.bodyX "All rules are inlined.">
+
+<!-- LOCALIZATION NOTE (csscoverage.footer1, csscoverage.footer2a,
+ - csscoverage.footer3, csscoverage.footer4): The text displayed at the
+ - bottom of the page, with 2a being the URL opened when the link text in 3
+ - is clicked -->
+<!ENTITY csscoverage.footer1 "See">
+<!ENTITY csscoverage.footer2a "https://developer.mozilla.org/docs/Tools/CSS_Coverage">
+<!ENTITY csscoverage.footer3 "the MDN article on the CSS Coverage Tool">
+<!ENTITY csscoverage.footer4 "for caveats in the generation of this report.">
diff --git a/devtools/shared/locales/en-US/csscoverage.properties b/devtools/shared/locales/en-US/csscoverage.properties
new file mode 100644
index 000000000..646e75981
--- /dev/null
+++ b/devtools/shared/locales/en-US/csscoverage.properties
@@ -0,0 +1,32 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used in the 'csscoverage' command and in
+# the user interface that this command creates.
+
+# LOCALIZATION NOTE (csscoverageDesc, csscoverageStartDesc2,
+# csscoverageStopDesc2, csscoverageOneShotDesc2, csscoverageToggleDesc2,
+# csscoverageReportDesc2): Short descriptions of the csscoverage commands
+csscoverageDesc=Control CSS coverage analysis
+csscoverageStartDesc2=Begin collecting CSS coverage data
+csscoverageStopDesc2=Stop collecting CSS coverage data
+csscoverageOneShotDesc2=Collect instantaneous CSS coverage data
+csscoverageToggleDesc2=Toggle collecting CSS coverage data
+csscoverageReportDesc2=Show CSS coverage report
+csscoverageStartNoReloadDesc=Don’t start with a page reload
+csscoverageStartNoReloadManual=It’s best if we start by reloading the current page because that starts the test at a known point, but there could be reasons why we don’t want to do that (e.g. the page contains state that will be lost across a reload)
+
+# LOCALIZATION NOTE (csscoverageRunningReply, csscoverageDoneReply): Text that
+# describes the current state of the css coverage system
+csscoverageRunningReply=Running CSS coverage analysis
+csscoverageDoneReply=CSS Coverage analysis completed
+
+# LOCALIZATION NOTE (csscoverageRunningError, csscoverageNotRunningError,
+# csscoverageNotRunError): Error message that describe things that can go wrong
+# with the css coverage system
+csscoverageRunningError=CSS coverage analysis already running
+csscoverageNotRunningError=CSS coverage analysis not running
+csscoverageNotRunError=CSS coverage analysis has not been run
+csscoverageNoRemoteError=Target does not support CSS Coverage
+csscoverageOneShotReportError=CSS coverage report is not available for ‘oneshot’ data. Please use start/stop.
diff --git a/devtools/shared/locales/en-US/debugger.properties b/devtools/shared/locales/en-US/debugger.properties
new file mode 100644
index 000000000..50e42a725
--- /dev/null
+++ b/devtools/shared/locales/en-US/debugger.properties
@@ -0,0 +1,59 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside the Debugger
+# which is available from the Web Developer sub-menu -> 'Debugger'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (remoteIncomingPromptTitle): The title displayed on the
+# dialog that prompts the user to allow the incoming connection.
+remoteIncomingPromptTitle=Incoming Connection
+
+# LOCALIZATION NOTE (remoteIncomingPromptHeader): Header displayed on the
+# dialog that prompts the user to allow the incoming connection.
+remoteIncomingPromptHeader=An incoming request to permit remote debugging connection was detected. A remote client can take complete control over your browser!
+# LOCALIZATION NOTE (remoteIncomingPromptClientEndpoint): Part of the prompt
+# dialog for the user to choose whether an incoming connection should be
+# allowed.
+# %1$S: The host and port of the client such as "127.0.0.1:6000"
+remoteIncomingPromptClientEndpoint=Client Endpoint: %1$S
+# LOCALIZATION NOTE (remoteIncomingPromptServerEndpoint): Part of the prompt
+# dialog for the user to choose whether an incoming connection should be
+# allowed.
+# %1$S: The host and port of the server such as "127.0.0.1:6000"
+remoteIncomingPromptServerEndpoint=Server Endpoint: %1$S
+# LOCALIZATION NOTE (remoteIncomingPromptFooter): Footer displayed on the
+# dialog that prompts the user to allow the incoming connection.
+remoteIncomingPromptFooter=Allow connection?
+
+# LOCALIZATION NOTE (remoteIncomingPromptDisable): The label displayed on the
+# third button in the incoming connection dialog that lets the user disable the
+# remote debugger server.
+remoteIncomingPromptDisable=Disable
+
+# LOCALIZATION NOTE (clientSendOOBTitle): The title displayed on the dialog that
+# instructs the user to transfer an authentication token to the server.
+clientSendOOBTitle=Client Identification
+# LOCALIZATION NOTE (clientSendOOBHeader): Header displayed on the dialog that
+# instructs the user to transfer an authentication token to the server.
+clientSendOOBHeader=The endpoint you are connecting to needs more information to authenticate this connection. Please provide the token below in the prompt that appears on the other end.
+# LOCALIZATION NOTE (clientSendOOBHash): Part of the dialog that instructs the
+# user to transfer an authentication token to the server.
+# %1$S: The client's cert fingerprint
+clientSendOOBHash=My Cert: %1$S
+# LOCALIZATION NOTE (clientSendOOBToken): Part of the dialog that instructs the
+# user to transfer an authentication token to the server.
+# %1$S: The authentication token that the user will transfer.
+clientSendOOBToken=Token: %1$S
+
+# LOCALIZATION NOTE (serverReceiveOOBTitle): The title displayed on the dialog
+# that instructs the user to provide an authentication token from the client.
+serverReceiveOOBTitle=Provide Client Token
+# LOCALIZATION NOTE (serverReceiveOOBBody): Main text displayed on the dialog
+# that instructs the user to provide an authentication token from the client.
+serverReceiveOOBBody=The client should be displaying a token value. Enter that token value here to complete authentication with this client.
diff --git a/devtools/shared/locales/en-US/gcli.properties b/devtools/shared/locales/en-US/gcli.properties
new file mode 100644
index 000000000..e5231e44a
--- /dev/null
+++ b/devtools/shared/locales/en-US/gcli.properties
@@ -0,0 +1,318 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside the Web Console
+# command line which is available from the Web Developer sub-menu
+# -> 'Web Console'.
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# For each command there are in general two strings. As an example consider
+# the 'pref' command.
+# commandDesc (e.g. prefDesc for the command 'pref'): this string contains a
+# very short description of the command. It's designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+# commandManual (e.g. prefManual for the command 'pref'): this string will
+# contain a fuller description of the command. It's diplayed when the user
+# asks for help about a specific command (e.g. 'help pref').
+
+# LOCALIZATION NOTE: This message is used to describe any command or command
+# parameter when no description has been provided.
+canonDescNone=(No description)
+
+# LOCALIZATION NOTE: The default name for a group of parameters.
+canonDefaultGroupName=Options
+
+# LOCALIZATION NOTE (canonProxyDesc, canonProxyManual): These commands are
+# used to execute commands on a remote system (using a proxy). Parameters: %S
+# is the name of the remote system.
+canonProxyDesc=Execute a command on %S
+canonProxyManual=A set of commands that are executed on a remote system. The remote system is reached via %S
+
+# LOCALIZATION NOTE: This error message is displayed when we try to add a new
+# command (using a proxy) where one already exists with the same name.
+canonProxyExists=There is already a command called ‘%S’
+
+# LOCALIZATION NOTE: This message describes the '{' command, which allows
+# entry of JavaScript like traditional developer tool command lines.
+cliEvalJavascript=Enter JavaScript directly
+
+# LOCALIZATION NOTE: This message is displayed when the command line has more
+# arguments than the current command can understand.
+cliUnusedArg=Too many arguments
+
+# LOCALIZATION NOTE: The title of the dialog which displays the options that
+# are available to the current command.
+cliOptions=Available Options
+
+# LOCALIZATION NOTE: The error message when the user types a command that
+# isn't registered
+cliUnknownCommand2=Invalid Command: ‘%1$S’.
+
+# LOCALIZATION NOTE: A parameter should have a value, but doesn't
+cliIncompleteParam=Value required for ‘%1$S’.
+
+# LOCALIZATION NOTE: Error message given when a file argument points to a file
+# that does not exist, but should (e.g. for use with File->Open) %1$S is a
+# filename
+fileErrNotExists=‘%1$S’ doesn’t exist
+
+# LOCALIZATION NOTE: Error message given when a file argument points to a file
+# that exists, but should not (e.g. for use with File->Save As) %1$S is a
+# filename
+fileErrExists=‘%1$S’ already exists
+
+# LOCALIZATION NOTE: Error message given when a file argument points to a
+# non-file, when a file is needed. %1$S is a filename
+fileErrIsNotFile=‘%1$S’ is not a file
+
+# LOCALIZATION NOTE: Error message given when a file argument points to a
+# non-directory, when a directory is needed (e.g. for use with 'cd') %1$S is a
+# filename
+fileErrIsNotDirectory=‘%1$S’ is not a directory
+
+# LOCALIZATION NOTE: Error message given when a file argument does not match
+# the specified regular expression %1$S is a filename %2$S is a regular
+# expression
+fileErrDoesntMatch=‘%1$S’ does not match ‘%2$S’
+
+# LOCALIZATION NOTE: When the menu has displayed all the matches that it
+# should (i.e. about 10 items) then we display this to alert the user that
+# more matches are available.
+fieldMenuMore=More matches, keep typing
+
+# LOCALIZATION NOTE: The command line provides completion for JavaScript
+# commands, however there are times when the scope of what we're completing
+# against can't be used. This error message is displayed when this happens.
+jstypeParseScope=Scope lost
+
+# LOCALIZATION NOTE (jstypeParseMissing, jstypeBeginSyntax,
+# jstypeBeginUnterm): These error messages are displayed when the command line
+# is doing JavaScript completion and encounters errors.
+jstypeParseMissing=Can’t find property ‘%S’
+jstypeBeginSyntax=Syntax error
+jstypeBeginUnterm=Unterminated string literal
+
+# LOCALIZATION NOTE: This message is displayed if the system for providing
+# JavaScript completions encounters and error it displays this.
+jstypeParseError=Error
+
+# LOCALIZATION NOTE (typesNumberNan, typesNumberNotInt2, typesDateNan): These
+# error messages are displayed when the command line is passed a variable
+# which has the wrong format and can't be converted. Parameters: %S is the
+# passed variable.
+typesNumberNan=Can’t convert “%S” to a number.
+typesNumberNotInt2=Can’t convert “%S” to an integer.
+typesDateNan=Can’t convert “%S” to a date.
+
+# LOCALIZATION NOTE (typesNumberMax, typesNumberMin, typesDateMax,
+# typesDateMin): These error messages are displayed when the command line is
+# passed a variable which has a value out of range (number or date).
+# Parameters: %1$S is the passed variable, %2$S is the limit value.
+typesNumberMax=%1$S is greater than maximum allowed: %2$S.
+typesNumberMin=%1$S is smaller than minimum allowed: %2$S.
+typesDateMax=%1$S is later than maximum allowed: %2$S.
+typesDateMin=%1$S is earlier than minimum allowed: %2$S.
+
+# LOCALIZATION NOTE: This error message is displayed when the command line is
+# passed an option with a limited number of correct values, but the passed
+# value is not one of them.
+typesSelectionNomatch=Can’t use ‘%S’.
+
+# LOCALIZATION NOTE: This error message is displayed when the command line is
+# expecting a CSS query string, however the passed string is not valid.
+nodeParseSyntax=Syntax error in CSS query
+
+# LOCALIZATION NOTE (nodeParseMultiple, nodeParseNone): These error messages
+# are displayed when the command line is expecting a CSS string that matches a
+# single node, but more nodes (or none) match.
+nodeParseMultiple=Too many matches (%S)
+nodeParseNone=No matches
+
+# LOCALIZATION NOTE (helpDesc, helpManual, helpSearchDesc, helpSearchManual3):
+# These strings describe the "help" command, used to display a description of
+# a command (e.g. "help pref"), and its parameter 'search'.
+helpDesc=Get help on the available commands
+helpManual=Provide help either on a specific command (if a search string is provided and an exact match is found) or on the available commands (if a search string is not provided, or if no exact match is found).
+helpSearchDesc=Search string
+helpSearchManual3=search string to use in narrowing down the displayed commands. Regular expressions not supported.
+
+# LOCALIZATION NOTE: These strings are displayed in the help page for a
+# command in the console.
+helpManSynopsis=Synopsis
+
+# LOCALIZATION NOTE: This message is displayed in the help page if the command
+# has no parameters.
+helpManNone=None
+
+# LOCALIZATION NOTE: This message is displayed in response to the 'help'
+# command when used without a filter, just above the list of known commands.
+helpListAll=Available Commands:
+
+# LOCALIZATION NOTE (helpListPrefix, helpListNone): These messages are
+# displayed in response to the 'help <search>' command (i.e. with a search
+# string), just above the list of matching commands. Parameters: %S is the
+# search string.
+helpListPrefix=Commands starting with ‘%S’:
+helpListNone=No commands starting with ‘%S’
+
+# LOCALIZATION NOTE (helpManRequired, helpManOptional, helpManDefault): When
+# the 'help x' command wants to show the manual for the 'x' command, it needs
+# to be able to describe the parameters as either required or optional, or if
+# they have a default value.
+helpManRequired=required
+helpManOptional=optional
+helpManDefault=optional, default=%S
+
+# LOCALIZATION NOTE: This forms part of the output from the 'help' command.
+# 'GCLI' is a project name and should be left untranslated.
+helpIntro=GCLI is an experiment to create a highly usable command line for web developers.
+
+# LOCALIZATION NOTE: Text shown as part of the output of the 'help' command
+# when the command in question has sub-commands, before a list of the matching
+# sub-commands.
+subCommands=Sub-Commands
+
+# LOCALIZATION NOTE: This error message is displayed when the command line is
+# cannot find a match for the parse types.
+commandParseError=Command line parsing error
+
+# LOCALIZATION NOTE (contextDesc, contextManual, contextPrefixDesc): These
+# strings are used to describe the 'context' command and its 'prefix'
+# parameter. See localization comment for 'connect' for an explanation about
+# 'prefix'.
+contextDesc=Concentrate on a group of commands
+contextManual=Setup a default prefix to future commands. For example ‘context git’ would allow you to type ‘commit’ rather than ‘git commit’.
+contextPrefixDesc=The command prefix
+
+# LOCALIZATION NOTE: This message message displayed during the processing of
+# the 'context' command, when the found command is not a parent command.
+contextNotParentError=Can’t use ‘%S’ as a prefix because it is not a parent command.
+
+# LOCALIZATION NOTE (contextReply, contextEmptyReply): These messages are
+# displayed during the processing of the 'context' command, to indicate
+# success or that there is no command prefix.
+contextReply=Using %S as a command prefix
+contextEmptyReply=Command prefix is unset
+
+# LOCALIZATION NOTE (connectDesc, connectManual, connectPrefixDesc,
+# connectMethodDesc, connectUrlDesc, connectDupReply): These strings describe
+# the 'connect' command and all its available parameters. A 'prefix' is an
+# alias for the remote server (think of it as a "connection name"), and it
+# allows to identify a specific server when connected to multiple remote
+# servers.
+connectDesc=Proxy commands to server
+connectManual=Connect to the server, creating local versions of the commands on the server. Remote commands initially have a prefix to distinguish them from local commands (but see the context command to get past this)
+connectPrefixDesc=Parent prefix for imported commands
+connectMethodDesc=The method of connecting
+connectUrlDesc=The URL to connect to
+connectDupReply=Connection called %S already exists.
+
+# LOCALIZATION NOTE: The output of the 'connect' command, telling the user
+# what it has done. Parameters: %S is the prefix command. See localization
+# comment for 'connect' for an explanation about 'prefix'.
+connectReply=Added %S commands.
+
+# LOCALIZATION NOTE (disconnectDesc2, disconnectManual2,
+# disconnectPrefixDesc): These strings describe the 'disconnect' command and
+# all its available parameters. See localization comment for 'connect' for an
+# explanation about 'prefix'.
+disconnectDesc2=Disconnect from server
+disconnectManual2=Disconnect from a server currently connected for remote commands execution
+disconnectPrefixDesc=Parent prefix for imported commands
+
+# LOCALIZATION NOTE: This is the output of the 'disconnect' command,
+# explaining the user what has been done. Parameters: %S is the number of
+# commands removed.
+disconnectReply=Removed %S commands.
+
+# LOCALIZATION NOTE (globalDesc, globalWindowDesc, globalOutput): These
+# strings describe the 'global' command and its parameters
+globalDesc=Change the JS global
+globalWindowDesc=The new window/global
+globalOutput=JS global is now %S
+
+# LOCALIZATION NOTE: These strings describe the 'clear' command
+clearDesc=Clear the output area
+
+# LOCALIZATION NOTE (langDesc, langOutput): These strings describe the 'lang'
+# command and its parameters
+langDesc=Enter commands in different languages
+langOutput=You are now using %S
+
+# LOCALIZATION NOTE (prefDesc, prefManual, prefListDesc, prefListManual,
+# prefListSearchDesc, prefListSearchManual, prefShowDesc, prefShowManual,
+# prefShowSettingDesc, prefShowSettingManual): These strings describe the
+# 'pref' command and all its available sub-commands and parameters.
+prefDesc=Commands to control settings
+prefManual=Commands to display and alter preferences both for GCLI and the surrounding environment
+prefListDesc=Display available settings
+prefListManual=Display a list of preferences, optionally filtered when using the ‘search’ parameter
+prefListSearchDesc=Filter the list of settings displayed
+prefListSearchManual=Search for the given string in the list of available preferences
+prefShowDesc=Display setting value
+prefShowManual=Display the value of a given preference
+prefShowSettingDesc=Setting to display
+prefShowSettingManual=The name of the setting to display
+
+# LOCALIZATION NOTE: This message is used to show the preference name and the
+# associated preference value. Parameters: %1$S is the preference name, %2$S
+# is the preference value.
+prefShowSettingValue=%1$S: %2$S
+
+# LOCALIZATION NOTE (prefSetDesc, prefSetManual, prefSetSettingDesc,
+# prefSetSettingManual, prefSetValueDesc, prefSetValueManual): These strings
+# describe the 'pref set' command and all its parameters.
+prefSetDesc=Alter a setting
+prefSetManual=Alter preferences defined by the environment
+prefSetSettingDesc=Setting to alter
+prefSetSettingManual=The name of the setting to alter.
+prefSetValueDesc=New value for setting
+prefSetValueManual=The new value for the specified setting
+
+# LOCALIZATION NOTE (prefResetDesc, prefResetManual, prefResetSettingDesc,
+# prefResetSettingManual): These strings describe the 'pref reset' command and
+# all its parameters.
+prefResetDesc=Reset a setting
+prefResetManual=Reset the value of a setting to the system defaults
+prefResetSettingDesc=Setting to reset
+prefResetSettingManual=The name of the setting to reset to the system default value
+
+# LOCALIZATION NOTE: This string is displayed in the output from the 'pref
+# list' command as a label to an input element that allows the user to filter
+# the results.
+prefOutputFilter=Filter
+
+# LOCALIZATION NOTE (prefOutputName, prefOutputValue): These strings are
+# displayed in the output from the 'pref list' command as table headings.
+prefOutputName=Name
+prefOutputValue=Value
+
+# LOCALIZATION NOTE (introDesc, introManual): These strings describe the
+# 'intro' command. The localization of 'Got it!' should be the same used in
+# introTextGo.
+introDesc=Show the opening message
+introManual=Redisplay the message that is shown to new users until they click the ‘Got it!’ button
+
+# LOCALIZATION NOTE (introTextOpening3, introTextCommands, introTextKeys2,
+# introTextF1Escape, introTextGo): These strings are displayed when the user
+# first opens the developer toolbar to explain the command line, and is shown
+# each time it is opened until the user clicks the 'Got it!' button.
+introTextOpening3=GCLI is an experiment to create a highly usable command line for web developers.
+introTextCommands=For a list of commands type
+introTextKeys2=, or to show/hide command hints press
+introTextF1Escape=F1/Escape
+introTextGo=Got it!
+
+# LOCALIZATION NOTE: This is a short description of the 'hideIntro' setting.
+hideIntroDesc=Show the initial welcome message
+
+# LOCALIZATION NOTE: This is a description of the 'eagerHelper' setting. It's
+# displayed when the user asks for help on the settings. eagerHelper allows
+# users to select between showing no tooltips, permanent tooltips, and only
+# important tooltips.
+eagerHelperDesc=How eager are the tooltips
diff --git a/devtools/shared/locales/en-US/gclicommands.properties b/devtools/shared/locales/en-US/gclicommands.properties
new file mode 100644
index 000000000..06a34e42b
--- /dev/null
+++ b/devtools/shared/locales/en-US/gclicommands.properties
@@ -0,0 +1,1530 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside Web Console commands.
+# The Web Console command line is available from the Web Developer sub-menu
+# -> 'Web Console'.
+#
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+# LOCALIZATION NOTE (helpDesc) A very short string used to describe the
+# function of the help command.
+helpDesc=Get help on the available commands
+
+# LOCALIZATION NOTE (helpAvailable) Used in the output of the help command to
+# explain the contents of the command help table.
+helpAvailable=Available Commands
+
+# LOCALIZATION NOTE (notAvailableInE10S) Used in the output of any command that
+# is not compatible with multiprocess mode (E10S).
+notAvailableInE10S=The command ‘%1$S’ is not available in multiprocess mode (E10S)
+
+# LOCALIZATION NOTE (consoleDesc) A very short string used to describe the
+# function of the console command.
+consoleDesc=Commands to control the console
+
+# LOCALIZATION NOTE (consoleManual) A longer description describing the
+# set of commands that control the console.
+consoleManual=Filter, clear and close the web console
+
+# LOCALIZATION NOTE (consoleclearDesc) A very short string used to describe the
+# function of the 'console clear' command.
+consoleclearDesc=Clear the console
+
+# LOCALIZATION NOTE (screenshotDesc) A very short description of the
+# 'screenshot' command. See screenshotManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+screenshotDesc=Save an image of the page
+
+# LOCALIZATION NOTE (screenshotManual) A fuller description of the 'screenshot'
+# command, displayed when the user asks for help on what it does.
+screenshotManual=Save a PNG image of the entire visible window (optionally after a delay)
+
+# LOCALIZATION NOTE (screenshotFilenameDesc) A very short string to describe
+# the 'filename' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotFilenameDesc=Destination filename
+
+# LOCALIZATION NOTE (screenshotFilenameManual) A fuller description of the
+# 'filename' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotFilenameManual=The name of the file (should have a ‘.png’ extension) to which we write the screenshot.
+
+# LOCALIZATION NOTE (screenshotClipboardDesc) A very short string to describe
+# the 'clipboard' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotClipboardDesc=Copy screenshot to clipboard? (true/false)
+
+# LOCALIZATION NOTE (screenshotClipboardManual) A fuller description of the
+# 'clipboard' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotClipboardManual=True if you want to copy the screenshot instead of saving it to a file.
+
+# LOCALIZATION NOTE (screenshotGroupOptions) A label for the optional options of
+# the screenshot command.
+screenshotGroupOptions=Options
+
+# LOCALIZATION NOTE (screenshotDelayDesc) A very short string to describe
+# the 'delay' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotDelayDesc=Delay (seconds)
+
+# LOCALIZATION NOTE (screenshotDelayManual) A fuller description of the
+# 'delay' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotDelayManual=The time to wait (in seconds) before the screenshot is taken
+
+# LOCALIZATION NOTE (screenshotDPRDesc) A very short string to describe
+# the 'dpr' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotDPRDesc=Device pixel ratio
+
+# LOCALIZATION NOTE (screenshotDPRManual) A fuller description of the
+# 'dpr' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotDPRManual=The device pixel ratio to use when taking the screenshot
+
+# LOCALIZATION NOTE (screenshotFullscreenDesc) A very short string to describe
+# the 'fullscreen' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotFullPageDesc=Entire webpage? (true/false)
+
+# LOCALIZATION NOTE (screenshotFullscreenManual) A fuller description of the
+# 'fullscreen' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotFullPageManual=True if the screenshot should also include parts of the webpage which are outside the current scrolled bounds.
+
+# LOCALIZATION NOTE (screenshotGeneratedFilename) The auto generated filename
+# when no file name is provided. The first argument (%1$S) is the date string
+# in yyyy-mm-dd format and the second argument (%2$S) is the time string
+# in HH.MM.SS format. Please don't add the extension here.
+screenshotGeneratedFilename=Screen Shot %1$S at %2$S
+
+# LOCALIZATION NOTE (screenshotErrorSavingToFile) Text displayed to user upon
+# encountering error while saving the screenshot to the file specified.
+screenshotErrorSavingToFile=Error saving to
+
+# LOCALIZATION NOTE (screenshotSavedToFile) Text displayed to user when the
+# screenshot is successfully saved to the file specified.
+screenshotSavedToFile=Saved to
+
+# LOCALIZATION NOTE (screenshotErrorCopying) Text displayed to user upon
+# encountering error while copying the screenshot to clipboard.
+screenshotErrorCopying=Error occurred while copying to clipboard.
+
+# LOCALIZATION NOTE (screenshotCopied) Text displayed to user when the
+# screenshot is successfully copied to the clipboard.
+screenshotCopied=Copied to clipboard.
+
+# LOCALIZATION NOTE (screenshotTooltipPage) Text displayed as tooltip for screenshot button in devtools ToolBox.
+screenshotTooltipPage=Take a screenshot of the entire page
+
+# LOCALIZATION NOTE (screenshotImgurDesc) A very short string to describe
+# the 'imgur' parameter to the 'screenshot' command, which is displayed in
+# a dialog when the user is using this command.
+screenshotImgurDesc=Upload to imgur.com
+
+# LOCALIZATION NOTE (screenshotImgurManual) A fuller description of the
+# 'imgur' parameter to the 'screenshot' command, displayed when the user
+# asks for help on what it does.
+screenshotImgurManual=Use if you want to upload to imgur.com instead of saving to disk
+
+# LOCALIZATION NOTE (screenshotImgurError) Text displayed to user upon
+# encountering error while uploading the screenshot to imgur.com.
+screenshotImgurError=Could not reach imgur API
+
+# LOCALIZATION NOTE (screenshotImgurUploading) Text displayed to user when the
+# screenshot is successfully sent to Imgur but the program is waiting on a response.
+# The argument (%1$S) is a new image URL at Imgur.
+screenshotImgurUploaded=Uploaded to %1$S
+
+# LOCALIZATION NOTE (highlightDesc) A very short description of the
+# 'highlight' command. See highlightManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+highlightDesc=Highlight nodes
+
+# LOCALIZATION NOTE (highlightManual) A fuller description of the 'highlight'
+# command, displayed when the user asks for help on what it does.
+highlightManual=Highlight nodes that match a selector on the page
+
+# LOCALIZATION NOTE (highlightSelectorDesc) A very short string to describe
+# the 'selector' parameter to the 'highlight' command, which is displayed in
+# a dialog when the user is using this command.
+highlightSelectorDesc=CSS selector
+
+# LOCALIZATION NOTE (highlightSelectorManual) A fuller description of the
+# 'selector' parameter to the 'highlight' command, displayed when the user
+# asks for help on what it does.
+highlightSelectorManual=The CSS selector used to match nodes in the page
+
+# LOCALIZATION NOTE (highlightOptionsDesc) The title of a set of options to
+# the 'highlight' command, displayed as a heading to the list of option.
+highlightOptionsDesc=Options
+
+# LOCALIZATION NOTE (highlightHideGuidesDesc) A very short string to describe
+# the 'hideguides' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightHideGuidesDesc=Hide guides
+
+# LOCALIZATION NOTE (highlightHideGuidesManual) A fuller description of the
+# 'hideguides' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightHideGuidesManual=Hide the guides around the highlighted node
+
+# LOCALIZATION NOTE (highlightShowInfoBarDesc) A very short string to describe
+# the 'showinfobar' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightShowInfoBarDesc=Show the node infobar
+
+# LOCALIZATION NOTE (highlightShowInfoBarManual) A fuller description of the
+# 'showinfobar' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightShowInfoBarManual=Show the infobar above the highlighted node (the infobar displays the tagname, attributes and dimension)
+
+# LOCALIZATION NOTE (highlightShowAllDesc) A very short string to describe
+# the 'showall' option parameter to the 'highlight' command, which is
+# displayed in a dialog when the user is using this command.
+highlightShowAllDesc=Show all matches
+
+# LOCALIZATION NOTE (highlightShowAllManual) A fuller description of the
+# 'showall' option parameter to the 'highlight' command, displayed when the
+# user asks for help on what it does.
+highlightShowAllManual=If too many nodes match the selector, only the first 100 will be shown to avoid slowing down the page too much. Use this option to show all matches instead
+
+# LOCALIZATION NOTE (highlightRegionDesc) A very short string to describe the
+# 'region' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightRegionDesc=Box model region
+
+# LOCALIZATION NOTE (highlightRegionManual) A fuller description of the 'region'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightRegionManual=Which box model region should be highlighted: ‘content’, ‘padding’, ‘border’ or ‘margin’
+
+# LOCALIZATION NOTE (highlightFillDesc) A very short string to describe the
+# 'fill' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightFillDesc=Fill style
+
+# LOCALIZATION NOTE (highlightFillManual) A fuller description of the 'fill'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightFillManual=Override the default region fill style with a custom color
+
+# LOCALIZATION NOTE (highlightKeepDesc) A very short string to describe the
+# 'keep' option parameter to the 'highlight' command, which is displayed in a
+# dialog when the user is using this command.
+highlightKeepDesc=Keep existing highlighters
+
+# LOCALIZATION NOTE (highlightKeepManual) A fuller description of the 'keep'
+# option parameter to the 'highlight' command, displayed when the user asks for
+# help on what it does.
+highlightKeepManual=By default, existing highlighters are hidden when running the command, unless this option is set
+
+# LOCALIZATION NOTE (highlightOutputConfirm) A confirmation message for the
+# 'highlight' command, displayed to the user once the command has been entered,
+# informing the user how many nodes have been highlighted successfully and how
+# to turn highlighting off
+highlightOutputConfirm2=%1$S node highlighted;%1$S nodes highlighted
+
+# LOCALIZATION NOTE (highlightOutputMaxReached) A confirmation message for the
+# 'highlight' command, displayed to the user once the command has been entered,
+# informing the user how many nodes have been highlighted successfully and that
+# some nodes could not be highlighted due to the maximum number of nodes being
+# reached, and how to turn highlighting off
+highlightOutputMaxReached=%1$S nodes matched, but only %2$S nodes highlighted. Use ‘--showall’ to show all
+
+# LOCALIZATION NOTE (unhighlightDesc) A very short description of the
+# 'unhighlight' command. See unhighlightManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+unhighlightDesc=Unhighlight all nodes
+
+# LOCALIZATION NOTE (unhighlightManual) A fuller description of the 'unhighlight'
+# command, displayed when the user asks for help on what it does.
+unhighlightManual=Unhighlight all nodes previously highlighted with the ‘highlight’ command
+
+# LOCALIZATION NOTE (restartBrowserDesc) A very short description of the
+# 'restart' command. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+# The argument (%1$S) is the browser name.
+restartBrowserDesc=Restart %1$S
+
+# LOCALIZATION NOTE (restartBrowserNocacheDesc) A very short string to
+# describe the 'nocache' parameter to the 'restart' command, which is
+# displayed in a dialog when the user is using this command.
+restartBrowserNocacheDesc=Disables loading content from cache upon restart
+
+# LOCALIZATION NOTE (restartBrowserRequestCancelled) A string displayed to the
+# user when a scheduled restart has been aborted by the user.
+restartBrowserRequestCancelled=Restart request cancelled by user.
+
+# LOCALIZATION NOTE (restartBrowserRestarting) A string displayed to the
+# user when a restart has been initiated without a delay.
+# The argument (%1$S) is the browser name.
+restartBrowserRestarting=Restarting %1$S…
+
+# LOCALIZATION NOTE (restartBrowserGroupOptions) A label for the optional options of
+# the restart command.
+restartBrowserGroupOptions=Options
+
+# LOCALIZATION NOTE (restartBrowserSafemodeDesc) A very short string to
+# describe the 'safemode' parameter to the 'restart' command, which is
+# displayed in a dialog when the user is using this command.
+restartBrowserSafemodeDesc=Enables Safe Mode upon restart
+
+# LOCALIZATION NOTE (inspectDesc) A very short description of the 'inspect'
+# command. See inspectManual for a fuller description of what it does. This
+# string is designed to be shown in a menu alongside the command name, which
+# is why it should be as short as possible.
+inspectDesc=Inspect a node
+
+# LOCALIZATION NOTE (inspectManual) A fuller description of the 'inspect'
+# command, displayed when the user asks for help on what it does.
+inspectManual=Investigate the dimensions and properties of an element using a CSS selector to open the DOM highlighter
+
+# LOCALIZATION NOTE (inspectNodeDesc) A very short string to describe the
+# 'node' parameter to the 'inspect' command, which is displayed in a dialog
+# when the user is using this command.
+inspectNodeDesc=CSS selector
+
+# LOCALIZATION NOTE (inspectNodeManual) A fuller description of the 'node'
+# parameter to the 'inspect' command, displayed when the user asks for help
+# on what it does.
+inspectNodeManual=A CSS selector for use with document.querySelector which identifies a single element
+
+# LOCALIZATION NOTE (eyedropperDesc) A very short description of the 'eyedropper'
+# command. See eyedropperManual for a fuller description of what it does. This
+# string is designed to be shown in a menu alongside the command name, which
+# is why it should be as short as possible.
+eyedropperDesc=Grab a color from the page
+
+# LOCALIZATION NOTE (eyedropperManual) A fuller description of the 'eyedropper'
+# command, displayed when the user asks for help on what it does.
+eyedropperManual=Open a panel that magnifies an area of page to inspect pixels and copy color values
+
+# LOCALIZATION NOTE (debuggerClosed) Used in the output of several commands
+# to explain that the debugger must be opened first.
+debuggerClosed=The debugger must be opened before using this command
+
+# LOCALIZATION NOTE (debuggerStopped) Used in the output of several commands
+# to explain that the debugger must be opened first before setting breakpoints.
+debuggerStopped=The debugger must be opened before setting breakpoints
+
+# LOCALIZATION NOTE (breakDesc) A very short string used to describe the
+# function of the break command.
+breakDesc=Manage breakpoints
+
+# LOCALIZATION NOTE (breakManual) A longer description describing the
+# set of commands that control breakpoints.
+breakManual=Commands to list, add and remove breakpoints
+
+# LOCALIZATION NOTE (breaklistDesc) A very short string used to describe the
+# function of the 'break list' command.
+breaklistDesc=Display known breakpoints
+
+# LOCALIZATION NOTE (breaklistNone) Used in the output of the 'break list'
+# command to explain that the list is empty.
+breaklistNone=No breakpoints set
+
+# LOCALIZATION NOTE (breaklistOutRemove) A title used in the output from the
+# 'break list' command on a button which can be used to remove breakpoints
+breaklistOutRemove=Remove
+
+# LOCALIZATION NOTE (breakaddAdded) Used in the output of the 'break add'
+# command to explain that a breakpoint was added.
+breakaddAdded=Added breakpoint
+
+# LOCALIZATION NOTE (breakaddFailed) Used in the output of the 'break add'
+# command to explain that a breakpoint could not be added.
+breakaddFailed=Could not set breakpoint: %S
+
+# LOCALIZATION NOTE (breakaddDesc) A very short string used to describe the
+# function of the 'break add' command.
+breakaddDesc=Add a breakpoint
+
+# LOCALIZATION NOTE (breakaddManual) A longer description describing the
+# set of commands that are responsible for adding breakpoints.
+breakaddManual=Breakpoint types supported: line
+
+# LOCALIZATION NOTE (breakaddlineDesc) A very short string used to describe the
+# function of the 'break add line' command.
+breakaddlineDesc=Add a line breakpoint
+
+# LOCALIZATION NOTE (breakaddlineFileDesc) A very short string used to describe
+# the function of the file parameter in the 'break add line' command.
+breakaddlineFileDesc=JS file URI
+
+# LOCALIZATION NOTE (breakaddlineLineDesc) A very short string used to describe
+# the function of the line parameter in the 'break add line' command.
+breakaddlineLineDesc=Line number
+
+# LOCALIZATION NOTE (breakdelDesc) A very short string used to describe the
+# function of the 'break del' command.
+breakdelDesc=Remove a breakpoint
+
+# LOCALIZATION NOTE (breakdelBreakidDesc) A very short string used to describe
+# the function of the index parameter in the 'break del' command.
+breakdelBreakidDesc=Index of breakpoint
+
+# LOCALIZATION NOTE (breakdelRemoved) Used in the output of the 'break del'
+# command to explain that a breakpoint was removed.
+breakdelRemoved=Breakpoint removed
+
+# LOCALIZATION NOTE (dbgDesc) A very short string used to describe the
+# function of the dbg command.
+dbgDesc=Manage debugger
+
+# LOCALIZATION NOTE (dbgManual) A longer description describing the
+# set of commands that control the debugger.
+dbgManual=Commands to interrupt or resume the main thread, step in, out and over lines of code
+
+# LOCALIZATION NOTE (dbgOpen) A very short string used to describe the function
+# of the dbg open command.
+dbgOpen=Open the debugger
+
+# LOCALIZATION NOTE (dbgClose) A very short string used to describe the function
+# of the dbg close command.
+dbgClose=Close the debugger
+
+# LOCALIZATION NOTE (dbgInterrupt) A very short string used to describe the
+# function of the dbg interrupt command.
+dbgInterrupt=Pauses the main thread
+
+# LOCALIZATION NOTE (dbgContinue) A very short string used to describe the
+# function of the dbg continue command.
+dbgContinue=Resumes the main thread, and continues execution following a breakpoint, until the next breakpoint or the termination of the script.
+
+# LOCALIZATION NOTE (dbgStepDesc) A very short string used to describe the
+# function of the dbg step command.
+dbgStepDesc=Manage stepping
+
+# LOCALIZATION NOTE (dbgStepManual) A longer description describing the
+# set of commands that control stepping.
+dbgStepManual=Commands to step in, out and over lines of code
+
+# LOCALIZATION NOTE (dbgStepOverDesc) A very short string used to describe the
+# function of the dbg step over command.
+dbgStepOverDesc=Executes the current statement and then stops at the next statement. If the current statement is a function call then the debugger executes the whole function, and it stops at the next statement after the function call
+
+# LOCALIZATION NOTE (dbgStepInDesc) A very short string used to describe the
+# function of the dbg step in command.
+dbgStepInDesc=Executes the current statement and then stops at the next statement. If the current statement is a function call, then the debugger steps into that function, otherwise it stops at the next statement
+
+# LOCALIZATION NOTE (dbgStepOutDesc) A very short string used to describe the
+# function of the dbg step out command.
+dbgStepOutDesc=Steps out of the current function and up one level if the function is nested. If in the main body, the script is executed to the end, or to the next breakpoint. The skipped statements are executed, but not stepped through
+
+# LOCALIZATION NOTE (dbgListSourcesDesc) A very short string used to describe the
+# function of the dbg list command.
+dbgListSourcesDesc=List the source URLs loaded in the debugger
+
+# LOCALIZATION NOTE (dbgBlackBoxDesc) A very short string used to describe the
+# function of the 'dbg blackbox' command.
+dbgBlackBoxDesc=Black box sources in the debugger
+
+# LOCALIZATION NOTE (dbgBlackBoxSourceDesc) A very short string used to describe the
+# 'source' parameter to the 'dbg blackbox' command.
+dbgBlackBoxSourceDesc=A specific source to black box
+
+# LOCALIZATION NOTE (dbgBlackBoxGlobDesc) A very short string used to describe the
+# 'glob' parameter to the 'dbg blackbox' command.
+dbgBlackBoxGlobDesc=Black box all sources that match this glob (for example: “*.min.js”)
+
+# LOCALIZATION NOTE (dbgBlackBoxInvertDesc) A very short string used to describe the
+# 'invert' parameter to the 'dbg blackbox' command.
+dbgBlackBoxInvertDesc=Invert matching, so that we black box every source that is not the source provided or does not match the provided glob pattern.
+
+# LOCALIZATION NOTE (dbgBlackBoxEmptyDesc) A very short string used to let the
+# user know that no sources were black boxed.
+dbgBlackBoxEmptyDesc=(No sources black boxed)
+
+# LOCALIZATION NOTE (dbgBlackBoxNonEmptyDesc) A very short string used to let the
+# user know which sources were black boxed.
+dbgBlackBoxNonEmptyDesc=The following sources were black boxed:
+
+# LOCALIZATION NOTE (dbgBlackBoxErrorDesc) A very short string used to let the
+# user know there was an error black boxing a source (whose url follows this
+# text).
+dbgBlackBoxErrorDesc=Error black boxing:
+
+# LOCALIZATION NOTE (dbgUnBlackBoxDesc) A very short string used to describe the
+# function of the 'dbg unblackbox' command.
+dbgUnBlackBoxDesc=Stop black boxing sources in the debugger
+
+# LOCALIZATION NOTE (dbgUnBlackBoxSourceDesc) A very short string used to describe the
+# 'source' parameter to the 'dbg unblackbox' command.
+dbgUnBlackBoxSourceDesc=A specific source to stop black boxing
+
+# LOCALIZATION NOTE (dbgUnBlackBoxGlobDesc) A very short string used to describe the
+# 'glob' parameter to the 'dbg blackbox' command.
+dbgUnBlackBoxGlobDesc=Stop black boxing all sources that match this glob (for example: “*.min.js”)
+
+# LOCALIZATION NOTE (dbgUnBlackBoxEmptyDesc) A very short string used to let the
+# user know that we did not stop black boxing any sources.
+dbgUnBlackBoxEmptyDesc=(Did not stop black boxing any sources)
+
+# LOCALIZATION NOTE (dbgUnBlackBoxNonEmptyDesc) A very short string used to let the
+# user know which sources we stopped black boxing.
+dbgUnBlackBoxNonEmptyDesc=Stopped black boxing the following sources:
+
+# LOCALIZATION NOTE (dbgUnBlackBoxErrorDesc) A very short string used to let the
+# user know there was an error black boxing a source (whose url follows this
+# text).
+dbgUnBlackBoxErrorDesc=Error stopping black boxing:
+
+# LOCALIZATION NOTE (dbgUnBlackBoxInvertDesc) A very short string used to describe the
+# 'invert' parameter to the 'dbg unblackbox' command.
+dbgUnBlackBoxInvertDesc=Invert matching, so that we stop black boxing every source that is not the source provided or does not match the provided glob pattern.
+
+# LOCALIZATION NOTE (consolecloseDesc) A very short description of the
+# 'console close' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+consolecloseDesc=Close the console
+
+# LOCALIZATION NOTE (consoleopenDesc) A very short description of the
+# 'console open' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+consoleopenDesc=Open the console
+
+# LOCALIZATION NOTE (editDesc) A very short description of the 'edit'
+# command. See editManual2 for a fuller description of what it does. This
+# string is designed to be shown in a menu alongside the command name, which
+# is why it should be as short as possible.
+editDesc=Tweak a page resource
+
+# LOCALIZATION NOTE (editManual2) A fuller description of the 'edit' command,
+# displayed when the user asks for help on what it does.
+editManual2=Edit one of the resources that is part of this page
+
+# LOCALIZATION NOTE (editResourceDesc) A very short string to describe the
+# 'resource' parameter to the 'edit' command, which is displayed in a dialog
+# when the user is using this command.
+editResourceDesc=URL to edit
+
+# LOCALIZATION NOTE (editLineToJumpToDesc) A very short string to describe the
+# 'line' parameter to the 'edit' command, which is displayed in a dialog
+# when the user is using this command.
+editLineToJumpToDesc=Line to jump to
+
+# LOCALIZATION NOTE (resizePageDesc) A very short string to describe the
+# 'resizepage' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizePageDesc=Resize the page
+
+# LOCALIZATION NOTE (resizePageArgWidthDesc) A very short string to describe the
+# 'width' parameter to the 'resizepage' command, which is displayed in a dialog
+# when the user is using this command.
+resizePageArgWidthDesc=Width in pixels
+
+# LOCALIZATION NOTE (resizePageArgWidthDesc) A very short string to describe the
+# 'height' parameter to the 'resizepage' command, which is displayed in a dialog
+# when the user is using this command.
+resizePageArgHeightDesc=Height in pixels
+
+# LOCALIZATION NOTE (resizeModeOnDesc) A very short string to describe the
+# 'resizeon ' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizeModeOnDesc=Enter Responsive Design Mode
+
+# LOCALIZATION NOTE (resizeModeOffDesc) A very short string to describe the
+# 'resize off' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizeModeOffDesc=Exit Responsive Design Mode
+
+# LOCALIZATION NOTE (resizeModeToggleDesc) A very short string to describe the
+# 'resize toggle' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizeModeToggleDesc=Toggle Responsive Design Mode
+
+# LOCALIZATION NOTE (resizeModeToggleTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles Responsive Design Mode.
+resizeModeToggleTooltip=Responsive Design Mode
+
+# LOCALIZATION NOTE (resizeModeToDesc) A very short string to describe the
+# 'resize to' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizeModeToDesc=Alter page size
+
+# LOCALIZATION NOTE (resizeModeDesc) A very short string to describe the
+# 'resize' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+resizeModeDesc=Control Responsive Design Mode
+
+# LOCALIZATION NOTE (resizeModeManual) A fuller description of the 'resize'
+# command, displayed when the user asks for help on what it does.
+# The argument (%1$S) is the browser name.
+resizeModeManual2=Responsive websites respond to their environment, so they look good on a mobile display, a cinema display and everything in-between. Responsive Design Mode allows you to easily test a variety of page sizes in %1$S without needing to resize your whole browser.
+
+# LOCALIZATION NOTE (cmdDesc) A very short description of the 'cmd'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+cmdDesc=Manipulate the commands
+
+# LOCALIZATION NOTE (cmdRefreshDesc) A very short description of the 'cmd refresh'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+cmdRefreshDesc=Re-read mozcmd directory
+
+# LOCALIZATION NOTE (cmdStatus3) When the we load new commands from mozcmd
+# directory, we report where we loaded from using %1$S.
+cmdStatus3=Loaded commands from ‘%1$S’
+
+# LOCALIZATION NOTE (cmdSetdirDesc) A very short description of the 'cmd setdir'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+cmdSetdirDesc=Setup a mozcmd directory
+
+# LOCALIZATION NOTE (cmdSetdirManual3) A fuller description of the 'cmd setdir'
+# command, displayed when the user asks for help on what it does.
+cmdSetdirManual3=A ‘mozcmd’ directory is an easy way to create new custom commands. For more information see https://developer.mozilla.org/docs/Tools/GCLI/Customization
+
+# LOCALIZATION NOTE (cmdSetdirDirectoryDesc) The description of the directory
+# parameter to the 'cmd setdir' command.
+cmdSetdirDirectoryDesc=Directory containing .mozcmd files
+
+# LOCALIZATION NOTE (addonDesc) A very short description of the 'addon'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+addonDesc=Manipulate add-ons
+
+# LOCALIZATION NOTE (addonListDesc) A very short description of the 'addon list'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+addonListDesc=List installed add-ons
+
+# LOCALIZATION NOTE (addonListTypeDesc) A very short description of the
+# 'addon list <type>' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+addonListTypeDesc=Select an add-on type
+
+# LOCALIZATION NOTE (addonListDictionaryHeading, addonListExtensionHeading,
+# addonListLocaleHeading, addonListPluginHeading, addonListThemeHeading,
+# addonListUnknownHeading) Used in the output of the 'addon list' command as the
+# first line of output.
+addonListDictionaryHeading=The following dictionaries are currently installed:
+addonListExtensionHeading=The following extensions are currently installed:
+addonListLocaleHeading=The following locales are currently installed:
+addonListPluginHeading=The following plugins are currently installed:
+addonListThemeHeading=The following themes are currently installed:
+addonListAllHeading=The following add-ons are currently installed:
+addonListUnknownHeading=The following add-ons of the selected type are currently installed:
+
+# LOCALIZATION NOTE (addonListOutEnable, addonListOutDisable) Used in the
+# output of the 'addon list' command as the labels for the enable/disable
+# action buttons in the listing. This string is designed to be shown in a
+# small action button next to the addon name, which is why it should be as
+# short as possible.
+addonListOutEnable=Enable
+addonListOutDisable=Disable
+
+# LOCALIZATION NOTE (addonPending, addonPendingEnable, addonPendingDisable,
+# addonPendingUninstall, addonPendingInstall, addonPendingUpgrade) Used in
+# the output of the 'addon list' command as the descriptions of pending
+# addon operations. addonPending is used as a prefix for a list of pending
+# actions (named by the other lookup variables). These strings are designed
+# to be shown alongside addon names, which is why they should be as short
+# as possible.
+addonPending=pending
+addonPendingEnable=enable
+addonPendingDisable=disable
+addonPendingUninstall=uninstall
+addonPendingInstall=install
+addonPendingUpgrade=upgrade
+
+# LOCALIZATION NOTE (addonNameDesc) A very short description of the
+# name parameter of numerous add-on commands. This string is designed to be shown
+# in a menu alongside the command name, which is why it should be as short as
+# possible.
+addonNameDesc=The name of the add-on
+
+# LOCALIZATION NOTE (addonNoneOfType) Used in the output of the 'addon list'
+# command when a search for add-ons of a particular type were not found.
+addonNoneOfType=There are no add-ons of that type installed.
+
+# LOCALIZATION NOTE (addonEnableDesc) A very short description of the
+# 'addon enable <type>' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+addonEnableDesc=Enable the specified add-on
+
+# LOCALIZATION NOTE (addonAlreadyEnabled) Used in the output of the
+# 'addon enable' command when an attempt is made to enable an add-on that is
+# already enabled.
+addonAlreadyEnabled=%S is already enabled.
+
+# LOCALIZATION NOTE (addonEnabled) Used in the output of the 'addon enable'
+# command when an add-on is enabled.
+addonEnabled=%S enabled.
+
+# LOCALIZATION NOTE (addonDisableDesc) A very short description of the
+# 'addon disable <type>' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+addonDisableDesc=Disable the specified add-on
+
+# LOCALIZATION NOTE (addonAlreadyDisabled) Used in the output of the
+# 'addon disable' command when an attempt is made to disable an add-on that is
+# already disabled.
+addonAlreadyDisabled=%S is already disabled.
+
+# LOCALIZATION NOTE (addonDisabled) Used in the output of the 'addon disable'
+# command when an add-on is disabled.
+addonDisabled=%S disabled.
+
+# LOCALIZATION NOTE (addonCtpDesc) A very short description of the
+# 'addon ctp <type>' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+addonCtpDesc=Set the specified plugin to click-to-play.
+
+# LOCALIZATION NOTE (addonCtp) Used in the output of the 'addon ctp'
+# command when a plugin is set to click-to-play.
+addonCtp=%S set to click-to-play.
+
+# LOCALIZATION NOTE (addonAlreadyCtp) Used in the output of the
+# 'addon ctp' command when an attempt is made to set a plugin to
+# click-to-play that is already set to click-to-play.
+addonAlreadyCtp=%S is already set to click-to-play.
+
+# LOCALIZATION NOTE (addonCantCtp) Used in the output of the 'addon
+# ctp' command when an attempt is made to set an addon to click-to-play,
+# but the addon is not a plugin.
+addonCantCtp=%S cannot be set to click-to-play because it is not a plugin.
+
+# LOCALIZATION NOTE (addonNoCtp) Used in the output of the 'addon
+# ctp' command when an attempt is made to set an addon to click-to-play,
+# but the plugin cannot be set to click-to-play for some reason.
+addonNoCtp=%S cannot be set to click-to-play.
+
+# LOCALIZATION NOTE (exportDesc) A very short description of the 'export'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+exportDesc=Export resources
+
+# LOCALIZATION NOTE (exportHtmlDesc) A very short description of the 'export
+# html' command. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+exportHtmlDesc=Export HTML from page
+
+# LOCALIZATION NOTE (pagemodDesc) A very short description of the 'pagemod'
+# command. This string is designed to be shown in a menu alongside the command
+# name, which is why it should be as short as possible.
+pagemodDesc=Make page changes
+
+# LOCALIZATION NOTE (pagemodReplaceDesc) A very short description of the
+# 'pagemod replace' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+pagemodReplaceDesc=Search and replace in page elements
+
+# LOCALIZATION NOTE (pagemodReplaceSearchDesc) A very short string to describe
+# the 'search' parameter to the 'pagemod replace' command, which is displayed in
+# a dialog when the user is using this command.
+pagemodReplaceSearchDesc=What to search for
+
+# LOCALIZATION NOTE (pagemodReplaceReplaceDesc) A very short string to describe
+# the 'replace' parameter to the 'pagemod replace' command, which is displayed in
+# a dialog when the user is using this command.
+pagemodReplaceReplaceDesc=Replacement string
+
+# LOCALIZATION NOTE (pagemodReplaceIgnoreCaseDesc) A very short string to
+# describe the 'ignoreCase' parameter to the 'pagemod replace' command, which is
+# displayed in a dialog when the user is using this command.
+pagemodReplaceIgnoreCaseDesc=Perform case-insensitive search
+
+# LOCALIZATION NOTE (pagemodReplaceRootDesc) A very short string to describe the
+# 'root' parameter to the 'pagemod replace' command, which is displayed in
+# a dialog when the user is using this command.
+pagemodReplaceRootDesc=CSS selector to root of search
+
+# LOCALIZATION NOTE (pagemodReplaceSelectorDesc) A very short string to describe
+# the 'selector' parameter to the 'pagemod replace' command, which is displayed
+# in a dialog when the user is using this command.
+pagemodReplaceSelectorDesc=CSS selector to match in search
+
+# LOCALIZATION NOTE (pagemodReplaceAttributesDesc) A very short string to
+# describe the 'attributes' parameter to the 'pagemod replace' command, which is
+# displayed in a dialog when the user is using this command.
+pagemodReplaceAttributesDesc=Attribute match regexp
+
+# LOCALIZATION NOTE (pagemodReplaceAttrOnlyDesc) A very short string to describe
+# the 'attrOnly' parameter to the 'pagemod replace' command, which is displayed
+# in a dialog when the user is using this command.
+pagemodReplaceAttrOnlyDesc=Restrict search to attributes
+
+# LOCALIZATION NOTE (pagemodReplaceContentOnlyDesc) A very short string to
+# describe the 'contentOnly' parameter to the 'pagemod replace' command, which
+# is displayed in a dialog when the user is using this command.
+pagemodReplaceContentOnlyDesc=Restrict search to text nodes
+
+# LOCALIZATION NOTE (pagemodReplaceResultMatchedElements) A string displayed as
+# the result of the 'pagemod replace' command.
+pagemodReplaceResult=Elements matched by selector: %1$S. Replaces in text nodes: %2$S. Replaces in attributes: %3$S.
+
+# LOCALIZATION NOTE (pagemodRemoveDesc) A very short description of the
+# 'pagemod remove' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+pagemodRemoveDesc=Remove elements and attributes from page
+
+# LOCALIZATION NOTE (pagemodRemoveElementDesc) A very short description of the
+# 'pagemod remove element' command. This string is designed to be shown in
+# a menu alongside the command name, which is why it should be as short as
+# possible.
+pagemodRemoveElementDesc=Remove elements from page
+
+# LOCALIZATION NOTE (pagemodRemoveElementSearchDesc) A very short string to
+# describe the 'search' parameter to the 'pagemod remove element' command, which
+# is displayed in a dialog when the user is using this command.
+pagemodRemoveElementSearchDesc=CSS selector specifying elements to remove
+
+# LOCALIZATION NOTE (pagemodRemoveElementRootDesc) A very short string to
+# describe the 'root' parameter to the 'pagemod remove element' command, which
+# is displayed in a dialog when the user is using this command.
+pagemodRemoveElementRootDesc=CSS selector specifying root of search
+
+# LOCALIZATION NOTE (pagemodRemoveElementStripOnlyDesc) A very short string to
+# describe the 'stripOnly' parameter to the 'pagemod remove element' command,
+# which is displayed in a dialog when the user is using this command.
+pagemodRemoveElementStripOnlyDesc=Remove element, but leave content
+
+# LOCALIZATION NOTE (pagemodRemoveElementIfEmptyOnlyDesc) A very short string to
+# describe the 'ifEmptyOnly' parameter to the 'pagemod remove element' command,
+# which is displayed in a dialog when the user is using this command.
+pagemodRemoveElementIfEmptyOnlyDesc=Remove only empty elements
+
+# LOCALIZATION NOTE (pagemodRemoveElementResultMatchedAndRemovedElements)
+# A string displayed as the result of the 'pagemod remove element' command.
+pagemodRemoveElementResultMatchedAndRemovedElements=Elements matched by selector: %1$S. Elements removed: %2$S.
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeDesc) A very short description of the
+# 'pagemod remove attribute' command. This string is designed to be shown in
+# a menu alongside the command name, which is why it should be as short as
+# possible.
+pagemodRemoveAttributeDesc=Remove matching attributes
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeSearchAttributesDesc) A very short
+# string to describe the 'searchAttributes' parameter to the 'pagemod remove
+# attribute' command, which is displayed in a dialog when the user is using this
+# command.
+pagemodRemoveAttributeSearchAttributesDesc=Regexp specifying attributes to remove
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeSearchElementsDesc) A very short
+# string to describe the 'searchElements' parameter to the 'pagemod remove
+# attribute' command, which is displayed in a dialog when the user is using this
+# command.
+pagemodRemoveAttributeSearchElementsDesc=CSS selector of elements to include
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeRootDesc) A very short string to
+# describe the 'root' parameter to the 'pagemod remove attribute' command, which
+# is displayed in a dialog when the user is using this command.
+pagemodRemoveAttributeRootDesc=CSS selector of root of search
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeIgnoreCaseDesc) A very short string
+# to describe the 'ignoreCase' parameter to the 'pagemod remove attribute'
+# command, which is displayed in a dialog when the user is using this command.
+pagemodRemoveAttributeIgnoreCaseDesc=Perform case-insensitive search
+
+# LOCALIZATION NOTE (pagemodRemoveAttributeResult) A string displayed as the
+# result of the 'pagemod remove attribute' command.
+pagemodRemoveAttributeResult=Elements matched by selector: %1$S. Attributes removed: %2$S.
+
+# LOCALIZATION NOTE (toolsDesc2) A very short description of the 'tools'
+# command, the parent command for tool-hacking commands.
+# The argument (%1$S) is the browser name.
+toolsDesc2=Hack the %1$S Developer Tools
+
+# LOCALIZATION NOTE (toolsManual2) A fuller description of the 'tools'
+# command. The argument (%1$S) is the browser name.
+toolsManual2=Various commands related to hacking directly on the %1$S Developer Tools.
+
+# LOCALIZATION NOTE (toolsSrcdirDesc) A very short description of the 'tools srcdir'
+# command, for pointing your developer tools loader at a mozilla-central source tree.
+toolsSrcdirDesc=Load tools from a mozilla-central checkout
+
+# LOCALIZATION NOTE (toolsSrcdirNotFound2) Shown when the 'tools srcdir' command was handed
+# an invalid srcdir.
+toolsSrcdirNotFound2=%1$S does not exist or is not a mozilla-central checkout.
+
+# LOCALIZATION NOTE (toolsSrcdirReloaded2) Displayed when tools have been reloaded by the
+# 'tools srcdir' command.
+toolsSrcdirReloaded2=Tools loaded from %1$S.
+
+# LOCALIZATION NOTE (toolsSrcdirManual2) A full description of the 'tools srcdir'
+# command. The argument (%1$S) is the browser name.
+toolsSrcdirManual2=Load the %1$S Developer Tools from a complete mozilla-central checkout.
+
+# LOCALIZATION NOTE (toolsSrcdirDir) The srcdir argument to the 'tools srcdir' command.
+toolsSrcdirDir=A mozilla-central checkout
+
+# LOCALIZATION NOTE (toolsBuiltinDesc) A short description of the 'tools builtin'
+# command, which overrides a previous 'tools srcdir' command.
+toolsBuiltinDesc=Use the builtin tools
+
+# LOCALIZATION NOTE (toolsBuiltinDesc) A fuller description of the 'tools builtin'
+# command.
+toolsBuiltinManual=Use the builtin tools, overriding any previous srcdir command.
+
+# LOCALIZATION NOTE (toolsBuiltinReloaded) Displayed when tools are loaded with the
+# 'tools builtin' command.
+toolsBuiltinReloaded=Builtin tools loaded.
+
+# LOCALIZATION NOTE (toolsReloadDesc) A short description of the 'tools reload' command.
+# which will reload the tools from the current srcdir.
+toolsReloadDesc=Reload the developer tools
+
+# LOCALIZATION NOTE (toolsReloaded2) Displayed when tools are reloaded with the 'tools
+# reload' command.
+toolsReloaded2=Tools reloaded.
+
+# LOCALIZATION NOTE (cookieDesc) A very short description of the 'cookie'
+# command. See cookieManual for a fuller description of what it does. This
+# string is designed to be shown in a menu alongside the command name, which
+# is why it should be as short as possible.
+cookieDesc=Display and alter cookies
+
+# LOCALIZATION NOTE (cookieManual) A fuller description of the 'cookie'
+# command, displayed when the user asks for help on what it does.
+cookieManual=Commands to list, create, delete and alter cookies for the current domain.
+
+# LOCALIZATION NOTE (cookieListDesc) A very short description of the
+# 'cookie list' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+cookieListDesc=Display cookies
+
+# LOCALIZATION NOTE (cookieListManual) A fuller description of the 'cookie list'
+# command, displayed when the user asks for help on what it does.
+cookieListManual=Display a list of the cookies relevant to the current page.
+
+# LOCALIZATION NOTE (cookieListOutHost,cookieListOutPath,cookieListOutExpires,cookieListOutAttributes):
+# The 'cookie list' command has a number of headings for cookie properties.
+# Particular care should be taken in translating these strings as they have
+# references to names in the cookies spec.
+cookieListOutHost=Host:
+cookieListOutPath=Path:
+cookieListOutExpires=Expires:
+cookieListOutAttributes=Attributes:
+
+# LOCALIZATION NOTE (cookieListOutNone) The output of the 'cookie list' command
+# uses this string when no cookie attributes (like httpOnly, secure, etc) apply
+cookieListOutNone=None
+
+# LOCALIZATION NOTE (cookieListOutSession) The output of the 'cookie list'
+# command uses this string to describe a cookie with an expiry value of '0'
+# that is to say it is a session cookie
+cookieListOutSession=At browser exit (session)
+
+# LOCALIZATION NOTE (cookieListOutNonePage) The output of the 'cookie list'
+# command uses this string for pages like 'about:blank' which can't contain
+# cookies
+cookieListOutNonePage=No cookies found for this page
+
+# LOCALIZATION NOTE (cookieListOutNoneHost) The output of the 'cookie list'
+# command uses this string when there are no cookies on a given web page
+cookieListOutNoneHost=No cookies found for host %1$S
+
+# LOCALIZATION NOTE (cookieListOutEdit) A title used in the output from the
+# 'cookie list' command on a button which can be used to edit cookie values
+cookieListOutEdit=Edit
+
+# LOCALIZATION NOTE (cookieListOutRemove) A title used in the output from the
+# 'cookie list' command on a button which can be used to remove cookies
+cookieListOutRemove=Remove
+
+# LOCALIZATION NOTE (cookieRemoveDesc) A very short description of the
+# 'cookie remove' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+cookieRemoveDesc=Remove a cookie
+
+# LOCALIZATION NOTE (cookieRemoveManual) A fuller description of the 'cookie remove'
+# command, displayed when the user asks for help on what it does.
+cookieRemoveManual=Remove a cookie, given its key
+
+# LOCALIZATION NOTE (cookieRemoveKeyDesc) A very short string to describe the
+# 'key' parameter to the 'cookie remove' command, which is displayed in a dialog
+# when the user is using this command.
+cookieRemoveKeyDesc=The key of the cookie to remove
+
+# LOCALIZATION NOTE (cookieSetDesc) A very short description of the
+# 'cookie set' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+cookieSetDesc=Set a cookie
+
+# LOCALIZATION NOTE (cookieSetManual) A fuller description of the 'cookie set'
+# command, displayed when the user asks for help on what it does.
+cookieSetManual=Set a cookie by specifying a key name, its value and optionally one or more of the following attributes: expires (max-age in seconds or the expires date in GMTString format), path, domain, secure
+
+# LOCALIZATION NOTE (cookieSetKeyDesc) A very short string to describe the
+# 'key' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetKeyDesc=The key of the cookie to set
+
+# LOCALIZATION NOTE (cookieSetValueDesc) A very short string to describe the
+# 'value' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetValueDesc=The value of the cookie to set
+
+# LOCALIZATION NOTE (cookieSetOptionsDesc) The title of a set of options to
+# the 'cookie set' command, displayed as a heading to the list of option.
+cookieSetOptionsDesc=Options
+
+# LOCALIZATION NOTE (cookieSetPathDesc) A very short string to describe the
+# 'path' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetPathDesc=The path of the cookie to set
+
+# LOCALIZATION NOTE (cookieSetDomainDesc) A very short string to describe the
+# 'domain' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetDomainDesc=The domain of the cookie to set
+
+# LOCALIZATION NOTE (cookieSetSecureDesc) A very short string to describe the
+# 'secure' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetSecureDesc=Only transmitted over https
+
+# LOCALIZATION NOTE (cookieSetHttpOnlyDesc) A very short string to describe the
+# 'httpOnly' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetHttpOnlyDesc=Not accessible from client side script
+
+# LOCALIZATION NOTE (cookieSetSessionDesc) A very short string to describe the
+# 'session' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetSessionDesc=Only valid for the lifetime of the browser session
+
+# LOCALIZATION NOTE (cookieSetExpiresDesc) A very short string to describe the
+# 'expires' parameter to the 'cookie set' command, which is displayed in a dialog
+# when the user is using this command.
+cookieSetExpiresDesc=The expiry date of the cookie (quoted RFC2822 or ISO 8601 date)
+
+# LOCALIZATION NOTE (jsbDesc) A very short description of the
+# 'jsb' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+jsbDesc=JavaScript beautifier
+
+# LOCALIZATION NOTE (jsbUrlDesc) A very short description of the
+# 'jsb <url>' parameter. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+jsbUrlDesc=The URL of the JS file to beautify
+
+# LOCALIZATION NOTE (jsbIndentSizeDesc) A very short description of the
+# 'jsb <indentSize>' parameter. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+jsbIndentSizeDesc=Indentation size in chars
+
+# LOCALIZATION NOTE (jsbIndentSizeManual) A fuller description of the
+# 'jsb <indentChar>' parameter, displayed when the user asks for help on what it
+# does.
+jsbIndentSizeManual=The number of chars with which to indent each line
+
+# LOCALIZATION NOTE (jsbIndentCharDesc) A very short description of the
+# 'jsb <indentChar>' parameter. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+jsbIndentCharDesc=The chars used to indent each line
+
+# LOCALIZATION NOTE (jsbIndentCharManual) A fuller description of the
+# 'jsb <indentChar>' parameter, displayed when the user asks for help on what it
+# does.
+jsbIndentCharManual=The chars used to indent each line. The possible choices are space or tab.
+
+# the 'jsb <doNotPreserveNewlines>' parameter. This string is designed to be
+# shown in a menu alongside the command name, which is why it should be as short
+# as possible.
+jsbDoNotPreserveNewlinesDesc=Do not preserve line breaks
+
+# LOCALIZATION NOTE (jsbPreserveNewlinesManual) A fuller description of the
+# 'jsb <jsbPreserveNewlines>' parameter, displayed when the user asks for help
+# on what it does.
+jsbPreserveNewlinesManual=Should existing line breaks be preserved
+
+# LOCALIZATION NOTE (jsbPreserveMaxNewlinesDesc) A very short description of the
+# 'jsb <preserveMaxNewlines>' parameter. This string is designed to be shown
+# in a menu alongside the command name, which is why it should be as short as
+# possible.
+jsbPreserveMaxNewlinesDesc=Max consecutive line breaks
+
+# LOCALIZATION NOTE (jsbPreserveMaxNewlinesManual) A fuller description of the
+# 'jsb <preserveMaxNewlines>' parameter, displayed when the user asks for help
+# on what it does.
+jsbPreserveMaxNewlinesManual=The maximum number of consecutive line breaks to preserve
+
+# LOCALIZATION NOTE (jsbJslintHappyDesc) A very short description of the
+# 'jsb <jslintHappy>' parameter. This string is designed to be shown
+# in a menu alongside the command name, which is why it should be as short as
+# possible.
+jsbJslintHappyDesc=Enforce jslint-stricter mode?
+
+# LOCALIZATION NOTE (jsbJslintHappyManual) A fuller description of the
+# 'jsb <jslintHappy>' parameter, displayed when the user asks for help
+# on what it does.
+jsbJslintHappyManual=When set to true, jslint-stricter mode is enforced
+
+# LOCALIZATION NOTE (jsbBraceStyleDesc2) A very short description of the
+# 'jsb <braceStyle>' parameter. This string is designed to be shown
+# in a menu alongside the command name, which is why it should be as short as
+# possible.
+jsbBraceStyleDesc2=Select the coding style of braces
+
+# LOCALIZATION NOTE (jsbBraceStyleManual2) A fuller description of the
+# 'jsb <braceStyle>' parameter, displayed when the user asks for help
+# on what it does.
+#
+# NOTES: The keywords collapse, expand, end-expand and expand-strict should not
+# be translated. "even if it will break your code" means that the resulting code
+# may no longer be functional.
+jsbBraceStyleManual2=Select the coding style of braces: collapse - put braces on the same line as control statements; expand - put braces on own line (Allman / ANSI style); end-expand - put end braces on own line; expand-strict - put braces on own line even if it will break your code.
+
+# LOCALIZATION NOTE (jsbNoSpaceBeforeConditionalDesc) A very short description
+# of the 'jsb <noSpaceBeforeConditional>' parameter. This string is designed to
+# be shown in a menu alongside the command name, which is why it should be as
+# short as possible.
+jsbNoSpaceBeforeConditionalDesc=No space before conditional statements
+
+# LOCALIZATION NOTE (jsbUnescapeStringsDesc) A very short description of the
+# 'jsb <unescapeStrings>' parameter. This string is designed to be shown
+# in a menu alongside the command name, which is why it should be as short as
+# possible.
+jsbUnescapeStringsDesc=Unescape \\xNN characters?
+
+# LOCALIZATION NOTE (jsbUnescapeStringsManual) A fuller description of the
+# 'jsb <unescapeStrings>' parameter, displayed when the user asks for help
+# on what it does.
+jsbUnescapeStringsManual=Should printable characters in strings encoded in \\xNN notation be unescaped?
+
+# LOCALIZATION NOTE (jsbInvalidURL) Displayed when an invalid URL is passed to
+# the jsb command.
+jsbInvalidURL=Please enter a valid URL
+
+# LOCALIZATION NOTE (jsbOptionsDesc) The title of a set of options to
+# the 'jsb' command, displayed as a heading to the list of options.
+jsbOptionsDesc=Options
+
+# LOCALIZATION NOTE (calllogDesc) A very short description of the
+# 'calllog' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogDesc=Commands to manipulate function call logging
+
+# LOCALIZATION NOTE (calllogStartDesc) A very short description of the
+# 'calllog start' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogStartDesc=Start logging function calls to the console
+
+# LOCALIZATION NOTE (calllogStartReply) A string displayed as the result of
+# the 'calllog start' command.
+calllogStartReply=Call logging started.
+
+# LOCALIZATION NOTE (calllogStopDesc) A very short description of the
+# 'calllog stop' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogStopDesc=Stop function call logging
+
+# LOCALIZATION NOTE (calllogStopNoLogging) A string displayed as the result of
+# the 'calllog stop' command when there is nothing to stop.
+calllogStopNoLogging=No call logging is currently active
+
+# LOCALIZATION NOTE (calllogStopReply) A string displayed as the result of
+# the 'calllog stop' command when there are logging actions to stop.
+calllogStopReply=Stopped call logging. Active contexts: %1$S.
+
+# LOCALIZATION NOTE (calllogStartChromeDesc) A very short description of the
+# 'calllog chromestart' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogChromeStartDesc=Start logging function calls for chrome code to the console
+
+# LOCALIZATION NOTE (calllogChromeSourceTypeDesc) A very short description of the
+# 'calllog chromestart <sourceType>' parameter. This string is designed to be
+# shown in a menu alongside the command name, which is why it should be as short as possible.
+calllogChromeSourceTypeDesc=Global object, JSM URI, or JS to get a global object from
+
+# LOCALIZATION NOTE (calllogChromeSourceTypeDesc) A very short description of the
+# 'calllog chromestart' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogChromeSourceTypeManual=The global object, URI of a JSM, or JS to execute in the chrome window from which to obtain a global object
+
+# LOCALIZATION NOTE (calllogChromeStartReply) A string displayed as the result
+# of the 'calllog chromestart' command.
+calllogChromeStartReply=Call logging started.
+
+# LOCALIZATION NOTE (calllogChromeStopDesc) A very short description of the
+# 'calllog chromestop' command. This string is designed to be shown in a menu
+# alongside the command name, which is why it should be as short as possible.
+calllogChromeStopDesc=Stop function call logging
+
+# LOCALIZATION NOTE (calllogChromeStopNoLogging) A string displayed as the
+# result of the 'calllog chromestop' command when there is nothing to stop.
+calllogChromeStopNoLogging=No call logging for chrome code is currently active
+
+# LOCALIZATION NOTE (calllogStopReply) A string displayed as the result of
+# the 'calllog chromestop' command when there are logging actions to stop.
+calllogChromeStopReply=Stopped call logging. Active contexts: %1$S.
+
+# LOCALIZATION NOTE (callLogChromeAnonFunction) A string displayed as the result
+# of the 'calllog chromestart' command when an anonymouse function is to be
+# logged.
+callLogChromeAnonFunction=<anonymous>
+
+# LOCALIZATION NOTE (callLogChromeMethodCall) A string displayed as the result
+# of the 'calllog chromestart' command to proceed a method name when it is to be
+# logged.
+callLogChromeMethodCall=Method call
+
+# LOCALIZATION NOTE (callLogChromeInvalidJSM) A string displayed as the result
+# of the 'calllog chromestart' command with an invalid JSM or JSM path.
+callLogChromeInvalidJSM=Invalid JSM!
+
+# LOCALIZATION NOTE (callLogChromeVarNotFoundContent) A string displayed as the
+# result of the 'calllog chromestart' command with a source type of
+# content-variable and an invalid variable name.
+callLogChromeVarNotFoundContent=Variable not found in content window.
+
+# LOCALIZATION NOTE (callLogChromeVarNotFoundChrome) A string displayed as the
+# result of the 'calllog chromestart' command with a source type of
+# chrome-variable and an invalid variable name.
+callLogChromeVarNotFoundChrome=Variable not found in chrome window.
+
+# LOCALIZATION NOTE (callLogChromeEvalException) A string displayed as the
+# result of the 'calllog chromestart' command with a source type of JavaScript
+# and invalid JavaScript code.
+callLogChromeEvalException=Evaluated JavaScript threw the following exception
+
+# LOCALIZATION NOTE (callLogChromeEvalNeedsObject) A string displayed as the
+# result of passing a non-JavaScript object creating source via the
+# 'calllog chromestart javascript' command.
+callLogChromeEvalNeedsObject=The JavaScript source must evaluate to an object whose method calls are to be logged e.g. “({a1: function() {this.a2()},a2: function() {}});”
+
+# LOCALIZATION NOTE (scratchpadOpenTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which opens Scratchpad.
+scratchpadOpenTooltip=Scratchpad
+
+# LOCALIZATION NOTE (paintflashingDesc) A very short string used to describe the
+# function of the "paintflashing" command
+paintflashingDesc=Highlight painted area
+
+# LOCALIZATION NOTE (paintflashingOnDesc) A very short string used to describe the
+# function of the "paintflashing on" command.
+paintflashingOnDesc=Turn on paint flashing
+
+# LOCALIZATION NOTE (paintflashingOffDesc) A very short string used to describe the
+# function of the "paintflashing off" command.
+paintflashingOffDesc=Turn off paint flashing
+
+# LOCALIZATION NOTE (paintflashingChrome) A very short string used to describe the
+# function of the "paintflashing on/off chrome" command.
+paintflashingChromeDesc=chrome frames
+
+# LOCALIZATION NOTE (paintflashingManual) A longer description describing the
+# set of commands that control paint flashing.
+paintflashingManual=Draw repainted areas in different colors
+
+# LOCALIZATION NOTE (paintflashingTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles paint flashing.
+paintflashingTooltip=Highlight painted area
+
+# LOCALIZATION NOTE (paintflashingToggleDesc) A very short string used to describe the
+# function of the "paintflashing toggle" command.
+paintflashingToggleDesc=Toggle paint flashing
+
+# LOCALIZATION NOTE (splitconsoleTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles the split webconsole.
+splitconsoleTooltip=Toggle split console
+
+# LOCALIZATION NOTE (appCacheDesc) A very short string used to describe the
+# function of the "appcache" command
+appCacheDesc=Application cache utilities
+
+# LOCALIZATION NOTE (appCacheValidateDesc) A very short string used to describe
+# the function of the "appcache validate" command.
+appCacheValidateDesc=Validate cache manifest
+
+# LOCALIZATION NOTE (appCacheValidateManual) A fuller description of the
+# 'validate' parameter to the 'appcache' command, displayed when the user asks
+# for help on what it does.
+appCacheValidateManual=Find issues relating to a cache manifest and the files that it references
+
+# LOCALIZATION NOTE (appCacheValidateUriDesc) A very short string used to describe
+# the function of the "uri" parameter of the appcache validate" command.
+appCacheValidateUriDesc=URI to check
+
+# LOCALIZATION NOTE (appCacheValidated) Displayed by the "appcache validate"
+# command when it has been successfully validated.
+appCacheValidatedSuccessfully=Appcache validated successfully.
+
+# LOCALIZATION NOTE (appCacheClearDesc) A very short string used to describe
+# the function of the "appcache clear" command.
+appCacheClearDesc=Clear entries from the application cache
+
+# LOCALIZATION NOTE (appCacheClearManual) A fuller description of the
+# 'appcache clear' command, displayed when the user asks for help on what it does.
+appCacheClearManual=Clear one or more entries from the application cache
+
+# LOCALIZATION NOTE (appCacheClearCleared) Displayed by the "appcache clear"
+# command when entries are successfully cleared.
+appCacheClearCleared=Entries cleared successfully.
+
+# LOCALIZATION NOTE (AppCacheListDesc) A very short string used to describe
+# the function of the "appcache list" command.
+appCacheListDesc=Display a list of application cache entries.
+
+# LOCALIZATION NOTE (AppCacheListManual) A fuller description of the
+# 'appcache list' command, displayed when the user asks for help on what it does.
+appCacheListManual=Display a list of all application cache entries. If the search parameter is used then the table displays the entries containing the search term.
+
+# LOCALIZATION NOTE (AppCacheListSearchDesc) A very short string used to describe
+# the function of the "search" parameter of the appcache list" command.
+appCacheListSearchDesc=Filter results using a search term.
+
+# LOCALIZATION NOTE (AppCacheList*) Row headers for the 'appcache list' command.
+appCacheListKey=Key:
+appCacheListDataSize=Data size:
+appCacheListDeviceID=Device ID:
+appCacheListExpirationTime=Expires:
+appCacheListFetchCount=Fetch count:
+appCacheListLastFetched=Last fetched:
+appCacheListLastModified=Last modified:
+
+# LOCALIZATION NOTE (appCacheListViewEntry) The text for the view entry button
+# of the 'appcache list' command.
+appCacheListViewEntry=View Entry
+
+# LOCALIZATION NOTE (appCacheViewEntryDesc) A very short string used to describe
+# the function of the "appcache viewentry" command.
+appCacheViewEntryDesc=Open a new tab containing the specified cache entry information.
+
+# LOCALIZATION NOTE (appCacheViewEntryManual) A fuller description of the
+# 'appcache viewentry' command, displayed when the user asks for help on what it
+# does.
+appCacheViewEntryManual=Open a new tab containing the specified cache entry information.
+
+# LOCALIZATION NOTE (appCacheViewEntryKey) A very short string used to describe
+# the function of the "key" parameter of the 'appcache viewentry' command.
+appCacheViewEntryKey=The key for the entry to display.
+
+# LOCALIZATION NOTE (profilerDesc) A very short string used to describe the
+# function of the profiler command.
+profilerDesc=Manage profiler
+
+# LOCALIZATION NOTE (profilerManual) A longer description describing the
+# set of commands that control the profiler.
+profilerManual=Commands to start or stop a JavaScript profiler
+
+# LOCALIZATION NOTE (profilerOpen) A very short string used to describe the function
+# of the profiler open command.
+profilerOpenDesc=Open the profiler
+
+# LOCALIZATION NOTE (profilerClose) A very short string used to describe the function
+# of the profiler close command.
+profilerCloseDesc=Close the profiler
+
+# LOCALIZATION NOTE (profilerStart) A very short string used to describe the function
+# of the profiler start command.
+profilerStartDesc=Start profiling
+
+# LOCALIZATION NOTE (profilerStartManual) A fuller description of the 'profile name'
+# parameter. This parameter is used to name a newly created profile or to lookup
+# an existing profile by its name.
+profilerStartManual=Name of a profile you wish to start.
+
+# LOCALIZATION NOTE (profilerStop) A very short string used to describe the function
+# of the profiler stop command.
+profilerStopDesc=Stop profiling
+
+# LOCALIZATION NOTE (profilerStopManual) A fuller description of the 'profile name'
+# parameter. This parameter is used to lookup an existing profile by its name.
+profilerStopManual=Name of a profile you wish to stop.
+
+# LOCALIZATION NOTE (profilerList) A very short string used to describe the function
+# of the profiler list command.
+profilerListDesc=List all profiles
+
+# LOCALIZATION NOTE (profilerShow) A very short string used to describe the function
+# of the profiler show command.
+profilerShowDesc=Show individual profile
+
+# LOCALIZATION NOTE (profilerShowManual) A fuller description of the 'profile name'
+# parameter. This parameter is used to name a newly created profile or to lookup
+# an existing profile by its name.
+profilerShowManual=Name of a profile.
+
+# LOCALIZATION NOTE (profilerAlreadyStarted) A message that is displayed whenever
+# an operation cannot be completed because the profile in question has already
+# been started.
+profilerAlreadyStarted2=Profile has already been started
+
+# LOCALIZATION NOTE (profilerNotFound) A message that is displayed whenever
+# an operation cannot be completed because the profile in question could not be
+# found.
+profilerNotFound=Profile not found
+
+# LOCALIZATION NOTE (profilerNotStarted) A message that is displayed whenever
+# an operation cannot be completed because the profile in question has not been
+# started yet. It also contains a hint to use the 'profile start' command to
+# start the profiler.
+profilerNotStarted3=Profiler has not been started yet. Use ‘profile start’ to start profiling
+
+# LOCALIZATION NOTE (profilerStarted2) A very short string that indicates that
+# we have started recording.
+profilerStarted2=Recording…
+
+# LOCALIZATION NOTE (profilerStopped) A very short string that indicates that
+# we have stopped recording.
+profilerStopped=Stopped…
+
+# LOCALIZATION NOTE (profilerNotReady) A message that is displayed whenever
+# an operation cannot be completed because the profiler has not been opened yet.
+profilerNotReady=For this command to work you need to open the profiler first
+
+# LOCALIZATION NOTE (listenDesc) A very short string used to describe the
+# function of the 'listen' command.
+listenDesc=Open a remote debug port
+
+# LOCALIZATION NOTE (listenManual2) A longer description of the 'listen'
+# command.
+listenManual2=%1$S can allow remote debugging over a TCP/IP connection. For security reasons this is turned off by default, but can be enabled using this command.
+
+# LOCALIZATION NOTE (listenPortDesc) A very short string used to describe the
+# function of 'port' parameter to the 'listen' command.
+listenPortDesc=The TCP port to listen on
+
+# LOCALIZATION NOTE (listenProtocolDesc) A very short string used to describe the
+# function of 'protocol' parameter to the 'listen' command.
+listenProtocolDesc=The protocol to be used
+
+# LOCALIZATION NOTE (listenDisabledOutput) Text of a message output during the
+# execution of the 'listen' command.
+listenDisabledOutput=Listen is disabled by the devtools.debugger.remote-enabled preference
+
+# LOCALIZATION NOTE (listenInitOutput) Text of a message output during the
+# execution of the 'listen' command. %1$S is a port number
+listenInitOutput=Listening on port %1$S
+
+# LOCALIZATION NOTE (listenNoInitOutput) Text of a message output during the
+# execution of the 'listen' command.
+listenNoInitOutput=DebuggerServer not initialized
+
+# LOCALIZATION NOTE (unlistenDesc) A very short string used to describe the
+# function of the 'unlisten' command.
+unlistenDesc=Close all remote debug ports
+
+# LOCALIZATION NOTE (unlistenManual) A longer description of the 'unlisten'
+# command.
+unlistenManual=Closes all the open ports for remote debugging.
+
+# LOCALIZATION NOTE (unlistenOutput) Text of a message output during the
+# execution of the 'unlisten' command.
+unlistenOutput=All TCP ports closed
+
+# LOCALIZATION NOTE (mediaDesc, mediaEmulateDesc, mediaEmulateManual,
+# mediaEmulateType, mediaResetDesc, mediaResetManual) These strings describe
+# the 'media' commands and all available parameters.
+mediaDesc=CSS media type emulation
+mediaEmulateDesc=Emulate a specified CSS media type
+mediaEmulateManual=View the document as if rendered on a device supporting the given media type, with the relevant CSS rules applied.
+mediaEmulateType=The media type to emulate
+mediaResetDesc=Stop emulating a CSS media type
+
+# LOCALIZATION NOTE (qsaDesc, qsaQueryDesc)
+# These strings describe the 'qsa' commands and all available parameters.
+qsaDesc=Perform querySelectorAll on the current document and return number of matches
+qsaQueryDesc=CSS selectors separated by comma
+
+# LOCALIZATION NOTE (injectDesc, injectManual, injectLibraryDesc, injectLoaded,
+# injectFailed) These strings describe the 'inject' commands and all available
+# parameters.
+injectDesc=Inject common libraries into the page
+injectManual2=Inject common libraries into the content of the page which can also be accessed from the console.
+injectLibraryDesc=Select the library to inject or enter a valid script URI to inject
+injectLoaded=%1$S loaded
+injectFailed=Failed to load %1$S - Invalid URI
+
+# LOCALIZATION NOTE (folderDesc, folderOpenDesc, folderOpenDir,
+# folderOpenProfileDesc) These strings describe the 'folder' commands and
+# all available parameters.
+folderDesc=Open folders
+folderOpenDesc=Open folder path
+folderOpenDir=Directory Path
+folderOpenProfileDesc=Open profile directory
+
+# LOCALIZATION NOTE (folderInvalidPath) A string displayed as the result
+# of the 'folder open' command with an invalid folder path.
+folderInvalidPath=Please enter a valid path
+
+# LOCALIZATION NOTE (folderOpenDirResult) A very short string used to
+# describe the result of the 'folder open' command.
+# The argument (%1$S) is the folder path.
+folderOpenDirResult=Opened %1$S
+
+# LOCALIZATION NOTE (mdnDesc) A very short string used to describe the
+# use of 'mdn' command.
+mdnDesc=Retrieve documentation from MDN
+# LOCALIZATION NOTE (mdnCssDesc) A very short string used to describe the
+# result of the 'mdn css' command.
+mdnCssDesc=Retrieve documentation about a given CSS property name from MDN
+# LOCALIZATION NOTE (mdnCssProp) String used to describe the 'property name'
+# parameter used in the 'mdn css' command.
+mdnCssProp=Property name
+# LOCALIZATION NOTE (mdnCssPropertyNotFound) String used to display an error in
+# the result of the 'mdn css' command. Errors occur when a given CSS property
+# wasn't found on MDN. The %1$S parameter will be replaced with the name of the
+# CSS property.
+mdnCssPropertyNotFound=MDN documentation for the CSS property ‘%1$S’ was not found.
+# LOCALIZATION NOTE (mdnCssVisitPage) String used as the label of a link to the
+# MDN page for a given CSS property.
+mdnCssVisitPage=Visit MDN page
+
+# LOCALIZATION NOTE (security)
+securityPrivacyDesc=Display supported security and privacy features
+securityManual=Commands to list and get suggestions about security features for the current domain.
+securityListDesc=Display security features
+securityListManual=Display a list of all relevant security features of the current page.
+# CSP specific
+securityCSPDesc=Display CSP specific security features
+securityCSPManual=Display feedback about the CSP applied to the current page.
+securityCSPRemWildCard=Can you remove the wildcard(*)?
+securityCSPPotentialXSS=Potential XSS vulnerability!
+# LOCALIZATION NOTE: do not translate 'Content-Security-Policy'
+securityCSPNoCSPOnPage=Could not find Content-Security-Policy for
+securityCSPHeaderOnPage=Content-Security-Policy for
+securityCSPROHeaderOnPage=Content-Security-Policy-Report-Only for
+# Referrer Policy specific
+securityReferrerPolicyDesc=Display the current Referrer Policy
+securityReferrerPolicyManual=Display the Referrer Policy for the current page with example referrers for different URIs.
+securityReferrerNextURI=When Visiting
+securityReferrerCalculatedReferrer=Referrer Will Be
+# LOCALIZATION NOTE: %1$S is the current page URI
+securityReferrerPolicyReportHeader=Referrer Policy for %1$S
+securityReferrerPolicyOtherDomain=Other Origin
+securityReferrerPolicyOtherDomainDowngrade=Other Origin HTTP
+securityReferrerPolicySameDomain=Same Origin
+securityReferrerPolicySameDomainDowngrade=Same Host HTTP
+
+# LOCALIZATION NOTE (rulersDesc) A very short description of the
+# 'rulers' command. See rulersManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+rulersDesc=Toggle rulers for the page
+
+# LOCALIZATION NOTE (rulersManual) A fuller description of the 'rulers'
+# command, displayed when the user asks for help on what it does.
+rulersManual=Toggle the horizontal and vertical rulers for the current page
+
+# LOCALIZATION NOTE (rulersTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles the rulers.
+rulersTooltip=Toggle rulers for the page
+
+# LOCALIZATION NOTE (measureDesc) A very short description of the
+# 'measure' command. See measureManual for a fuller description of what
+# it does. This string is designed to be shown in a menu alongside the
+# command name, which is why it should be as short as possible.
+measureDesc=Measure a portion of the page
+
+# LOCALIZATION NOTE (measureManual) A fuller description of the 'measure'
+# command, displayed when the user asks for help on what it does.
+measureManual=Activate the measuring tool to measure an arbitrary area of the page
+
+# LOCALIZATION NOTE (measureTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles the measuring tool.
+measureTooltip=Measure a portion of the page
diff --git a/devtools/shared/locales/en-US/shared.properties b/devtools/shared/locales/en-US/shared.properties
new file mode 100644
index 000000000..1fb1dc230
--- /dev/null
+++ b/devtools/shared/locales/en-US/shared.properties
@@ -0,0 +1,6 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE (ellipsis): The ellipsis (three dots) character
+ellipsis=… \ No newline at end of file
diff --git a/devtools/shared/locales/en-US/styleinspector.properties b/devtools/shared/locales/en-US/styleinspector.properties
new file mode 100644
index 000000000..b56650086
--- /dev/null
+++ b/devtools/shared/locales/en-US/styleinspector.properties
@@ -0,0 +1,188 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# LOCALIZATION NOTE These strings are used inside the Style Inspector.
+#
+# The correct localization of this file might be to keep it in
+# English, or another language commonly spoken among web developers.
+# You want to make that choice consistent across the developer tools.
+# A good criteria is the language in which you'd find the best
+# documentation on web development on the web.
+
+
+# LOCALIZATION NOTE (panelTitle): This is the panel title
+panelTitle=Style Inspector
+
+# LOCALIZATION NOTE (rule.status): For each style property the panel shows
+# the rules which hold that specific property. For every rule, the rule status
+# is also displayed: a rule can be the best match, a match, a parent match, or a
+# rule did not match the element the user has highlighted.
+rule.status.BEST=Best Match
+rule.status.MATCHED=Matched
+rule.status.PARENT_MATCH=Parent Match
+
+# LOCALIZATION NOTE (rule.sourceElement, rule.sourceInline): For each
+# style property the panel shows the rules which hold that specific property.
+# For every rule, the rule source is also displayed: a rule can come from a
+# file, from the same page (inline), or from the element itself (element).
+rule.sourceInline=inline
+rule.sourceElement=element
+
+# LOCALIZATION NOTE (rule.inheritedFrom): Shown for CSS rules
+# that were inherited from a parent node. Will be passed a node
+# identifier of the parent node.
+# e.g "Inherited from body#bodyID"
+rule.inheritedFrom=Inherited from %S
+
+# LOCALIZATION NOTE (rule.keyframe): Shown for CSS Rules keyframe header.
+# Will be passed an identifier of the keyframe animation name.
+rule.keyframe=Keyframes %S
+
+# LOCALIZATION NOTE (rule.userAgentStyles): Shown next to the style sheet
+# link for CSS rules that were loaded from a user agent style sheet.
+# These styles will not be editable, and will only be visible if the
+# devtools.inspector.showUserAgentStyles pref is true.
+rule.userAgentStyles=(user agent)
+
+# LOCALIZATION NOTE (rule.pseudoElement): Shown for CSS rules
+# pseudo element header
+rule.pseudoElement=Pseudo-elements
+
+# LOCALIZATION NOTE (rule.pseudoElement): Shown for CSS rules
+# pseudo element header
+rule.selectedElement=This Element
+
+# LOCALIZATION NOTE (rule.warning.title): When an invalid property value is
+# entered into the rule view a warning icon is displayed. This text is used for
+# the title attribute of the warning icon.
+rule.warning.title=Invalid property value
+
+# LOCALIZATION NOTE (rule.filterProperty.title): Text displayed in the tooltip
+# of the search button that is shown next to a property that has been overridden
+# in the rule view.
+rule.filterProperty.title=Filter rules containing this property
+
+# LOCALIZATION NOTE (ruleView.empty): Text displayed when the highlighter is
+# first opened and there's no node selected in the rule view.
+rule.empty=No element selected.
+
+# LOCALIZATION NOTE (ruleView.selectorHighlighter.tooltip): Text displayed in a
+# tooltip when the mouse is over a selector highlighter icon in the rule view.
+rule.selectorHighlighter.tooltip=Highlight all elements matching this selector
+
+# LOCALIZATION NOTE (rule.colorSwatch.tooltip): Text displayed in a tooltip
+# when the mouse is over a color swatch in the rule view.
+rule.colorSwatch.tooltip=Click to open the color picker, shift+click to change the color format
+
+# LOCALIZATION NOTE (rule.bezierSwatch.tooltip): Text displayed in a tooltip
+# when the mouse is over a cubic-bezier swatch in the rule view.
+rule.bezierSwatch.tooltip=Click to open the timing-function editor
+
+# LOCALIZATION NOTE (rule.filterSwatch.tooltip): Text displayed in a tooltip
+# when the mouse is over a filter swatch in the rule view.
+rule.filterSwatch.tooltip=Click to open the filter editor
+
+# LOCALIZATION NOTE (rule.angleSwatch.tooltip): Text displayed in a tooltip
+# when the mouse is over a angle swatch in the rule view.
+rule.angleSwatch.tooltip=Shift+click to change the angle format
+
+# LOCALIZATION NOTE (rule.gridToggle.tooltip): Text displayed in a tooltip
+# when the mouse is over a CSS Grid toggle icon in the rule view.
+rule.gridToggle.tooltip=Click to toggle the CSS Grid highlighter
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyColor): Text displayed in the rule
+# and computed view context menu when a color value was clicked.
+styleinspector.contextmenu.copyColor=Copy Color
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyColor.accessKey): Access key for
+# the rule and computed view context menu "Copy Color" entry.
+styleinspector.contextmenu.copyColor.accessKey=L
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyUrl): In rule and computed view :
+# text displayed in the context menu for an image URL.
+# Clicking it copies the URL to the clipboard of the user.
+styleinspector.contextmenu.copyUrl=Copy URL
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyUrl.accessKey): Access key for
+# the rule and computed view context menu "Copy URL" entry.
+styleinspector.contextmenu.copyUrl.accessKey=U
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyImageDataUrl): In rule and computed view :
+# text displayed in the context menu for an image URL.
+# Clicking it copies the image as Data-URL to the clipboard of the user.
+styleinspector.contextmenu.copyImageDataUrl=Copy Image Data-URL
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyDataUri.accessKey): Access key for
+# the rule and computed view context menu "Copy Image Data-URL" entry.
+styleinspector.contextmenu.copyImageDataUrl.accessKey=I
+
+# LOCALIZATION NOTE (styleinspector.copyDataUriError): Text set in the clipboard
+# if an error occurs when using the copyImageDataUrl context menu action
+# (invalid image link, timeout, etc...)
+styleinspector.copyImageDataUrlError=Failed to copy image Data-URL
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.toggleOrigSources): Text displayed in the rule view
+# context menu.
+styleinspector.contextmenu.toggleOrigSources=Show Original Sources
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.toggleOrigSources.accessKey): Access key for
+# the rule view context menu "Show original sources" entry.
+styleinspector.contextmenu.toggleOrigSources.accessKey=O
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.showMdnDocs): Text displayed in the rule view
+# context menu to display docs from MDN for an item.
+styleinspector.contextmenu.showMdnDocs=Show MDN Docs
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.showMdnDocs.accessKey): Access key for
+# the rule view context menu "Show MDN docs" entry.
+styleinspector.contextmenu.showMdnDocs.accessKey=D
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.addNewRule): Text displayed in the
+# rule view context menu for adding a new rule to the element.
+# This should match inspector.addRule.tooltip in inspector.properties
+styleinspector.contextmenu.addNewRule=Add New Rule
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.addRule.accessKey): Access key for
+# the rule view context menu "Add rule" entry.
+styleinspector.contextmenu.addNewRule.accessKey=R
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.selectAll): Text displayed in the
+# computed view context menu.
+styleinspector.contextmenu.selectAll=Select All
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.selectAll.accessKey): Access key for
+# the computed view context menu "Select all" entry.
+styleinspector.contextmenu.selectAll.accessKey=A
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copy): Text displayed in the
+# computed view context menu.
+styleinspector.contextmenu.copy=Copy
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copy.accessKey): Access key for
+# the computed view context menu "Copy" entry.
+styleinspector.contextmenu.copy.accessKey=C
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyLocation): Text displayed in the
+# rule view context menu for copying the source location.
+styleinspector.contextmenu.copyLocation=Copy Location
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyPropertyDeclaration): Text
+# displayed in the rule view context menu for copying the property declaration.
+styleinspector.contextmenu.copyPropertyDeclaration=Copy Property Declaration
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyPropertyName): Text displayed in
+# the rule view context menu for copying the property name.
+styleinspector.contextmenu.copyPropertyName=Copy Property Name
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyPropertyValue): Text displayed in
+# the rule view context menu for copying the property value.
+styleinspector.contextmenu.copyPropertyValue=Copy Property Value
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copyRule): Text displayed in the
+# rule view context menu for copying the rule.
+styleinspector.contextmenu.copyRule=Copy Rule
+
+# LOCALIZATION NOTE (styleinspector.contextmenu.copySelector): Text displayed in the
+# rule view context menu for copying the selector.
+styleinspector.contextmenu.copySelector=Copy Selector
diff --git a/devtools/shared/locales/jar.mn b/devtools/shared/locales/jar.mn
new file mode 100644
index 000000000..2d33b5425
--- /dev/null
+++ b/devtools/shared/locales/jar.mn
@@ -0,0 +1,8 @@
+#filter substitution
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+@AB_CD@.jar:
+% locale devtools-shared @AB_CD@ %locale/@AB_CD@/devtools/shared/
+ locale/@AB_CD@/devtools/shared/ (%*)
diff --git a/devtools/shared/locales/moz.build b/devtools/shared/locales/moz.build
new file mode 100644
index 000000000..aac3a838c
--- /dev/null
+++ b/devtools/shared/locales/moz.build
@@ -0,0 +1,7 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+JAR_MANIFESTS += ['jar.mn']
diff --git a/devtools/shared/moz.build b/devtools/shared/moz.build
new file mode 100644
index 000000000..e4de1d84a
--- /dev/null
+++ b/devtools/shared/moz.build
@@ -0,0 +1,67 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+include('../templates.mozbuild')
+
+DIRS += [
+ 'acorn',
+ 'apps',
+ 'client',
+ 'css',
+ 'discovery',
+ 'fronts',
+ 'gcli',
+ 'heapsnapshot',
+ 'inspector',
+ 'jsbeautify',
+ 'layout',
+ 'locales',
+ 'node-properties',
+ 'performance',
+ 'platform',
+ 'pretty-fast',
+ 'qrcode',
+ 'security',
+ 'sourcemap',
+ 'sprintfjs',
+ 'shims',
+ 'specs',
+ 'touch',
+ 'transport',
+ 'webconsole',
+ 'worker',
+]
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+JAR_MANIFESTS += ['jar.mn']
+
+DevToolsModules(
+ 'async-storage.js',
+ 'async-utils.js',
+ 'builtin-modules.js',
+ 'content-observer.js',
+ 'defer.js',
+ 'deprecated-sync-thenables.js',
+ 'DevToolsUtils.js',
+ 'dom-node-constants.js',
+ 'dom-node-filter-constants.js',
+ 'event-emitter.js',
+ 'flags.js',
+ 'indentation.js',
+ 'l10n.js',
+ 'loader-plugin-raw.jsm',
+ 'Loader.jsm',
+ 'Parser.jsm',
+ 'path.js',
+ 'plural-form.js',
+ 'protocol.js',
+ 'system.js',
+ 'task.js',
+ 'ThreadSafeDevToolsUtils.js',
+)
diff --git a/devtools/shared/node-properties/UPGRADING.md b/devtools/shared/node-properties/UPGRADING.md
new file mode 100644
index 000000000..086621f2b
--- /dev/null
+++ b/devtools/shared/node-properties/UPGRADING.md
@@ -0,0 +1,12 @@
+NODE PROPERTIES UPGRADING
+
+Original library at https://github.com/gagle/node-properties
+The original library is intended for node and not for the browser. Most files are not
+needed here.
+
+To update
+- copy https://github.com/gagle/node-properties/blob/master/lib/parse.js
+- update the initial "module.exports" to "var parse" in parse.js
+- copy https://github.com/gagle/node-properties/blob/master/lib/read.js
+- remove the require statements at the beginning
+- merge the two files, parse.js first, read.js second \ No newline at end of file
diff --git a/devtools/shared/node-properties/moz.build b/devtools/shared/node-properties/moz.build
new file mode 100644
index 000000000..e58b1fdb6
--- /dev/null
+++ b/devtools/shared/node-properties/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'node-properties.js'
+)
diff --git a/devtools/shared/node-properties/node-properties.js b/devtools/shared/node-properties/node-properties.js
new file mode 100644
index 000000000..05feba857
--- /dev/null
+++ b/devtools/shared/node-properties/node-properties.js
@@ -0,0 +1,776 @@
+/**
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2014 Gabriel Llamas
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ *
+ */
+
+"use strict";
+
+var hex = function (c){
+ switch (c){
+ case "0": return 0;
+ case "1": return 1;
+ case "2": return 2;
+ case "3": return 3;
+ case "4": return 4;
+ case "5": return 5;
+ case "6": return 6;
+ case "7": return 7;
+ case "8": return 8;
+ case "9": return 9;
+ case "a": case "A": return 10;
+ case "b": case "B": return 11;
+ case "c": case "C": return 12;
+ case "d": case "D": return 13;
+ case "e": case "E": return 14;
+ case "f": case "F": return 15;
+ }
+};
+
+var parse = function (data, options, handlers, control){
+ var c;
+ var code;
+ var escape;
+ var skipSpace = true;
+ var isCommentLine;
+ var isSectionLine;
+ var newLine = true;
+ var multiLine;
+ var isKey = true;
+ var key = "";
+ var value = "";
+ var section;
+ var unicode;
+ var unicodeRemaining;
+ var escapingUnicode;
+ var keySpace;
+ var sep;
+ var ignoreLine;
+
+ var line = function (){
+ if (key || value || sep){
+ handlers.line (key, value);
+ key = "";
+ value = "";
+ sep = false;
+ }
+ };
+
+ var escapeString = function (key, c, code){
+ if (escapingUnicode && unicodeRemaining){
+ unicode = (unicode << 4) + hex (c);
+ if (--unicodeRemaining) return key;
+ escape = false;
+ escapingUnicode = false;
+ return key + String.fromCharCode (unicode);
+ }
+
+ //code 117: u
+ if (code === 117){
+ unicode = 0;
+ escapingUnicode = true;
+ unicodeRemaining = 4;
+ return key;
+ }
+
+ escape = false;
+
+ //code 116: t
+ //code 114: r
+ //code 110: n
+ //code 102: f
+ if (code === 116) return key + "\t";
+ else if (code === 114) return key + "\r";
+ else if (code === 110) return key + "\n";
+ else if (code === 102) return key + "\f";
+
+ return key + c;
+ };
+
+ var isComment;
+ var isSeparator;
+
+ if (options._strict){
+ isComment = function (c, code, options){
+ return options._comments[c];
+ };
+
+ isSeparator = function (c, code, options){
+ return options._separators[c];
+ };
+ }else{
+ isComment = function (c, code, options){
+ //code 35: #
+ //code 33: !
+ return code === 35 || code === 33 || options._comments[c];
+ };
+
+ isSeparator = function (c, code, options){
+ //code 61: =
+ //code 58: :
+ return code === 61 || code === 58 || options._separators[c];
+ };
+ }
+
+ for (var i=~~control.resume; i<data.length; i++){
+ if (control.abort) return;
+ if (control.pause){
+ //The next index is always the start of a new line, it's a like a fresh
+ //start, there's no need to save the current state
+ control.resume = i;
+ return;
+ }
+
+ c = data[i];
+ code = data.charCodeAt (i);
+
+ //code 13: \r
+ if (code === 13) continue;
+
+ if (isCommentLine){
+ //code 10: \n
+ if (code === 10){
+ isCommentLine = false;
+ newLine = true;
+ skipSpace = true;
+ }
+ continue;
+ }
+
+ //code 93: ]
+ if (isSectionLine && code === 93){
+ handlers.section (section);
+ //Ignore chars after the section in the same line
+ ignoreLine = true;
+ continue;
+ }
+
+ if (skipSpace){
+ //code 32: " " (space)
+ //code 9: \t
+ //code 12: \f
+ if (code === 32 || code === 9 || code === 12){
+ continue;
+ }
+ //code 10: \n
+ if (!multiLine && code === 10){
+ //Empty line or key w/ separator and w/o value
+ isKey = true;
+ keySpace = false;
+ newLine = true;
+ line ();
+ continue;
+ }
+ skipSpace = false;
+ multiLine = false;
+ }
+
+ if (newLine){
+ newLine = false;
+ if (isComment (c, code, options)){
+ isCommentLine = true;
+ continue;
+ }
+ //code 91: [
+ if (options.sections && code === 91){
+ section = "";
+ isSectionLine = true;
+ control.skipSection = false;
+ continue;
+ }
+ }
+
+ //code 10: \n
+ if (code !== 10){
+ if (control.skipSection || ignoreLine) continue;
+
+ if (!isSectionLine){
+ if (!escape && isKey && isSeparator (c, code, options)){
+ //sep is needed to detect empty key and empty value with a
+ //non-whitespace separator
+ sep = true;
+ isKey = false;
+ keySpace = false;
+ //Skip whitespace between separator and value
+ skipSpace = true;
+ continue;
+ }
+ }
+
+ //code 92: "\" (backslash)
+ if (code === 92){
+ if (escape){
+ if (escapingUnicode) continue;
+
+ if (keySpace){
+ //Line with whitespace separator
+ keySpace = false;
+ isKey = false;
+ }
+
+ if (isSectionLine) section += "\\";
+ else if (isKey) key += "\\";
+ else value += "\\";
+ }
+ escape = !escape;
+ }else{
+ if (keySpace){
+ //Line with whitespace separator
+ keySpace = false;
+ isKey = false;
+ }
+
+ if (isSectionLine){
+ if (escape) section = escapeString (section, c, code);
+ else section += c;
+ }else if (isKey){
+ if (escape){
+ key = escapeString (key, c, code);
+ }else{
+ //code 32: " " (space)
+ //code 9: \t
+ //code 12: \f
+ if (code === 32 || code === 9 || code === 12){
+ keySpace = true;
+ //Skip whitespace between key and separator
+ skipSpace = true;
+ continue;
+ }
+ key += c;
+ }
+ }else{
+ if (escape) value = escapeString (value, c, code);
+ else value += c;
+ }
+ }
+ }else{
+ if (escape){
+ if (!escapingUnicode){
+ escape = false;
+ }
+ skipSpace = true;
+ multiLine = true;
+ }else{
+ if (isSectionLine){
+ isSectionLine = false;
+ if (!ignoreLine){
+ //The section doesn't end with ], it's a key
+ control.error = new Error ("The section line \"" + section +
+ "\" must end with \"]\"");
+ return;
+ }
+ ignoreLine = false;
+ }
+ newLine = true;
+ skipSpace = true;
+ isKey = true;
+
+ line ();
+ }
+ }
+ }
+
+ control.parsed = true;
+
+ if (isSectionLine && !ignoreLine){
+ //The section doesn't end with ], it's a key
+ control.error = new Error ("The section line \"" + section + "\" must end" +
+ "with \"]\"");
+ return;
+ }
+ line ();
+};
+
+var INCLUDE_KEY = "include";
+var INDEX_FILE = "index.properties";
+
+var cast = function (value){
+ if (value === null || value === "null") return null;
+ if (value === "undefined") return undefined;
+ if (value === "true") return true;
+ if (value === "false") return false;
+ var v = Number (value);
+ return isNaN (v) ? value : v;
+};
+
+var expand = function (o, str, options, cb){
+ if (!options.variables || !str) return cb (null, str);
+
+ var stack = [];
+ var c;
+ var cp;
+ var key = "";
+ var section = null;
+ var v;
+ var holder;
+ var t;
+ var n;
+
+ for (var i=0; i<str.length; i++){
+ c = str[i];
+
+ if (cp === "$" && c === "{"){
+ key = key.substring (0, key.length - 1);
+ stack.push ({
+ key: key,
+ section: section
+ });
+ key = "";
+ section = null;
+ continue;
+ }else if (stack.length){
+ if (options.sections && c === "|"){
+ section = key;
+ key = "";
+ continue;
+ }else if (c === "}"){
+ holder = section !== null ? searchValue (o, section, true) : o;
+ if (!holder){
+ return cb (new Error ("The section \"" + section + "\" does not " +
+ "exist"));
+ }
+
+ v = options.namespaces ? searchValue (holder, key) : holder[key];
+ if (v === undefined){
+ //Read the external vars
+ v = options.namespaces
+ ? searchValue (options._vars, key)
+ : options._vars[key]
+
+ if (v === undefined){
+ return cb (new Error ("The property \"" + key + "\" does not " +
+ "exist"));
+ }
+ }
+
+ t = stack.pop ();
+ section = t.section;
+ key = t.key + (v === null ? "" : v);
+ continue;
+ }
+ }
+
+ cp = c;
+ key += c;
+ }
+
+ if (stack.length !== 0){
+ return cb (new Error ("Malformed variable: " + str));
+ }
+
+ cb (null, key);
+};
+
+var searchValue = function (o, chain, section){
+ var n = chain.split (".");
+ var str;
+
+ for (var i=0; i<n.length-1; i++){
+ str = n[i];
+ if (o[str] === undefined) return;
+ o = o[str];
+ }
+
+ var v = o[n[n.length - 1]];
+ if (section){
+ if (typeof v !== "object") return;
+ return v;
+ }else{
+ if (typeof v === "object") return;
+ return v;
+ }
+};
+
+var namespaceKey = function (o, key, value){
+ var n = key.split (".");
+ var str;
+
+ for (var i=0; i<n.length-1; i++){
+ str = n[i];
+ if (o[str] === undefined){
+ o[str] = {};
+ }else if (typeof o[str] !== "object"){
+ throw new Error ("Invalid namespace chain in the property name '" +
+ key + "' ('" + str + "' has already a value)");
+ }
+ o = o[str];
+ }
+
+ o[n[n.length - 1]] = value;
+};
+
+var namespaceSection = function (o, section){
+ var n = section.split (".");
+ var str;
+
+ for (var i=0; i<n.length; i++){
+ str = n[i];
+ if (o[str] === undefined){
+ o[str] = {};
+ }else if (typeof o[str] !== "object"){
+ throw new Error ("Invalid namespace chain in the section name '" +
+ section + "' ('" + str + "' has already a value)");
+ }
+ o = o[str];
+ }
+
+ return o;
+};
+
+var merge = function (o1, o2){
+ for (var p in o2){
+ try{
+ if (o1[p].constructor === Object){
+ o1[p] = merge (o1[p], o2[p]);
+ }else{
+ o1[p] = o2[p];
+ }
+ }catch (e){
+ o1[p] = o2[p];
+ }
+ }
+ return o1;
+}
+
+var build = function (data, options, dirname, cb){
+ var o = {};
+
+ if (options.namespaces){
+ var n = {};
+ }
+
+ var control = {
+ abort: false,
+ skipSection: false
+ };
+
+ if (options.include){
+ var remainingIncluded = 0;
+
+ var include = function (value){
+ if (currentSection !== null){
+ return abort (new Error ("Cannot include files from inside a " +
+ "section: " + currentSection));
+ }
+
+ var p = path.resolve (dirname, value);
+ if (options._included[p]) return;
+
+ options._included[p] = true;
+ remainingIncluded++;
+ control.pause = true;
+
+ read (p, options, function (error, included){
+ if (error) return abort (error);
+
+ remainingIncluded--;
+ merge (options.namespaces ? n : o, included);
+ control.pause = false;
+
+ if (!control.parsed){
+ parse (data, options, handlers, control);
+ if (control.error) return abort (control.error);
+ }
+
+ if (!remainingIncluded) cb (null, options.namespaces ? n : o);
+ });
+ };
+ }
+
+ if (!data){
+ if (cb) return cb (null, o);
+ return o;
+ }
+
+ var currentSection = null;
+ var currentSectionStr = null;
+
+ var abort = function (error){
+ control.abort = true;
+ if (cb) return cb (error);
+ throw error;
+ };
+
+ var handlers = {};
+ var reviver = {
+ assert: function (){
+ return this.isProperty ? reviverLine.value : true;
+ }
+ };
+ var reviverLine = {};
+
+ //Line handler
+ //For speed reasons, if "namespaces" is enabled, the old object is still
+ //populated, e.g.: ${a.b} reads the "a.b" property from { "a.b": 1 }, instead
+ //of having a unique object { a: { b: 1 } } which is slower to search for
+ //the "a.b" value
+ //If "a.b" is not found, then the external vars are read. If "namespaces" is
+ //enabled, the var "a.b" is split and it searches the a.b value. If it is not
+ //enabled, then the var "a.b" searches the "a.b" value
+
+ var line;
+ var error;
+
+ if (options.reviver){
+ if (options.sections){
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ reviverLine.value = value;
+ reviver.isProperty = true;
+ reviver.isSection = false;
+
+ value = options.reviver.call (reviver, key, value, currentSectionStr);
+ if (value !== undefined){
+ if (options.namespaces){
+ try{
+ namespaceKey (currentSection === null ? n : currentSection,
+ key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ if (currentSection === null) o[key] = value;
+ else currentSection[key] = value;
+ }
+ }
+ };
+ }else{
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ reviverLine.value = value;
+ reviver.isProperty = true;
+ reviver.isSection = false;
+
+ value = options.reviver.call (reviver, key, value);
+ if (value !== undefined){
+ if (options.namespaces){
+ try{
+ namespaceKey (n, key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ o[key] = value;
+ }
+ }
+ };
+ }
+ }else{
+ if (options.sections){
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ if (options.namespaces){
+ try{
+ namespaceKey (currentSection === null ? n : currentSection, key,
+ value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ if (currentSection === null) o[key] = value;
+ else currentSection[key] = value;
+ }
+ };
+ }else{
+ line = function (key, value){
+ if (options.include && key === INCLUDE_KEY) return include (value);
+
+ if (options.namespaces){
+ try{
+ namespaceKey (n, key, value);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ o[key] = value;
+ }
+ };
+ }
+ }
+
+ //Section handler
+ var section;
+ if (options.sections){
+ if (options.reviver){
+ section = function (section){
+ currentSectionStr = section;
+ reviverLine.section = section;
+ reviver.isProperty = false;
+ reviver.isSection = true;
+
+ var add = options.reviver.call (reviver, null, null, section);
+ if (add){
+ if (options.namespaces){
+ try{
+ currentSection = namespaceSection (n, section);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ currentSection = o[section] = {};
+ }
+ }else{
+ control.skipSection = true;
+ }
+ };
+ }else{
+ section = function (section){
+ currentSectionStr = section;
+ if (options.namespaces){
+ try{
+ currentSection = namespaceSection (n, section);
+ }catch (error){
+ abort (error);
+ }
+ }else{
+ currentSection = o[section] = {};
+ }
+ };
+ }
+ }
+
+ //Variables
+ if (options.variables){
+ handlers.line = function (key, value){
+ expand (options.namespaces ? n : o, key, options, function (error, key){
+ if (error) return abort (error);
+
+ expand (options.namespaces ? n : o, value, options,
+ function (error, value){
+ if (error) return abort (error);
+
+ line (key, cast (value || null));
+ });
+ });
+ };
+
+ if (options.sections){
+ handlers.section = function (s){
+ expand (options.namespaces ? n : o, s, options, function (error, s){
+ if (error) return abort (error);
+
+ section (s);
+ });
+ };
+ }
+ }else{
+ handlers.line = function (key, value){
+ line (key, cast (value || null));
+ };
+
+ if (options.sections){
+ handlers.section = section;
+ }
+ }
+
+ parse (data, options, handlers, control);
+ if (control.error) return abort (control.error);
+
+ if (control.abort || control.pause) return;
+
+ if (cb) return cb (null, options.namespaces ? n : o);
+ return options.namespaces ? n : o;
+};
+
+var read = function (f, options, cb){
+ fs.stat (f, function (error, stats){
+ if (error) return cb (error);
+
+ var dirname;
+
+ if (stats.isDirectory ()){
+ dirname = f;
+ f = path.join (f, INDEX_FILE);
+ }else{
+ dirname = path.dirname (f);
+ }
+
+ fs.readFile (f, { encoding: "utf8" }, function (error, data){
+ if (error) return cb (error);
+ build (data, options, dirname, cb);
+ });
+ });
+};
+
+module.exports = function (data, options, cb){
+ if (typeof options === "function"){
+ cb = options;
+ options = {};
+ }
+
+ options = options || {};
+ var code;
+
+ if (options.include){
+ if (!cb) throw new Error ("A callback must be passed if the 'include' " +
+ "option is enabled");
+ options._included = {};
+ }
+
+ options = options || {};
+ options._strict = options.strict && (options.comments || options.separators);
+ options._vars = options.vars || {};
+
+ var comments = options.comments || [];
+ if (!Array.isArray (comments)) comments = [comments];
+ var c = {};
+ comments.forEach (function (comment){
+ code = comment.charCodeAt (0);
+ if (comment.length > 1 || code < 33 || code > 126){
+ throw new Error ("The comment token must be a single printable ASCII " +
+ "character");
+ }
+ c[comment] = true;
+ });
+ options._comments = c;
+
+ var separators = options.separators || [];
+ if (!Array.isArray (separators)) separators = [separators];
+ var s = {};
+ separators.forEach (function (separator){
+ code = separator.charCodeAt (0);
+ if (separator.length > 1 || code < 33 || code > 126){
+ throw new Error ("The separator token must be a single printable ASCII " +
+ "character");
+ }
+ s[separator] = true;
+ });
+ options._separators = s;
+
+ if (options.path){
+ if (!cb) throw new Error ("A callback must be passed if the 'path' " +
+ "option is enabled");
+ if (options.include){
+ read (data, options, cb);
+ }else{
+ fs.readFile (data, { encoding: "utf8" }, function (error, data){
+ if (error) return cb (error);
+ build (data, options, ".", cb);
+ });
+ }
+ }else{
+ return build (data, options, ".", cb);
+ }
+};
diff --git a/devtools/shared/path.js b/devtools/shared/path.js
new file mode 100644
index 000000000..94083679a
--- /dev/null
+++ b/devtools/shared/path.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/*
+ * Join all the arguments together and normalize the resulting URI.
+ * The initial path must be an full URI with a protocol (i.e. http://).
+ */
+exports.joinURI = (initialPath, ...paths) => {
+ let url;
+
+ try {
+ url = new URL(initialPath);
+ }
+ catch (e) {
+ return;
+ }
+
+ for (let path of paths) {
+ if (path) {
+ url = new URL(path, url);
+ }
+ }
+
+ return url.href;
+};
diff --git a/devtools/shared/performance/moz.build b/devtools/shared/performance/moz.build
new file mode 100644
index 000000000..202aac278
--- /dev/null
+++ b/devtools/shared/performance/moz.build
@@ -0,0 +1,12 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
+
+DevToolsModules(
+ 'recording-common.js',
+ 'recording-utils.js',
+)
diff --git a/devtools/shared/performance/recording-common.js b/devtools/shared/performance/recording-common.js
new file mode 100644
index 000000000..d0826bd18
--- /dev/null
+++ b/devtools/shared/performance/recording-common.js
@@ -0,0 +1,97 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * A mixin to be used for PerformanceRecordingActor, PerformanceRecordingFront,
+ * and LegacyPerformanceRecording for helper methods to access data.
+ */
+
+const PerformanceRecordingCommon = exports.PerformanceRecordingCommon = {
+ // Private fields, only needed when a recording is started or stopped.
+ _console: false,
+ _imported: false,
+ _recording: false,
+ _completed: false,
+ _configuration: {},
+ _startingBufferStatus: null,
+ _localStartTime: 0,
+
+ // Serializable fields, necessary and sufficient for import and export.
+ _label: "",
+ _duration: 0,
+ _markers: null,
+ _frames: null,
+ _memory: null,
+ _ticks: null,
+ _allocations: null,
+ _profile: null,
+ _systemHost: null,
+ _systemClient: null,
+
+ /**
+ * Helper methods for returning the status of the recording.
+ * These methods should be consistent on both the front and actor.
+ */
+ isRecording: function () { return this._recording; },
+ isCompleted: function () { return this._completed || this.isImported(); },
+ isFinalizing: function () { return !this.isRecording() && !this.isCompleted(); },
+ isConsole: function () { return this._console; },
+ isImported: function () { return this._imported; },
+
+ /**
+ * Helper methods for returning configuration for the recording.
+ * These methods should be consistent on both the front and actor.
+ */
+ getConfiguration: function () { return this._configuration; },
+ getLabel: function () { return this._label; },
+
+ /**
+ * Gets duration of this recording, in milliseconds.
+ * @return number
+ */
+ getDuration: function () {
+ // Compute an approximate ending time for the current recording if it is
+ // still in progress. This is needed to ensure that the view updates even
+ // when new data is not being generated. If recording is completed, use
+ // the duration from the profiler; if between recording and being finalized,
+ // use the last estimated duration.
+ if (this.isRecording()) {
+ return this._estimatedDuration = Date.now() - this._localStartTime;
+ } else {
+ return this._duration || this._estimatedDuration || 0;
+ }
+ },
+
+ /**
+ * Helper methods for returning recording data.
+ * These methods should be consistent on both the front and actor.
+ */
+ getMarkers: function () { return this._markers; },
+ getFrames: function () { return this._frames; },
+ getMemory: function () { return this._memory; },
+ getTicks: function () { return this._ticks; },
+ getAllocations: function () { return this._allocations; },
+ getProfile: function () { return this._profile; },
+ getHostSystemInfo: function () { return this._systemHost; },
+ getClientSystemInfo: function () { return this._systemClient; },
+ getStartingBufferStatus: function () { return this._startingBufferStatus; },
+
+ getAllData: function () {
+ let label = this.getLabel();
+ let duration = this.getDuration();
+ let markers = this.getMarkers();
+ let frames = this.getFrames();
+ let memory = this.getMemory();
+ let ticks = this.getTicks();
+ let allocations = this.getAllocations();
+ let profile = this.getProfile();
+ let configuration = this.getConfiguration();
+ let systemHost = this.getHostSystemInfo();
+ let systemClient = this.getClientSystemInfo();
+
+ return { label, duration, markers, frames, memory, ticks, allocations, profile, configuration, systemHost, systemClient };
+ },
+};
diff --git a/devtools/shared/performance/recording-utils.js b/devtools/shared/performance/recording-utils.js
new file mode 100644
index 000000000..64ed12c71
--- /dev/null
+++ b/devtools/shared/performance/recording-utils.js
@@ -0,0 +1,628 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cc, Ci, Cu, Cr } = require("chrome");
+loader.lazyRequireGetter(this, "extend",
+ "sdk/util/object", true);
+
+/**
+ * Utility functions for managing recording models and their internal data,
+ * such as filtering profile samples or offsetting timestamps.
+ */
+
+function mapRecordingOptions(type, options) {
+ if (type === "profiler") {
+ return {
+ entries: options.bufferSize,
+ interval: options.sampleFrequency ? (1000 / (options.sampleFrequency * 1000)) : void 0
+ };
+ }
+
+ if (type === "memory") {
+ return {
+ probability: options.allocationsSampleProbability,
+ maxLogLength: options.allocationsMaxLogLength
+ };
+ }
+
+ if (type === "timeline") {
+ return {
+ withMarkers: true,
+ withTicks: options.withTicks,
+ withMemory: options.withMemory,
+ withFrames: true,
+ withGCEvents: true,
+ withDocLoadingEvents: false
+ };
+ }
+
+ return options;
+}
+
+/**
+ * Takes an options object for `startRecording`, and normalizes
+ * it based off of server support. For example, if the user
+ * requests to record memory `withMemory = true`, but the server does
+ * not support that feature, then the `false` will overwrite user preference
+ * in order to define the recording with what is actually available, not
+ * what the user initially requested.
+ *
+ * @param {object} options
+ * @param {boolean}
+ */
+function normalizePerformanceFeatures(options, supportedFeatures) {
+ return Object.keys(options).reduce((modifiedOptions, feature) => {
+ if (supportedFeatures[feature] !== false) {
+ modifiedOptions[feature] = options[feature];
+ }
+ return modifiedOptions;
+ }, Object.create(null));
+}
+
+/**
+ * Filters all the samples in the provided profiler data to be more recent
+ * than the specified start time.
+ *
+ * @param object profile
+ * The profiler data received from the backend.
+ * @param number profilerStartTime
+ * The earliest acceptable sample time (in milliseconds).
+ */
+function filterSamples(profile, profilerStartTime) {
+ let firstThread = profile.threads[0];
+ const TIME_SLOT = firstThread.samples.schema.time;
+ firstThread.samples.data = firstThread.samples.data.filter(e => {
+ return e[TIME_SLOT] >= profilerStartTime;
+ });
+}
+
+/**
+ * Offsets all the samples in the provided profiler data by the specified time.
+ *
+ * @param object profile
+ * The profiler data received from the backend.
+ * @param number timeOffset
+ * The amount of time to offset by (in milliseconds).
+ */
+function offsetSampleTimes(profile, timeOffset) {
+ let firstThread = profile.threads[0];
+ const TIME_SLOT = firstThread.samples.schema.time;
+ let samplesData = firstThread.samples.data;
+ for (let i = 0; i < samplesData.length; i++) {
+ samplesData[i][TIME_SLOT] -= timeOffset;
+ }
+}
+
+/**
+ * Offsets all the markers in the provided timeline data by the specified time.
+ *
+ * @param array markers
+ * The markers array received from the backend.
+ * @param number timeOffset
+ * The amount of time to offset by (in milliseconds).
+ */
+function offsetMarkerTimes(markers, timeOffset) {
+ for (let marker of markers) {
+ marker.start -= timeOffset;
+ marker.end -= timeOffset;
+ }
+}
+
+/**
+ * Offsets and scales all the timestamps in the provided array by the
+ * specified time and scale factor.
+ *
+ * @param array array
+ * A list of timestamps received from the backend.
+ * @param number timeOffset
+ * The amount of time to offset by (in milliseconds).
+ * @param number timeScale
+ * The factor to scale by, after offsetting.
+ */
+function offsetAndScaleTimestamps(timestamps, timeOffset, timeScale) {
+ for (let i = 0, len = timestamps.length; i < len; i++) {
+ timestamps[i] -= timeOffset;
+ if (timeScale) {
+ timestamps[i] /= timeScale;
+ }
+ }
+}
+
+/**
+ * Push all elements of src array into dest array. Marker data will come in small chunks
+ * and add up over time, whereas allocation arrays can be > 500000 elements (and
+ * Function.prototype.apply throws if applying more than 500000 elements, which
+ * is what spawned this separate function), so iterate one element at a time.
+ * @see bug 1166823
+ * @see http://jsperf.com/concat-large-arrays
+ * @see http://jsperf.com/concat-large-arrays/2
+ *
+ * @param {Array} dest
+ * @param {Array} src
+ */
+function pushAll(dest, src) {
+ let length = src.length;
+ for (let i = 0; i < length; i++) {
+ dest.push(src[i]);
+ }
+}
+
+/**
+ * Cache used in `RecordingUtils.getProfileThreadFromAllocations`.
+ */
+var gProfileThreadFromAllocationCache = new WeakMap();
+
+/**
+ * Converts allocation data from the memory actor to something that follows
+ * the same structure as the samples data received from the profiler.
+ *
+ * @see MemoryActor.prototype.getAllocations for more information.
+ *
+ * @param object allocations
+ * A list of { sites, timestamps, frames, sizes } arrays.
+ * @return object
+ * The "profile" describing the allocations log.
+ */
+function getProfileThreadFromAllocations(allocations) {
+ let cached = gProfileThreadFromAllocationCache.get(allocations);
+ if (cached) {
+ return cached;
+ }
+
+ let { sites, timestamps, frames, sizes } = allocations;
+ let uniqueStrings = new UniqueStrings();
+
+ // Convert allocation frames to the the stack and frame tables expected by
+ // the profiler format.
+ //
+ // Since the allocations log is already presented as a tree, we would be
+ // wasting time if we jumped through the same hoops as deflateProfile below
+ // and instead use the existing structure of the allocations log to build up
+ // the profile JSON.
+ //
+ // The allocations.frames array corresponds roughly to the profile stack
+ // table: a trie of all stacks. We could work harder to further deduplicate
+ // each individual frame as the profiler does, but it is not necessary for
+ // correctness.
+ let stackTable = new Array(frames.length);
+ let frameTable = new Array(frames.length);
+
+ // Array used to concat the location.
+ let locationConcatArray = new Array(5);
+
+ for (let i = 0; i < frames.length; i++) {
+ let frame = frames[i];
+ if (!frame) {
+ stackTable[i] = frameTable[i] = null;
+ continue;
+ }
+
+ let prefix = frame.parent;
+
+ // Schema:
+ // [prefix, frame]
+ stackTable[i] = [frames[prefix] ? prefix : null, i];
+
+ // Schema:
+ // [location]
+ //
+ // The only field a frame will have in an allocations profile is location.
+ //
+ // If frame.functionDisplayName is present, the format is
+ // "functionDisplayName (source:line:column)"
+ // Otherwise, it is
+ // "source:line:column"
+ //
+ // A static array is used to join to save memory on intermediate strings.
+ locationConcatArray[0] = frame.source;
+ locationConcatArray[1] = ":";
+ locationConcatArray[2] = String(frame.line);
+ locationConcatArray[3] = ":";
+ locationConcatArray[4] = String(frame.column);
+ locationConcatArray[5] = "";
+
+ let location = locationConcatArray.join("");
+ let funcName = frame.functionDisplayName;
+
+ if (funcName) {
+ locationConcatArray[0] = funcName;
+ locationConcatArray[1] = " (";
+ locationConcatArray[2] = location;
+ locationConcatArray[3] = ")";
+ locationConcatArray[4] = "";
+ locationConcatArray[5] = "";
+ location = locationConcatArray.join("");
+ }
+
+ frameTable[i] = [uniqueStrings.getOrAddStringIndex(location)];
+ }
+
+ let samples = new Array(sites.length);
+ let writePos = 0;
+ for (let i = 0; i < sites.length; i++) {
+ // Schema:
+ // [stack, time, size]
+ //
+ // Originally, sites[i] indexes into the frames array. Note that in the
+ // loop above, stackTable[sites[i]] and frames[sites[i]] index the same
+ // information.
+ let stackIndex = sites[i];
+ if (frames[stackIndex]) {
+ samples[writePos++] = [stackIndex, timestamps[i], sizes[i]];
+ }
+ }
+ samples.length = writePos;
+
+ let thread = {
+ name: "allocations",
+ samples: allocationsWithSchema(samples),
+ stackTable: stackTableWithSchema(stackTable),
+ frameTable: frameTableWithSchema(frameTable),
+ stringTable: uniqueStrings.stringTable
+ };
+
+ gProfileThreadFromAllocationCache.set(allocations, thread);
+ return thread;
+}
+
+function allocationsWithSchema(data) {
+ let slot = 0;
+ return {
+ schema: {
+ stack: slot++,
+ time: slot++,
+ size: slot++,
+ },
+ data: data
+ };
+}
+
+/**
+ * Deduplicates a profile by deduplicating stacks, frames, and strings.
+ *
+ * This is used to adapt version 2 profiles from the backend to version 3, for
+ * use with older Geckos (like B2G).
+ *
+ * Note that the schemas used by this must be kept in sync with schemas used
+ * by the C++ UniqueStacks class in tools/profiler/ProfileEntry.cpp.
+ *
+ * @param object profile
+ * A profile with version 2.
+ */
+function deflateProfile(profile) {
+ profile.threads = profile.threads.map((thread) => {
+ let uniqueStacks = new UniqueStacks();
+ return deflateThread(thread, uniqueStacks);
+ });
+
+ profile.meta.version = 3;
+ return profile;
+}
+
+/**
+ * Given an array of frame objects, deduplicates each frame as well as all
+ * prefixes in the stack. Returns the index of the deduplicated stack.
+ *
+ * @param object frames
+ * Array of frame objects.
+ * @param UniqueStacks uniqueStacks
+ * @return number index
+ */
+function deflateStack(frames, uniqueStacks) {
+ // Deduplicate every prefix in the stack by keeping track of the current
+ // prefix hash.
+ let prefixIndex = null;
+ for (let i = 0; i < frames.length; i++) {
+ let frameIndex = uniqueStacks.getOrAddFrameIndex(frames[i]);
+ prefixIndex = uniqueStacks.getOrAddStackIndex(prefixIndex, frameIndex);
+ }
+ return prefixIndex;
+}
+
+/**
+ * Given an array of sample objects, deduplicate each sample's stack and
+ * convert the samples to a table with a schema. Returns the deflated samples.
+ *
+ * @param object samples
+ * Array of samples
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateSamples(samples, uniqueStacks) {
+ // Schema:
+ // [stack, time, responsiveness, rss, uss, frameNumber, power]
+
+ let deflatedSamples = new Array(samples.length);
+ for (let i = 0; i < samples.length; i++) {
+ let sample = samples[i];
+ deflatedSamples[i] = [
+ deflateStack(sample.frames, uniqueStacks),
+ sample.time,
+ sample.responsiveness,
+ sample.rss,
+ sample.uss,
+ sample.frameNumber,
+ sample.power
+ ];
+ }
+
+ return samplesWithSchema(deflatedSamples);
+}
+
+/**
+ * Given an array of marker objects, convert the markers to a table with a
+ * schema. Returns the deflated markers.
+ *
+ * If a marker contains a backtrace as its payload, the backtrace stack is
+ * deduplicated in the context of the profile it's in.
+ *
+ * @param object markers
+ * Array of markers
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateMarkers(markers, uniqueStacks) {
+ // Schema:
+ // [name, time, data]
+
+ let deflatedMarkers = new Array(markers.length);
+ for (let i = 0; i < markers.length; i++) {
+ let marker = markers[i];
+ if (marker.data && marker.data.type === "tracing" && marker.data.stack) {
+ marker.data.stack = deflateThread(marker.data.stack, uniqueStacks);
+ }
+
+ deflatedMarkers[i] = [
+ uniqueStacks.getOrAddStringIndex(marker.name),
+ marker.time,
+ marker.data
+ ];
+ }
+
+ let slot = 0;
+ return {
+ schema: {
+ name: slot++,
+ time: slot++,
+ data: slot++
+ },
+ data: deflatedMarkers
+ };
+}
+
+/**
+ * Deflate a thread.
+ *
+ * @param object thread
+ * The profile thread.
+ * @param UniqueStacks uniqueStacks
+ * @return object
+ */
+function deflateThread(thread, uniqueStacks) {
+ // Some extra threads in a profile come stringified as a full profile (so
+ // it has nested threads itself) so the top level "thread" does not have markers
+ // or samples. We don't use this anyway so just make this safe to deflate.
+ // can be a string rather than an object on import. Bug 1173695
+ if (typeof thread === "string") {
+ thread = JSON.parse(thread);
+ }
+ if (!thread.samples) {
+ thread.samples = [];
+ }
+ if (!thread.markers) {
+ thread.markers = [];
+ }
+
+ return {
+ name: thread.name,
+ tid: thread.tid,
+ samples: deflateSamples(thread.samples, uniqueStacks),
+ markers: deflateMarkers(thread.markers, uniqueStacks),
+ stackTable: uniqueStacks.getStackTableWithSchema(),
+ frameTable: uniqueStacks.getFrameTableWithSchema(),
+ stringTable: uniqueStacks.getStringTable()
+ };
+}
+
+function stackTableWithSchema(data) {
+ let slot = 0;
+ return {
+ schema: {
+ prefix: slot++,
+ frame: slot++
+ },
+ data: data
+ };
+}
+
+function frameTableWithSchema(data) {
+ let slot = 0;
+ return {
+ schema: {
+ location: slot++,
+ implementation: slot++,
+ optimizations: slot++,
+ line: slot++,
+ category: slot++
+ },
+ data: data
+ };
+}
+
+function samplesWithSchema(data) {
+ let slot = 0;
+ return {
+ schema: {
+ stack: slot++,
+ time: slot++,
+ responsiveness: slot++,
+ rss: slot++,
+ uss: slot++,
+ frameNumber: slot++,
+ power: slot++
+ },
+ data: data
+ };
+}
+
+/**
+ * A helper class to deduplicate strings.
+ */
+function UniqueStrings() {
+ this.stringTable = [];
+ this._stringHash = Object.create(null);
+}
+
+UniqueStrings.prototype.getOrAddStringIndex = function (s) {
+ if (!s) {
+ return null;
+ }
+
+ let stringHash = this._stringHash;
+ let stringTable = this.stringTable;
+ let index = stringHash[s];
+ if (index !== undefined) {
+ return index;
+ }
+
+ index = stringTable.length;
+ stringHash[s] = index;
+ stringTable.push(s);
+ return index;
+};
+
+/**
+ * A helper class to deduplicate old-version profiles.
+ *
+ * The main functionality provided is deduplicating frames and stacks.
+ *
+ * For example, given 2 stacks
+ * [A, B, C]
+ * and
+ * [A, B, D]
+ *
+ * There are 4 unique frames: A, B, C, and D.
+ * There are 4 unique prefixes: [A], [A, B], [A, B, C], [A, B, D]
+ *
+ * For the example, the output of using UniqueStacks is:
+ *
+ * Frame table:
+ * [A, B, C, D]
+ *
+ * That is, A has id 0, B has id 1, etc.
+ *
+ * Since stack prefixes are themselves deduplicated (shared), stacks are
+ * represented as a tree, or more concretely, a pair of ids, the prefix and
+ * the leaf.
+ *
+ * Stack table:
+ * [
+ * [null, 0],
+ * [0, 1],
+ * [1, 2],
+ * [1, 3]
+ * ]
+ *
+ * That is, [A] has id 0 and value [null, 0]. This means it has no prefix, and
+ * has the leaf frame 0, which resolves to A in the frame table.
+ *
+ * [A, B] has id 1 and value [0, 1]. This means it has prefix 0, which is [A],
+ * and leaf 1, thus [A, B].
+ *
+ * [A, B, C] has id 2 and value [1, 2]. This means it has prefix 1, which in
+ * turn is [A, B], and leaf 2, thus [A, B, C].
+ *
+ * [A, B, D] has id 3 and value [1, 3]. Note how it shares the prefix 1 with
+ * [A, B, C].
+ */
+function UniqueStacks() {
+ this._frameTable = [];
+ this._stackTable = [];
+ this._frameHash = Object.create(null);
+ this._stackHash = Object.create(null);
+ this._uniqueStrings = new UniqueStrings();
+}
+
+UniqueStacks.prototype.getStackTableWithSchema = function () {
+ return stackTableWithSchema(this._stackTable);
+};
+
+UniqueStacks.prototype.getFrameTableWithSchema = function () {
+ return frameTableWithSchema(this._frameTable);
+};
+
+UniqueStacks.prototype.getStringTable = function () {
+ return this._uniqueStrings.stringTable;
+};
+
+UniqueStacks.prototype.getOrAddFrameIndex = function (frame) {
+ // Schema:
+ // [location, implementation, optimizations, line, category]
+
+ let frameHash = this._frameHash;
+ let frameTable = this._frameTable;
+
+ let locationIndex = this.getOrAddStringIndex(frame.location);
+ let implementationIndex = this.getOrAddStringIndex(frame.implementation);
+
+ // Super dumb.
+ let hash = `${locationIndex} ${implementationIndex || ""} ${frame.line || ""} ${frame.category || ""}`;
+
+ let index = frameHash[hash];
+ if (index !== undefined) {
+ return index;
+ }
+
+ index = frameTable.length;
+ frameHash[hash] = index;
+ frameTable.push([
+ this.getOrAddStringIndex(frame.location),
+ this.getOrAddStringIndex(frame.implementation),
+ // Don't bother with JIT optimization info for deflating old profile data
+ // format to the new format.
+ null,
+ frame.line,
+ frame.category
+ ]);
+ return index;
+};
+
+UniqueStacks.prototype.getOrAddStackIndex = function (prefixIndex, frameIndex) {
+ // Schema:
+ // [prefix, frame]
+
+ let stackHash = this._stackHash;
+ let stackTable = this._stackTable;
+
+ // Also super dumb.
+ let hash = prefixIndex + " " + frameIndex;
+
+ let index = stackHash[hash];
+ if (index !== undefined) {
+ return index;
+ }
+
+ index = stackTable.length;
+ stackHash[hash] = index;
+ stackTable.push([prefixIndex, frameIndex]);
+ return index;
+};
+
+UniqueStacks.prototype.getOrAddStringIndex = function (s) {
+ return this._uniqueStrings.getOrAddStringIndex(s);
+};
+
+exports.pushAll = pushAll;
+exports.mapRecordingOptions = mapRecordingOptions;
+exports.normalizePerformanceFeatures = normalizePerformanceFeatures;
+exports.filterSamples = filterSamples;
+exports.offsetSampleTimes = offsetSampleTimes;
+exports.offsetMarkerTimes = offsetMarkerTimes;
+exports.offsetAndScaleTimestamps = offsetAndScaleTimestamps;
+exports.getProfileThreadFromAllocations = getProfileThreadFromAllocations;
+exports.deflateProfile = deflateProfile;
+exports.deflateThread = deflateThread;
+exports.UniqueStrings = UniqueStrings;
+exports.UniqueStacks = UniqueStacks;
diff --git a/devtools/shared/performance/test/head.js b/devtools/shared/performance/test/head.js
new file mode 100644
index 000000000..9e7748055
--- /dev/null
+++ b/devtools/shared/performance/test/head.js
@@ -0,0 +1,7 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
+
+var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
diff --git a/devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js b/devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js
new file mode 100644
index 000000000..6429eff2d
--- /dev/null
+++ b/devtools/shared/performance/test/test_perf-utils-allocations-to-samples.js
@@ -0,0 +1,93 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests if allocations data received from the performance actor is properly
+ * converted to something that follows the same structure as the samples data
+ * received from the profiler.
+ */
+
+function run_test() {
+ run_next_test();
+}
+
+add_task(function () {
+ const { getProfileThreadFromAllocations } = require("devtools/shared/performance/recording-utils");
+ let output = getProfileThreadFromAllocations(TEST_DATA);
+ equal(output.toSource(), EXPECTED_OUTPUT.toSource(), "The output is correct.");
+});
+
+var TEST_DATA = {
+ sites: [0, 0, 1, 2, 3],
+ timestamps: [50, 100, 150, 200, 250],
+ sizes: [0, 0, 100, 200, 300],
+ frames: [
+ null, {
+ source: "A",
+ line: 1,
+ column: 2,
+ functionDisplayName: "x",
+ parent: 0
+ }, {
+ source: "B",
+ line: 3,
+ column: 4,
+ functionDisplayName: "y",
+ parent: 1
+ }, {
+ source: "C",
+ line: 5,
+ column: 6,
+ functionDisplayName: null,
+ parent: 2
+ }
+ ]
+};
+
+var EXPECTED_OUTPUT = {
+ name: "allocations",
+ samples: {
+ "schema": {
+ "stack": 0,
+ "time": 1,
+ "size": 2,
+ },
+ data: [
+ [ 1, 150, 100 ],
+ [ 2, 200, 200 ],
+ [ 3, 250, 300 ]
+ ]
+ },
+ stackTable: {
+ "schema": {
+ "prefix": 0,
+ "frame": 1
+ },
+ "data": [
+ null,
+ [ null, 1 ], // x (A:1:2)
+ [ 1, 2 ], // x (A:1:2) > y (B:3:4)
+ [ 2, 3 ] // x (A:1:2) > y (B:3:4) > C:5:6
+ ]
+ },
+ frameTable: {
+ "schema": {
+ "location": 0,
+ "implementation": 1,
+ "optimizations": 2,
+ "line": 3,
+ "category": 4
+ },
+ data: [
+ null,
+ [ 0 ],
+ [ 1 ],
+ [ 2 ]
+ ]
+ },
+ "stringTable": [
+ "x (A:1:2)",
+ "y (B:3:4)",
+ "C:5:6"
+ ],
+};
diff --git a/devtools/shared/performance/test/xpcshell.ini b/devtools/shared/performance/test/xpcshell.ini
new file mode 100644
index 000000000..737f6d67b
--- /dev/null
+++ b/devtools/shared/performance/test/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools
+head = head.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test_perf-utils-allocations-to-samples.js]
diff --git a/devtools/shared/platform/README.md b/devtools/shared/platform/README.md
new file mode 100644
index 000000000..84accd495
--- /dev/null
+++ b/devtools/shared/platform/README.md
@@ -0,0 +1,13 @@
+This directory is treated specially by the loaders.
+
+In particular, when running in chrome, a resource like
+"devtools/shared/platform/mumble" will be found in the chrome
+subdirectory; and when running in content, it will be found in the
+content subdirectory.
+
+Outside of tests, it's not ok to require a specific version of a file;
+and there is an eslint test to check for that. That is,
+require("devtools/shared/platform/client/mumble") is an error.
+
+When adding a new file, you must add two copies, one to chrome and one
+to content. Otherwise, one case or the other will fail to work.
diff --git a/devtools/shared/platform/chrome/clipboard.js b/devtools/shared/platform/chrome/clipboard.js
new file mode 100644
index 000000000..fd98dfbc5
--- /dev/null
+++ b/devtools/shared/platform/chrome/clipboard.js
@@ -0,0 +1,28 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Helpers for clipboard handling.
+
+"use strict";
+
+const {Cc, Ci} = require("chrome");
+const clipboardHelper = Cc["@mozilla.org/widget/clipboardhelper;1"]
+ .getService(Ci.nsIClipboardHelper);
+var clipboard = require("sdk/clipboard");
+
+function copyString(string) {
+ clipboardHelper.copyString(string);
+}
+
+function getCurrentFlavors() {
+ return clipboard.currentFlavors;
+}
+
+function getData() {
+ return clipboard.get();
+}
+
+exports.copyString = copyString;
+exports.getCurrentFlavors = getCurrentFlavors;
+exports.getData = getData;
diff --git a/devtools/shared/platform/chrome/moz.build b/devtools/shared/platform/chrome/moz.build
new file mode 100644
index 000000000..bb3bdacbb
--- /dev/null
+++ b/devtools/shared/platform/chrome/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'clipboard.js',
+ 'stack.js',
+)
diff --git a/devtools/shared/platform/chrome/stack.js b/devtools/shared/platform/chrome/stack.js
new file mode 100644
index 000000000..abbe54120
--- /dev/null
+++ b/devtools/shared/platform/chrome/stack.js
@@ -0,0 +1,75 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A few wrappers for stack-manipulation. This version of the module
+// is used in chrome code.
+
+"use strict";
+
+(function (factory) {
+ // This file might be require()d, but might also be loaded via
+ // Cu.import. Account for the differences here.
+ if (this.module && module.id.indexOf("stack") >= 0) {
+ // require.
+ const {components, Cu} = require("chrome");
+ factory.call(this, components, Cu, exports);
+ } else {
+ // Cu.import.
+ this.isWorker = false;
+ factory.call(this, Components, Components.utils, this);
+ this.EXPORTED_SYMBOLS = ["callFunctionWithAsyncStack", "describeNthCaller",
+ "getStack"];
+ }
+}).call(this, function (components, Cu, exports) {
+ /**
+ * Return a description of the Nth caller, suitable for logging.
+ *
+ * @param {Number} n the caller to describe
+ * @return {String} a description of the nth caller.
+ */
+ function describeNthCaller(n) {
+ if (isWorker) {
+ return "";
+ }
+
+ let caller = components.stack;
+ // Do one extra iteration to skip this function.
+ while (n >= 0) {
+ --n;
+ caller = caller.caller;
+ }
+
+ let func = caller.name;
+ let file = caller.filename;
+ if (file.includes(" -> ")) {
+ file = caller.filename.split(/ -> /)[1];
+ }
+ let path = file + ":" + caller.lineNumber;
+
+ return func + "() -> " + path;
+ }
+
+ /**
+ * Return a stack object that can be serialized and, when
+ * deserialized, passed to callFunctionWithAsyncStack.
+ */
+ function getStack() {
+ return components.stack.caller;
+ }
+
+ /**
+ * Like Cu.callFunctionWithAsyncStack but handles the isWorker case
+ * -- |Cu| isn't defined in workers.
+ */
+ function callFunctionWithAsyncStack(callee, stack, id) {
+ if (isWorker) {
+ return callee();
+ }
+ return Cu.callFunctionWithAsyncStack(callee, stack, id);
+ }
+
+ exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
+ exports.describeNthCaller = describeNthCaller;
+ exports.getStack = getStack;
+});
diff --git a/devtools/shared/platform/content/.eslintrc.js b/devtools/shared/platform/content/.eslintrc.js
new file mode 100644
index 000000000..515fe0261
--- /dev/null
+++ b/devtools/shared/platform/content/.eslintrc.js
@@ -0,0 +1,12 @@
+"use strict";
+
+module.exports = {
+ // Extend from the devtools eslintrc.
+ "extends": "../../../.eslintrc.js",
+
+ "rules": {
+ /* eslint-disable max-len */
+ // All code in this directory must be content-clean.
+ "mozilla/reject-some-requires": ["error", "^(chrome|chrome:.*|resource:.*|devtools/server/.*|.*\\.jsm|devtools/shared/platform/(chome|content)/.*)$"],
+ },
+};
diff --git a/devtools/shared/platform/content/clipboard.js b/devtools/shared/platform/content/clipboard.js
new file mode 100644
index 000000000..b43b996c2
--- /dev/null
+++ b/devtools/shared/platform/content/clipboard.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Helpers for clipboard handling.
+
+/* globals document */
+
+"use strict";
+
+function copyString(string) {
+ let doCopy = function (e) {
+ e.clipboardData.setData("text/plain", string);
+ e.preventDefault();
+ };
+
+ document.addEventListener("copy", doCopy);
+ document.execCommand("copy", false, null);
+ document.removeEventListener("copy", doCopy);
+}
+
+function getCurrentFlavors() {
+ // See bug 1295692.
+ return [];
+}
+
+function getData() {
+ // See bug 1295692.
+ return null;
+}
+
+exports.copyString = copyString;
+exports.getCurrentFlavors = getCurrentFlavors;
+exports.getData = getData;
diff --git a/devtools/shared/platform/content/moz.build b/devtools/shared/platform/content/moz.build
new file mode 100644
index 000000000..b62205eb8
--- /dev/null
+++ b/devtools/shared/platform/content/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'clipboard.js',
+ 'stack.js',
+)
+
+XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
+
+MOCHITEST_MANIFESTS += [
+ 'test/mochitest.ini',
+]
diff --git a/devtools/shared/platform/content/stack.js b/devtools/shared/platform/content/stack.js
new file mode 100644
index 000000000..87c7c4111
--- /dev/null
+++ b/devtools/shared/platform/content/stack.js
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// A few wrappers for stack-manipulation. This version of the module
+// is used in content code. Note that this particular copy of the
+// file can only be loaded via require(), because Cu.import doesn't
+// exist in the content case. So, we don't need the code to handle
+// both require and import here.
+
+"use strict";
+
+/**
+ * Looks like Cu.callFunctionWithAsyncStack, but just calls the callee.
+ */
+function callFunctionWithAsyncStack(callee, stack, id) {
+ return callee();
+}
+
+/**
+ * Return a description of the Nth caller, suitable for logging.
+ *
+ * @param {Number} n the caller to describe
+ * @return {String} a description of the nth caller.
+ */
+function describeNthCaller(n) {
+ if (isWorker) {
+ return "";
+ }
+
+ let stack = new Error().stack.split("\n");
+ // Add one here to skip this function.
+ return stack[n + 1];
+}
+
+/**
+ * Return a stack object that can be serialized and, when
+ * deserialized, passed to callFunctionWithAsyncStack.
+ */
+function getStack() {
+ // There's no reason for this to do anything fancy, since it's only
+ // used to pass back into callFunctionWithAsyncStack, which we can't
+ // implement.
+ return null;
+}
+
+exports.callFunctionWithAsyncStack = callFunctionWithAsyncStack;
+exports.describeNthCaller = describeNthCaller;
+exports.getStack = getStack;
diff --git a/devtools/shared/platform/content/test/.eslintrc.js b/devtools/shared/platform/content/test/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/platform/content/test/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/platform/content/test/mochitest.ini b/devtools/shared/platform/content/test/mochitest.ini
new file mode 100644
index 000000000..f62cada6b
--- /dev/null
+++ b/devtools/shared/platform/content/test/mochitest.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+support-files =
+
+[test_clipboard.html]
+subsuite = clipboard
diff --git a/devtools/shared/platform/content/test/test_clipboard.html b/devtools/shared/platform/content/test/test_clipboard.html
new file mode 100644
index 000000000..ccf5e6ccf
--- /dev/null
+++ b/devtools/shared/platform/content/test/test_clipboard.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1290230
+-->
+<head>
+ <title>Test for Bug 1290230 - clipboard helpers</title>
+ <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+
+<script type="application/javascript;version=1.8">
+"use strict";
+var exports = {}
+</script>
+
+ <script type="application/javascript;version=1.8"
+ src="resource://devtools/shared/platform/content/clipboard.js"></script>
+
+</head>
+<body onload="do_tests()">
+<script type="application/javascript;version=1.8">
+"use strict";
+
+const RESULT = "lark bunting";
+
+function doCopy(e) {
+ console.log(e.isTrusted);
+ copyString(RESULT);
+}
+
+function do_tests() {
+ let elt = document.querySelector("#key");
+ elt.addEventListener("keydown", doCopy);
+
+ // Set the clipboard to something other than what we expect.
+ SpecialPowers.clipboardCopyString("snowy owl");
+
+ elt.focus();
+ synthesizeKey("x", {});
+
+ is(SpecialPowers.getClipboardData("text/unicode"), RESULT, "clipboard copying worked");
+
+ SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+
+</script>
+<div id="key" tabindex="-1">Type Here</div>
+</body>
diff --git a/devtools/shared/platform/content/test/test_stack.js b/devtools/shared/platform/content/test/test_stack.js
new file mode 100644
index 000000000..4dbb66541
--- /dev/null
+++ b/devtools/shared/platform/content/test/test_stack.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// There isn't really very much about the content stack.js that we can
+// test, but we'll do what we can.
+
+"use strict";
+
+var Cu = Components.utils;
+const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+// Make sure to explicitly require the content version of this module.
+// We have to use the ".." trick due to the way the loader remaps
+// devtools/shared/platform.
+const {
+ callFunctionWithAsyncStack,
+ getStack,
+ describeNthCaller
+} = require("devtools/shared/platform/../content/stack");
+
+function f3() {
+ return describeNthCaller(2);
+}
+
+function f2() {
+ return f3();
+}
+
+function f1() {
+ return f2();
+}
+
+function run_test() {
+ let value = 7;
+
+ const changeValue = () => {
+ value = 9;
+ };
+
+ callFunctionWithAsyncStack(changeValue, getStack(), "test_stack");
+ equal(value, 9, "callFunctionWithAsyncStack worked");
+
+ let stack = getStack();
+ equal(JSON.parse(JSON.stringify(stack)), stack, "stack is serializable");
+
+ let desc = f1();
+ ok(desc.includes("f1"), "stack description includes f1");
+}
diff --git a/devtools/shared/platform/content/test/xpcshell.ini b/devtools/shared/platform/content/test/xpcshell.ini
new file mode 100644
index 000000000..fa475165a
--- /dev/null
+++ b/devtools/shared/platform/content/test/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+
+[test_stack.js]
diff --git a/devtools/shared/platform/moz.build b/devtools/shared/platform/moz.build
new file mode 100644
index 000000000..84f56dce1
--- /dev/null
+++ b/devtools/shared/platform/moz.build
@@ -0,0 +1,10 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'chrome',
+ 'content',
+]
diff --git a/devtools/shared/plural-form.js b/devtools/shared/plural-form.js
new file mode 100644
index 000000000..4e54ae963
--- /dev/null
+++ b/devtools/shared/plural-form.js
@@ -0,0 +1,196 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * The code below is mostly is a slight modification of intl/locale/PluralForm.jsm that
+ * removes dependencies on chrome privileged APIs. To make maintenance easier, this file
+ * is kept as close as possible to the original in terms of implementation.
+ * The modified methods here are
+ * - makeGetter (remove code adding the caller name to the log)
+ * - get ruleNum() (rely on LocalizationHelper instead of String.services)
+ * - log() (rely on console.log)
+ *
+ * Disable eslint warnings to preserve original code style.
+ */
+
+/* eslint-disable */
+
+/**
+ * This module provides the PluralForm object which contains a method to figure
+ * out which plural form of a word to use for a given number based on the
+ * current localization. There is also a makeGetter method that creates a get
+ * function for the desired plural rule. This is useful for extensions that
+ * specify their own plural rule instead of relying on the browser default.
+ * (I.e., the extension hasn't been localized to the browser's locale.)
+ *
+ * See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+ *
+ * List of methods:
+ *
+ * string pluralForm
+ * get(int aNum, string aWords)
+ *
+ * int numForms
+ * numForms()
+ *
+ * [string pluralForm get(int aNum, string aWords), int numForms numForms()]
+ * makeGetter(int aRuleNum)
+ * Note: Basically, makeGetter returns 2 functions that do "get" and "numForm"
+ */
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("toolkit/locales/intl.properties");
+
+// These are the available plural functions that give the appropriate index
+// based on the plural rule number specified. The first element is the number
+// of plural forms and the second is the function to figure out the index.
+var gFunctions = [
+ // 0: Chinese
+ [1, (n) => 0],
+ // 1: English
+ [2, (n) => n!=1?1:0],
+ // 2: French
+ [2, (n) => n>1?1:0],
+ // 3: Latvian
+ [3, (n) => n%10==1&&n%100!=11?1:n!=0?2:0],
+ // 4: Scottish Gaelic
+ [4, (n) => n==1||n==11?0:n==2||n==12?1:n>0&&n<20?2:3],
+ // 5: Romanian
+ [3, (n) => n==1?0:n==0||n%100>0&&n%100<20?1:2],
+ // 6: Lithuanian
+ [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&(n%100<10||n%100>=20)?2:1],
+ // 7: Russian
+ [3, (n) => n%10==1&&n%100!=11?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
+ // 8: Slovak
+ [3, (n) => n==1?0:n>=2&&n<=4?1:2],
+ // 9: Polish
+ [3, (n) => n==1?0:n%10>=2&&n%10<=4&&(n%100<10||n%100>=20)?1:2],
+ // 10: Slovenian
+ [4, (n) => n%100==1?0:n%100==2?1:n%100==3||n%100==4?2:3],
+ // 11: Irish Gaeilge
+ [5, (n) => n==1?0:n==2?1:n>=3&&n<=6?2:n>=7&&n<=10?3:4],
+ // 12: Arabic
+ [6, (n) => n==0?5:n==1?0:n==2?1:n%100>=3&&n%100<=10?2:n%100>=11&&n%100<=99?3:4],
+ // 13: Maltese
+ [4, (n) => n==1?0:n==0||n%100>0&&n%100<=10?1:n%100>10&&n%100<20?2:3],
+ // 14: Macedonian
+ [3, (n) => n%10==1?0:n%10==2?1:2],
+ // 15: Icelandic
+ [2, (n) => n%10==1&&n%100!=11?0:1],
+ // 16: Breton
+ [5, (n) => n%10==1&&n%100!=11&&n%100!=71&&n%100!=91?0:n%10==2&&n%100!=12&&n%100!=72&&n%100!=92?1:(n%10==3||n%10==4||n%10==9)&&n%100!=13&&n%100!=14&&n%100!=19&&n%100!=73&&n%100!=74&&n%100!=79&&n%100!=93&&n%100!=94&&n%100!=99?2:n%1000000==0&&n!=0?3:4],
+];
+
+this.PluralForm = {
+ /**
+ * Get the correct plural form of a word based on the number
+ *
+ * @param aNum
+ * The number to decide which plural form to use
+ * @param aWords
+ * A semi-colon (;) separated string of words to pick the plural form
+ * @return The appropriate plural form of the word
+ */
+ get get()
+ {
+ // This method will lazily load to avoid perf when it is first needed and
+ // creates getPluralForm function. The function it creates is based on the
+ // value of pluralRule specified in the intl stringbundle.
+ // See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+
+ // Delete the getters to be overwritten
+ delete PluralForm.numForms;
+ delete PluralForm.get;
+
+ // Make the plural form get function and set it as the default get
+ [PluralForm.get, PluralForm.numForms] = PluralForm.makeGetter(PluralForm.ruleNum);
+ return PluralForm.get;
+ },
+
+ /**
+ * Create a pair of plural form functions for the given plural rule number.
+ *
+ * @param aRuleNum
+ * The plural rule number to create functions
+ * @return A pair: [function that gets the right plural form,
+ * function that returns the number of plural forms]
+ */
+ makeGetter: function(aRuleNum)
+ {
+ // Default to "all plural" if the value is out of bounds or invalid
+ if (aRuleNum < 0 || aRuleNum >= gFunctions.length || isNaN(aRuleNum)) {
+ log(["Invalid rule number: ", aRuleNum, " -- defaulting to 0"]);
+ aRuleNum = 0;
+ }
+
+ // Get the desired pluralRule function
+ let [numForms, pluralFunc] = gFunctions[aRuleNum];
+
+ // Return functions that give 1) the number of forms and 2) gets the right
+ // plural form
+ return [function(aNum, aWords) {
+ // Figure out which index to use for the semi-colon separated words
+ let index = pluralFunc(aNum ? Number(aNum) : 0);
+ let words = aWords ? aWords.split(/;/) : [""];
+
+ // Explicitly check bounds to avoid strict warnings
+ let ret = index < words.length ? words[index] : undefined;
+
+ // Check for array out of bounds or empty strings
+ if ((ret == undefined) || (ret == "")) {
+ // Display a message in the error console
+ log(["Index #", index, " of '", aWords, "' for value ", aNum,
+ " is invalid -- plural rule #", aRuleNum, ";"]);
+
+ // Default to the first entry (which might be empty, but not undefined)
+ ret = words[0];
+ }
+
+ return ret;
+ }, () => numForms];
+ },
+
+ /**
+ * Get the number of forms for the current plural rule
+ *
+ * @return The number of forms
+ */
+ get numForms()
+ {
+ // We lazily load numForms, so trigger the init logic with get()
+ PluralForm.get();
+ return PluralForm.numForms;
+ },
+
+ /**
+ * Get the plural rule number from the intl stringbundle
+ *
+ * @return The plural rule number
+ */
+ get ruleNum()
+ {
+ try {
+ return parseInt(L10N.getStr("pluralRule"), 10);
+ } catch (e) {
+ // Fallback to English if the pluralRule property is not available.
+ return 1;
+ }
+ }
+};
+
+/**
+ * Private helper function to log errors to the error console and command line
+ *
+ * @param aMsg
+ * Error message to log or an array of strings to concat
+ */
+function log(aMsg)
+{
+ let msg = "plural-form.js: " + (aMsg.join ? aMsg.join("") : aMsg);
+ console.log(msg + "\n");
+}
+
+exports.PluralForm = this.PluralForm;
+
+/* eslint-ensable */
diff --git a/devtools/shared/pretty-fast/UPGRADING.md b/devtools/shared/pretty-fast/UPGRADING.md
new file mode 100644
index 000000000..9758096eb
--- /dev/null
+++ b/devtools/shared/pretty-fast/UPGRADING.md
@@ -0,0 +1,11 @@
+# UPGRADING
+
+1. `git clone https://github.com/mozilla/pretty-fast.git`
+
+2. Copy `pretty-fast/pretty-fast.js` to `devtools/shared/pretty-fast/pretty-fast.js`
+
+3. Copy `pretty-fast/test.js` to `devtools/shared/pretty-fast/tests/unit/test.js`
+
+4. If necessary, upgrade acorn (see devtools/shared/acorn/UPGRADING.md)
+
+5. Replace `acorn/dist/` with `acorn/` in pretty-fast.js. \ No newline at end of file
diff --git a/devtools/shared/pretty-fast/moz.build b/devtools/shared/pretty-fast/moz.build
new file mode 100644
index 000000000..3c72c8ff6
--- /dev/null
+++ b/devtools/shared/pretty-fast/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'pretty-fast.js',
+)
diff --git a/devtools/shared/pretty-fast/pretty-fast.js b/devtools/shared/pretty-fast/pretty-fast.js
new file mode 100644
index 000000000..838a7b348
--- /dev/null
+++ b/devtools/shared/pretty-fast/pretty-fast.js
@@ -0,0 +1,873 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
+/*
+ * Copyright 2013 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.md or:
+ * http://opensource.org/licenses/BSD-2-Clause
+ */
+(function (root, factory) {
+ "use strict";
+
+ if (typeof define === "function" && define.amd) {
+ define(factory);
+ } else if (typeof exports === "object") {
+ module.exports = factory();
+ } else {
+ root.prettyFast = factory();
+ }
+}(this, function () {
+ "use strict";
+
+ var acorn = this.acorn || require("acorn/acorn");
+ var sourceMap = this.sourceMap || require("source-map");
+ var SourceNode = sourceMap.SourceNode;
+
+ // If any of these tokens are seen before a "[" token, we know that "[" token
+ // is the start of an array literal, rather than a property access.
+ //
+ // The only exception is "}", which would need to be disambiguated by
+ // parsing. The majority of the time, an open bracket following a closing
+ // curly is going to be an array literal, so we brush the complication under
+ // the rug, and handle the ambiguity by always assuming that it will be an
+ // array literal.
+ var PRE_ARRAY_LITERAL_TOKENS = {
+ "typeof": true,
+ "void": true,
+ "delete": true,
+ "case": true,
+ "do": true,
+ "=": true,
+ "in": true,
+ "{": true,
+ "*": true,
+ "/": true,
+ "%": true,
+ "else": true,
+ ";": true,
+ "++": true,
+ "--": true,
+ "+": true,
+ "-": true,
+ "~": true,
+ "!": true,
+ ":": true,
+ "?": true,
+ ">>": true,
+ ">>>": true,
+ "<<": true,
+ "||": true,
+ "&&": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ ",": true,
+
+ "}": true
+ };
+
+ /**
+ * Determines if we think that the given token starts an array literal.
+ *
+ * @param Object token
+ * The token we want to determine if it is an array literal.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results.
+ *
+ * @returns Boolean
+ * True if we believe it is an array literal, false otherwise.
+ */
+ function isArrayLiteral(token, lastToken) {
+ if (token.type.label != "[") {
+ return false;
+ }
+ if (!lastToken) {
+ return true;
+ }
+ if (lastToken.type.isAssign) {
+ return true;
+ }
+ return !!PRE_ARRAY_LITERAL_TOKENS[
+ lastToken.type.keyword || lastToken.type.label
+ ];
+ }
+
+ // If any of these tokens are followed by a token on a new line, we know that
+ // ASI cannot happen.
+ var PREVENT_ASI_AFTER_TOKENS = {
+ // Binary operators
+ "*": true,
+ "/": true,
+ "%": true,
+ "+": true,
+ "-": true,
+ "<<": true,
+ ">>": true,
+ ">>>": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "in": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "&&": true,
+ "||": true,
+ ",": true,
+ ".": true,
+ "=": true,
+ "*=": true,
+ "/=": true,
+ "%=": true,
+ "+=": true,
+ "-=": true,
+ "<<=": true,
+ ">>=": true,
+ ">>>=": true,
+ "&=": true,
+ "^=": true,
+ "|=": true,
+ // Unary operators
+ "delete": true,
+ "void": true,
+ "typeof": true,
+ "~": true,
+ "!": true,
+ "new": true,
+ // Function calls and grouped expressions
+ "(": true
+ };
+
+ // If any of these tokens are on a line after the token before it, we know
+ // that ASI cannot happen.
+ var PREVENT_ASI_BEFORE_TOKENS = {
+ // Binary operators
+ "*": true,
+ "/": true,
+ "%": true,
+ "<<": true,
+ ">>": true,
+ ">>>": true,
+ "<": true,
+ ">": true,
+ "<=": true,
+ ">=": true,
+ "instanceof": true,
+ "in": true,
+ "==": true,
+ "!=": true,
+ "===": true,
+ "!==": true,
+ "&": true,
+ "^": true,
+ "|": true,
+ "&&": true,
+ "||": true,
+ ",": true,
+ ".": true,
+ "=": true,
+ "*=": true,
+ "/=": true,
+ "%=": true,
+ "+=": true,
+ "-=": true,
+ "<<=": true,
+ ">>=": true,
+ ">>>=": true,
+ "&=": true,
+ "^=": true,
+ "|=": true,
+ // Function calls
+ "(": true
+ };
+
+ /**
+ * Determines if Automatic Semicolon Insertion (ASI) occurs between these
+ * tokens.
+ *
+ * @param Object token
+ * The current token.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results.
+ *
+ * @returns Boolean
+ * True if we believe ASI occurs.
+ */
+ function isASI(token, lastToken) {
+ if (!lastToken) {
+ return false;
+ }
+ if (token.loc.start.line === lastToken.loc.start.line) {
+ return false;
+ }
+ if (PREVENT_ASI_AFTER_TOKENS[
+ lastToken.type.label || lastToken.type.keyword
+ ]) {
+ return false;
+ }
+ if (PREVENT_ASI_BEFORE_TOKENS[token.type.label || token.type.keyword]) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Determine if we have encountered a getter or setter.
+ *
+ * @param Object token
+ * The current token. If this is a getter or setter, it would be the
+ * property name.
+ * @param Object lastToken
+ * The last token we added to the pretty printed results. If this is a
+ * getter or setter, it would be the `get` or `set` keyword
+ * respectively.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * True if this is a getter or setter.
+ */
+ function isGetterOrSetter(token, lastToken, stack) {
+ return stack[stack.length - 1] == "{"
+ && lastToken
+ && lastToken.type.label == "name"
+ && (lastToken.value == "get" || lastToken.value == "set")
+ && token.type.label == "name";
+ }
+
+ /**
+ * Determine if we should add a newline after the given token.
+ *
+ * @param Object token
+ * The token we are looking at.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * True if we should add a newline.
+ */
+ function isLineDelimiter(token, stack) {
+ if (token.isArrayLiteral) {
+ return true;
+ }
+ var ttl = token.type.label;
+ var top = stack[stack.length - 1];
+ return ttl == ";" && top != "("
+ || ttl == "{"
+ || ttl == "," && top != "("
+ || ttl == ":" && (top == "case" || top == "default");
+ }
+
+ /**
+ * Append the necessary whitespace to the result after we have added the given
+ * token.
+ *
+ * @param Object token
+ * The token that was just added to the result.
+ * @param Function write
+ * The function to write to the pretty printed results.
+ * @param Array stack
+ * The stack of open parens/curlies/brackets/etc.
+ *
+ * @returns Boolean
+ * Returns true if we added a newline to result, false in all other
+ * cases.
+ */
+ function appendNewline(token, write, stack) {
+ if (isLineDelimiter(token, stack)) {
+ write("\n", token.loc.start.line, token.loc.start.column);
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines if we need to add a space between the last token we added and
+ * the token we are about to add.
+ *
+ * @param Object token
+ * The token we are about to add to the pretty printed code.
+ * @param Object lastToken
+ * The last token added to the pretty printed code.
+ */
+ function needsSpaceAfter(token, lastToken) {
+ if (lastToken) {
+ if (lastToken.type.isLoop) {
+ return true;
+ }
+ if (lastToken.type.isAssign) {
+ return true;
+ }
+ if (lastToken.type.binop != null) {
+ return true;
+ }
+
+ var ltt = lastToken.type.label;
+ if (ltt == "?") {
+ return true;
+ }
+ if (ltt == ":") {
+ return true;
+ }
+ if (ltt == ",") {
+ return true;
+ }
+ if (ltt == ";") {
+ return true;
+ }
+
+ var ltk = lastToken.type.keyword;
+ if (ltk != null) {
+ if (ltk == "break" || ltk == "continue" || ltk == "return") {
+ return token.type.label != ";";
+ }
+ if (ltk != "debugger"
+ && ltk != "null"
+ && ltk != "true"
+ && ltk != "false"
+ && ltk != "this"
+ && ltk != "default") {
+ return true;
+ }
+ }
+
+ if (ltt == ")" && (token.type.label != ")"
+ && token.type.label != "]"
+ && token.type.label != ";"
+ && token.type.label != ","
+ && token.type.label != ".")) {
+ return true;
+ }
+ }
+
+ if (token.type.isAssign) {
+ return true;
+ }
+ if (token.type.binop != null) {
+ return true;
+ }
+ if (token.type.label == "?") {
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Add the required whitespace before this token, whether that is a single
+ * space, newline, and/or the indent on fresh lines.
+ *
+ * @param Object token
+ * The token we are about to add to the pretty printed code.
+ * @param Object lastToken
+ * The last token we added to the pretty printed code.
+ * @param Boolean addedNewline
+ * Whether we added a newline after adding the last token to the pretty
+ * printed code.
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ * @param Object options
+ * The options object.
+ * @param Number indentLevel
+ * The number of indents deep we are.
+ * @param Array stack
+ * The stack of open curlies, brackets, etc.
+ */
+ function prependWhiteSpace(token, lastToken, addedNewline, write, options,
+ indentLevel, stack) {
+ var ttk = token.type.keyword;
+ var ttl = token.type.label;
+ var newlineAdded = addedNewline;
+ var ltt = lastToken ? lastToken.type.label : null;
+
+ // Handle whitespace and newlines after "}" here instead of in
+ // `isLineDelimiter` because it is only a line delimiter some of the
+ // time. For example, we don't want to put "else if" on a new line after
+ // the first if's block.
+ if (lastToken && ltt == "}") {
+ if (ttk == "while" && stack[stack.length - 1] == "do") {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ } else if (ttk == "else" ||
+ ttk == "catch" ||
+ ttk == "finally") {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ } else if (ttl != "(" &&
+ ttl != ";" &&
+ ttl != "," &&
+ ttl != ")" &&
+ ttl != ".") {
+ write("\n",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ newlineAdded = true;
+ }
+ }
+
+ if (isGetterOrSetter(token, lastToken, stack)) {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ }
+
+ if (ttl == ":" && stack[stack.length - 1] == "?") {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ }
+
+ if (lastToken && ltt != "}" && ttk == "else") {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ }
+
+ function ensureNewline() {
+ if (!newlineAdded) {
+ write("\n",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ newlineAdded = true;
+ }
+ }
+
+ if (isASI(token, lastToken)) {
+ ensureNewline();
+ }
+
+ if (decrementsIndent(ttl, stack)) {
+ ensureNewline();
+ }
+
+ if (newlineAdded) {
+ if (ttk == "case" || ttk == "default") {
+ write(repeat(options.indent, indentLevel - 1),
+ token.loc.start.line,
+ token.loc.start.column);
+ } else {
+ write(repeat(options.indent, indentLevel),
+ token.loc.start.line,
+ token.loc.start.column);
+ }
+ } else if (needsSpaceAfter(token, lastToken)) {
+ write(" ",
+ lastToken.loc.start.line,
+ lastToken.loc.start.column);
+ }
+ }
+
+ /**
+ * Repeat the `str` string `n` times.
+ *
+ * @param String str
+ * The string to be repeated.
+ * @param Number n
+ * The number of times to repeat the string.
+ *
+ * @returns String
+ * The repeated string.
+ */
+ function repeat(str, n) {
+ var result = "";
+ while (n > 0) {
+ if (n & 1) {
+ result += str;
+ }
+ n >>= 1;
+ str += str;
+ }
+ return result;
+ }
+
+ /**
+ * Make sure that we output the escaped character combination inside string
+ * literals instead of various problematic characters.
+ */
+ var sanitize = (function () {
+ var escapeCharacters = {
+ // Backslash
+ "\\": "\\\\",
+ // Newlines
+ "\n": "\\n",
+ // Carriage return
+ "\r": "\\r",
+ // Tab
+ "\t": "\\t",
+ // Vertical tab
+ "\v": "\\v",
+ // Form feed
+ "\f": "\\f",
+ // Null character
+ "\0": "\\0",
+ // Single quotes
+ "'": "\\'"
+ };
+
+ var regExpString = "("
+ + Object.keys(escapeCharacters)
+ .map(function (c) { return escapeCharacters[c]; })
+ .join("|")
+ + ")";
+ var escapeCharactersRegExp = new RegExp(regExpString, "g");
+
+ return function (str) {
+ return str.replace(escapeCharactersRegExp, function (_, c) {
+ return escapeCharacters[c];
+ });
+ };
+ }());
+ /**
+ * Add the given token to the pretty printed results.
+ *
+ * @param Object token
+ * The token to add.
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ */
+ function addToken(token, write) {
+ if (token.type.label == "string") {
+ write("'" + sanitize(token.value) + "'",
+ token.loc.start.line,
+ token.loc.start.column);
+ } else if (token.type.label == "regexp") {
+ write(String(token.value.value),
+ token.loc.start.line,
+ token.loc.start.column);
+ } else {
+ write(String(token.value != null ? token.value : token.type.label),
+ token.loc.start.line,
+ token.loc.start.column);
+ }
+ }
+
+ /**
+ * Returns true if the given token type belongs on the stack.
+ */
+ function belongsOnStack(token) {
+ var ttl = token.type.label;
+ var ttk = token.type.keyword;
+ return ttl == "{"
+ || ttl == "("
+ || ttl == "["
+ || ttl == "?"
+ || ttk == "do"
+ || ttk == "switch"
+ || ttk == "case"
+ || ttk == "default";
+ }
+
+ /**
+ * Returns true if the given token should cause us to pop the stack.
+ */
+ function shouldStackPop(token, stack) {
+ var ttl = token.type.label;
+ var ttk = token.type.keyword;
+ var top = stack[stack.length - 1];
+ return ttl == "]"
+ || ttl == ")"
+ || ttl == "}"
+ || (ttl == ":" && (top == "case" || top == "default" || top == "?"))
+ || (ttk == "while" && top == "do");
+ }
+
+ /**
+ * Returns true if the given token type should cause us to decrement the
+ * indent level.
+ */
+ function decrementsIndent(tokenType, stack) {
+ return tokenType == "}"
+ || (tokenType == "]" && stack[stack.length - 1] == "[\n");
+ }
+
+ /**
+ * Returns true if the given token should cause us to increment the indent
+ * level.
+ */
+ function incrementsIndent(token) {
+ return token.type.label == "{"
+ || token.isArrayLiteral
+ || token.type.keyword == "switch";
+ }
+
+ /**
+ * Add a comment to the pretty printed code.
+ *
+ * @param Function write
+ * The function to write pretty printed code to the result SourceNode.
+ * @param Number indentLevel
+ * The number of indents deep we are.
+ * @param Object options
+ * The options object.
+ * @param Boolean block
+ * True if the comment is a multiline block style comment.
+ * @param String text
+ * The text of the comment.
+ * @param Number line
+ * The line number to comment appeared on.
+ * @param Number column
+ * The column number the comment appeared on.
+ */
+ function addComment(write, indentLevel, options, block, text, line, column) {
+ var indentString = repeat(options.indent, indentLevel);
+
+ write(indentString, line, column);
+ if (block) {
+ write("/*");
+ write(text
+ .split(new RegExp("/\n" + indentString + "/", "g"))
+ .join("\n" + indentString));
+ write("*/");
+ } else {
+ write("//");
+ write(text);
+ }
+ write("\n");
+ }
+
+ /**
+ * The main function.
+ *
+ * @param String input
+ * The ugly JS code we want to pretty print.
+ * @param Object options
+ * The options object. Provides configurability of the pretty
+ * printing. Properties:
+ * - url: The URL string of the ugly JS code.
+ * - indent: The string to indent code by.
+ *
+ * @returns Object
+ * An object with the following properties:
+ * - code: The pretty printed code string.
+ * - map: A SourceMapGenerator instance.
+ */
+ return function prettyFast(input, options) {
+ // The level of indents deep we are.
+ var indentLevel = 0;
+
+ // We will accumulate the pretty printed code in this SourceNode.
+ var result = new SourceNode();
+
+ /**
+ * Write a pretty printed string to the result SourceNode.
+ *
+ * We buffer our writes so that we only create one mapping for each line in
+ * the source map. This enhances performance by avoiding extraneous mapping
+ * serialization, and flattening the tree that
+ * `SourceNode#toStringWithSourceMap` will have to recursively walk. When
+ * timing how long it takes to pretty print jQuery, this optimization
+ * brought the time down from ~390 ms to ~190ms!
+ *
+ * @param String str
+ * The string to be added to the result.
+ * @param Number line
+ * The line number the string came from in the ugly source.
+ * @param Number column
+ * The column number the string came from in the ugly source.
+ */
+ var write = (function () {
+ var buffer = [];
+ var bufferLine = -1;
+ var bufferColumn = -1;
+ return function write(str, line, column) {
+ if (line != null && bufferLine === -1) {
+ bufferLine = line;
+ }
+ if (column != null && bufferColumn === -1) {
+ bufferColumn = column;
+ }
+ buffer.push(str);
+
+ if (str == "\n") {
+ var lineStr = "";
+ for (var i = 0, len = buffer.length; i < len; i++) {
+ lineStr += buffer[i];
+ }
+ result.add(new SourceNode(bufferLine, bufferColumn, options.url,
+ lineStr));
+ buffer.splice(0, buffer.length);
+ bufferLine = -1;
+ bufferColumn = -1;
+ }
+ };
+ }());
+
+ // Whether or not we added a newline on after we added the last token.
+ var addedNewline = false;
+
+ // The current token we will be adding to the pretty printed code.
+ var token;
+
+ // Shorthand for token.type.label, so we don't have to repeatedly access
+ // properties.
+ var ttl;
+
+ // Shorthand for token.type.keyword, so we don't have to repeatedly access
+ // properties.
+ var ttk;
+
+ // The last token we added to the pretty printed code.
+ var lastToken;
+
+ // Stack of token types/keywords that can affect whether we want to add a
+ // newline or a space. We can make that decision based on what token type is
+ // on the top of the stack. For example, a comma in a parameter list should
+ // be followed by a space, while a comma in an object literal should be
+ // followed by a newline.
+ //
+ // Strings that go on the stack:
+ //
+ // - "{"
+ // - "("
+ // - "["
+ // - "[\n"
+ // - "do"
+ // - "?"
+ // - "switch"
+ // - "case"
+ // - "default"
+ //
+ // The difference between "[" and "[\n" is that "[\n" is used when we are
+ // treating "[" and "]" tokens as line delimiters and should increment and
+ // decrement the indent level when we find them.
+ var stack = [];
+
+ // Pass through acorn's tokenizer and append tokens and comments into a
+ // single queue to process. For example, the source file:
+ //
+ // foo
+ // // a
+ // // b
+ // bar
+ //
+ // After this process, tokenQueue has the following token stream:
+ //
+ // [ foo, '// a', '// b', bar]
+ var tokenQueue = [];
+
+ var tokens = acorn.tokenizer(input, {
+ locations: true,
+ sourceFile: options.url,
+ onComment: function (block, text, start, end, startLoc, endLoc) {
+ tokenQueue.push({
+ type: {},
+ comment: true,
+ block: block,
+ text: text,
+ loc: { start: startLoc, end: endLoc }
+ });
+ }
+ });
+
+ for (;;) {
+ token = tokens.getToken();
+ tokenQueue.push(token);
+ if (token.type.label == "eof") {
+ break;
+ }
+ }
+
+ for (var i = 0; i < tokenQueue.length; i++) {
+ token = tokenQueue[i];
+
+ if (token.comment) {
+ var commentIndentLevel = indentLevel;
+ if (lastToken && (lastToken.loc.end.line == token.loc.start.line)) {
+ commentIndentLevel = 0;
+ write(" ");
+ }
+ addComment(write, commentIndentLevel, options, token.block, token.text,
+ token.loc.start.line, token.loc.start.column);
+ addedNewline = true;
+ continue;
+ }
+
+ ttk = token.type.keyword;
+ ttl = token.type.label;
+
+ if (ttl == "eof") {
+ if (!addedNewline) {
+ write("\n");
+ }
+ break;
+ }
+
+ token.isArrayLiteral = isArrayLiteral(token, lastToken);
+
+ if (belongsOnStack(token)) {
+ if (token.isArrayLiteral) {
+ stack.push("[\n");
+ } else {
+ stack.push(ttl || ttk);
+ }
+ }
+
+ if (decrementsIndent(ttl, stack)) {
+ indentLevel--;
+ if (ttl == "}"
+ && stack.length > 1
+ && stack[stack.length - 2] == "switch") {
+ indentLevel--;
+ }
+ }
+
+ prependWhiteSpace(token, lastToken, addedNewline, write, options,
+ indentLevel, stack);
+ addToken(token, write);
+
+ // If the next token is going to be a comment starting on the same line,
+ // then no need to add one here
+ var nextToken = tokenQueue[i + 1];
+ if (!nextToken || !nextToken.comment || token.loc.end.line != nextToken.loc.start.line) {
+ addedNewline = appendNewline(token, write, stack);
+ }
+
+ if (shouldStackPop(token, stack)) {
+ stack.pop();
+ if (token == "}" && stack.length
+ && stack[stack.length - 1] == "switch") {
+ stack.pop();
+ }
+ }
+
+ if (incrementsIndent(token)) {
+ indentLevel++;
+ }
+
+ // Acorn's tokenizer re-uses tokens, so we have to copy the last token on
+ // every iteration. We follow acorn's lead here, and reuse the lastToken
+ // object the same way that acorn reuses the token object. This allows us
+ // to avoid allocations and minimize GC pauses.
+ if (!lastToken) {
+ lastToken = { loc: { start: {}, end: {} } };
+ }
+ lastToken.start = token.start;
+ lastToken.end = token.end;
+ lastToken.loc.start.line = token.loc.start.line;
+ lastToken.loc.start.column = token.loc.start.column;
+ lastToken.loc.end.line = token.loc.end.line;
+ lastToken.loc.end.column = token.loc.end.column;
+ lastToken.type = token.type;
+ lastToken.value = token.value;
+ lastToken.isArrayLiteral = token.isArrayLiteral;
+ }
+
+ return result.toStringWithSourceMap({ file: options.url });
+ };
+
+}.bind(this)));
diff --git a/devtools/shared/pretty-fast/tests/unit/head_pretty-fast.js b/devtools/shared/pretty-fast/tests/unit/head_pretty-fast.js
new file mode 100644
index 000000000..abde4b197
--- /dev/null
+++ b/devtools/shared/pretty-fast/tests/unit/head_pretty-fast.js
@@ -0,0 +1,49 @@
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+this.sourceMap = require("source-map");
+this.acorn = require("acorn/acorn");
+this.prettyFast = require("devtools/shared/pretty-fast/pretty-fast");
+const { console } = Cu.import("resource://gre/modules/Console.jsm", {});
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Ignored until they are fixed in bug 1242968.
+ if (string.includes("JavaScript Warning")) {
+ return;
+ }
+
+ do_throw("head_pretty-fast.js got console message: " + string + "\n");
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
diff --git a/devtools/shared/pretty-fast/tests/unit/test.js b/devtools/shared/pretty-fast/tests/unit/test.js
new file mode 100644
index 000000000..b462726a2
--- /dev/null
+++ b/devtools/shared/pretty-fast/tests/unit/test.js
@@ -0,0 +1,572 @@
+/* -*- Mode: js; tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2; fill-column: 80 -*- */
+
+"use strict";
+
+/*
+ * Copyright 2013 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.md or:
+ * http://opensource.org/licenses/BSD-2-Clause
+ */
+var prettyFast = this.prettyFast || require("./pretty-fast");
+
+var testCases = [
+
+ {
+ name: "Simple function",
+ input: "function foo() { bar(); }",
+ output: "function foo() {\n" +
+ " bar();\n" +
+ "}\n",
+ mappings: [
+ // function foo() {
+ {
+ inputLine: 1,
+ outputLine: 1
+ },
+ // bar();
+ {
+ inputLine: 1,
+ outputLine: 2
+ },
+ // }
+ {
+ inputLine: 1,
+ outputLine: 3
+ },
+ ]
+ },
+
+ {
+ name: "Nested function",
+ input: "function foo() { function bar() { debugger; } bar(); }",
+ output: "function foo() {\n" +
+ " function bar() {\n" +
+ " debugger;\n" +
+ " }\n" +
+ " bar();\n" +
+ "}\n",
+ mappings: [
+ // function bar() {
+ {
+ inputLine: 1,
+ outputLine: 2
+ },
+ // debugger;
+ {
+ inputLine: 1,
+ outputLine: 3
+ },
+ // bar();
+ {
+ inputLine: 1,
+ outputLine: 5
+ },
+ ]
+ },
+
+ {
+ name: "Immediately invoked function expression",
+ input: "(function(){thingy()}())",
+ output: "(function () {\n" +
+ " thingy()\n" +
+ "}())\n"
+ },
+
+ {
+ name: "Single line comment",
+ input: "// Comment\n" +
+ "function foo() { bar(); }\n",
+ output: "// Comment\n" +
+ "function foo() {\n" +
+ " bar();\n" +
+ "}\n",
+ mappings: [
+ // // Comment
+ {
+ inputLine: 1,
+ outputLine: 1
+ }
+ ]
+ },
+
+ {
+ name: "Multi line comment",
+ input: "/* Comment\n" +
+ "more comment */\n" +
+ "function foo() { bar(); }\n",
+ output: "/* Comment\n" +
+ "more comment */\n" +
+ "function foo() {\n" +
+ " bar();\n" +
+ "}\n",
+ mappings: [
+ // /* Comment
+ {
+ inputLine: 1,
+ outputLine: 1
+ },
+ // \nmore comment */
+ {
+ inputLine: 1,
+ outputLine: 2
+ }
+ ]
+ },
+
+ {
+ name: "Null assignment",
+ input: "var i=null;\n",
+ output: "var i = null;\n",
+ mappings: [
+ {
+ inputLine: 1,
+ outputLine: 1
+ }
+ ]
+ },
+
+ {
+ name: "Undefined assignment",
+ input: "var i=undefined;\n",
+ output: "var i = undefined;\n"
+ },
+
+ {
+ name: "Void 0 assignment",
+ input: "var i=void 0;\n",
+ output: "var i = void 0;\n"
+ },
+
+ {
+ name: "This property access",
+ input: "var foo=this.foo;\n",
+ output: "var foo = this.foo;\n"
+ },
+
+ {
+ name: "True assignment",
+ input: "var foo=true;\n",
+ output: "var foo = true;\n"
+ },
+
+ {
+ name: "False assignment",
+ input: "var foo=false;\n",
+ output: "var foo = false;\n"
+ },
+
+ {
+ name: "For loop",
+ input: "for (var i = 0; i < n; i++) { console.log(i); }",
+ output: "for (var i = 0; i < n; i++) {\n" +
+ " console.log(i);\n" +
+ "}\n",
+ mappings: [
+ // for (var i = 0; i < n; i++) {
+ {
+ inputLine: 1,
+ outputLine: 1
+ },
+ // console.log(i);
+ {
+ inputLine: 1,
+ outputLine: 2
+ },
+ ]
+ },
+
+ {
+ name: "String with semicolon",
+ input: "var foo = ';';\n",
+ output: "var foo = ';';\n"
+ },
+
+ {
+ name: "String with quote",
+ input: "var foo = \"'\";\n",
+ output: "var foo = '\\'';\n"
+ },
+
+ {
+ name: "Function calls",
+ input: "var result=func(a,b,c,d);",
+ output: "var result = func(a, b, c, d);\n"
+ },
+
+ {
+ name: "Regexp",
+ input: "var r=/foobar/g;",
+ output: "var r = /foobar/g;\n"
+ },
+
+ {
+ name: "In operator",
+ input: "if(foo in bar){doThing()}",
+ output: "if (foo in bar) {\n" +
+ " doThing()\n" +
+ "}\n"
+ },
+
+ {
+ name: "With statement",
+ input: "with(obj){crock()}",
+ output: "with (obj) {\n" +
+ " crock()\n" +
+ "}\n"
+ },
+
+ {
+ name: "New expression",
+ input: "var foo=new Foo();",
+ output: "var foo = new Foo();\n"
+ },
+
+ {
+ name: "Continue/break statements",
+ input: "while(1){if(x){continue}if(y){break}if(z){break foo}}",
+ output: "while (1) {\n" +
+ " if (x) {\n" +
+ " continue\n" +
+ " }\n" +
+ " if (y) {\n" +
+ " break\n" +
+ " }\n" +
+ " if (z) {\n" +
+ " break foo\n" +
+ " }\n" +
+ "}\n"
+ },
+
+ {
+ name: "Instanceof",
+ input: "var a=x instanceof y;",
+ output: "var a = x instanceof y;\n"
+ },
+
+ {
+ name: "Binary operators",
+ input: "var a=5*30;var b=5>>3;",
+ output: "var a = 5 * 30;\n" +
+ "var b = 5 >> 3;\n"
+ },
+
+ {
+ name: "Delete",
+ input: "delete obj.prop;",
+ output: "delete obj.prop;\n"
+ },
+
+ {
+ name: "Try/catch/finally statement",
+ input: "try{dangerous()}catch(e){handle(e)}finally{cleanup()}",
+ output: "try {\n" +
+ " dangerous()\n" +
+ "} catch (e) {\n" +
+ " handle(e)\n" +
+ "} finally {\n" +
+ " cleanup()\n" +
+ "}\n"
+ },
+
+ {
+ name: "If/else statement",
+ input: "if(c){then()}else{other()}",
+ output: "if (c) {\n" +
+ " then()\n" +
+ "} else {\n" +
+ " other()\n" +
+ "}\n"
+ },
+
+ {
+ name: "If/else without curlies",
+ input: "if(c) a else b",
+ output: "if (c) a else b\n"
+ },
+
+ {
+ name: "Objects",
+ input: "var o={a:1,\n" +
+ " b:2};",
+ output: "var o = {\n" +
+ " a: 1,\n" +
+ " b: 2\n" +
+ "};\n",
+ mappings: [
+ // a: 1,
+ {
+ inputLine: 1,
+ outputLine: 2
+ },
+ // b: 2
+ {
+ inputLine: 2,
+ outputLine: 3
+ },
+ ]
+ },
+
+ {
+ name: "Do/while loop",
+ input: "do{x}while(y)",
+ output: "do {\n" +
+ " x\n" +
+ "} while (y)\n"
+ },
+
+ {
+ name: "Arrays",
+ input: "var a=[1,2,3];",
+ output: "var a = [\n" +
+ " 1,\n" +
+ " 2,\n" +
+ " 3\n" +
+ "];\n"
+ },
+
+ {
+ name: "Code that relies on ASI",
+ input: "var foo = 10\n" +
+ "var bar = 20\n" +
+ "function g() {\n" +
+ " a()\n" +
+ " b()\n" +
+ "}",
+ output: "var foo = 10\n" +
+ "var bar = 20\n" +
+ "function g() {\n" +
+ " a()\n" +
+ " b()\n" +
+ "}\n"
+ },
+
+ {
+ name: "Ternary operator",
+ input: "bar?baz:bang;",
+ output: "bar ? baz : bang;\n"
+ },
+
+ {
+ name: "Switch statements",
+ input: "switch(x){case a:foo();break;default:bar()}",
+ output: "switch (x) {\n" +
+ " case a:\n" +
+ " foo();\n" +
+ " break;\n" +
+ " default:\n" +
+ " bar()\n" +
+ "}\n"
+ },
+
+ {
+ name: "Multiple single line comments",
+ input: "function f() {\n" +
+ " // a\n" +
+ " // b\n" +
+ " // c\n" +
+ "}\n",
+ output: "function f() {\n" +
+ " // a\n" +
+ " // b\n" +
+ " // c\n" +
+ "}\n",
+ },
+
+ {
+ name: "Indented multiline comment",
+ input: "function foo() {\n" +
+ " /**\n" +
+ " * java doc style comment\n" +
+ " * more comment\n" +
+ " */\n" +
+ " bar();\n" +
+ "}\n",
+ output: "function foo() {\n" +
+ " /**\n" +
+ " * java doc style comment\n" +
+ " * more comment\n" +
+ " */\n" +
+ " bar();\n" +
+ "}\n",
+ },
+
+ {
+ name: "ASI return",
+ input: "function f() {\n" +
+ " return\n" +
+ " {}\n" +
+ "}\n",
+ output: "function f() {\n" +
+ " return\n" +
+ " {\n" +
+ " }\n" +
+ "}\n",
+ },
+
+ {
+ name: "Non-ASI property access",
+ input: "[1,2,3]\n" +
+ "[0]",
+ output: "[\n" +
+ " 1,\n" +
+ " 2,\n" +
+ " 3\n" +
+ "]\n" +
+ "[0]\n"
+ },
+
+ {
+ name: "Non-ASI in",
+ input: "'x'\n" +
+ "in foo",
+ output: "'x' in foo\n"
+ },
+
+ {
+ name: "Non-ASI function call",
+ input: "f\n" +
+ "()",
+ output: "f()\n"
+ },
+
+ {
+ name: "Non-ASI new",
+ input: "new\n" +
+ "F()",
+ output: "new F()\n"
+ },
+
+ {
+ name: "Getter and setter literals",
+ input: "var obj={get foo(){return this._foo},set foo(v){this._foo=v}}",
+ output: "var obj = {\n" +
+ " get foo() {\n" +
+ " return this._foo\n" +
+ " },\n" +
+ " set foo(v) {\n" +
+ " this._foo = v\n" +
+ " }\n" +
+ "}\n"
+ },
+
+ {
+ name: "Escaping backslashes in strings",
+ input: "'\\\\'\n",
+ output: "'\\\\'\n"
+ },
+
+ {
+ name: "Escaping carriage return in strings",
+ input: "'\\r'\n",
+ output: "'\\r'\n"
+ },
+
+ {
+ name: "Escaping tab in strings",
+ input: "'\\t'\n",
+ output: "'\\t'\n"
+ },
+
+ {
+ name: "Escaping vertical tab in strings",
+ input: "'\\v'\n",
+ output: "'\\v'\n"
+ },
+
+ {
+ name: "Escaping form feed in strings",
+ input: "'\\f'\n",
+ output: "'\\f'\n"
+ },
+
+ {
+ name: "Escaping null character in strings",
+ input: "'\\0'\n",
+ output: "'\\0'\n"
+ },
+
+ {
+ name: "Bug 977082 - space between grouping operator and dot notation",
+ input: "JSON.stringify(3).length;\n" +
+ "([1,2,3]).length;\n" +
+ "(new Date()).toLocaleString();\n",
+ output: "JSON.stringify(3).length;\n" +
+ "([1,\n" +
+ "2,\n" +
+ "3]).length;\n" +
+ "(new Date()).toLocaleString();\n"
+ },
+
+ {
+ name: "Bug 975477 don't move end of line comments to next line",
+ input: "switch (request.action) {\n" +
+ " case 'show': //$NON-NLS-0$\n" +
+ " if (localStorage.hideicon !== 'true') { //$NON-NLS-0$\n" +
+ " chrome.pageAction.show(sender.tab.id);\n" +
+ " }\n" +
+ " break;\n" +
+ " case 'hide': /*Multiline\n" +
+ " Comment */" +
+ " break;\n" +
+ " default:\n" +
+ " console.warn('unknown request'); //$NON-NLS-0$\n" +
+ " // don't respond if you don't understand the message.\n" +
+ " return;\n" +
+ "}\n",
+ output: "switch (request.action) {\n" +
+ " case 'show': //$NON-NLS-0$\n" +
+ " if (localStorage.hideicon !== 'true') { //$NON-NLS-0$\n" +
+ " chrome.pageAction.show(sender.tab.id);\n" +
+ " }\n" +
+ " break;\n" +
+ " case 'hide': /*Multiline\n" +
+ " Comment */\n" +
+ " break;\n" +
+ " default:\n" +
+ " console.warn('unknown request'); //$NON-NLS-0$\n" +
+ " // don't respond if you don't understand the message.\n" +
+ " return;\n" +
+ "}\n"
+ }
+
+];
+
+var sourceMap = this.sourceMap || require("source-map");
+
+function run_test() {
+ testCases.forEach(function (test) {
+ console.log(test.name);
+
+ var actual = prettyFast(test.input, {
+ indent: " ",
+ url: "test.js"
+ });
+
+ if (actual.code !== test.output) {
+ throw new Error("Expected:\n" + test.output
+ + "\nGot:\n" + actual.code);
+ }
+
+ if (test.mappings) {
+ var smc = new sourceMap.SourceMapConsumer(actual.map.toJSON());
+ test.mappings.forEach(function (m) {
+ var query = { line: m.outputLine, column: 0 };
+ var original = smc.originalPositionFor(query);
+ if (original.line != m.inputLine) {
+ throw new Error("Querying:\n" + JSON.stringify(query, null, 2) + "\n"
+ + "Expected line:\n" + m.inputLine + "\n"
+ + "Got:\n" + JSON.stringify(original, null, 2));
+ }
+ });
+ }
+ });
+ console.log("✓ All tests pass!");
+}
+
+// Only run the tests if this is node and we are running this file
+// directly. (Firefox's test runner will import this test file, and then call
+// run_test itself.)
+if (typeof require == "function" && typeof module == "object"
+ && require.main === module) {
+ run_test();
+}
diff --git a/devtools/shared/pretty-fast/tests/unit/xpcshell.ini b/devtools/shared/pretty-fast/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..cb92b1825
--- /dev/null
+++ b/devtools/shared/pretty-fast/tests/unit/xpcshell.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools
+head = head_pretty-fast.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+[test.js]
diff --git a/devtools/shared/protocol.js b/devtools/shared/protocol.js
new file mode 100644
index 000000000..4035d5016
--- /dev/null
+++ b/devtools/shared/protocol.js
@@ -0,0 +1,1517 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var {Class} = require("sdk/core/heritage");
+var {EventTarget} = require("sdk/event/target");
+var events = require("sdk/event/core");
+var object = require("sdk/util/object");
+var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
+
+exports.emit = events.emit;
+
+/**
+ * Types: named marshallers/demarshallers.
+ *
+ * Types provide a 'write' function that takes a js representation and
+ * returns a protocol representation, and a "read" function that
+ * takes a protocol representation and returns a js representation.
+ *
+ * The read and write methods are also passed a context object that
+ * represent the actor or front requesting the translation.
+ *
+ * Types are referred to with a typestring. Basic types are
+ * registered by name using addType, and more complex types can
+ * be generated by adding detail to the type name.
+ */
+
+var types = Object.create(null);
+exports.types = types;
+
+var registeredTypes = types.registeredTypes = new Map();
+var registeredLifetimes = types.registeredLifetimes = new Map();
+
+/**
+ * Return the type object associated with a given typestring.
+ * If passed a type object, it will be returned unchanged.
+ *
+ * Types can be registered with addType, or can be created on
+ * the fly with typestrings. Examples:
+ *
+ * boolean
+ * threadActor
+ * threadActor#detail
+ * array:threadActor
+ * array:array:threadActor#detail
+ *
+ * @param [typestring|type] type
+ * Either a typestring naming a type or a type object.
+ *
+ * @returns a type object.
+ */
+types.getType = function (type) {
+ if (!type) {
+ return types.Primitive;
+ }
+
+ if (typeof (type) !== "string") {
+ return type;
+ }
+
+ // If already registered, we're done here.
+ let reg = registeredTypes.get(type);
+ if (reg) return reg;
+
+ // New type, see if it's a collection/lifetime type:
+ let sep = type.indexOf(":");
+ if (sep >= 0) {
+ let collection = type.substring(0, sep);
+ let subtype = types.getType(type.substring(sep + 1));
+
+ if (collection === "array") {
+ return types.addArrayType(subtype);
+ } else if (collection === "nullable") {
+ return types.addNullableType(subtype);
+ }
+
+ if (registeredLifetimes.has(collection)) {
+ return types.addLifetimeType(collection, subtype);
+ }
+
+ throw Error("Unknown collection type: " + collection);
+ }
+
+ // Not a collection, might be actor detail
+ let pieces = type.split("#", 2);
+ if (pieces.length > 1) {
+ return types.addActorDetail(type, pieces[0], pieces[1]);
+ }
+
+ // Might be a lazily-loaded type
+ if (type === "longstring") {
+ require("devtools/shared/specs/string");
+ return registeredTypes.get("longstring");
+ }
+
+ throw Error("Unknown type: " + type);
+};
+
+/**
+ * Don't allow undefined when writing primitive types to packets. If
+ * you want to allow undefined, use a nullable type.
+ */
+function identityWrite(v) {
+ if (v === undefined) {
+ throw Error("undefined passed where a value is required");
+ }
+ // This has to handle iterator->array conversion because arrays of
+ // primitive types pass through here.
+ if (v && typeof (v) === "object" && Symbol.iterator in v) {
+ return [...v];
+ }
+ return v;
+}
+
+/**
+ * Add a type to the type system.
+ *
+ * When registering a type, you can provide `read` and `write` methods.
+ *
+ * The `read` method will be passed a JS object value from the JSON
+ * packet and must return a native representation. The `write` method will
+ * be passed a native representation and should provide a JSONable value.
+ *
+ * These methods will both be passed a context. The context is the object
+ * performing or servicing the request - on the server side it will be
+ * an Actor, on the client side it will be a Front.
+ *
+ * @param typestring name
+ * Name to register
+ * @param object typeObject
+ * An object whose properties will be stored in the type, including
+ * the `read` and `write` methods.
+ * @param object options
+ * Can specify `thawed` to prevent the type from being frozen.
+ *
+ * @returns a type object that can be used in protocol definitions.
+ */
+types.addType = function (name, typeObject = {}, options = {}) {
+ if (registeredTypes.has(name)) {
+ throw Error("Type '" + name + "' already exists.");
+ }
+
+ let type = object.merge({
+ toString() { return "[protocol type:" + name + "]";},
+ name: name,
+ primitive: !(typeObject.read || typeObject.write),
+ read: identityWrite,
+ write: identityWrite
+ }, typeObject);
+
+ registeredTypes.set(name, type);
+
+ return type;
+};
+
+/**
+ * Remove a type previously registered with the system.
+ * Primarily useful for types registered by addons.
+ */
+types.removeType = function (name) {
+ // This type may still be referenced by other types, make sure
+ // those references don't work.
+ let type = registeredTypes.get(name);
+
+ type.name = "DEFUNCT:" + name;
+ type.category = "defunct";
+ type.primitive = false;
+ type.read = type.write = function () { throw new Error("Using defunct type: " + name); };
+
+ registeredTypes.delete(name);
+};
+
+/**
+ * Add an array type to the type system.
+ *
+ * getType() will call this function if provided an "array:<type>"
+ * typestring.
+ *
+ * @param type subtype
+ * The subtype to be held by the array.
+ */
+types.addArrayType = function (subtype) {
+ subtype = types.getType(subtype);
+
+ let name = "array:" + subtype.name;
+
+ // Arrays of primitive types are primitive types themselves.
+ if (subtype.primitive) {
+ return types.addType(name);
+ }
+ return types.addType(name, {
+ category: "array",
+ read: (v, ctx) => [...v].map(i => subtype.read(i, ctx)),
+ write: (v, ctx) => [...v].map(i => subtype.write(i, ctx))
+ });
+};
+
+/**
+ * Add a dict type to the type system. This allows you to serialize
+ * a JS object that contains non-primitive subtypes.
+ *
+ * Properties of the value that aren't included in the specializations
+ * will be serialized as primitive values.
+ *
+ * @param object specializations
+ * A dict of property names => type
+ */
+types.addDictType = function (name, specializations) {
+ return types.addType(name, {
+ category: "dict",
+ specializations: specializations,
+ read: (v, ctx) => {
+ let ret = {};
+ for (let prop in v) {
+ if (prop in specializations) {
+ ret[prop] = types.getType(specializations[prop]).read(v[prop], ctx);
+ } else {
+ ret[prop] = v[prop];
+ }
+ }
+ return ret;
+ },
+
+ write: (v, ctx) => {
+ let ret = {};
+ for (let prop in v) {
+ if (prop in specializations) {
+ ret[prop] = types.getType(specializations[prop]).write(v[prop], ctx);
+ } else {
+ ret[prop] = v[prop];
+ }
+ }
+ return ret;
+ }
+ });
+};
+
+/**
+ * Register an actor type with the type system.
+ *
+ * Types are marshalled differently when communicating server->client
+ * than they are when communicating client->server. The server needs
+ * to provide useful information to the client, so uses the actor's
+ * `form` method to get a json representation of the actor. When
+ * making a request from the client we only need the actor ID string.
+ *
+ * This function can be called before the associated actor has been
+ * constructed, but the read and write methods won't work until
+ * the associated addActorImpl or addActorFront methods have been
+ * called during actor/front construction.
+ *
+ * @param string name
+ * The typestring to register.
+ */
+types.addActorType = function (name) {
+ let type = types.addType(name, {
+ _actor: true,
+ category: "actor",
+ read: (v, ctx, detail) => {
+ // If we're reading a request on the server side, just
+ // find the actor registered with this actorID.
+ if (ctx instanceof Actor) {
+ return ctx.conn.getActor(v);
+ }
+
+ // Reading a response on the client side, check for an
+ // existing front on the connection, and create the front
+ // if it isn't found.
+ let actorID = typeof (v) === "string" ? v : v.actor;
+ let front = ctx.conn.getActor(actorID);
+ if (!front) {
+ front = new type.frontClass(ctx.conn);
+ front.actorID = actorID;
+ ctx.marshallPool().manage(front);
+ }
+
+ v = type.formType(detail).read(v, front, detail);
+ front.form(v, detail, ctx);
+
+ return front;
+ },
+ write: (v, ctx, detail) => {
+ // If returning a response from the server side, make sure
+ // the actor is added to a parent object and return its form.
+ if (v instanceof Actor) {
+ if (!v.actorID) {
+ ctx.marshallPool().manage(v);
+ }
+ return type.formType(detail).write(v.form(detail), ctx, detail);
+ }
+
+ // Writing a request from the client side, just send the actor id.
+ return v.actorID;
+ },
+ formType: (detail) => {
+ if (!("formType" in type.actorSpec)) {
+ return types.Primitive;
+ }
+
+ let formAttr = "formType";
+ if (detail) {
+ formAttr += "#" + detail;
+ }
+
+ if (!(formAttr in type.actorSpec)) {
+ throw new Error("No type defined for " + formAttr);
+ }
+
+ return type.actorSpec[formAttr];
+ }
+ });
+ return type;
+};
+
+types.addNullableType = function (subtype) {
+ subtype = types.getType(subtype);
+ return types.addType("nullable:" + subtype.name, {
+ category: "nullable",
+ read: (value, ctx) => {
+ if (value == null) {
+ return value;
+ }
+ return subtype.read(value, ctx);
+ },
+ write: (value, ctx) => {
+ if (value == null) {
+ return value;
+ }
+ return subtype.write(value, ctx);
+ }
+ });
+};
+
+/**
+ * Register an actor detail type. This is just like an actor type, but
+ * will pass a detail hint to the actor's form method during serialization/
+ * deserialization.
+ *
+ * This is called by getType() when passed an 'actorType#detail' string.
+ *
+ * @param string name
+ * The typestring to register this type as.
+ * @param type actorType
+ * The actor type you'll be detailing.
+ * @param string detail
+ * The detail to pass.
+ */
+types.addActorDetail = function (name, actorType, detail) {
+ actorType = types.getType(actorType);
+ if (!actorType._actor) {
+ throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
+ }
+ return types.addType(name, {
+ _actor: true,
+ category: "detail",
+ read: (v, ctx) => actorType.read(v, ctx, detail),
+ write: (v, ctx) => actorType.write(v, ctx, detail)
+ });
+};
+
+/**
+ * Register an actor lifetime. This lets the type system find a parent
+ * actor that differs from the actor fulfilling the request.
+ *
+ * @param string name
+ * The lifetime name to use in typestrings.
+ * @param string prop
+ * The property of the actor that holds the parent that should be used.
+ */
+types.addLifetime = function (name, prop) {
+ if (registeredLifetimes.has(name)) {
+ throw Error("Lifetime '" + name + "' already registered.");
+ }
+ registeredLifetimes.set(name, prop);
+};
+
+/**
+ * Remove a previously-registered lifetime. Useful for lifetimes registered
+ * in addons.
+ */
+types.removeLifetime = function (name) {
+ registeredLifetimes.delete(name);
+};
+
+/**
+ * Register a lifetime type. This creates an actor type tied to the given
+ * lifetime.
+ *
+ * This is called by getType() when passed a '<lifetimeType>:<actorType>'
+ * typestring.
+ *
+ * @param string lifetime
+ * A lifetime string previously regisered with addLifetime()
+ * @param type subtype
+ * An actor type
+ */
+types.addLifetimeType = function (lifetime, subtype) {
+ subtype = types.getType(subtype);
+ if (!subtype._actor) {
+ throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
+ }
+ let prop = registeredLifetimes.get(lifetime);
+ return types.addType(lifetime + ":" + subtype.name, {
+ category: "lifetime",
+ read: (value, ctx) => subtype.read(value, ctx[prop]),
+ write: (value, ctx) => subtype.write(value, ctx[prop])
+ });
+};
+
+// Add a few named primitive types.
+types.Primitive = types.addType("primitive");
+types.String = types.addType("string");
+types.Number = types.addType("number");
+types.Boolean = types.addType("boolean");
+types.JSON = types.addType("json");
+
+/**
+ * Request/Response templates and generation
+ *
+ * Request packets are specified as json templates with
+ * Arg and Option placeholders where arguments should be
+ * placed.
+ *
+ * Reponse packets are also specified as json templates,
+ * with a RetVal placeholder where the return value should be
+ * placed.
+ */
+
+/**
+ * Placeholder for simple arguments.
+ *
+ * @param number index
+ * The argument index to place at this position.
+ * @param type type
+ * The argument should be marshalled as this type.
+ * @constructor
+ */
+var Arg = Class({
+ initialize: function (index, type) {
+ this.index = index;
+ this.type = types.getType(type);
+ },
+
+ write: function (arg, ctx) {
+ return this.type.write(arg, ctx);
+ },
+
+ read: function (v, ctx, outArgs) {
+ outArgs[this.index] = this.type.read(v, ctx);
+ },
+
+ describe: function () {
+ return {
+ _arg: this.index,
+ type: this.type.name,
+ };
+ }
+});
+exports.Arg = Arg;
+
+/**
+ * Placeholder for an options argument value that should be hoisted
+ * into the packet.
+ *
+ * If provided in a method specification:
+ *
+ * { optionArg: Option(1)}
+ *
+ * Then arguments[1].optionArg will be placed in the packet in this
+ * value's place.
+ *
+ * @param number index
+ * The argument index of the options value.
+ * @param type type
+ * The argument should be marshalled as this type.
+ * @constructor
+ */
+var Option = Class({
+ extends: Arg,
+ initialize: function (index, type) {
+ Arg.prototype.initialize.call(this, index, type);
+ },
+
+ write: function (arg, ctx, name) {
+ // Ignore if arg is undefined or null; allow other falsy values
+ if (arg == undefined || arg[name] == undefined) {
+ return undefined;
+ }
+ let v = arg[name];
+ return this.type.write(v, ctx);
+ },
+ read: function (v, ctx, outArgs, name) {
+ if (outArgs[this.index] === undefined) {
+ outArgs[this.index] = {};
+ }
+ if (v === undefined) {
+ return;
+ }
+ outArgs[this.index][name] = this.type.read(v, ctx);
+ },
+
+ describe: function () {
+ return {
+ _option: this.index,
+ type: this.type.name,
+ };
+ }
+});
+
+exports.Option = Option;
+
+/**
+ * Placeholder for return values in a response template.
+ *
+ * @param type type
+ * The return value should be marshalled as this type.
+ */
+var RetVal = Class({
+ initialize: function (type) {
+ this.type = types.getType(type);
+ },
+
+ write: function (v, ctx) {
+ return this.type.write(v, ctx);
+ },
+
+ read: function (v, ctx) {
+ return this.type.read(v, ctx);
+ },
+
+ describe: function () {
+ return {
+ _retval: this.type.name
+ };
+ }
+});
+
+exports.RetVal = RetVal;
+
+/* Template handling functions */
+
+/**
+ * Get the value at a given path, or undefined if not found.
+ */
+function getPath(obj, path) {
+ for (let name of path) {
+ if (!(name in obj)) {
+ return undefined;
+ }
+ obj = obj[name];
+ }
+ return obj;
+}
+
+/**
+ * Find Placeholders in the template and save them along with their
+ * paths.
+ */
+function findPlaceholders(template, constructor, path = [], placeholders = []) {
+ if (!template || typeof (template) != "object") {
+ return placeholders;
+ }
+
+ if (template instanceof constructor) {
+ placeholders.push({ placeholder: template, path: [...path] });
+ return placeholders;
+ }
+
+ for (let name in template) {
+ path.push(name);
+ findPlaceholders(template[name], constructor, path, placeholders);
+ path.pop();
+ }
+
+ return placeholders;
+}
+
+
+function describeTemplate(template) {
+ return JSON.parse(JSON.stringify(template, (key, value) => {
+ if (value.describe) {
+ return value.describe();
+ }
+ return value;
+ }));
+}
+
+/**
+ * Manages a request template.
+ *
+ * @param object template
+ * The request template.
+ * @construcor
+ */
+var Request = Class({
+ initialize: function (template = {}) {
+ this.type = template.type;
+ this.template = template;
+ this.args = findPlaceholders(template, Arg);
+ },
+
+ /**
+ * Write a request.
+ *
+ * @param array fnArgs
+ * The function arguments to place in the request.
+ * @param object ctx
+ * The object making the request.
+ * @returns a request packet.
+ */
+ write: function (fnArgs, ctx) {
+ let str = JSON.stringify(this.template, (key, value) => {
+ if (value instanceof Arg) {
+ return value.write(value.index in fnArgs ? fnArgs[value.index] : undefined,
+ ctx, key);
+ }
+ return value;
+ });
+ return JSON.parse(str);
+ },
+
+ /**
+ * Read a request.
+ *
+ * @param object packet
+ * The request packet.
+ * @param object ctx
+ * The object making the request.
+ * @returns an arguments array
+ */
+ read: function (packet, ctx) {
+ let fnArgs = [];
+ for (let templateArg of this.args) {
+ let arg = templateArg.placeholder;
+ let path = templateArg.path;
+ let name = path[path.length - 1];
+ arg.read(getPath(packet, path), ctx, fnArgs, name);
+ }
+ return fnArgs;
+ },
+
+ describe: function () { return describeTemplate(this.template); }
+});
+
+/**
+ * Manages a response template.
+ *
+ * @param object template
+ * The response template.
+ * @construcor
+ */
+var Response = Class({
+ initialize: function (template = {}) {
+ this.template = template;
+ let placeholders = findPlaceholders(template, RetVal);
+ if (placeholders.length > 1) {
+ throw Error("More than one RetVal specified in response");
+ }
+ let placeholder = placeholders.shift();
+ if (placeholder) {
+ this.retVal = placeholder.placeholder;
+ this.path = placeholder.path;
+ }
+ },
+
+ /**
+ * Write a response for the given return value.
+ *
+ * @param val ret
+ * The return value.
+ * @param object ctx
+ * The object writing the response.
+ */
+ write: function (ret, ctx) {
+ return JSON.parse(JSON.stringify(this.template, function (key, value) {
+ if (value instanceof RetVal) {
+ return value.write(ret, ctx);
+ }
+ return value;
+ }));
+ },
+
+ /**
+ * Read a return value from the given response.
+ *
+ * @param object packet
+ * The response packet.
+ * @param object ctx
+ * The object reading the response.
+ */
+ read: function (packet, ctx) {
+ if (!this.retVal) {
+ return undefined;
+ }
+ let v = getPath(packet, this.path);
+ return this.retVal.read(v, ctx);
+ },
+
+ describe: function () { return describeTemplate(this.template); }
+});
+
+/**
+ * Actor and Front implementations
+ */
+
+/**
+ * A protocol object that can manage the lifetime of other protocol
+ * objects.
+ */
+var Pool = Class({
+ extends: EventTarget,
+
+ /**
+ * Pools are used on both sides of the connection to help coordinate
+ * lifetimes.
+ *
+ * @param optional conn
+ * Either a DebuggerServerConnection or a DebuggerClient. Must have
+ * addActorPool, removeActorPool, and poolFor.
+ * conn can be null if the subclass provides a conn property.
+ * @constructor
+ */
+ initialize: function (conn) {
+ if (conn) {
+ this.conn = conn;
+ }
+ },
+
+ /**
+ * Return the parent pool for this client.
+ */
+ parent: function () { return this.conn.poolFor(this.actorID); },
+
+ /**
+ * Override this if you want actors returned by this actor
+ * to belong to a different actor by default.
+ */
+ marshallPool: function () { return this; },
+
+ /**
+ * Pool is the base class for all actors, even leaf nodes.
+ * If the child map is actually referenced, go ahead and create
+ * the stuff needed by the pool.
+ */
+ __poolMap: null,
+ get _poolMap() {
+ if (this.__poolMap) return this.__poolMap;
+ this.__poolMap = new Map();
+ this.conn.addActorPool(this);
+ return this.__poolMap;
+ },
+
+ /**
+ * Add an actor as a child of this pool.
+ */
+ manage: function (actor) {
+ if (!actor.actorID) {
+ actor.actorID = this.conn.allocID(actor.actorPrefix || actor.typeName);
+ }
+
+ this._poolMap.set(actor.actorID, actor);
+ return actor;
+ },
+
+ /**
+ * Remove an actor as a child of this pool.
+ */
+ unmanage: function (actor) {
+ this.__poolMap && this.__poolMap.delete(actor.actorID);
+ },
+
+ // true if the given actor ID exists in the pool.
+ has: function (actorID) {
+ return this.__poolMap && this._poolMap.has(actorID);
+ },
+
+ // The actor for a given actor id stored in this pool
+ actor: function (actorID) {
+ return this.__poolMap ? this._poolMap.get(actorID) : null;
+ },
+
+ // Same as actor, should update debugger connection to use 'actor'
+ // and then remove this.
+ get: function (actorID) {
+ return this.__poolMap ? this._poolMap.get(actorID) : null;
+ },
+
+ // True if this pool has no children.
+ isEmpty: function () {
+ return !this.__poolMap || this._poolMap.size == 0;
+ },
+
+ /**
+ * Destroy this item, removing it from a parent if it has one,
+ * and destroying all children if necessary.
+ */
+ destroy: function () {
+ let parent = this.parent();
+ if (parent) {
+ parent.unmanage(this);
+ }
+ if (!this.__poolMap) {
+ return;
+ }
+ for (let actor of this.__poolMap.values()) {
+ // Self-owned actors are ok, but don't need destroying twice.
+ if (actor === this) {
+ continue;
+ }
+ let destroy = actor.destroy;
+ if (destroy) {
+ // Disconnect destroy while we're destroying in case of (misbehaving)
+ // circular ownership.
+ actor.destroy = null;
+ destroy.call(actor);
+ actor.destroy = destroy;
+ }
+ }
+ this.conn.removeActorPool(this, true);
+ this.__poolMap.clear();
+ this.__poolMap = null;
+ },
+
+ /**
+ * For getting along with the debugger server pools, should be removable
+ * eventually.
+ */
+ cleanup: function () {
+ this.destroy();
+ }
+});
+exports.Pool = Pool;
+
+/**
+ * An actor in the actor tree.
+ */
+var Actor = Class({
+ extends: Pool,
+
+ // Will contain the actor's ID
+ actorID: null,
+
+ /**
+ * Initialize an actor.
+ *
+ * @param optional conn
+ * Either a DebuggerServerConnection or a DebuggerClient. Must have
+ * addActorPool, removeActorPool, and poolFor.
+ * conn can be null if the subclass provides a conn property.
+ * @constructor
+ */
+ initialize: function (conn) {
+ Pool.prototype.initialize.call(this, conn);
+
+ // Forward events to the connection.
+ if (this._actorSpec && this._actorSpec.events) {
+ for (let key of this._actorSpec.events.keys()) {
+ let name = key;
+ let sendEvent = this._sendEvent.bind(this, name);
+ this.on(name, (...args) => {
+ sendEvent.apply(null, args);
+ });
+ }
+ }
+ },
+
+ toString: function () { return "[Actor " + this.typeName + "/" + this.actorID + "]"; },
+
+ _sendEvent: function (name, ...args) {
+ if (!this._actorSpec.events.has(name)) {
+ // It's ok to emit events that don't go over the wire.
+ return;
+ }
+ let request = this._actorSpec.events.get(name);
+ let packet;
+ try {
+ packet = request.write(args, this);
+ } catch (ex) {
+ console.error("Error sending event: " + name);
+ throw ex;
+ }
+ packet.from = packet.from || this.actorID;
+ this.conn.send(packet);
+ },
+
+ destroy: function () {
+ Pool.prototype.destroy.call(this);
+ this.actorID = null;
+ },
+
+ /**
+ * Override this method in subclasses to serialize the actor.
+ * @param [optional] string hint
+ * Optional string to customize the form.
+ * @returns A jsonable object.
+ */
+ form: function (hint) {
+ return { actor: this.actorID };
+ },
+
+ writeError: function (error) {
+ console.error(error);
+ if (error.stack) {
+ dump(error.stack);
+ }
+ this.conn.send({
+ from: this.actorID,
+ error: error.error || "unknownError",
+ message: error.message
+ });
+ },
+
+ _queueResponse: function (create) {
+ let pending = this._pendingResponse || promise.resolve(null);
+ let response = create(pending);
+ this._pendingResponse = response;
+ }
+});
+exports.Actor = Actor;
+
+/**
+ * Tags a prtotype method as an actor method implementation.
+ *
+ * @param function fn
+ * The implementation function, will be returned.
+ * @param spec
+ * The method specification, with the following (optional) properties:
+ * request (object): a request template.
+ * response (object): a response template.
+ * oneway (bool): 'true' if no response should be sent.
+ */
+exports.method = function (fn, spec = {}) {
+ fn._methodSpec = Object.freeze(spec);
+ if (spec.request) Object.freeze(spec.request);
+ if (spec.response) Object.freeze(spec.response);
+ return fn;
+};
+
+/**
+ * Generates an actor specification from an actor description.
+ */
+var generateActorSpec = function (actorDesc) {
+ let actorSpec = {
+ typeName: actorDesc.typeName,
+ methods: []
+ };
+
+ // Find method and form specifications attached to properties.
+ for (let name of Object.getOwnPropertyNames(actorDesc)) {
+ let desc = Object.getOwnPropertyDescriptor(actorDesc, name);
+ if (!desc.value) {
+ continue;
+ }
+
+ if (name.startsWith("formType")) {
+ if (typeof (desc.value) === "string") {
+ actorSpec[name] = types.getType(desc.value);
+ } else if (desc.value.name && registeredTypes.has(desc.value.name)) {
+ actorSpec[name] = desc.value;
+ } else {
+ // Shorthand for a newly-registered DictType.
+ actorSpec[name] = types.addDictType(actorDesc.typeName + "__" + name, desc.value);
+ }
+ }
+
+ if (desc.value._methodSpec) {
+ let methodSpec = desc.value._methodSpec;
+ let spec = {};
+ spec.name = methodSpec.name || name;
+ spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+ spec.response = Response(methodSpec.response || undefined);
+ spec.release = methodSpec.release;
+ spec.oneway = methodSpec.oneway;
+
+ actorSpec.methods.push(spec);
+ }
+ }
+
+ // Find additional method specifications
+ if (actorDesc.methods) {
+ for (let name in actorDesc.methods) {
+ let methodSpec = actorDesc.methods[name];
+ let spec = {};
+
+ spec.name = methodSpec.name || name;
+ spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+ spec.response = Response(methodSpec.response || undefined);
+ spec.release = methodSpec.release;
+ spec.oneway = methodSpec.oneway;
+
+ actorSpec.methods.push(spec);
+ }
+ }
+
+ // Find event specifications
+ if (actorDesc.events) {
+ actorSpec.events = new Map();
+ for (let name in actorDesc.events) {
+ let eventRequest = actorDesc.events[name];
+ Object.freeze(eventRequest);
+ actorSpec.events.set(name, Request(object.merge({type: name}, eventRequest)));
+ }
+ }
+
+ if (!registeredTypes.has(actorSpec.typeName)) {
+ types.addActorType(actorSpec.typeName);
+ }
+ registeredTypes.get(actorSpec.typeName).actorSpec = actorSpec;
+
+ return actorSpec;
+};
+exports.generateActorSpec = generateActorSpec;
+
+/**
+ * Generates request handlers as described by the given actor specification on
+ * the given actor prototype. Returns the actor prototype.
+ */
+var generateRequestHandlers = function (actorSpec, actorProto) {
+ if (actorProto._actorSpec) {
+ throw new Error("actorProto called twice on the same actor prototype!");
+ }
+
+ actorProto.typeName = actorSpec.typeName;
+
+ // Generate request handlers for each method definition
+ actorProto.requestTypes = Object.create(null);
+ actorSpec.methods.forEach(spec => {
+ let handler = function (packet, conn) {
+ try {
+ let args;
+ try {
+ args = spec.request.read(packet, this);
+ } catch (ex) {
+ console.error("Error reading request: " + packet.type);
+ throw ex;
+ }
+
+ let ret = this[spec.name].apply(this, args);
+
+ let sendReturn = (ret) => {
+ if (spec.oneway) {
+ // No need to send a response.
+ return;
+ }
+
+ let response;
+ try {
+ response = spec.response.write(ret, this);
+ } catch (ex) {
+ console.error("Error writing response to: " + spec.name);
+ throw ex;
+ }
+ response.from = this.actorID;
+ // If spec.release has been specified, destroy the object.
+ if (spec.release) {
+ try {
+ this.destroy();
+ } catch (e) {
+ this.writeError(e);
+ return;
+ }
+ }
+
+ conn.send(response);
+ };
+
+ this._queueResponse(p => {
+ return p
+ .then(() => ret)
+ .then(sendReturn)
+ .then(null, this.writeError.bind(this));
+ });
+ } catch (e) {
+ this._queueResponse(p => {
+ return p.then(() => this.writeError(e));
+ });
+ }
+ };
+
+ actorProto.requestTypes[spec.request.type] = handler;
+ });
+
+ actorProto._actorSpec = actorSpec;
+
+ return actorProto;
+};
+
+/**
+ * THIS METHOD IS DEPRECATED, AND PRESERVED ONLY FOR ADD-ONS. IT SHOULD NOT BE
+ * USED INSIDE THE TREE.
+ *
+ * Create an actor class for the given actor prototype.
+ *
+ * @param object actorProto
+ * The actor prototype. Must have a 'typeName' property,
+ * should have method definitions, can have event definitions.
+ */
+exports.ActorClass = function (actorProto) {
+ return ActorClassWithSpec(generateActorSpec(actorProto), actorProto);
+};
+
+/**
+ * THIS METHOD IS DEPRECATED, AND PRESERVED ONLY FOR ADD-ONS. IT SHOULD NOT BE
+ * USED INSIDE THE TREE.
+ *
+ * Create an actor class for the given actor specification and prototype.
+ *
+ * @param object actorSpec
+ * The actor specification. Must have a 'typeName' property.
+ * @param object actorProto
+ * The actor prototype. Should have method definitions, can have event
+ * definitions.
+ */
+var ActorClassWithSpec = function (actorSpec, actorProto) {
+ if (!actorSpec.typeName) {
+ throw Error("Actor specification must have a typeName member.");
+ }
+
+ actorProto.extends = Actor;
+ let cls = Class(generateRequestHandlers(actorSpec, actorProto));
+
+ return cls;
+};
+exports.ActorClassWithSpec = ActorClassWithSpec;
+
+/**
+ * Base class for client-side actor fronts.
+ */
+var Front = Class({
+ extends: Pool,
+
+ actorID: null,
+
+ /**
+ * The base class for client-side actor fronts.
+ *
+ * @param optional conn
+ * Either a DebuggerServerConnection or a DebuggerClient. Must have
+ * addActorPool, removeActorPool, and poolFor.
+ * conn can be null if the subclass provides a conn property.
+ * @param optional form
+ * The json form provided by the server.
+ * @constructor
+ */
+ initialize: function (conn = null, form = null, detail = null, context = null) {
+ Pool.prototype.initialize.call(this, conn);
+ this._requests = [];
+
+ // protocol.js no longer uses this data in the constructor, only external
+ // uses do. External usage of manually-constructed fronts will be
+ // drastically reduced if we convert the root and tab actors to
+ // protocol.js, in which case this can probably go away.
+ if (form) {
+ this.actorID = form.actor;
+ form = types.getType(this.typeName).formType(detail).read(form, this, detail);
+ this.form(form, detail, context);
+ }
+ },
+
+ destroy: function () {
+ // Reject all outstanding requests, they won't make sense after
+ // the front is destroyed.
+ while (this._requests && this._requests.length > 0) {
+ let { deferred, to, type, stack } = this._requests.shift();
+ let msg = "Connection closed, pending request to " + to +
+ ", type " + type + " failed" +
+ "\n\nRequest stack:\n" + stack.formattedStack;
+ deferred.reject(new Error(msg));
+ }
+ Pool.prototype.destroy.call(this);
+ this.actorID = null;
+ },
+
+ manage: function (front) {
+ if (!front.actorID) {
+ throw new Error("Can't manage front without an actor ID.\n" +
+ "Ensure server supports " + front.typeName + ".");
+ }
+ return Pool.prototype.manage.call(this, front);
+ },
+
+ /**
+ * @returns a promise that will resolve to the actorID this front
+ * represents.
+ */
+ actor: function () { return promise.resolve(this.actorID); },
+
+ toString: function () { return "[Front for " + this.typeName + "/" + this.actorID + "]"; },
+
+ /**
+ * Update the actor from its representation.
+ * Subclasses should override this.
+ */
+ form: function (form) {},
+
+ /**
+ * Send a packet on the connection.
+ */
+ send: function (packet) {
+ if (packet.to) {
+ this.conn._transport.send(packet);
+ } else {
+ this.actor().then(actorID => {
+ packet.to = actorID;
+ this.conn._transport.send(packet);
+ }).then(null, e => console.error(e));
+ }
+ },
+
+ /**
+ * Send a two-way request on the connection.
+ */
+ request: function (packet) {
+ let deferred = defer();
+ // Save packet basics for debugging
+ let { to, type } = packet;
+ this._requests.push({
+ deferred,
+ to: to || this.actorID,
+ type,
+ stack: getStack(),
+ });
+ this.send(packet);
+ return deferred.promise;
+ },
+
+ /**
+ * Handler for incoming packets from the client's actor.
+ */
+ onPacket: function (packet) {
+ // Pick off event packets
+ let type = packet.type || undefined;
+ if (this._clientSpec.events && this._clientSpec.events.has(type)) {
+ let event = this._clientSpec.events.get(packet.type);
+ let args;
+ try {
+ args = event.request.read(packet, this);
+ } catch (ex) {
+ console.error("Error reading event: " + packet.type);
+ console.exception(ex);
+ throw ex;
+ }
+ if (event.pre) {
+ let results = event.pre.map(pre => pre.apply(this, args));
+
+ // Check to see if any of the preEvents returned a promise -- if so,
+ // wait for their resolution before emitting. Otherwise, emit synchronously.
+ if (results.some(result => result && typeof result.then === "function")) {
+ promise.all(results).then(() => events.emit.apply(null, [this, event.name].concat(args)));
+ return;
+ }
+ }
+
+ events.emit.apply(null, [this, event.name].concat(args));
+ return;
+ }
+
+ // Remaining packets must be responses.
+ if (this._requests.length === 0) {
+ let msg = "Unexpected packet " + this.actorID + ", " + JSON.stringify(packet);
+ let err = Error(msg);
+ console.error(err);
+ throw err;
+ }
+
+ let { deferred, stack } = this._requests.shift();
+ callFunctionWithAsyncStack(() => {
+ if (packet.error) {
+ // "Protocol error" is here to avoid TBPL heuristics. See also
+ // https://dxr.mozilla.org/webtools-central/source/tbpl/php/inc/GeneralErrorFilter.php
+ let message;
+ if (packet.error && packet.message) {
+ message = "Protocol error (" + packet.error + "): " + packet.message;
+ } else {
+ message = packet.error;
+ }
+ deferred.reject(message);
+ } else {
+ deferred.resolve(packet);
+ }
+ }, stack, "DevTools RDP");
+ }
+});
+exports.Front = Front;
+
+/**
+ * A method tagged with preEvent will be called after recieving a packet
+ * for that event, and before the front emits the event.
+ */
+exports.preEvent = function (eventName, fn) {
+ fn._preEvent = eventName;
+ return fn;
+};
+
+/**
+ * Mark a method as a custom front implementation, replacing the generated
+ * front method.
+ *
+ * @param function fn
+ * The front implementation, will be returned.
+ * @param object options
+ * Options object:
+ * impl (string): If provided, the generated front method will be
+ * stored as this property on the prototype.
+ */
+exports.custom = function (fn, options = {}) {
+ fn._customFront = options;
+ return fn;
+};
+
+function prototypeOf(obj) {
+ return typeof (obj) === "function" ? obj.prototype : obj;
+}
+
+/**
+ * Generates request methods as described by the given actor specification on
+ * the given front prototype. Returns the front prototype.
+ */
+var generateRequestMethods = function (actorSpec, frontProto) {
+ if (frontProto._actorSpec) {
+ throw new Error("frontProto called twice on the same front prototype!");
+ }
+
+ frontProto.typeName = actorSpec.typeName;
+
+ // Generate request methods.
+ let methods = actorSpec.methods;
+ methods.forEach(spec => {
+ let name = spec.name;
+
+ // If there's already a property by this name in the front, it must
+ // be a custom front method.
+ if (name in frontProto) {
+ let custom = frontProto[spec.name]._customFront;
+ if (custom === undefined) {
+ throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + ".");
+ }
+ // If the user doesn't need the impl don't generate it.
+ if (!custom.impl) {
+ return;
+ }
+ name = custom.impl;
+ }
+
+ frontProto[name] = function (...args) {
+ let packet;
+ try {
+ packet = spec.request.write(args, this);
+ } catch (ex) {
+ console.error("Error writing request: " + name);
+ throw ex;
+ }
+ if (spec.oneway) {
+ // Fire-and-forget oneway packets.
+ this.send(packet);
+ return undefined;
+ }
+
+ return this.request(packet).then(response => {
+ let ret;
+ try {
+ ret = spec.response.read(response, this);
+ } catch (ex) {
+ console.error("Error reading response to: " + name);
+ throw ex;
+ }
+ return ret;
+ });
+ };
+
+ // Release methods should call the destroy function on return.
+ if (spec.release) {
+ let fn = frontProto[name];
+ frontProto[name] = function (...args) {
+ return fn.apply(this, args).then(result => {
+ this.destroy();
+ return result;
+ });
+ };
+ }
+ });
+
+
+ // Process event specifications
+ frontProto._clientSpec = {};
+
+ let events = actorSpec.events;
+ if (events) {
+ // This actor has events, scan the prototype for preEvent handlers...
+ let preHandlers = new Map();
+ for (let name of Object.getOwnPropertyNames(frontProto)) {
+ let desc = Object.getOwnPropertyDescriptor(frontProto, name);
+ if (!desc.value) {
+ continue;
+ }
+ if (desc.value._preEvent) {
+ let preEvent = desc.value._preEvent;
+ if (!events.has(preEvent)) {
+ throw Error("preEvent for event that doesn't exist: " + preEvent);
+ }
+ let handlers = preHandlers.get(preEvent);
+ if (!handlers) {
+ handlers = [];
+ preHandlers.set(preEvent, handlers);
+ }
+ handlers.push(desc.value);
+ }
+ }
+
+ frontProto._clientSpec.events = new Map();
+
+ for (let [name, request] of events) {
+ frontProto._clientSpec.events.set(request.type, {
+ name: name,
+ request: request,
+ pre: preHandlers.get(name)
+ });
+ }
+ }
+
+ frontProto._actorSpec = actorSpec;
+
+ return frontProto;
+};
+
+/**
+ * Create a front class for the given actor class and front prototype.
+ *
+ * @param ActorClass actorType
+ * The actor class you're creating a front for.
+ * @param object frontProto
+ * The front prototype. Must have a 'typeName' property,
+ * should have method definitions, can have event definitions.
+ */
+exports.FrontClass = function (actorType, frontProto) {
+ return FrontClassWithSpec(prototypeOf(actorType)._actorSpec, frontProto);
+};
+
+/**
+ * Create a front class for the given actor specification and front prototype.
+ *
+ * @param object actorSpec
+ * The actor specification you're creating a front for.
+ * @param object proto
+ * The object prototype. Must have a 'typeName' property,
+ * should have method definitions, can have event definitions.
+ */
+var FrontClassWithSpec = function (actorSpec, frontProto) {
+ frontProto.extends = Front;
+ let cls = Class(generateRequestMethods(actorSpec, frontProto));
+
+ if (!registeredTypes.has(actorSpec.typeName)) {
+ types.addActorType(actorSpec.typeName);
+ }
+ registeredTypes.get(actorSpec.typeName).frontClass = cls;
+
+ return cls;
+};
+exports.FrontClassWithSpec = FrontClassWithSpec;
+
+exports.dumpActorSpec = function (type) {
+ let actorSpec = type.actorSpec;
+ let ret = {
+ category: "actor",
+ typeName: type.name,
+ methods: [],
+ events: {}
+ };
+
+ for (let method of actorSpec.methods) {
+ ret.methods.push({
+ name: method.name,
+ release: method.release || undefined,
+ oneway: method.oneway || undefined,
+ request: method.request.describe(),
+ response: method.response.describe()
+ });
+ }
+
+ if (actorSpec.events) {
+ for (let [name, request] of actorSpec.events) {
+ ret.events[name] = request.describe();
+ }
+ }
+
+
+ JSON.stringify(ret);
+
+ return ret;
+};
+
+exports.dumpProtocolSpec = function () {
+ let ret = {
+ types: {},
+ };
+
+ for (let [name, type] of registeredTypes) {
+ // Force lazy instantiation if needed.
+ type = types.getType(name);
+ let category = type.category || undefined;
+ if (category === "dict") {
+ ret.types[name] = {
+ category: "dict",
+ typeName: name,
+ specializations: type.specializations
+ };
+ } else if (category === "actor") {
+ ret.types[name] = exports.dumpActorSpec(type);
+ }
+ }
+
+ return ret;
+};
diff --git a/devtools/shared/qrcode/decoder/LICENSE b/devtools/shared/qrcode/decoder/LICENSE
new file mode 100644
index 000000000..261eeb9e9
--- /dev/null
+++ b/devtools/shared/qrcode/decoder/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/devtools/shared/qrcode/decoder/index.js b/devtools/shared/qrcode/decoder/index.js
new file mode 100644
index 000000000..28a46de0f
--- /dev/null
+++ b/devtools/shared/qrcode/decoder/index.js
@@ -0,0 +1,2375 @@
+/*
+ Ported to JavaScript by Lazar Laszlo 2011
+
+ lazarsoft@gmail.com, www.lazarsoft.info
+*/
+/*
+*
+* Copyright 2007 ZXing authors
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+var imgU8 = null;
+
+var imgU32 = null;
+
+var imgWidth = 0;
+
+var imgHeight = 0;
+
+var maxImgSize = 1024 * 1024;
+
+var sizeOfDataLengthInfo = [ [ 10, 9, 8, 8 ], [ 12, 11, 16, 10 ], [ 14, 13, 16, 12 ] ];
+
+var GridSampler = {};
+
+GridSampler.checkAndNudgePoints = function(image, points) {
+ let width = imgWidth;
+ let height = imgHeight;
+ let nudged = true;
+ for (let offset = 0; offset < points.length && nudged; offset += 2) {
+ let x = Math.floor(points[offset]);
+ let y = Math.floor(points[offset + 1]);
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw "Error.checkAndNudgePoints ";
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+ nudged = true;
+ for (let offset = points.length - 2; offset >= 0 && nudged; offset -= 2) {
+ let x = Math.floor(points[offset]);
+ let y = Math.floor(points[offset + 1]);
+ if (x < -1 || x > width || y < -1 || y > height) {
+ throw "Error.checkAndNudgePoints ";
+ }
+ nudged = false;
+ if (x == -1) {
+ points[offset] = 0;
+ nudged = true;
+ } else if (x == width) {
+ points[offset] = width - 1;
+ nudged = true;
+ }
+ if (y == -1) {
+ points[offset + 1] = 0;
+ nudged = true;
+ } else if (y == height) {
+ points[offset + 1] = height - 1;
+ nudged = true;
+ }
+ }
+};
+
+GridSampler.sampleGrid3 = function(image, dimension, transform) {
+ let bits = new BitMatrix(dimension);
+ let points = new Array(dimension << 1);
+ for (let y = 0; y < dimension; y++) {
+ let max = points.length;
+ let iValue = y + 0.5;
+ for (let x = 0; x < max; x += 2) {
+ points[x] = (x >> 1) + 0.5;
+ points[x + 1] = iValue;
+ }
+ transform.transformPoints1(points);
+ GridSampler.checkAndNudgePoints(image, points);
+ try {
+ for (let x = 0; x < max; x += 2) {
+ let xpoint = Math.floor(points[x]) * 4 + Math.floor(points[x + 1]) * imgWidth * 4;
+ let bit = image[Math.floor(points[x]) + imgWidth * Math.floor(points[x + 1])];
+ imgU8[xpoint] = bit ? 255 : 0;
+ imgU8[xpoint + 1] = bit ? 255 : 0;
+ imgU8[xpoint + 2] = 0;
+ imgU8[xpoint + 3] = 255;
+ if (bit) bits.set_Renamed(x >> 1, y);
+ }
+ } catch (aioobe) {
+ throw "Error.checkAndNudgePoints";
+ }
+ }
+ return bits;
+};
+
+GridSampler.sampleGridx = function(image, dimension, p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY) {
+ let transform = PerspectiveTransform.quadrilateralToQuadrilateral(p1ToX, p1ToY, p2ToX, p2ToY, p3ToX, p3ToY, p4ToX, p4ToY, p1FromX, p1FromY, p2FromX, p2FromY, p3FromX, p3FromY, p4FromX, p4FromY);
+ return GridSampler.sampleGrid3(image, dimension, transform);
+};
+
+function ECB(count, dataCodewords) {
+ this.count = count;
+ this.dataCodewords = dataCodewords;
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("DataCodewords", function() {
+ return this.dataCodewords;
+ });
+}
+
+function ECBlocks(ecCodewordsPerBlock, ecBlocks1, ecBlocks2) {
+ this.ecCodewordsPerBlock = ecCodewordsPerBlock;
+ if (ecBlocks2) this.ecBlocks = new Array(ecBlocks1, ecBlocks2); else this.ecBlocks = new Array(ecBlocks1);
+ this.__defineGetter__("ECCodewordsPerBlock", function() {
+ return this.ecCodewordsPerBlock;
+ });
+ this.__defineGetter__("TotalECCodewords", function() {
+ return this.ecCodewordsPerBlock * this.NumBlocks;
+ });
+ this.__defineGetter__("NumBlocks", function() {
+ let total = 0;
+ for (let i = 0; i < this.ecBlocks.length; i++) {
+ total += this.ecBlocks[i].length;
+ }
+ return total;
+ });
+ this.getECBlocks = function() {
+ return this.ecBlocks;
+ };
+}
+
+function Version(versionNumber, alignmentPatternCenters, ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4) {
+ this.versionNumber = versionNumber;
+ this.alignmentPatternCenters = alignmentPatternCenters;
+ this.ecBlocks = new Array(ecBlocks1, ecBlocks2, ecBlocks3, ecBlocks4);
+ let total = 0;
+ let ecCodewords = ecBlocks1.ECCodewordsPerBlock;
+ let ecbArray = ecBlocks1.getECBlocks();
+ for (let i = 0; i < ecbArray.length; i++) {
+ let ecBlock = ecbArray[i];
+ total += ecBlock.Count * (ecBlock.DataCodewords + ecCodewords);
+ }
+ this.totalCodewords = total;
+ this.__defineGetter__("VersionNumber", function() {
+ return this.versionNumber;
+ });
+ this.__defineGetter__("AlignmentPatternCenters", function() {
+ return this.alignmentPatternCenters;
+ });
+ this.__defineGetter__("TotalCodewords", function() {
+ return this.totalCodewords;
+ });
+ this.__defineGetter__("DimensionForVersion", function() {
+ return 17 + 4 * this.versionNumber;
+ });
+ this.buildFunctionPattern = function() {
+ let dimension = this.DimensionForVersion;
+ let bitMatrix = new BitMatrix(dimension);
+ bitMatrix.setRegion(0, 0, 9, 9);
+ bitMatrix.setRegion(dimension - 8, 0, 8, 9);
+ bitMatrix.setRegion(0, dimension - 8, 9, 8);
+ let max = this.alignmentPatternCenters.length;
+ for (let x = 0; x < max; x++) {
+ let i = this.alignmentPatternCenters[x] - 2;
+ for (let y = 0; y < max; y++) {
+ if (x === 0 && (y === 0 || y === max - 1) || x === max - 1 && y === 0) {
+ continue;
+ }
+ bitMatrix.setRegion(this.alignmentPatternCenters[y] - 2, i, 5, 5);
+ }
+ }
+ bitMatrix.setRegion(6, 9, 1, dimension - 17);
+ bitMatrix.setRegion(9, 6, dimension - 17, 1);
+ if (this.versionNumber > 6) {
+ bitMatrix.setRegion(dimension - 11, 0, 3, 6);
+ bitMatrix.setRegion(0, dimension - 11, 6, 3);
+ }
+ return bitMatrix;
+ };
+ this.getECBlocksForLevel = function(ecLevel) {
+ return this.ecBlocks[ecLevel.ordinal()];
+ };
+}
+
+Version.VERSION_DECODE_INFO = new Array(31892, 34236, 39577, 42195, 48118, 51042, 55367, 58893, 63784, 68472, 70749, 76311, 79154, 84390, 87683, 92361, 96236, 102084, 102881, 110507, 110734, 117786, 119615, 126325, 127568, 133589, 136944, 141498, 145311, 150283, 152622, 158308, 161089, 167017);
+
+Version.VERSIONS = buildVersions();
+
+Version.getVersionForNumber = function(versionNumber) {
+ if (versionNumber < 1 || versionNumber > 40) {
+ throw "ArgumentException";
+ }
+ return Version.VERSIONS[versionNumber - 1];
+};
+
+Version.getProvisionalVersionForDimension = function(dimension) {
+ if (dimension % 4 != 1) {
+ throw "Error getProvisionalVersionForDimension";
+ }
+ try {
+ return Version.getVersionForNumber(dimension - 17 >> 2);
+ } catch (iae) {
+ throw "Error getVersionForNumber";
+ }
+};
+
+Version.decodeVersionInformation = function(versionBits) {
+ let bestDifference = 4294967295;
+ let bestVersion = 0;
+ for (let i = 0; i < Version.VERSION_DECODE_INFO.length; i++) {
+ let targetVersion = Version.VERSION_DECODE_INFO[i];
+ if (targetVersion == versionBits) {
+ return this.getVersionForNumber(i + 7);
+ }
+ let bitsDifference = FormatInformation.numBitsDiffering(versionBits, targetVersion);
+ if (bitsDifference < bestDifference) {
+ bestVersion = i + 7;
+ bestDifference = bitsDifference;
+ }
+ }
+ if (bestDifference <= 3) {
+ return this.getVersionForNumber(bestVersion);
+ }
+ return null;
+};
+
+function buildVersions() {
+ return new Array(new Version(1, new Array(), new ECBlocks(7, new ECB(1, 19)), new ECBlocks(10, new ECB(1, 16)), new ECBlocks(13, new ECB(1, 13)), new ECBlocks(17, new ECB(1, 9))), new Version(2, new Array(6, 18), new ECBlocks(10, new ECB(1, 34)), new ECBlocks(16, new ECB(1, 28)), new ECBlocks(22, new ECB(1, 22)), new ECBlocks(28, new ECB(1, 16))), new Version(3, new Array(6, 22), new ECBlocks(15, new ECB(1, 55)), new ECBlocks(26, new ECB(1, 44)), new ECBlocks(18, new ECB(2, 17)), new ECBlocks(22, new ECB(2, 13))), new Version(4, new Array(6, 26), new ECBlocks(20, new ECB(1, 80)), new ECBlocks(18, new ECB(2, 32)), new ECBlocks(26, new ECB(2, 24)), new ECBlocks(16, new ECB(4, 9))), new Version(5, new Array(6, 30), new ECBlocks(26, new ECB(1, 108)), new ECBlocks(24, new ECB(2, 43)), new ECBlocks(18, new ECB(2, 15), new ECB(2, 16)), new ECBlocks(22, new ECB(2, 11), new ECB(2, 12))), new Version(6, new Array(6, 34), new ECBlocks(18, new ECB(2, 68)), new ECBlocks(16, new ECB(4, 27)), new ECBlocks(24, new ECB(4, 19)), new ECBlocks(28, new ECB(4, 15))), new Version(7, new Array(6, 22, 38), new ECBlocks(20, new ECB(2, 78)), new ECBlocks(18, new ECB(4, 31)), new ECBlocks(18, new ECB(2, 14), new ECB(4, 15)), new ECBlocks(26, new ECB(4, 13), new ECB(1, 14))), new Version(8, new Array(6, 24, 42), new ECBlocks(24, new ECB(2, 97)), new ECBlocks(22, new ECB(2, 38), new ECB(2, 39)), new ECBlocks(22, new ECB(4, 18), new ECB(2, 19)), new ECBlocks(26, new ECB(4, 14), new ECB(2, 15))), new Version(9, new Array(6, 26, 46), new ECBlocks(30, new ECB(2, 116)), new ECBlocks(22, new ECB(3, 36), new ECB(2, 37)), new ECBlocks(20, new ECB(4, 16), new ECB(4, 17)), new ECBlocks(24, new ECB(4, 12), new ECB(4, 13))), new Version(10, new Array(6, 28, 50), new ECBlocks(18, new ECB(2, 68), new ECB(2, 69)), new ECBlocks(26, new ECB(4, 43), new ECB(1, 44)), new ECBlocks(24, new ECB(6, 19), new ECB(2, 20)), new ECBlocks(28, new ECB(6, 15), new ECB(2, 16))), new Version(11, new Array(6, 30, 54), new ECBlocks(20, new ECB(4, 81)), new ECBlocks(30, new ECB(1, 50), new ECB(4, 51)), new ECBlocks(28, new ECB(4, 22), new ECB(4, 23)), new ECBlocks(24, new ECB(3, 12), new ECB(8, 13))), new Version(12, new Array(6, 32, 58), new ECBlocks(24, new ECB(2, 92), new ECB(2, 93)), new ECBlocks(22, new ECB(6, 36), new ECB(2, 37)), new ECBlocks(26, new ECB(4, 20), new ECB(6, 21)), new ECBlocks(28, new ECB(7, 14), new ECB(4, 15))), new Version(13, new Array(6, 34, 62), new ECBlocks(26, new ECB(4, 107)), new ECBlocks(22, new ECB(8, 37), new ECB(1, 38)), new ECBlocks(24, new ECB(8, 20), new ECB(4, 21)), new ECBlocks(22, new ECB(12, 11), new ECB(4, 12))), new Version(14, new Array(6, 26, 46, 66), new ECBlocks(30, new ECB(3, 115), new ECB(1, 116)), new ECBlocks(24, new ECB(4, 40), new ECB(5, 41)), new ECBlocks(20, new ECB(11, 16), new ECB(5, 17)), new ECBlocks(24, new ECB(11, 12), new ECB(5, 13))), new Version(15, new Array(6, 26, 48, 70), new ECBlocks(22, new ECB(5, 87), new ECB(1, 88)), new ECBlocks(24, new ECB(5, 41), new ECB(5, 42)), new ECBlocks(30, new ECB(5, 24), new ECB(7, 25)), new ECBlocks(24, new ECB(11, 12), new ECB(7, 13))), new Version(16, new Array(6, 26, 50, 74), new ECBlocks(24, new ECB(5, 98), new ECB(1, 99)), new ECBlocks(28, new ECB(7, 45), new ECB(3, 46)), new ECBlocks(24, new ECB(15, 19), new ECB(2, 20)), new ECBlocks(30, new ECB(3, 15), new ECB(13, 16))), new Version(17, new Array(6, 30, 54, 78), new ECBlocks(28, new ECB(1, 107), new ECB(5, 108)), new ECBlocks(28, new ECB(10, 46), new ECB(1, 47)), new ECBlocks(28, new ECB(1, 22), new ECB(15, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(17, 15))), new Version(18, new Array(6, 30, 56, 82), new ECBlocks(30, new ECB(5, 120), new ECB(1, 121)), new ECBlocks(26, new ECB(9, 43), new ECB(4, 44)), new ECBlocks(28, new ECB(17, 22), new ECB(1, 23)), new ECBlocks(28, new ECB(2, 14), new ECB(19, 15))), new Version(19, new Array(6, 30, 58, 86), new ECBlocks(28, new ECB(3, 113), new ECB(4, 114)), new ECBlocks(26, new ECB(3, 44), new ECB(11, 45)), new ECBlocks(26, new ECB(17, 21), new ECB(4, 22)), new ECBlocks(26, new ECB(9, 13), new ECB(16, 14))), new Version(20, new Array(6, 34, 62, 90), new ECBlocks(28, new ECB(3, 107), new ECB(5, 108)), new ECBlocks(26, new ECB(3, 41), new ECB(13, 42)), new ECBlocks(30, new ECB(15, 24), new ECB(5, 25)), new ECBlocks(28, new ECB(15, 15), new ECB(10, 16))), new Version(21, new Array(6, 28, 50, 72, 94), new ECBlocks(28, new ECB(4, 116), new ECB(4, 117)), new ECBlocks(26, new ECB(17, 42)), new ECBlocks(28, new ECB(17, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(19, 16), new ECB(6, 17))), new Version(22, new Array(6, 26, 50, 74, 98), new ECBlocks(28, new ECB(2, 111), new ECB(7, 112)), new ECBlocks(28, new ECB(17, 46)), new ECBlocks(30, new ECB(7, 24), new ECB(16, 25)), new ECBlocks(24, new ECB(34, 13))), new Version(23, new Array(6, 30, 54, 74, 102), new ECBlocks(30, new ECB(4, 121), new ECB(5, 122)), new ECBlocks(28, new ECB(4, 47), new ECB(14, 48)), new ECBlocks(30, new ECB(11, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(16, 15), new ECB(14, 16))), new Version(24, new Array(6, 28, 54, 80, 106), new ECBlocks(30, new ECB(6, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(6, 45), new ECB(14, 46)), new ECBlocks(30, new ECB(11, 24), new ECB(16, 25)), new ECBlocks(30, new ECB(30, 16), new ECB(2, 17))), new Version(25, new Array(6, 32, 58, 84, 110), new ECBlocks(26, new ECB(8, 106), new ECB(4, 107)), new ECBlocks(28, new ECB(8, 47), new ECB(13, 48)), new ECBlocks(30, new ECB(7, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(13, 16))), new Version(26, new Array(6, 30, 58, 86, 114), new ECBlocks(28, new ECB(10, 114), new ECB(2, 115)), new ECBlocks(28, new ECB(19, 46), new ECB(4, 47)), new ECBlocks(28, new ECB(28, 22), new ECB(6, 23)), new ECBlocks(30, new ECB(33, 16), new ECB(4, 17))), new Version(27, new Array(6, 34, 62, 90, 118), new ECBlocks(30, new ECB(8, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(22, 45), new ECB(3, 46)), new ECBlocks(30, new ECB(8, 23), new ECB(26, 24)), new ECBlocks(30, new ECB(12, 15), new ECB(28, 16))), new Version(28, new Array(6, 26, 50, 74, 98, 122), new ECBlocks(30, new ECB(3, 117), new ECB(10, 118)), new ECBlocks(28, new ECB(3, 45), new ECB(23, 46)), new ECBlocks(30, new ECB(4, 24), new ECB(31, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(31, 16))), new Version(29, new Array(6, 30, 54, 78, 102, 126), new ECBlocks(30, new ECB(7, 116), new ECB(7, 117)), new ECBlocks(28, new ECB(21, 45), new ECB(7, 46)), new ECBlocks(30, new ECB(1, 23), new ECB(37, 24)), new ECBlocks(30, new ECB(19, 15), new ECB(26, 16))), new Version(30, new Array(6, 26, 52, 78, 104, 130), new ECBlocks(30, new ECB(5, 115), new ECB(10, 116)), new ECBlocks(28, new ECB(19, 47), new ECB(10, 48)), new ECBlocks(30, new ECB(15, 24), new ECB(25, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(25, 16))), new Version(31, new Array(6, 30, 56, 82, 108, 134), new ECBlocks(30, new ECB(13, 115), new ECB(3, 116)), new ECBlocks(28, new ECB(2, 46), new ECB(29, 47)), new ECBlocks(30, new ECB(42, 24), new ECB(1, 25)), new ECBlocks(30, new ECB(23, 15), new ECB(28, 16))), new Version(32, new Array(6, 34, 60, 86, 112, 138), new ECBlocks(30, new ECB(17, 115)), new ECBlocks(28, new ECB(10, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(10, 24), new ECB(35, 25)), new ECBlocks(30, new ECB(19, 15), new ECB(35, 16))), new Version(33, new Array(6, 30, 58, 86, 114, 142), new ECBlocks(30, new ECB(17, 115), new ECB(1, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(21, 47)), new ECBlocks(30, new ECB(29, 24), new ECB(19, 25)), new ECBlocks(30, new ECB(11, 15), new ECB(46, 16))), new Version(34, new Array(6, 34, 62, 90, 118, 146), new ECBlocks(30, new ECB(13, 115), new ECB(6, 116)), new ECBlocks(28, new ECB(14, 46), new ECB(23, 47)), new ECBlocks(30, new ECB(44, 24), new ECB(7, 25)), new ECBlocks(30, new ECB(59, 16), new ECB(1, 17))), new Version(35, new Array(6, 30, 54, 78, 102, 126, 150), new ECBlocks(30, new ECB(12, 121), new ECB(7, 122)), new ECBlocks(28, new ECB(12, 47), new ECB(26, 48)), new ECBlocks(30, new ECB(39, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(22, 15), new ECB(41, 16))), new Version(36, new Array(6, 24, 50, 76, 102, 128, 154), new ECBlocks(30, new ECB(6, 121), new ECB(14, 122)), new ECBlocks(28, new ECB(6, 47), new ECB(34, 48)), new ECBlocks(30, new ECB(46, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(2, 15), new ECB(64, 16))), new Version(37, new Array(6, 28, 54, 80, 106, 132, 158), new ECBlocks(30, new ECB(17, 122), new ECB(4, 123)), new ECBlocks(28, new ECB(29, 46), new ECB(14, 47)), new ECBlocks(30, new ECB(49, 24), new ECB(10, 25)), new ECBlocks(30, new ECB(24, 15), new ECB(46, 16))), new Version(38, new Array(6, 32, 58, 84, 110, 136, 162), new ECBlocks(30, new ECB(4, 122), new ECB(18, 123)), new ECBlocks(28, new ECB(13, 46), new ECB(32, 47)), new ECBlocks(30, new ECB(48, 24), new ECB(14, 25)), new ECBlocks(30, new ECB(42, 15), new ECB(32, 16))), new Version(39, new Array(6, 26, 54, 82, 110, 138, 166), new ECBlocks(30, new ECB(20, 117), new ECB(4, 118)), new ECBlocks(28, new ECB(40, 47), new ECB(7, 48)), new ECBlocks(30, new ECB(43, 24), new ECB(22, 25)), new ECBlocks(30, new ECB(10, 15), new ECB(67, 16))), new Version(40, new Array(6, 30, 58, 86, 114, 142, 170), new ECBlocks(30, new ECB(19, 118), new ECB(6, 119)), new ECBlocks(28, new ECB(18, 47), new ECB(31, 48)), new ECBlocks(30, new ECB(34, 24), new ECB(34, 25)), new ECBlocks(30, new ECB(20, 15), new ECB(61, 16))));
+}
+
+function PerspectiveTransform(a11, a21, a31, a12, a22, a32, a13, a23, a33) {
+ this.a11 = a11;
+ this.a12 = a12;
+ this.a13 = a13;
+ this.a21 = a21;
+ this.a22 = a22;
+ this.a23 = a23;
+ this.a31 = a31;
+ this.a32 = a32;
+ this.a33 = a33;
+ this.transformPoints1 = function(points) {
+ let max = points.length;
+ let a11 = this.a11;
+ let a12 = this.a12;
+ let a13 = this.a13;
+ let a21 = this.a21;
+ let a22 = this.a22;
+ let a23 = this.a23;
+ let a31 = this.a31;
+ let a32 = this.a32;
+ let a33 = this.a33;
+ for (let i = 0; i < max; i += 2) {
+ let x = points[i];
+ let y = points[i + 1];
+ let denominator = a13 * x + a23 * y + a33;
+ points[i] = (a11 * x + a21 * y + a31) / denominator;
+ points[i + 1] = (a12 * x + a22 * y + a32) / denominator;
+ }
+ };
+ this.transformPoints2 = function(xValues, yValues) {
+ let n = xValues.length;
+ for (let i = 0; i < n; i++) {
+ let x = xValues[i];
+ let y = yValues[i];
+ let denominator = this.a13 * x + this.a23 * y + this.a33;
+ xValues[i] = (this.a11 * x + this.a21 * y + this.a31) / denominator;
+ yValues[i] = (this.a12 * x + this.a22 * y + this.a32) / denominator;
+ }
+ };
+ this.buildAdjoint = function() {
+ return new PerspectiveTransform(this.a22 * this.a33 - this.a23 * this.a32, this.a23 * this.a31 - this.a21 * this.a33, this.a21 * this.a32 - this.a22 * this.a31, this.a13 * this.a32 - this.a12 * this.a33, this.a11 * this.a33 - this.a13 * this.a31, this.a12 * this.a31 - this.a11 * this.a32, this.a12 * this.a23 - this.a13 * this.a22, this.a13 * this.a21 - this.a11 * this.a23, this.a11 * this.a22 - this.a12 * this.a21);
+ };
+ this.times = function(other) {
+ return new PerspectiveTransform(this.a11 * other.a11 + this.a21 * other.a12 + this.a31 * other.a13, this.a11 * other.a21 + this.a21 * other.a22 + this.a31 * other.a23, this.a11 * other.a31 + this.a21 * other.a32 + this.a31 * other.a33, this.a12 * other.a11 + this.a22 * other.a12 + this.a32 * other.a13, this.a12 * other.a21 + this.a22 * other.a22 + this.a32 * other.a23, this.a12 * other.a31 + this.a22 * other.a32 + this.a32 * other.a33, this.a13 * other.a11 + this.a23 * other.a12 + this.a33 * other.a13, this.a13 * other.a21 + this.a23 * other.a22 + this.a33 * other.a23, this.a13 * other.a31 + this.a23 * other.a32 + this.a33 * other.a33);
+ };
+}
+
+PerspectiveTransform.quadrilateralToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3, x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p) {
+ let qToS = this.quadrilateralToSquare(x0, y0, x1, y1, x2, y2, x3, y3);
+ let sToQ = this.squareToQuadrilateral(x0p, y0p, x1p, y1p, x2p, y2p, x3p, y3p);
+ return sToQ.times(qToS);
+};
+
+PerspectiveTransform.squareToQuadrilateral = function(x0, y0, x1, y1, x2, y2, x3, y3) {
+ let dy2 = y3 - y2;
+ let dy3 = y0 - y1 + y2 - y3;
+ if (dy2 === 0 && dy3 === 0) {
+ return new PerspectiveTransform(x1 - x0, x2 - x1, x0, y1 - y0, y2 - y1, y0, 0, 0, 1);
+ } else {
+ let dx1 = x1 - x2;
+ let dx2 = x3 - x2;
+ let dx3 = x0 - x1 + x2 - x3;
+ let dy1 = y1 - y2;
+ let denominator = dx1 * dy2 - dx2 * dy1;
+ let a13 = (dx3 * dy2 - dx2 * dy3) / denominator;
+ let a23 = (dx1 * dy3 - dx3 * dy1) / denominator;
+ return new PerspectiveTransform(x1 - x0 + a13 * x1, x3 - x0 + a23 * x3, x0, y1 - y0 + a13 * y1, y3 - y0 + a23 * y3, y0, a13, a23, 1);
+ }
+};
+
+PerspectiveTransform.quadrilateralToSquare = function(x0, y0, x1, y1, x2, y2, x3, y3) {
+ return this.squareToQuadrilateral(x0, y0, x1, y1, x2, y2, x3, y3).buildAdjoint();
+};
+
+function DetectorResult(bits, points) {
+ this.bits = bits;
+ this.points = points;
+}
+
+function Detector(image) {
+ this.image = image;
+ this.resultPointCallback = null;
+ this.sizeOfBlackWhiteBlackRun = function(fromX, fromY, toX, toY) {
+ let steep = Math.abs(toY - fromY) > Math.abs(toX - fromX);
+ if (steep) {
+ let temp = fromX;
+ fromX = fromY;
+ fromY = temp;
+ temp = toX;
+ toX = toY;
+ toY = temp;
+ }
+ let dx = Math.abs(toX - fromX);
+ let dy = Math.abs(toY - fromY);
+ let error = -dx >> 1;
+ let ystep = fromY < toY ? 1 : -1;
+ let xstep = fromX < toX ? 1 : -1;
+ let state = 0;
+ for (let x = fromX, y = fromY; x != toX; x += xstep) {
+ let realX = steep ? y : x;
+ let realY = steep ? x : y;
+ if (state == 1) {
+ if (this.image[realX + realY * imgWidth]) {
+ state++;
+ }
+ } else {
+ if (!this.image[realX + realY * imgWidth]) {
+ state++;
+ }
+ }
+ if (state == 3) {
+ let diffX = x - fromX;
+ let diffY = y - fromY;
+ return Math.sqrt(diffX * diffX + diffY * diffY);
+ }
+ error += dy;
+ if (error > 0) {
+ if (y == toY) {
+ break;
+ }
+ y += ystep;
+ error -= dx;
+ }
+ }
+ let diffX2 = toX - fromX;
+ let diffY2 = toY - fromY;
+ return Math.sqrt(diffX2 * diffX2 + diffY2 * diffY2);
+ };
+ this.sizeOfBlackWhiteBlackRunBothWays = function(fromX, fromY, toX, toY) {
+ let result = this.sizeOfBlackWhiteBlackRun(fromX, fromY, toX, toY);
+ let scale = 1;
+ let otherToX = fromX - (toX - fromX);
+ if (otherToX < 0) {
+ scale = fromX / (fromX - otherToX);
+ otherToX = 0;
+ } else if (otherToX >= imgWidth) {
+ scale = (imgWidth - 1 - fromX) / (otherToX - fromX);
+ otherToX = imgWidth - 1;
+ }
+ let otherToY = Math.floor(fromY - (toY - fromY) * scale);
+ scale = 1;
+ if (otherToY < 0) {
+ scale = fromY / (fromY - otherToY);
+ otherToY = 0;
+ } else if (otherToY >= imgHeight) {
+ scale = (imgHeight - 1 - fromY) / (otherToY - fromY);
+ otherToY = imgHeight - 1;
+ }
+ otherToX = Math.floor(fromX + (otherToX - fromX) * scale);
+ result += this.sizeOfBlackWhiteBlackRun(fromX, fromY, otherToX, otherToY);
+ return result - 1;
+ };
+ this.calculateModuleSizeOneWay = function(pattern, otherPattern) {
+ let moduleSizeEst1 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(pattern.X), Math.floor(pattern.Y), Math.floor(otherPattern.X), Math.floor(otherPattern.Y));
+ let moduleSizeEst2 = this.sizeOfBlackWhiteBlackRunBothWays(Math.floor(otherPattern.X), Math.floor(otherPattern.Y), Math.floor(pattern.X), Math.floor(pattern.Y));
+ if (isNaN(moduleSizeEst1)) {
+ return moduleSizeEst2 / 7;
+ }
+ if (isNaN(moduleSizeEst2)) {
+ return moduleSizeEst1 / 7;
+ }
+ return (moduleSizeEst1 + moduleSizeEst2) / 14;
+ };
+ this.calculateModuleSize = function(topLeft, topRight, bottomLeft) {
+ return (this.calculateModuleSizeOneWay(topLeft, topRight) + this.calculateModuleSizeOneWay(topLeft, bottomLeft)) / 2;
+ };
+ this.distance = function(pattern1, pattern2) {
+ let xDiff = pattern1.X - pattern2.X;
+ let yDiff = pattern1.Y - pattern2.Y;
+ return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ };
+ this.computeDimension = function(topLeft, topRight, bottomLeft, moduleSize) {
+ let tltrCentersDimension = Math.round(this.distance(topLeft, topRight) / moduleSize);
+ let tlblCentersDimension = Math.round(this.distance(topLeft, bottomLeft) / moduleSize);
+ let dimension = (tltrCentersDimension + tlblCentersDimension >> 1) + 7;
+ switch (dimension & 3) {
+ case 0:
+ dimension++;
+ break;
+
+ case 2:
+ dimension--;
+ break;
+
+ case 3:
+ throw "Error";
+ }
+ return dimension;
+ };
+ this.findAlignmentInRegion = function(overallEstModuleSize, estAlignmentX, estAlignmentY, allowanceFactor) {
+ let allowance = Math.floor(allowanceFactor * overallEstModuleSize);
+ let alignmentAreaLeftX = Math.max(0, estAlignmentX - allowance);
+ let alignmentAreaRightX = Math.min(imgWidth - 1, estAlignmentX + allowance);
+ if (alignmentAreaRightX - alignmentAreaLeftX < overallEstModuleSize * 3) {
+ throw "Error";
+ }
+ let alignmentAreaTopY = Math.max(0, estAlignmentY - allowance);
+ let alignmentAreaBottomY = Math.min(imgHeight - 1, estAlignmentY + allowance);
+ let alignmentFinder = new AlignmentPatternFinder(this.image, alignmentAreaLeftX, alignmentAreaTopY, alignmentAreaRightX - alignmentAreaLeftX, alignmentAreaBottomY - alignmentAreaTopY, overallEstModuleSize, this.resultPointCallback);
+ return alignmentFinder.find();
+ };
+ this.createTransform = function(topLeft, topRight, bottomLeft, alignmentPattern, dimension) {
+ let dimMinusThree = dimension - 3.5;
+ let bottomRightX;
+ let bottomRightY;
+ let sourceBottomRightX;
+ let sourceBottomRightY;
+ if (alignmentPattern !== null) {
+ bottomRightX = alignmentPattern.X;
+ bottomRightY = alignmentPattern.Y;
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree - 3;
+ } else {
+ bottomRightX = topRight.X - topLeft.X + bottomLeft.X;
+ bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y;
+ sourceBottomRightX = sourceBottomRightY = dimMinusThree;
+ }
+ let transform = PerspectiveTransform.quadrilateralToQuadrilateral(3.5, 3.5, dimMinusThree, 3.5, sourceBottomRightX, sourceBottomRightY, 3.5, dimMinusThree, topLeft.X, topLeft.Y, topRight.X, topRight.Y, bottomRightX, bottomRightY, bottomLeft.X, bottomLeft.Y);
+ return transform;
+ };
+ this.sampleGrid = function(image, transform, dimension) {
+ let sampler = GridSampler;
+ return sampler.sampleGrid3(image, dimension, transform);
+ };
+ this.processFinderPatternInfo = function(info) {
+ let topLeft = info.TopLeft;
+ let topRight = info.TopRight;
+ let bottomLeft = info.BottomLeft;
+ let moduleSize = this.calculateModuleSize(topLeft, topRight, bottomLeft);
+ if (moduleSize < 1) {
+ throw "Error";
+ }
+ let dimension = this.computeDimension(topLeft, topRight, bottomLeft, moduleSize);
+ let provisionalVersion = Version.getProvisionalVersionForDimension(dimension);
+ let modulesBetweenFPCenters = provisionalVersion.DimensionForVersion - 7;
+ let alignmentPattern = null;
+ if (provisionalVersion.AlignmentPatternCenters.length > 0) {
+ let bottomRightX = topRight.X - topLeft.X + bottomLeft.X;
+ let bottomRightY = topRight.Y - topLeft.Y + bottomLeft.Y;
+ let correctionToTopLeft = 1 - 3 / modulesBetweenFPCenters;
+ let estAlignmentX = Math.floor(topLeft.X + correctionToTopLeft * (bottomRightX - topLeft.X));
+ let estAlignmentY = Math.floor(topLeft.Y + correctionToTopLeft * (bottomRightY - topLeft.Y));
+ for (let i = 4; i <= 16; i <<= 1) {
+ alignmentPattern = this.findAlignmentInRegion(moduleSize, estAlignmentX, estAlignmentY, i);
+ break;
+ }
+ }
+ let transform = this.createTransform(topLeft, topRight, bottomLeft, alignmentPattern, dimension);
+ let bits = this.sampleGrid(this.image, transform, dimension);
+ let points;
+ if (alignmentPattern === null) {
+ points = new Array(bottomLeft, topLeft, topRight);
+ } else {
+ points = new Array(bottomLeft, topLeft, topRight, alignmentPattern);
+ }
+ return new DetectorResult(bits, points);
+ };
+ this.detect = function() {
+ let info = new FinderPatternFinder().findFinderPattern(this.image);
+ return this.processFinderPatternInfo(info);
+ };
+}
+
+var FORMAT_INFO_MASK_QR = 21522;
+
+var FORMAT_INFO_DECODE_LOOKUP = new Array(new Array(21522, 0), new Array(20773, 1), new Array(24188, 2), new Array(23371, 3), new Array(17913, 4), new Array(16590, 5), new Array(20375, 6), new Array(19104, 7), new Array(30660, 8), new Array(29427, 9), new Array(32170, 10), new Array(30877, 11), new Array(26159, 12), new Array(25368, 13), new Array(27713, 14), new Array(26998, 15), new Array(5769, 16), new Array(5054, 17), new Array(7399, 18), new Array(6608, 19), new Array(1890, 20), new Array(597, 21), new Array(3340, 22), new Array(2107, 23), new Array(13663, 24), new Array(12392, 25), new Array(16177, 26), new Array(14854, 27), new Array(9396, 28), new Array(8579, 29), new Array(11994, 30), new Array(11245, 31));
+
+var BITS_SET_IN_HALF_BYTE = new Array(0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4);
+
+function FormatInformation(formatInfo) {
+ this.errorCorrectionLevel = ErrorCorrectionLevel.forBits(formatInfo >> 3 & 3);
+ this.dataMask = formatInfo & 7;
+ this.__defineGetter__("ErrorCorrectionLevel", function() {
+ return this.errorCorrectionLevel;
+ });
+ this.__defineGetter__("DataMask", function() {
+ return this.dataMask;
+ });
+ this.GetHashCode = function() {
+ return this.errorCorrectionLevel.ordinal() << 3 | this.dataMask;
+ };
+ this.Equals = function(o) {
+ let other = o;
+ return this.errorCorrectionLevel == other.errorCorrectionLevel && this.dataMask == other.dataMask;
+ };
+}
+
+FormatInformation.numBitsDiffering = function(a, b) {
+ a ^= b;
+ return BITS_SET_IN_HALF_BYTE[a & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 4) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 8) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 12) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 16) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 20) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 24) & 15] + BITS_SET_IN_HALF_BYTE[URShift(a, 28) & 15];
+};
+
+FormatInformation.decodeFormatInformation = function(maskedFormatInfo) {
+ let formatInfo = FormatInformation.doDecodeFormatInformation(maskedFormatInfo);
+ if (formatInfo !== null) {
+ return formatInfo;
+ }
+ return FormatInformation.doDecodeFormatInformation(maskedFormatInfo ^ FORMAT_INFO_MASK_QR);
+};
+
+FormatInformation.doDecodeFormatInformation = function(maskedFormatInfo) {
+ let bestDifference = 4294967295;
+ let bestFormatInfo = 0;
+ for (let i = 0; i < FORMAT_INFO_DECODE_LOOKUP.length; i++) {
+ let decodeInfo = FORMAT_INFO_DECODE_LOOKUP[i];
+ let targetInfo = decodeInfo[0];
+ if (targetInfo == maskedFormatInfo) {
+ return new FormatInformation(decodeInfo[1]);
+ }
+ let bitsDifference = this.numBitsDiffering(maskedFormatInfo, targetInfo);
+ if (bitsDifference < bestDifference) {
+ bestFormatInfo = decodeInfo[1];
+ bestDifference = bitsDifference;
+ }
+ }
+ if (bestDifference <= 3) {
+ return new FormatInformation(bestFormatInfo);
+ }
+ return null;
+};
+
+function ErrorCorrectionLevel(ordinal, bits, name) {
+ this.ordinal_Renamed_Field = ordinal;
+ this.bits = bits;
+ this.name = name;
+ this.__defineGetter__("Bits", function() {
+ return this.bits;
+ });
+ this.__defineGetter__("Name", function() {
+ return this.name;
+ });
+ this.ordinal = function() {
+ return this.ordinal_Renamed_Field;
+ };
+}
+
+var L = new ErrorCorrectionLevel(0, 1, "L");
+
+var M = new ErrorCorrectionLevel(1, 0, "M");
+
+var Q = new ErrorCorrectionLevel(2, 3, "Q");
+
+var H = new ErrorCorrectionLevel(3, 2, "H");
+
+var FOR_BITS = new Array(M, L, H, Q);
+
+ErrorCorrectionLevel.forBits = function(bits) {
+ if (bits < 0 || bits >= FOR_BITS.length) {
+ throw "ArgumentException";
+ }
+ return FOR_BITS[bits];
+};
+
+function BitMatrix(width, height) {
+ if (!height) height = width;
+ if (width < 1 || height < 1) {
+ throw "Both dimensions must be greater than 0";
+ }
+ this.width = width;
+ this.height = height;
+ let rowSize = width >> 5;
+ if ((width & 31) !== 0) {
+ rowSize++;
+ }
+ this.rowSize = rowSize;
+ this.bits = new Array(rowSize * height);
+ for (let i = 0; i < this.bits.length; i++) this.bits[i] = 0;
+ this.__defineGetter__("Width", function() {
+ return this.width;
+ });
+ this.__defineGetter__("Height", function() {
+ return this.height;
+ });
+ this.__defineGetter__("Dimension", function() {
+ if (this.width != this.height) {
+ throw "Can't call getDimension() on a non-square matrix";
+ }
+ return this.width;
+ });
+ this.get_Renamed = function(x, y) {
+ let offset = y * this.rowSize + (x >> 5);
+ return (URShift(this.bits[offset], x & 31) & 1) !== 0;
+ };
+ this.set_Renamed = function(x, y) {
+ let offset = y * this.rowSize + (x >> 5);
+ this.bits[offset] |= 1 << (x & 31);
+ };
+ this.flip = function(x, y) {
+ let offset = y * this.rowSize + (x >> 5);
+ this.bits[offset] ^= 1 << (x & 31);
+ };
+ this.clear = function() {
+ let max = this.bits.length;
+ for (let i = 0; i < max; i++) {
+ this.bits[i] = 0;
+ }
+ };
+ this.setRegion = function(left, top, width, height) {
+ if (top < 0 || left < 0) {
+ throw "Left and top must be nonnegative";
+ }
+ if (height < 1 || width < 1) {
+ throw "Height and width must be at least 1";
+ }
+ let right = left + width;
+ let bottom = top + height;
+ if (bottom > this.height || right > this.width) {
+ throw "The region must fit inside the matrix";
+ }
+ for (let y = top; y < bottom; y++) {
+ let offset = y * this.rowSize;
+ for (let x = left; x < right; x++) {
+ this.bits[offset + (x >> 5)] |= 1 << (x & 31);
+ }
+ }
+ };
+}
+
+function DataBlock(numDataCodewords, codewords) {
+ this.numDataCodewords = numDataCodewords;
+ this.codewords = codewords;
+ this.__defineGetter__("NumDataCodewords", function() {
+ return this.numDataCodewords;
+ });
+ this.__defineGetter__("Codewords", function() {
+ return this.codewords;
+ });
+}
+
+DataBlock.getDataBlocks = function(rawCodewords, version, ecLevel) {
+ if (rawCodewords.length != version.TotalCodewords) {
+ throw "ArgumentException";
+ }
+ let ecBlocks = version.getECBlocksForLevel(ecLevel);
+ let totalBlocks = 0;
+ let ecBlockArray = ecBlocks.getECBlocks();
+ for (let i = 0; i < ecBlockArray.length; i++) {
+ totalBlocks += ecBlockArray[i].Count;
+ }
+ let result = new Array(totalBlocks);
+ let numResultBlocks = 0;
+ for (let j = 0; j < ecBlockArray.length; j++) {
+ let ecBlock = ecBlockArray[j];
+ for (let i = 0; i < ecBlock.Count; i++) {
+ let numDataCodewords = ecBlock.DataCodewords;
+ let numBlockCodewords = ecBlocks.ECCodewordsPerBlock + numDataCodewords;
+ result[numResultBlocks++] = new DataBlock(numDataCodewords, new Array(numBlockCodewords));
+ }
+ }
+ let shorterBlocksTotalCodewords = result[0].codewords.length;
+ let longerBlocksStartAt = result.length - 1;
+ while (longerBlocksStartAt >= 0) {
+ let numCodewords = result[longerBlocksStartAt].codewords.length;
+ if (numCodewords == shorterBlocksTotalCodewords) {
+ break;
+ }
+ longerBlocksStartAt--;
+ }
+ longerBlocksStartAt++;
+ let shorterBlocksNumDataCodewords = shorterBlocksTotalCodewords - ecBlocks.ECCodewordsPerBlock;
+ let rawCodewordsOffset = 0;
+ for (let i = 0; i < shorterBlocksNumDataCodewords; i++) {
+ for (let j = 0; j < numResultBlocks; j++) {
+ result[j].codewords[i] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ for (let j = longerBlocksStartAt; j < numResultBlocks; j++) {
+ result[j].codewords[shorterBlocksNumDataCodewords] = rawCodewords[rawCodewordsOffset++];
+ }
+ let max = result[0].codewords.length;
+ for (let i = shorterBlocksNumDataCodewords; i < max; i++) {
+ for (let j = 0; j < numResultBlocks; j++) {
+ let iOffset = j < longerBlocksStartAt ? i : i + 1;
+ result[j].codewords[iOffset] = rawCodewords[rawCodewordsOffset++];
+ }
+ }
+ return result;
+};
+
+var DataMask = {};
+
+function BitMatrixParser(bitMatrix) {
+ let dimension = bitMatrix.Dimension;
+ if (dimension < 21 || (dimension & 3) != 1) {
+ throw "Error BitMatrixParser";
+ }
+ this.bitMatrix = bitMatrix;
+ this.parsedVersion = null;
+ this.parsedFormatInfo = null;
+ this.copyBit = function(i, j, versionBits) {
+ return this.bitMatrix.get_Renamed(i, j) ? versionBits << 1 | 1 : versionBits << 1;
+ };
+ this.readFormatInformation = function() {
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+ let formatInfoBits = 0;
+ for (let i = 0; i < 6; i++) {
+ formatInfoBits = this.copyBit(i, 8, formatInfoBits);
+ }
+ formatInfoBits = this.copyBit(7, 8, formatInfoBits);
+ formatInfoBits = this.copyBit(8, 8, formatInfoBits);
+ formatInfoBits = this.copyBit(8, 7, formatInfoBits);
+ for (let j = 5; j >= 0; j--) {
+ formatInfoBits = this.copyBit(8, j, formatInfoBits);
+ }
+ this.parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits);
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+ let dimension = this.bitMatrix.Dimension;
+ formatInfoBits = 0;
+ let iMin = dimension - 8;
+ for (let i = dimension - 1; i >= iMin; i--) {
+ formatInfoBits = this.copyBit(i, 8, formatInfoBits);
+ }
+ for (let j = dimension - 7; j < dimension; j++) {
+ formatInfoBits = this.copyBit(8, j, formatInfoBits);
+ }
+ this.parsedFormatInfo = FormatInformation.decodeFormatInformation(formatInfoBits);
+ if (this.parsedFormatInfo !== null) {
+ return this.parsedFormatInfo;
+ }
+ throw "Error readFormatInformation";
+ };
+ this.readVersion = function() {
+ if (this.parsedVersion !== null) {
+ return this.parsedVersion;
+ }
+ let dimension = this.bitMatrix.Dimension;
+ let provisionalVersion = dimension - 17 >> 2;
+ if (provisionalVersion <= 6) {
+ return Version.getVersionForNumber(provisionalVersion);
+ }
+ let versionBits = 0;
+ let ijMin = dimension - 11;
+ for (let j = 5; j >= 0; j--) {
+ for (let i = dimension - 9; i >= ijMin; i--) {
+ versionBits = this.copyBit(i, j, versionBits);
+ }
+ }
+ this.parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion == dimension) {
+ return this.parsedVersion;
+ }
+ versionBits = 0;
+ for (let i = 5; i >= 0; i--) {
+ for (let j = dimension - 9; j >= ijMin; j--) {
+ versionBits = this.copyBit(i, j, versionBits);
+ }
+ }
+ this.parsedVersion = Version.decodeVersionInformation(versionBits);
+ if (this.parsedVersion !== null && this.parsedVersion.DimensionForVersion == dimension) {
+ return this.parsedVersion;
+ }
+ throw "Error readVersion";
+ };
+ this.readCodewords = function() {
+ let formatInfo = this.readFormatInformation();
+ let version = this.readVersion();
+ let dataMask = DataMask.forReference(formatInfo.DataMask);
+ let dimension = this.bitMatrix.Dimension;
+ dataMask.unmaskBitMatrix(this.bitMatrix, dimension);
+ let functionPattern = version.buildFunctionPattern();
+ let readingUp = true;
+ let result = new Array(version.TotalCodewords);
+ let resultOffset = 0;
+ let currentByte = 0;
+ let bitsRead = 0;
+ for (let j = dimension - 1; j > 0; j -= 2) {
+ if (j == 6) {
+ j--;
+ }
+ for (let count = 0; count < dimension; count++) {
+ let i = readingUp ? dimension - 1 - count : count;
+ for (let col = 0; col < 2; col++) {
+ if (!functionPattern.get_Renamed(j - col, i)) {
+ bitsRead++;
+ currentByte <<= 1;
+ if (this.bitMatrix.get_Renamed(j - col, i)) {
+ currentByte |= 1;
+ }
+ if (bitsRead == 8) {
+ result[resultOffset++] = currentByte;
+ bitsRead = 0;
+ currentByte = 0;
+ }
+ }
+ }
+ }
+ readingUp ^= true;
+ }
+ if (resultOffset != version.TotalCodewords) {
+ throw "Error readCodewords";
+ }
+ return result;
+ };
+}
+
+DataMask.forReference = function(reference) {
+ if (reference < 0 || reference > 7) {
+ throw "System.ArgumentException";
+ }
+ return DataMask.DATA_MASKS[reference];
+};
+
+function DataMask000() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (i + j & 1) === 0;
+ };
+}
+
+function DataMask001() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (i & 1) === 0;
+ };
+}
+
+function DataMask010() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return j % 3 === 0;
+ };
+}
+
+function DataMask011() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (i + j) % 3 === 0;
+ };
+}
+
+function DataMask100() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return (URShift(i, 1) + j / 3 & 1) === 0;
+ };
+}
+
+function DataMask101() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ let temp = i * j;
+ return (temp & 1) + temp % 3 === 0;
+ };
+}
+
+function DataMask110() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ let temp = i * j;
+ return ((temp & 1) + temp % 3 & 1) === 0;
+ };
+}
+
+function DataMask111() {
+ this.unmaskBitMatrix = function(bits, dimension) {
+ for (let i = 0; i < dimension; i++) {
+ for (let j = 0; j < dimension; j++) {
+ if (this.isMasked(i, j)) {
+ bits.flip(j, i);
+ }
+ }
+ }
+ };
+ this.isMasked = function(i, j) {
+ return ((i + j & 1) + i * j % 3 & 1) === 0;
+ };
+}
+
+DataMask.DATA_MASKS = new Array(new DataMask000(), new DataMask001(), new DataMask010(), new DataMask011(), new DataMask100(), new DataMask101(), new DataMask110(), new DataMask111());
+
+function ReedSolomonDecoder(field) {
+ this.field = field;
+ this.decode = function(received, twoS) {
+ let poly = new GF256Poly(this.field, received);
+ let syndromeCoefficients = new Array(twoS);
+ for (let i = 0; i < syndromeCoefficients.length; i++) syndromeCoefficients[i] = 0;
+ let dataMatrix = false;
+ let noError = true;
+ for (let i = 0; i < twoS; i++) {
+ let value = poly.evaluateAt(this.field.exp(dataMatrix ? i + 1 : i));
+ syndromeCoefficients[syndromeCoefficients.length - 1 - i] = value;
+ if (value !== 0) {
+ noError = false;
+ }
+ }
+ if (noError) {
+ return;
+ }
+ let syndrome = new GF256Poly(this.field, syndromeCoefficients);
+ let sigmaOmega = this.runEuclideanAlgorithm(this.field.buildMonomial(twoS, 1), syndrome, twoS);
+ let sigma = sigmaOmega[0];
+ let omega = sigmaOmega[1];
+ let errorLocations = this.findErrorLocations(sigma);
+ let errorMagnitudes = this.findErrorMagnitudes(omega, errorLocations, dataMatrix);
+ for (let i = 0; i < errorLocations.length; i++) {
+ let position = received.length - 1 - this.field.log(errorLocations[i]);
+ if (position < 0) {
+ throw "ReedSolomonException Bad error location";
+ }
+ received[position] = GF256.addOrSubtract(received[position], errorMagnitudes[i]);
+ }
+ };
+ this.runEuclideanAlgorithm = function(a, b, R) {
+ if (a.Degree < b.Degree) {
+ let temp = a;
+ a = b;
+ b = temp;
+ }
+ let rLast = a;
+ let r = b;
+ let sLast = this.field.One;
+ let s = this.field.Zero;
+ let tLast = this.field.Zero;
+ let t = this.field.One;
+ while (r.Degree >= Math.floor(R / 2)) {
+ let rLastLast = rLast;
+ let sLastLast = sLast;
+ let tLastLast = tLast;
+ rLast = r;
+ sLast = s;
+ tLast = t;
+ if (rLast.Zero) {
+ throw "r_{i-1} was zero";
+ }
+ r = rLastLast;
+ let q = this.field.Zero;
+ let denominatorLeadingTerm = rLast.getCoefficient(rLast.Degree);
+ let dltInverse = this.field.inverse(denominatorLeadingTerm);
+ while (r.Degree >= rLast.Degree && !r.Zero) {
+ let degreeDiff = r.Degree - rLast.Degree;
+ let scale = this.field.multiply(r.getCoefficient(r.Degree), dltInverse);
+ q = q.addOrSubtract(this.field.buildMonomial(degreeDiff, scale));
+ r = r.addOrSubtract(rLast.multiplyByMonomial(degreeDiff, scale));
+ }
+ s = q.multiply1(sLast).addOrSubtract(sLastLast);
+ t = q.multiply1(tLast).addOrSubtract(tLastLast);
+ }
+ let sigmaTildeAtZero = t.getCoefficient(0);
+ if (sigmaTildeAtZero === 0) {
+ throw "ReedSolomonException sigmaTilde(0) was zero";
+ }
+ let inverse = this.field.inverse(sigmaTildeAtZero);
+ let sigma = t.multiply2(inverse);
+ let omega = r.multiply2(inverse);
+ return new Array(sigma, omega);
+ };
+ this.findErrorLocations = function(errorLocator) {
+ let numErrors = errorLocator.Degree;
+ if (numErrors == 1) {
+ return new Array(errorLocator.getCoefficient(1));
+ }
+ let result = new Array(numErrors);
+ let e = 0;
+ for (let i = 1; i < 256 && e < numErrors; i++) {
+ if (errorLocator.evaluateAt(i) === 0) {
+ result[e] = this.field.inverse(i);
+ e++;
+ }
+ }
+ if (e != numErrors) {
+ throw "Error locator degree does not match number of roots";
+ }
+ return result;
+ };
+ this.findErrorMagnitudes = function(errorEvaluator, errorLocations, dataMatrix) {
+ let s = errorLocations.length;
+ let result = new Array(s);
+ for (let i = 0; i < s; i++) {
+ let xiInverse = this.field.inverse(errorLocations[i]);
+ let denominator = 1;
+ for (let j = 0; j < s; j++) {
+ if (i != j) {
+ denominator = this.field.multiply(denominator, GF256.addOrSubtract(1, this.field.multiply(errorLocations[j], xiInverse)));
+ }
+ }
+ result[i] = this.field.multiply(errorEvaluator.evaluateAt(xiInverse), this.field.inverse(denominator));
+ if (dataMatrix) {
+ result[i] = this.field.multiply(result[i], xiInverse);
+ }
+ }
+ return result;
+ };
+}
+
+function GF256Poly(field, coefficients) {
+ if (coefficients === null || coefficients.length === 0) {
+ throw "System.ArgumentException";
+ }
+ this.field = field;
+ let coefficientsLength = coefficients.length;
+ if (coefficientsLength > 1 && coefficients[0] === 0) {
+ let firstNonZero = 1;
+ while (firstNonZero < coefficientsLength && coefficients[firstNonZero] === 0) {
+ firstNonZero++;
+ }
+ if (firstNonZero == coefficientsLength) {
+ this.coefficients = field.Zero.coefficients;
+ } else {
+ this.coefficients = new Array(coefficientsLength - firstNonZero);
+ for (let i = 0; i < this.coefficients.length; i++) this.coefficients[i] = 0;
+ for (let ci = 0; ci < this.coefficients.length; ci++) this.coefficients[ci] = coefficients[firstNonZero + ci];
+ }
+ } else {
+ this.coefficients = coefficients;
+ }
+ this.__defineGetter__("Zero", function() {
+ return this.coefficients[0] === 0;
+ });
+ this.__defineGetter__("Degree", function() {
+ return this.coefficients.length - 1;
+ });
+ this.__defineGetter__("Coefficients", function() {
+ return this.coefficients;
+ });
+ this.getCoefficient = function(degree) {
+ return this.coefficients[this.coefficients.length - 1 - degree];
+ };
+ this.evaluateAt = function(a) {
+ if (a === 0) {
+ return this.getCoefficient(0);
+ }
+ let size = this.coefficients.length;
+ if (a == 1) {
+ let result = 0;
+ for (let i = 0; i < size; i++) {
+ result = GF256.addOrSubtract(result, this.coefficients[i]);
+ }
+ return result;
+ }
+ let result2 = this.coefficients[0];
+ for (let i = 1; i < size; i++) {
+ result2 = GF256.addOrSubtract(this.field.multiply(a, result2), this.coefficients[i]);
+ }
+ return result2;
+ };
+ this.addOrSubtract = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (this.Zero) {
+ return other;
+ }
+ if (other.Zero) {
+ return this;
+ }
+ let smallerCoefficients = this.coefficients;
+ let largerCoefficients = other.coefficients;
+ if (smallerCoefficients.length > largerCoefficients.length) {
+ let temp = smallerCoefficients;
+ smallerCoefficients = largerCoefficients;
+ largerCoefficients = temp;
+ }
+ let sumDiff = new Array(largerCoefficients.length);
+ let lengthDiff = largerCoefficients.length - smallerCoefficients.length;
+ for (let ci = 0; ci < lengthDiff; ci++) sumDiff[ci] = largerCoefficients[ci];
+ for (let i = lengthDiff; i < largerCoefficients.length; i++) {
+ sumDiff[i] = GF256.addOrSubtract(smallerCoefficients[i - lengthDiff], largerCoefficients[i]);
+ }
+ return new GF256Poly(field, sumDiff);
+ };
+ this.multiply1 = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (this.Zero || other.Zero) {
+ return this.field.Zero;
+ }
+ let aCoefficients = this.coefficients;
+ let aLength = aCoefficients.length;
+ let bCoefficients = other.coefficients;
+ let bLength = bCoefficients.length;
+ let product = new Array(aLength + bLength - 1);
+ for (let i = 0; i < aLength; i++) {
+ let aCoeff = aCoefficients[i];
+ for (let j = 0; j < bLength; j++) {
+ product[i + j] = GF256.addOrSubtract(product[i + j], this.field.multiply(aCoeff, bCoefficients[j]));
+ }
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.multiply2 = function(scalar) {
+ if (scalar === 0) {
+ return this.field.Zero;
+ }
+ if (scalar == 1) {
+ return this;
+ }
+ let size = this.coefficients.length;
+ let product = new Array(size);
+ for (let i = 0; i < size; i++) {
+ product[i] = this.field.multiply(this.coefficients[i], scalar);
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.multiplyByMonomial = function(degree, coefficient) {
+ if (degree < 0) {
+ throw "System.ArgumentException";
+ }
+ if (coefficient === 0) {
+ return this.field.Zero;
+ }
+ let size = this.coefficients.length;
+ let product = new Array(size + degree);
+ for (let i = 0; i < product.length; i++) product[i] = 0;
+ for (let i = 0; i < size; i++) {
+ product[i] = this.field.multiply(this.coefficients[i], coefficient);
+ }
+ return new GF256Poly(this.field, product);
+ };
+ this.divide = function(other) {
+ if (this.field != other.field) {
+ throw "GF256Polys do not have same GF256 field";
+ }
+ if (other.Zero) {
+ throw "Divide by 0";
+ }
+ let quotient = this.field.Zero;
+ let remainder = this;
+ let denominatorLeadingTerm = other.getCoefficient(other.Degree);
+ let inverseDenominatorLeadingTerm = this.field.inverse(denominatorLeadingTerm);
+ while (remainder.Degree >= other.Degree && !remainder.Zero) {
+ let degreeDifference = remainder.Degree - other.Degree;
+ let scale = this.field.multiply(remainder.getCoefficient(remainder.Degree), inverseDenominatorLeadingTerm);
+ let term = other.multiplyByMonomial(degreeDifference, scale);
+ let iterationQuotient = this.field.buildMonomial(degreeDifference, scale);
+ quotient = quotient.addOrSubtract(iterationQuotient);
+ remainder = remainder.addOrSubtract(term);
+ }
+ return new Array(quotient, remainder);
+ };
+}
+
+function GF256(primitive) {
+ this.expTable = new Array(256);
+ this.logTable = new Array(256);
+ let x = 1;
+ for (let i = 0; i < 256; i++) {
+ this.expTable[i] = x;
+ x <<= 1;
+ if (x >= 256) {
+ x ^= primitive;
+ }
+ }
+ for (let i = 0; i < 255; i++) {
+ this.logTable[this.expTable[i]] = i;
+ }
+ let at0 = new Array(1);
+ at0[0] = 0;
+ this.zero = new GF256Poly(this, new Array(at0));
+ let at1 = new Array(1);
+ at1[0] = 1;
+ this.one = new GF256Poly(this, new Array(at1));
+ this.__defineGetter__("Zero", function() {
+ return this.zero;
+ });
+ this.__defineGetter__("One", function() {
+ return this.one;
+ });
+ this.buildMonomial = function(degree, coefficient) {
+ if (degree < 0) {
+ throw "System.ArgumentException";
+ }
+ if (coefficient === 0) {
+ return this.zero;
+ }
+ let coefficients = new Array(degree + 1);
+ for (let i = 0; i < coefficients.length; i++) coefficients[i] = 0;
+ coefficients[0] = coefficient;
+ return new GF256Poly(this, coefficients);
+ };
+ this.exp = function(a) {
+ return this.expTable[a];
+ };
+ this.log = function(a) {
+ if (a === 0) {
+ throw "System.ArgumentException";
+ }
+ return this.logTable[a];
+ };
+ this.inverse = function(a) {
+ if (a === 0) {
+ throw "System.ArithmeticException";
+ }
+ return this.expTable[255 - this.logTable[a]];
+ };
+ this.multiply = function(a, b) {
+ if (a === 0 || b === 0) {
+ return 0;
+ }
+ if (a == 1) {
+ return b;
+ }
+ if (b == 1) {
+ return a;
+ }
+ return this.expTable[(this.logTable[a] + this.logTable[b]) % 255];
+ };
+}
+
+GF256.QR_CODE_FIELD = new GF256(285);
+
+GF256.DATA_MATRIX_FIELD = new GF256(301);
+
+GF256.addOrSubtract = function(a, b) {
+ return a ^ b;
+};
+
+var Decoder = {};
+
+Decoder.rsDecoder = new ReedSolomonDecoder(GF256.QR_CODE_FIELD);
+
+Decoder.correctErrors = function(codewordBytes, numDataCodewords) {
+ let numCodewords = codewordBytes.length;
+ let codewordsInts = new Array(numCodewords);
+ for (let i = 0; i < numCodewords; i++) {
+ codewordsInts[i] = codewordBytes[i] & 255;
+ }
+ let numECCodewords = codewordBytes.length - numDataCodewords;
+ try {
+ Decoder.rsDecoder.decode(codewordsInts, numECCodewords);
+ } catch (rse) {
+ throw rse;
+ }
+ for (let i = 0; i < numDataCodewords; i++) {
+ codewordBytes[i] = codewordsInts[i];
+ }
+};
+
+Decoder.decode = function(bits) {
+ let parser = new BitMatrixParser(bits);
+ let version = parser.readVersion();
+ let ecLevel = parser.readFormatInformation().ErrorCorrectionLevel;
+ let codewords = parser.readCodewords();
+ let dataBlocks = DataBlock.getDataBlocks(codewords, version, ecLevel);
+ let totalBytes = 0;
+ for (let i = 0; i < dataBlocks.length; i++) {
+ totalBytes += dataBlocks[i].NumDataCodewords;
+ }
+ let resultBytes = new Array(totalBytes);
+ let resultOffset = 0;
+ for (let j = 0; j < dataBlocks.length; j++) {
+ let dataBlock = dataBlocks[j];
+ let codewordBytes = dataBlock.Codewords;
+ let numDataCodewords = dataBlock.NumDataCodewords;
+ Decoder.correctErrors(codewordBytes, numDataCodewords);
+ for (let i = 0; i < numDataCodewords; i++) {
+ resultBytes[resultOffset++] = codewordBytes[i];
+ }
+ }
+ let reader = new QRCodeDataBlockReader(resultBytes, version.VersionNumber, ecLevel.Bits);
+ return reader;
+};
+
+// mozilla: Get access to a window
+var Services = require("Services");
+
+var DebuggerServer = require("devtools/server/main").DebuggerServer;
+
+var window = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+
+var document = window.document;
+
+var Image = window.Image;
+
+var HTML_NS = "http://www.w3.org/1999/xhtml";
+
+var qrcode = {};
+
+qrcode.callback = null;
+
+qrcode.errback = null;
+
+qrcode.decode = function(src) {
+ if (arguments.length === 0) {
+ let canvas_qr = document.getElementById("qr-canvas");
+ let context = canvas_qr.getContext("2d");
+ imgWidth = canvas_qr.width;
+ imgHeight = canvas_qr.height;
+ imgU8 = context.getImageData(0, 0, imgWidth, imgHeight).data;
+ imgU32 = new Uint32Array(imgU8.buffer);
+ qrcode.result = qrcode.process(context);
+ if (qrcode.callback !== null) {
+ qrcode.callback(qrcode.result);
+ }
+ return qrcode.result;
+ } else {
+ let image = new Image();
+ image.onload = function() {
+ // mozilla: Use HTML namespace explicitly
+ let canvas_qr = document.createElementNS(HTML_NS, "canvas");
+ let context = canvas_qr.getContext("2d");
+ let nheight = image.height;
+ let nwidth = image.width;
+ if (image.width * image.height > maxImgSize) {
+ let ir = image.width / image.height;
+ nheight = Math.sqrt(maxImgSize / ir);
+ nwidth = ir * nheight;
+ }
+ canvas_qr.width = nwidth;
+ canvas_qr.height = nheight;
+ context.drawImage(image, 0, 0, canvas_qr.width, canvas_qr.height);
+ imgWidth = canvas_qr.width;
+ imgHeight = canvas_qr.height;
+ try {
+ imgU8 = context.getImageData(0, 0, canvas_qr.width, canvas_qr.height).data;
+ imgU32 = new Uint32Array(imgU8.buffer);
+ } catch (e) {
+ qrcode.result = "Cross domain image reading not supported in your browser! Save it to your computer then drag and drop the file!";
+ if (qrcode.callback !== null) {
+ qrcode.callback(qrcode.result);
+ }
+ return;
+ }
+ try {
+ qrcode.result = qrcode.process(context);
+ if (qrcode.callback !== null) {
+ qrcode.callback(qrcode.result);
+ }
+ } catch (e) {
+ if (qrcode.errback !== null) {
+ qrcode.errback(e);
+ } else {
+ console.error(e);
+ }
+ qrcode.result = "error decoding QR Code";
+ }
+ };
+ image.src = src;
+ }
+};
+
+qrcode.isUrl = function(s) {
+ let regexp = /(ftp|http|https):\/\/(\w+:{0,1}\w*@)?(\S+)(:[0-9]+)?(\/|\/([\w#!:.?+=&%@!\-\/]))?/;
+ return regexp.test(s);
+};
+
+qrcode.decode_url = function(s) {
+ let escaped = "";
+ try {
+ escaped = escape(s);
+ } catch (e) {
+ console.log(e);
+ escaped = s;
+ }
+ let ret = "";
+ try {
+ ret = decodeURIComponent(escaped);
+ } catch (e) {
+ console.log(e);
+ ret = escaped;
+ }
+ return ret;
+};
+
+qrcode.decode_utf8 = function(s) {
+ if (qrcode.isUrl(s)) return qrcode.decode_url(s); else return s;
+};
+
+qrcode.process = function(ctx) {
+ let image = qrcode.grayScaleToBitmap(qrcode.grayscale());
+ let detector = new Detector(image);
+ let qRCodeMatrix = detector.detect();
+ let reader = Decoder.decode(qRCodeMatrix.bits);
+ let data = reader.DataByte;
+ let str = "";
+ for (let i = 0; i < data.length; i++) {
+ for (let j = 0; j < data[i].length; j++) str += String.fromCharCode(data[i][j]);
+ }
+ return qrcode.decode_utf8(str);
+};
+
+qrcode.getMiddleBrightnessPerArea = function(image) {
+ let numSqrtArea = 4;
+ let areaWidth = Math.floor(imgWidth / numSqrtArea);
+ let areaHeight = Math.floor(imgHeight / numSqrtArea);
+ let minmax = new Array(numSqrtArea);
+ for (let i = 0; i < numSqrtArea; i++) {
+ minmax[i] = new Array(numSqrtArea);
+ for (let i2 = 0; i2 < numSqrtArea; i2++) {
+ minmax[i][i2] = new Array(0, 0);
+ }
+ }
+ for (let ay = 0; ay < numSqrtArea; ay++) {
+ for (let ax = 0; ax < numSqrtArea; ax++) {
+ minmax[ax][ay][0] = 255;
+ for (let dy = 0; dy < areaHeight; dy++) {
+ for (let dx = 0; dx < areaWidth; dx++) {
+ let target = image[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth];
+ if (target < minmax[ax][ay][0]) minmax[ax][ay][0] = target;
+ if (target > minmax[ax][ay][1]) minmax[ax][ay][1] = target;
+ }
+ }
+ }
+ }
+ let middle = new Array(numSqrtArea);
+ for (let i3 = 0; i3 < numSqrtArea; i3++) {
+ middle[i3] = new Array(numSqrtArea);
+ }
+ for (let ay = 0; ay < numSqrtArea; ay++) {
+ for (let ax = 0; ax < numSqrtArea; ax++) {
+ middle[ax][ay] = Math.floor((minmax[ax][ay][0] + minmax[ax][ay][1]) / 2);
+ }
+ }
+ return middle;
+};
+
+qrcode.grayScaleToBitmap = function(grayScale) {
+ let middle = qrcode.getMiddleBrightnessPerArea(grayScale);
+ let sqrtNumArea = middle.length;
+ let areaWidth = Math.floor(imgWidth / sqrtNumArea);
+ let areaHeight = Math.floor(imgHeight / sqrtNumArea);
+ let bitmap = new Array(imgHeight * imgWidth);
+ for (let ay = 0; ay < sqrtNumArea; ay++) {
+ for (let ax = 0; ax < sqrtNumArea; ax++) {
+ for (let dy = 0; dy < areaHeight; dy++) {
+ for (let dx = 0; dx < areaWidth; dx++) {
+ bitmap[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth] = grayScale[areaWidth * ax + dx + (areaHeight * ay + dy) * imgWidth] < middle[ax][ay] ? true : false;
+ }
+ }
+ }
+ }
+ return bitmap;
+};
+
+qrcode.grayscale = function() {
+ let ret = new Uint8ClampedArray(imgWidth * imgHeight);
+ for (let y = 0; y < imgHeight; y++) {
+ for (let x = 0; x < imgWidth; x++) {
+ let point = x + y * imgWidth;
+ let rgba = imgU32[point];
+ let p = (rgba & 0xFF) + ((rgba >> 8) & 0xFF) + ((rgba >> 16) & 0xFF);
+ ret[x + y * imgWidth] = p / 3;
+ }
+ }
+ return ret;
+};
+
+function URShift(number, bits) {
+ if (number >= 0) return number >> bits; else return (number >> bits) + (2 << ~bits);
+}
+
+// mozilla: Add module support
+module.exports = {
+ decodeFromURI: function(src, cb, errcb) {
+ if (cb) {
+ qrcode.callback = cb;
+ }
+ if (errcb) {
+ qrcode.errback = errcb;
+ }
+ return qrcode.decode(src);
+ },
+ decodeFromCanvas: function(canvas, cb) {
+ let context = canvas.getContext("2d");
+ imgWidth = canvas.width;
+ imgHeight = canvas.height;
+ imgU8 = context.getImageData(0, 0, imgWidth, imgHeight).data;
+ imgU32 = new Uint32Array(imgU8.buffer);
+ let result = qrcode.process(context);
+ if (cb) {
+ cb(result);
+ }
+ return result;
+ }
+};
+
+var MIN_SKIP = 3;
+
+var MAX_MODULES = 57;
+
+var INTEGER_MATH_SHIFT = 8;
+
+var CENTER_QUORUM = 2;
+
+qrcode.orderBestPatterns = function(patterns) {
+ function distance(pattern1, pattern2) {
+ let xDiff = pattern1.X - pattern2.X;
+ let yDiff = pattern1.Y - pattern2.Y;
+ return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
+ }
+ function crossProductZ(pointA, pointB, pointC) {
+ let bX = pointB.x;
+ let bY = pointB.y;
+ return (pointC.x - bX) * (pointA.y - bY) - (pointC.y - bY) * (pointA.x - bX);
+ }
+ let zeroOneDistance = distance(patterns[0], patterns[1]);
+ let oneTwoDistance = distance(patterns[1], patterns[2]);
+ let zeroTwoDistance = distance(patterns[0], patterns[2]);
+ let pointA, pointB, pointC;
+ if (oneTwoDistance >= zeroOneDistance && oneTwoDistance >= zeroTwoDistance) {
+ pointB = patterns[0];
+ pointA = patterns[1];
+ pointC = patterns[2];
+ } else if (zeroTwoDistance >= oneTwoDistance && zeroTwoDistance >= zeroOneDistance) {
+ pointB = patterns[1];
+ pointA = patterns[0];
+ pointC = patterns[2];
+ } else {
+ pointB = patterns[2];
+ pointA = patterns[0];
+ pointC = patterns[1];
+ }
+ if (crossProductZ(pointA, pointB, pointC) < 0) {
+ let temp = pointA;
+ pointA = pointC;
+ pointC = temp;
+ }
+ patterns[0] = pointA;
+ patterns[1] = pointB;
+ patterns[2] = pointC;
+};
+
+function FinderPattern(posX, posY, estimatedModuleSize) {
+ this.x = posX;
+ this.y = posY;
+ this.count = 1;
+ this.estimatedModuleSize = estimatedModuleSize;
+ this.__defineGetter__("EstimatedModuleSize", function() {
+ return this.estimatedModuleSize;
+ });
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("X", function() {
+ return this.x;
+ });
+ this.__defineGetter__("Y", function() {
+ return this.y;
+ });
+ this.incrementCount = function() {
+ this.count++;
+ };
+ this.aboutEquals = function(moduleSize, i, j) {
+ if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) {
+ let moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
+ return moduleSizeDiff <= 1 || moduleSizeDiff / this.estimatedModuleSize <= 1;
+ }
+ return false;
+ };
+}
+
+function FinderPatternInfo(patternCenters) {
+ this.bottomLeft = patternCenters[0];
+ this.topLeft = patternCenters[1];
+ this.topRight = patternCenters[2];
+ this.__defineGetter__("BottomLeft", function() {
+ return this.bottomLeft;
+ });
+ this.__defineGetter__("TopLeft", function() {
+ return this.topLeft;
+ });
+ this.__defineGetter__("TopRight", function() {
+ return this.topRight;
+ });
+}
+
+function FinderPatternFinder() {
+ this.image = null;
+ this.possibleCenters = [];
+ this.hasSkipped = false;
+ this.crossCheckStateCount = new Array(0, 0, 0, 0, 0);
+ this.resultPointCallback = null;
+ this.__defineGetter__("CrossCheckStateCount", function() {
+ this.crossCheckStateCount[0] = 0;
+ this.crossCheckStateCount[1] = 0;
+ this.crossCheckStateCount[2] = 0;
+ this.crossCheckStateCount[3] = 0;
+ this.crossCheckStateCount[4] = 0;
+ return this.crossCheckStateCount;
+ });
+ this.foundPatternCross = function(stateCount) {
+ let totalModuleSize = 0;
+ for (let i = 0; i < 5; i++) {
+ let count = stateCount[i];
+ if (count === 0) {
+ return false;
+ }
+ totalModuleSize += count;
+ }
+ if (totalModuleSize < 7) {
+ return false;
+ }
+ let moduleSize = Math.floor((totalModuleSize << INTEGER_MATH_SHIFT) / 7);
+ let maxVariance = Math.floor(moduleSize / 2);
+ return Math.abs(moduleSize - (stateCount[0] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[1] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(3 * moduleSize - (stateCount[2] << INTEGER_MATH_SHIFT)) < 3 * maxVariance && Math.abs(moduleSize - (stateCount[3] << INTEGER_MATH_SHIFT)) < maxVariance && Math.abs(moduleSize - (stateCount[4] << INTEGER_MATH_SHIFT)) < maxVariance;
+ };
+ this.centerFromEnd = function(stateCount, end) {
+ return end - stateCount[4] - stateCount[3] - stateCount[2] / 2;
+ };
+ this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) {
+ let image = this.image;
+ let maxI = imgHeight;
+ let stateCount = this.CrossCheckStateCount;
+ let i = startI;
+ while (i >= 0 && image[centerJ + i * imgWidth]) {
+ stateCount[2]++;
+ i--;
+ }
+ if (i < 0) {
+ return NaN;
+ }
+ while (i >= 0 && !image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ if (i < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i >= 0 && image[centerJ + i * imgWidth] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+ i = startI + 1;
+ while (i < maxI && image[centerJ + i * imgWidth]) {
+ stateCount[2]++;
+ i++;
+ }
+ if (i == maxI) {
+ return NaN;
+ }
+ while (i < maxI && !image[centerJ + i * imgWidth] && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ i++;
+ }
+ if (i == maxI || stateCount[3] >= maxCount) {
+ return NaN;
+ }
+ while (i < maxI && image[centerJ + i * imgWidth] && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ i++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return NaN;
+ }
+ let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return NaN;
+ }
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN;
+ };
+ this.crossCheckHorizontal = function(startJ, centerI, maxCount, originalStateCountTotal) {
+ let image = this.image;
+ let maxJ = imgWidth;
+ let stateCount = this.CrossCheckStateCount;
+ let j = startJ;
+ while (j >= 0 && image[j + centerI * imgWidth]) {
+ stateCount[2]++;
+ j--;
+ }
+ if (j < 0) {
+ return NaN;
+ }
+ while (j >= 0 && !image[j + centerI * imgWidth] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ j--;
+ }
+ if (j < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (j >= 0 && image[j + centerI * imgWidth] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ j--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+ j = startJ + 1;
+ while (j < maxJ && image[j + centerI * imgWidth]) {
+ stateCount[2]++;
+ j++;
+ }
+ if (j == maxJ) {
+ return NaN;
+ }
+ while (j < maxJ && !image[j + centerI * imgWidth] && stateCount[3] < maxCount) {
+ stateCount[3]++;
+ j++;
+ }
+ if (j == maxJ || stateCount[3] >= maxCount) {
+ return NaN;
+ }
+ while (j < maxJ && image[j + centerI * imgWidth] && stateCount[4] < maxCount) {
+ stateCount[4]++;
+ j++;
+ }
+ if (stateCount[4] >= maxCount) {
+ return NaN;
+ }
+ let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= originalStateCountTotal) {
+ return NaN;
+ }
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, j) : NaN;
+ };
+ this.handlePossibleCenter = function(stateCount, i, j) {
+ let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2] + stateCount[3] + stateCount[4];
+ let centerJ = this.centerFromEnd(stateCount, j);
+ let centerI = this.crossCheckVertical(i, Math.floor(centerJ), stateCount[2], stateCountTotal);
+ if (!isNaN(centerI)) {
+ centerJ = this.crossCheckHorizontal(Math.floor(centerJ), Math.floor(centerI), stateCount[2], stateCountTotal);
+ if (!isNaN(centerJ)) {
+ let estimatedModuleSize = stateCountTotal / 7;
+ let found = false;
+ let max = this.possibleCenters.length;
+ for (let index = 0; index < max; index++) {
+ let center = this.possibleCenters[index];
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ center.incrementCount();
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ let point = new FinderPattern(centerJ, centerI, estimatedModuleSize);
+ this.possibleCenters.push(point);
+ if (this.resultPointCallback !== null) {
+ this.resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ };
+ this.selectBestPatterns = function() {
+ let startSize = this.possibleCenters.length;
+ if (startSize < 3) {
+ throw Error("Couldn't find enough finder patterns");
+ }
+ if (startSize > 3) {
+ let totalModuleSize = 0;
+ let square = 0;
+ for (let i = 0; i < startSize; i++) {
+ let centerValue = this.possibleCenters[i].EstimatedModuleSize;
+ totalModuleSize += centerValue;
+ square += centerValue * centerValue;
+ }
+ let average = totalModuleSize / startSize;
+ this.possibleCenters.sort(function(center1, center2) {
+ let dA = Math.abs(center2.EstimatedModuleSize - average);
+ let dB = Math.abs(center1.EstimatedModuleSize - average);
+ if (dA < dB) {
+ return -1;
+ } else if (dA == dB) {
+ return 0;
+ } else {
+ return 1;
+ }
+ });
+ let stdDev = Math.sqrt(square / startSize - average * average);
+ let limit = Math.max(0.2 * average, stdDev);
+ for (let i = 0; i < this.possibleCenters.length && this.possibleCenters.length > 3; i++) {
+ let pattern = this.possibleCenters[i];
+ if (Math.abs(pattern.EstimatedModuleSize - average) > limit) {
+ // mozilla: use splice instead
+ this.possibleCenters.splice(i, 1);
+ i--;
+ }
+ }
+ }
+ if (this.possibleCenters.length > 3) {
+ this.possibleCenters.sort(function(a, b) {
+ if (a.count > b.count) {
+ return -1;
+ }
+ if (a.count < b.count) {
+ return 1;
+ }
+ return 0;
+ });
+ }
+ return new Array(this.possibleCenters[0], this.possibleCenters[1], this.possibleCenters[2]);
+ };
+ this.findRowSkip = function() {
+ let max = this.possibleCenters.length;
+ if (max <= 1) {
+ return 0;
+ }
+ let firstConfirmedCenter = null;
+ for (let i = 0; i < max; i++) {
+ let center = this.possibleCenters[i];
+ if (center.Count >= CENTER_QUORUM) {
+ if (firstConfirmedCenter === null) {
+ firstConfirmedCenter = center;
+ } else {
+ this.hasSkipped = true;
+ return Math.floor((Math.abs(firstConfirmedCenter.X - center.X) - Math.abs(firstConfirmedCenter.Y - center.Y)) / 2);
+ }
+ }
+ }
+ return 0;
+ };
+ this.haveMultiplyConfirmedCenters = function() {
+ let confirmedCount = 0;
+ let totalModuleSize = 0;
+ let max = this.possibleCenters.length;
+ for (let i = 0; i < max; i++) {
+ let pattern = this.possibleCenters[i];
+ if (pattern.Count >= CENTER_QUORUM) {
+ confirmedCount++;
+ totalModuleSize += pattern.EstimatedModuleSize;
+ }
+ }
+ if (confirmedCount < 3) {
+ return false;
+ }
+ let average = totalModuleSize / max;
+ let totalDeviation = 0;
+ for (let i = 0; i < max; i++) {
+ let pattern = this.possibleCenters[i];
+ totalDeviation += Math.abs(pattern.EstimatedModuleSize - average);
+ }
+ return totalDeviation <= 0.05 * totalModuleSize;
+ };
+ this.findFinderPattern = function(image) {
+ let tryHarder = false;
+ this.image = image;
+ let maxI = imgHeight;
+ let maxJ = imgWidth;
+ let iSkip = Math.floor(3 * maxI / (4 * MAX_MODULES));
+ if (iSkip < MIN_SKIP || tryHarder) {
+ iSkip = MIN_SKIP;
+ }
+ let done = false;
+ let stateCount = new Array(5);
+ for (let i = iSkip - 1; i < maxI && !done; i += iSkip) {
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ let currentState = 0;
+ for (let j = 0; j < maxJ; j++) {
+ if (image[j + i * imgWidth]) {
+ if ((currentState & 1) == 1) {
+ currentState++;
+ }
+ stateCount[currentState]++;
+ } else {
+ if ((currentState & 1) === 0) {
+ if (currentState == 4) {
+ if (this.foundPatternCross(stateCount)) {
+ let confirmed = this.handlePossibleCenter(stateCount, i, j);
+ if (confirmed) {
+ iSkip = 2;
+ if (this.hasSkipped) {
+ done = this.haveMultiplyConfirmedCenters();
+ } else {
+ let rowSkip = this.findRowSkip();
+ if (rowSkip > stateCount[2]) {
+ i += rowSkip - stateCount[2] - iSkip;
+ j = maxJ - 1;
+ }
+ }
+ } else {
+ do {
+ j++;
+ } while (j < maxJ && !image[j + i * imgWidth]);
+ j--;
+ }
+ currentState = 0;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ stateCount[3] = 0;
+ stateCount[4] = 0;
+ } else {
+ stateCount[0] = stateCount[2];
+ stateCount[1] = stateCount[3];
+ stateCount[2] = stateCount[4];
+ stateCount[3] = 1;
+ stateCount[4] = 0;
+ currentState = 3;
+ }
+ } else {
+ stateCount[++currentState]++;
+ }
+ } else {
+ stateCount[currentState]++;
+ }
+ }
+ }
+ if (this.foundPatternCross(stateCount)) {
+ let confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
+ if (confirmed) {
+ iSkip = stateCount[0];
+ if (this.hasSkipped) {
+ done = this.haveMultiplyConfirmedCenters();
+ }
+ }
+ }
+ }
+ let patternInfo = this.selectBestPatterns();
+ qrcode.orderBestPatterns(patternInfo);
+ return new FinderPatternInfo(patternInfo);
+ };
+}
+
+function AlignmentPattern(posX, posY, estimatedModuleSize) {
+ this.x = posX;
+ this.y = posY;
+ this.count = 1;
+ this.estimatedModuleSize = estimatedModuleSize;
+ this.__defineGetter__("EstimatedModuleSize", function() {
+ return this.estimatedModuleSize;
+ });
+ this.__defineGetter__("Count", function() {
+ return this.count;
+ });
+ this.__defineGetter__("X", function() {
+ return Math.floor(this.x);
+ });
+ this.__defineGetter__("Y", function() {
+ return Math.floor(this.y);
+ });
+ this.incrementCount = function() {
+ this.count++;
+ };
+ this.aboutEquals = function(moduleSize, i, j) {
+ if (Math.abs(i - this.y) <= moduleSize && Math.abs(j - this.x) <= moduleSize) {
+ let moduleSizeDiff = Math.abs(moduleSize - this.estimatedModuleSize);
+ return moduleSizeDiff <= 1 || moduleSizeDiff / this.estimatedModuleSize <= 1;
+ }
+ return false;
+ };
+}
+
+function AlignmentPatternFinder(image, startX, startY, width, height, moduleSize, resultPointCallback) {
+ this.image = image;
+ this.possibleCenters = [];
+ this.startX = startX;
+ this.startY = startY;
+ this.width = width;
+ this.height = height;
+ this.moduleSize = moduleSize;
+ this.crossCheckStateCount = new Array(0, 0, 0);
+ this.resultPointCallback = resultPointCallback;
+ this.centerFromEnd = function(stateCount, end) {
+ return end - stateCount[2] - stateCount[1] / 2;
+ };
+ this.foundPatternCross = function(stateCount) {
+ let moduleSize = this.moduleSize;
+ let maxVariance = moduleSize / 2;
+ for (let i = 0; i < 3; i++) {
+ if (Math.abs(moduleSize - stateCount[i]) >= maxVariance) {
+ return false;
+ }
+ }
+ return true;
+ };
+ this.crossCheckVertical = function(startI, centerJ, maxCount, originalStateCountTotal) {
+ let image = this.image;
+ let maxI = imgHeight;
+ let stateCount = this.crossCheckStateCount;
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ let i = startI;
+ while (i >= 0 && image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i--;
+ }
+ if (i < 0 || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i >= 0 && !image[centerJ + i * imgWidth] && stateCount[0] <= maxCount) {
+ stateCount[0]++;
+ i--;
+ }
+ if (stateCount[0] > maxCount) {
+ return NaN;
+ }
+ i = startI + 1;
+ while (i < maxI && image[centerJ + i * imgWidth] && stateCount[1] <= maxCount) {
+ stateCount[1]++;
+ i++;
+ }
+ if (i == maxI || stateCount[1] > maxCount) {
+ return NaN;
+ }
+ while (i < maxI && !image[centerJ + i * imgWidth] && stateCount[2] <= maxCount) {
+ stateCount[2]++;
+ i++;
+ }
+ if (stateCount[2] > maxCount) {
+ return NaN;
+ }
+ let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ if (5 * Math.abs(stateCountTotal - originalStateCountTotal) >= 2 * originalStateCountTotal) {
+ return NaN;
+ }
+ return this.foundPatternCross(stateCount) ? this.centerFromEnd(stateCount, i) : NaN;
+ };
+ this.handlePossibleCenter = function(stateCount, i, j) {
+ let stateCountTotal = stateCount[0] + stateCount[1] + stateCount[2];
+ let centerJ = this.centerFromEnd(stateCount, j);
+ let centerI = this.crossCheckVertical(i, Math.floor(centerJ), 2 * stateCount[1], stateCountTotal);
+ if (!isNaN(centerI)) {
+ let estimatedModuleSize = (stateCount[0] + stateCount[1] + stateCount[2]) / 3;
+ let max = this.possibleCenters.length;
+ for (let index = 0; index < max; index++) {
+ let center = this.possibleCenters[index];
+ if (center.aboutEquals(estimatedModuleSize, centerI, centerJ)) {
+ return new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ }
+ }
+ let point = new AlignmentPattern(centerJ, centerI, estimatedModuleSize);
+ this.possibleCenters.push(point);
+ if (this.resultPointCallback !== null) {
+ this.resultPointCallback.foundPossibleResultPoint(point);
+ }
+ }
+ return null;
+ };
+ this.find = function() {
+ let startX = this.startX;
+ let height = this.height;
+ let maxJ = startX + width;
+ let middleI = startY + (height >> 1);
+ let stateCount = new Array(0, 0, 0);
+ for (let iGen = 0; iGen < height; iGen++) {
+ let i = middleI + ((iGen & 1) === 0 ? iGen + 1 >> 1 : -(iGen + 1 >> 1));
+ stateCount[0] = 0;
+ stateCount[1] = 0;
+ stateCount[2] = 0;
+ let j = startX;
+ while (j < maxJ && !image[j + imgWidth * i]) {
+ j++;
+ }
+ let currentState = 0;
+ while (j < maxJ) {
+ if (image[j + i * imgWidth]) {
+ if (currentState == 1) {
+ stateCount[currentState]++;
+ } else {
+ if (currentState == 2) {
+ if (this.foundPatternCross(stateCount)) {
+ let confirmed = this.handlePossibleCenter(stateCount, i, j);
+ if (confirmed !== null) {
+ return confirmed;
+ }
+ }
+ stateCount[0] = stateCount[2];
+ stateCount[1] = 1;
+ stateCount[2] = 0;
+ currentState = 1;
+ } else {
+ stateCount[++currentState]++;
+ }
+ }
+ } else {
+ if (currentState == 1) {
+ currentState++;
+ }
+ stateCount[currentState]++;
+ }
+ j++;
+ }
+ if (this.foundPatternCross(stateCount)) {
+ let confirmed = this.handlePossibleCenter(stateCount, i, maxJ);
+ if (confirmed !== null) {
+ return confirmed;
+ }
+ }
+ }
+ if (this.possibleCenters.length !== 0) {
+ return this.possibleCenters[0];
+ }
+ throw "Couldn't find enough alignment patterns";
+ };
+}
+
+function QRCodeDataBlockReader(blocks, version, numErrorCorrectionCode) {
+ this.blockPointer = 0;
+ this.bitPointer = 7;
+ this.dataLength = 0;
+ this.blocks = blocks;
+ this.numErrorCorrectionCode = numErrorCorrectionCode;
+ if (version <= 9) this.dataLengthMode = 0; else if (version >= 10 && version <= 26) this.dataLengthMode = 1; else if (version >= 27 && version <= 40) this.dataLengthMode = 2;
+ this.getNextBits = function(numBits) {
+ let bits = 0;
+ if (numBits < this.bitPointer + 1) {
+ let mask = 0;
+ for (let i = 0; i < numBits; i++) {
+ mask += 1 << i;
+ }
+ mask <<= this.bitPointer - numBits + 1;
+ bits = (this.blocks[this.blockPointer] & mask) >> this.bitPointer - numBits + 1;
+ this.bitPointer -= numBits;
+ return bits;
+ } else if (numBits < this.bitPointer + 1 + 8) {
+ let mask1 = 0;
+ for (let i = 0; i < this.bitPointer + 1; i++) {
+ mask1 += 1 << i;
+ }
+ bits = (this.blocks[this.blockPointer] & mask1) << numBits - (this.bitPointer + 1);
+ this.blockPointer++;
+ bits += this.blocks[this.blockPointer] >> 8 - (numBits - (this.bitPointer + 1));
+ this.bitPointer = this.bitPointer - numBits % 8;
+ if (this.bitPointer < 0) {
+ this.bitPointer = 8 + this.bitPointer;
+ }
+ return bits;
+ } else if (numBits < this.bitPointer + 1 + 16) {
+ let mask1 = 0;
+ let mask3 = 0;
+ for (let i = 0; i < this.bitPointer + 1; i++) {
+ mask1 += 1 << i;
+ }
+ let bitsFirstBlock = (this.blocks[this.blockPointer] & mask1) << numBits - (this.bitPointer + 1);
+ this.blockPointer++;
+ let bitsSecondBlock = this.blocks[this.blockPointer] << numBits - (this.bitPointer + 1 + 8);
+ this.blockPointer++;
+ for (let i = 0; i < numBits - (this.bitPointer + 1 + 8); i++) {
+ mask3 += 1 << i;
+ }
+ mask3 <<= 8 - (numBits - (this.bitPointer + 1 + 8));
+ let bitsThirdBlock = (this.blocks[this.blockPointer] & mask3) >> 8 - (numBits - (this.bitPointer + 1 + 8));
+ bits = bitsFirstBlock + bitsSecondBlock + bitsThirdBlock;
+ this.bitPointer = this.bitPointer - (numBits - 8) % 8;
+ if (this.bitPointer < 0) {
+ this.bitPointer = 8 + this.bitPointer;
+ }
+ return bits;
+ } else {
+ return 0;
+ }
+ };
+ this.NextMode = function() {
+ if (this.blockPointer > this.blocks.length - this.numErrorCorrectionCode - 2) return 0; else return this.getNextBits(4);
+ };
+ this.getDataLength = function(modeIndicator) {
+ let index = 0;
+ while (true) {
+ if (modeIndicator >> index == 1) break;
+ index++;
+ }
+ return this.getNextBits(sizeOfDataLengthInfo[this.dataLengthMode][index]);
+ };
+ this.getRomanAndFigureString = function(dataLength) {
+ var length = dataLength;
+ let intData = 0;
+ let strData = "";
+ let tableRomanAndFigure = new Array("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K", "L", "M", "N", "O", "P", "Q", "R", "S", "T", "U", "V", "W", "X", "Y", "Z", " ", "$", "%", "*", "+", "-", ".", "/", ":");
+ do {
+ if (length > 1) {
+ intData = this.getNextBits(11);
+ let firstLetter = Math.floor(intData / 45);
+ let secondLetter = intData % 45;
+ strData += tableRomanAndFigure[firstLetter];
+ strData += tableRomanAndFigure[secondLetter];
+ length -= 2;
+ } else if (length == 1) {
+ intData = this.getNextBits(6);
+ strData += tableRomanAndFigure[intData];
+ length -= 1;
+ }
+ } while (length > 0);
+ return strData;
+ };
+ this.getFigureString = function(dataLength) {
+ var length = dataLength;
+ let intData = 0;
+ let strData = "";
+ do {
+ if (length >= 3) {
+ intData = this.getNextBits(10);
+ if (intData < 100) strData += "0";
+ if (intData < 10) strData += "0";
+ length -= 3;
+ } else if (length == 2) {
+ intData = this.getNextBits(7);
+ if (intData < 10) strData += "0";
+ length -= 2;
+ } else if (length == 1) {
+ intData = this.getNextBits(4);
+ length -= 1;
+ }
+ strData += intData;
+ } while (length > 0);
+ return strData;
+ };
+ this.get8bitByteArray = function(dataLength) {
+ var length = dataLength;
+ let intData = 0;
+ let output = [];
+ do {
+ intData = this.getNextBits(8);
+ output.push(intData);
+ length--;
+ } while (length > 0);
+ return output;
+ };
+ this.getKanjiString = function(dataLength) {
+ var length = dataLength;
+ let intData = 0;
+ let unicodeString = "";
+ do {
+ intData = this.getNextBits(13);
+ let lowerByte = intData % 192;
+ let higherByte = intData / 192;
+ let tempWord = (higherByte << 8) + lowerByte;
+ let shiftjisWord = 0;
+ if (tempWord + 33088 <= 40956) {
+ shiftjisWord = tempWord + 33088;
+ } else {
+ shiftjisWord = tempWord + 49472;
+ }
+ unicodeString += String.fromCharCode(shiftjisWord);
+ length--;
+ } while (length > 0);
+ return unicodeString;
+ };
+ this.__defineGetter__("DataByte", function() {
+ let output = [];
+ let MODE_NUMBER = 1;
+ let MODE_ROMAN_AND_NUMBER = 2;
+ let MODE_8BIT_BYTE = 4;
+ let MODE_KANJI = 8;
+ do {
+ let mode = this.NextMode();
+ if (mode === 0) {
+ if (output.length > 0) break; else throw "Empty data block";
+ }
+ if (mode != MODE_NUMBER && mode != MODE_ROMAN_AND_NUMBER && mode != MODE_8BIT_BYTE && mode != MODE_KANJI) {
+ throw "Invalid mode: " + mode + " in (block:" + this.blockPointer + " bit:" + this.bitPointer + ")";
+ }
+ let dataLength = this.getDataLength(mode);
+ if (dataLength < 1) {
+ throw "Invalid data length: " + dataLength;
+ }
+ let temp_str;
+ let ta;
+ switch (mode) {
+ case MODE_NUMBER:
+ temp_str = this.getFigureString(dataLength);
+ ta = new Array(temp_str.length);
+ for (let j = 0; j < temp_str.length; j++) ta[j] = temp_str.charCodeAt(j);
+ output.push(ta);
+ break;
+
+ case MODE_ROMAN_AND_NUMBER:
+ temp_str = this.getRomanAndFigureString(dataLength);
+ ta = new Array(temp_str.length);
+ for (let j = 0; j < temp_str.length; j++) ta[j] = temp_str.charCodeAt(j);
+ output.push(ta);
+ break;
+
+ case MODE_8BIT_BYTE:
+ let temp_sbyteArray3 = this.get8bitByteArray(dataLength);
+ output.push(temp_sbyteArray3);
+ break;
+
+ case MODE_KANJI:
+ temp_str = this.getKanjiString(dataLength);
+ output.push(temp_str);
+ break;
+ }
+ } while (true);
+ return output;
+ });
+}
diff --git a/devtools/shared/qrcode/decoder/moz.build b/devtools/shared/qrcode/decoder/moz.build
new file mode 100644
index 000000000..4442a2e90
--- /dev/null
+++ b/devtools/shared/qrcode/decoder/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'index.js',
+)
diff --git a/devtools/shared/qrcode/encoder/LICENSE b/devtools/shared/qrcode/encoder/LICENSE
new file mode 100644
index 000000000..a93630a3b
--- /dev/null
+++ b/devtools/shared/qrcode/encoder/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2009 Kazuhiko Arase
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/devtools/shared/qrcode/encoder/index.js b/devtools/shared/qrcode/encoder/index.js
new file mode 100644
index 000000000..487d0f471
--- /dev/null
+++ b/devtools/shared/qrcode/encoder/index.js
@@ -0,0 +1,1674 @@
+//---------------------------------------------------------------------
+//
+// QR Code Generator for JavaScript
+//
+// Copyright (c) 2009 Kazuhiko Arase
+//
+// URL: http://www.d-project.com/
+//
+// Licensed under the MIT license:
+// http://www.opensource.org/licenses/mit-license.php
+//
+// The word 'QR Code' is registered trademark of
+// DENSO WAVE INCORPORATED
+// http://www.denso-wave.com/qrcode/faqpatent-e.html
+//
+//---------------------------------------------------------------------
+
+var qrcode = function() {
+
+ //---------------------------------------------------------------------
+ // qrcode
+ //---------------------------------------------------------------------
+
+ /**
+ * qrcode
+ * @param typeNumber 1 to 10
+ * @param errorCorrectLevel 'L','M','Q','H'
+ */
+ var qrcode = function(typeNumber, errorCorrectLevel) {
+
+ var PAD0 = 0xEC;
+ var PAD1 = 0x11;
+
+ var _typeNumber = typeNumber;
+ var _errorCorrectLevel = QRErrorCorrectLevel[errorCorrectLevel];
+ var _modules = null;
+ var _moduleCount = 0;
+ var _dataCache = null;
+ var _dataList = new Array();
+
+ var _this = {};
+
+ var makeImpl = function(test, maskPattern) {
+
+ _moduleCount = _typeNumber * 4 + 17;
+ _modules = function(moduleCount) {
+ var modules = new Array(moduleCount);
+ for (var row = 0; row < moduleCount; row += 1) {
+ modules[row] = new Array(moduleCount);
+ for (var col = 0; col < moduleCount; col += 1) {
+ modules[row][col] = null;
+ }
+ }
+ return modules;
+ }(_moduleCount);
+
+ setupPositionProbePattern(0, 0);
+ setupPositionProbePattern(_moduleCount - 7, 0);
+ setupPositionProbePattern(0, _moduleCount - 7);
+ setupPositionAdjustPattern();
+ setupTimingPattern();
+ setupTypeInfo(test, maskPattern);
+
+ if (_typeNumber >= 7) {
+ setupTypeNumber(test);
+ }
+
+ if (_dataCache == null) {
+ _dataCache = createData(_typeNumber, _errorCorrectLevel, _dataList);
+ }
+
+ mapData(_dataCache, maskPattern);
+ };
+
+ var setupPositionProbePattern = function(row, col) {
+
+ for (var r = -1; r <= 7; r += 1) {
+
+ if (row + r <= -1 || _moduleCount <= row + r) continue;
+
+ for (var c = -1; c <= 7; c += 1) {
+
+ if (col + c <= -1 || _moduleCount <= col + c) continue;
+
+ if ( (0 <= r && r <= 6 && (c == 0 || c == 6) )
+ || (0 <= c && c <= 6 && (r == 0 || r == 6) )
+ || (2 <= r && r <= 4 && 2 <= c && c <= 4) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ };
+
+ var getBestMaskPattern = function() {
+
+ var minLostPoint = 0;
+ var pattern = 0;
+
+ for (var i = 0; i < 8; i += 1) {
+
+ makeImpl(true, i);
+
+ var lostPoint = QRUtil.getLostPoint(_this);
+
+ if (i == 0 || minLostPoint > lostPoint) {
+ minLostPoint = lostPoint;
+ pattern = i;
+ }
+ }
+
+ return pattern;
+ };
+
+ var setupTimingPattern = function() {
+
+ for (var r = 8; r < _moduleCount - 8; r += 1) {
+ if (_modules[r][6] != null) {
+ continue;
+ }
+ _modules[r][6] = (r % 2 == 0);
+ }
+
+ for (var c = 8; c < _moduleCount - 8; c += 1) {
+ if (_modules[6][c] != null) {
+ continue;
+ }
+ _modules[6][c] = (c % 2 == 0);
+ }
+ };
+
+ var setupPositionAdjustPattern = function() {
+
+ var pos = QRUtil.getPatternPosition(_typeNumber);
+
+ for (var i = 0; i < pos.length; i += 1) {
+
+ for (var j = 0; j < pos.length; j += 1) {
+
+ var row = pos[i];
+ var col = pos[j];
+
+ if (_modules[row][col] != null) {
+ continue;
+ }
+
+ for (var r = -2; r <= 2; r += 1) {
+
+ for (var c = -2; c <= 2; c += 1) {
+
+ if (r == -2 || r == 2 || c == -2 || c == 2
+ || (r == 0 && c == 0) ) {
+ _modules[row + r][col + c] = true;
+ } else {
+ _modules[row + r][col + c] = false;
+ }
+ }
+ }
+ }
+ }
+ };
+
+ var setupTypeNumber = function(test) {
+
+ var bits = QRUtil.getBCHTypeNumber(_typeNumber);
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[Math.floor(i / 3)][i % 3 + _moduleCount - 8 - 3] = mod;
+ }
+
+ for (var i = 0; i < 18; i += 1) {
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+ _modules[i % 3 + _moduleCount - 8 - 3][Math.floor(i / 3)] = mod;
+ }
+ };
+
+ var setupTypeInfo = function(test, maskPattern) {
+
+ var data = (_errorCorrectLevel << 3) | maskPattern;
+ var bits = QRUtil.getBCHTypeInfo(data);
+
+ // vertical
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 6) {
+ _modules[i][8] = mod;
+ } else if (i < 8) {
+ _modules[i + 1][8] = mod;
+ } else {
+ _modules[_moduleCount - 15 + i][8] = mod;
+ }
+ }
+
+ // horizontal
+ for (var i = 0; i < 15; i += 1) {
+
+ var mod = (!test && ( (bits >> i) & 1) == 1);
+
+ if (i < 8) {
+ _modules[8][_moduleCount - i - 1] = mod;
+ } else if (i < 9) {
+ _modules[8][15 - i - 1 + 1] = mod;
+ } else {
+ _modules[8][15 - i - 1] = mod;
+ }
+ }
+
+ // fixed module
+ _modules[_moduleCount - 8][8] = (!test);
+ };
+
+ var mapData = function(data, maskPattern) {
+
+ var inc = -1;
+ var row = _moduleCount - 1;
+ var bitIndex = 7;
+ var byteIndex = 0;
+ var maskFunc = QRUtil.getMaskFunction(maskPattern);
+
+ for (var col = _moduleCount - 1; col > 0; col -= 2) {
+
+ if (col == 6) col -= 1;
+
+ while (true) {
+
+ for (var c = 0; c < 2; c += 1) {
+
+ if (_modules[row][col - c] == null) {
+
+ var dark = false;
+
+ if (byteIndex < data.length) {
+ dark = ( ( (data[byteIndex] >>> bitIndex) & 1) == 1);
+ }
+
+ var mask = maskFunc(row, col - c);
+
+ if (mask) {
+ dark = !dark;
+ }
+
+ _modules[row][col - c] = dark;
+ bitIndex -= 1;
+
+ if (bitIndex == -1) {
+ byteIndex += 1;
+ bitIndex = 7;
+ }
+ }
+ }
+
+ row += inc;
+
+ if (row < 0 || _moduleCount <= row) {
+ row -= inc;
+ inc = -inc;
+ break;
+ }
+ }
+ }
+ };
+
+ var createBytes = function(buffer, rsBlocks) {
+
+ var offset = 0;
+
+ var maxDcCount = 0;
+ var maxEcCount = 0;
+
+ var dcdata = new Array(rsBlocks.length);
+ var ecdata = new Array(rsBlocks.length);
+
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+
+ var dcCount = rsBlocks[r].dataCount;
+ var ecCount = rsBlocks[r].totalCount - dcCount;
+
+ maxDcCount = Math.max(maxDcCount, dcCount);
+ maxEcCount = Math.max(maxEcCount, ecCount);
+
+ dcdata[r] = new Array(dcCount);
+
+ for (var i = 0; i < dcdata[r].length; i += 1) {
+ dcdata[r][i] = 0xff & buffer.getBuffer()[i + offset];
+ }
+ offset += dcCount;
+
+ var rsPoly = QRUtil.getErrorCorrectPolynomial(ecCount);
+ var rawPoly = qrPolynomial(dcdata[r], rsPoly.getLength() - 1);
+
+ var modPoly = rawPoly.mod(rsPoly);
+ ecdata[r] = new Array(rsPoly.getLength() - 1);
+ for (var i = 0; i < ecdata[r].length; i += 1) {
+ var modIndex = i + modPoly.getLength() - ecdata[r].length;
+ ecdata[r][i] = (modIndex >= 0)? modPoly.getAt(modIndex) : 0;
+ }
+ }
+
+ var totalCodeCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalCodeCount += rsBlocks[i].totalCount;
+ }
+
+ var data = new Array(totalCodeCount);
+ var index = 0;
+
+ for (var i = 0; i < maxDcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < dcdata[r].length) {
+ data[index] = dcdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ for (var i = 0; i < maxEcCount; i += 1) {
+ for (var r = 0; r < rsBlocks.length; r += 1) {
+ if (i < ecdata[r].length) {
+ data[index] = ecdata[r][i];
+ index += 1;
+ }
+ }
+ }
+
+ return data;
+ };
+
+ var createData = function(typeNumber, errorCorrectLevel, dataList) {
+
+ var rsBlocks = QRRSBlock.getRSBlocks(typeNumber, errorCorrectLevel);
+
+ var buffer = qrBitBuffer();
+
+ for (var i = 0; i < dataList.length; i += 1) {
+ var data = dataList[i];
+ buffer.put(data.getMode(), 4);
+ buffer.put(data.getLength(), QRUtil.getLengthInBits(data.getMode(), typeNumber) );
+ data.write(buffer);
+ }
+
+ // calc num max data.
+ var totalDataCount = 0;
+ for (var i = 0; i < rsBlocks.length; i += 1) {
+ totalDataCount += rsBlocks[i].dataCount;
+ }
+
+ if (buffer.getLengthInBits() > totalDataCount * 8) {
+ throw new Error('code length overflow. ('
+ + buffer.getLengthInBits()
+ + '>'
+ + totalDataCount * 8
+ + ')');
+ }
+
+ // end code
+ if (buffer.getLengthInBits() + 4 <= totalDataCount * 8) {
+ buffer.put(0, 4);
+ }
+
+ // padding
+ while (buffer.getLengthInBits() % 8 != 0) {
+ buffer.putBit(false);
+ }
+
+ // padding
+ while (true) {
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD0, 8);
+
+ if (buffer.getLengthInBits() >= totalDataCount * 8) {
+ break;
+ }
+ buffer.put(PAD1, 8);
+ }
+
+ return createBytes(buffer, rsBlocks);
+ };
+
+ _this.addData = function(data) {
+ var newData = qr8BitByte(data);
+ _dataList.push(newData);
+ _dataCache = null;
+ };
+
+ _this.isDark = function(row, col) {
+ if (row < 0 || _moduleCount <= row || col < 0 || _moduleCount <= col) {
+ throw new Error(row + ',' + col);
+ }
+ return _modules[row][col];
+ };
+
+ _this.getModuleCount = function() {
+ return _moduleCount;
+ };
+
+ _this.make = function() {
+ makeImpl(false, getBestMaskPattern() );
+ };
+
+ _this.createTableTag = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var qrHtml = '';
+
+ qrHtml += '<table style="';
+ qrHtml += ' border-width: 0px; border-style: none;';
+ qrHtml += ' border-collapse: collapse;';
+ qrHtml += ' padding: 0px; margin: ' + margin + 'px;';
+ qrHtml += '">';
+ qrHtml += '<tbody>';
+
+ for (var r = 0; r < _this.getModuleCount(); r += 1) {
+
+ qrHtml += '<tr>';
+
+ for (var c = 0; c < _this.getModuleCount(); c += 1) {
+ qrHtml += '<td style="';
+ qrHtml += ' border-width: 0px; border-style: none;';
+ qrHtml += ' border-collapse: collapse;';
+ qrHtml += ' padding: 0px; margin: 0px;';
+ qrHtml += ' width: ' + cellSize + 'px;';
+ qrHtml += ' height: ' + cellSize + 'px;';
+ qrHtml += ' background-color: ';
+ qrHtml += _this.isDark(r, c)? '#000000' : '#ffffff';
+ qrHtml += ';';
+ qrHtml += '"/>';
+ }
+
+ qrHtml += '</tr>';
+ }
+
+ qrHtml += '</tbody>';
+ qrHtml += '</table>';
+
+ return qrHtml;
+ };
+
+ _this.createImgTag = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ return createImgTag(size, size, function(x, y) {
+ if (min <= x && x < max && min <= y && y < max) {
+ var c = Math.floor( (x - min) / cellSize);
+ var r = Math.floor( (y - min) / cellSize);
+ return _this.isDark(r, c)? 0 : 1;
+ } else {
+ return 1;
+ }
+ } );
+ };
+
+ _this.createImgData = function(cellSize, margin) {
+
+ cellSize = cellSize || 2;
+ margin = (typeof margin == 'undefined')? cellSize * 4 : margin;
+
+ var size = _this.getModuleCount() * cellSize + margin * 2;
+ var min = margin;
+ var max = size - margin;
+
+ var base64 = createGifData(size, size, function(x, y) {
+ if (min <= x && x < max && min <= y && y < max) {
+ var c = Math.floor( (x - min) / cellSize);
+ var r = Math.floor( (y - min) / cellSize);
+ return _this.isDark(r, c)? 0 : 1;
+ } else {
+ return 1;
+ }
+ } );
+
+ return {
+ src: 'data:image/gif;base64,' + base64,
+ width: size,
+ height: size
+ };
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qrcode.stringToBytes
+ //---------------------------------------------------------------------
+
+ qrcode.stringToBytes = function(s) {
+ var bytes = new Array();
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ bytes.push(c & 0xff);
+ }
+ return bytes;
+ };
+
+ //---------------------------------------------------------------------
+ // qrcode.createStringToBytes
+ //---------------------------------------------------------------------
+
+ /**
+ * @param unicodeData base64 string of byte array.
+ * [16bit Unicode],[16bit Bytes], ...
+ * @param numChars
+ */
+ qrcode.createStringToBytes = function(unicodeData, numChars) {
+
+ // create conversion map.
+
+ var unicodeMap = function() {
+
+ var bin = base64DecodeInputStream(unicodeData);
+ var read = function() {
+ var b = bin.read();
+ if (b == -1) throw new Error();
+ return b;
+ };
+
+ var count = 0;
+ var unicodeMap = {};
+ while (true) {
+ var b0 = bin.read();
+ if (b0 == -1) break;
+ var b1 = read();
+ var b2 = read();
+ var b3 = read();
+ var k = String.fromCharCode( (b0 << 8) | b1);
+ var v = (b2 << 8) | b3;
+ unicodeMap[k] = v;
+ count += 1;
+ }
+ if (count != numChars) {
+ throw new Error(count + ' != ' + numChars);
+ }
+
+ return unicodeMap;
+ }();
+
+ var unknownChar = '?'.charCodeAt(0);
+
+ return function(s) {
+ var bytes = new Array();
+ for (var i = 0; i < s.length; i += 1) {
+ var c = s.charCodeAt(i);
+ if (c < 128) {
+ bytes.push(c);
+ } else {
+ var b = unicodeMap[s.charAt(i)];
+ if (typeof b == 'number') {
+ if ( (b & 0xff) == b) {
+ // 1byte
+ bytes.push(b);
+ } else {
+ // 2bytes
+ bytes.push(b >>> 8);
+ bytes.push(b & 0xff);
+ }
+ } else {
+ bytes.push(unknownChar);
+ }
+ }
+ }
+ return bytes;
+ };
+ };
+
+ //---------------------------------------------------------------------
+ // QRMode
+ //---------------------------------------------------------------------
+
+ var QRMode = {
+ MODE_NUMBER : 1 << 0,
+ MODE_ALPHA_NUM : 1 << 1,
+ MODE_8BIT_BYTE : 1 << 2,
+ MODE_KANJI : 1 << 3
+ };
+
+ //---------------------------------------------------------------------
+ // QRErrorCorrectLevel
+ //---------------------------------------------------------------------
+
+ var QRErrorCorrectLevel = {
+ L : 1,
+ M : 0,
+ Q : 3,
+ H : 2
+ };
+ // mozilla: Add module support
+ exports.QRErrorCorrectLevel = QRErrorCorrectLevel;
+
+ //---------------------------------------------------------------------
+ // QRMaskPattern
+ //---------------------------------------------------------------------
+
+ var QRMaskPattern = {
+ PATTERN000 : 0,
+ PATTERN001 : 1,
+ PATTERN010 : 2,
+ PATTERN011 : 3,
+ PATTERN100 : 4,
+ PATTERN101 : 5,
+ PATTERN110 : 6,
+ PATTERN111 : 7
+ };
+
+ //---------------------------------------------------------------------
+ // QRUtil
+ //---------------------------------------------------------------------
+
+ var QRUtil = function() {
+
+ var PATTERN_POSITION_TABLE = [
+ [],
+ [6, 18],
+ [6, 22],
+ [6, 26],
+ [6, 30],
+ [6, 34],
+ [6, 22, 38],
+ [6, 24, 42],
+ [6, 26, 46],
+ [6, 28, 50],
+ [6, 30, 54],
+ [6, 32, 58],
+ [6, 34, 62],
+ [6, 26, 46, 66],
+ [6, 26, 48, 70],
+ [6, 26, 50, 74],
+ [6, 30, 54, 78],
+ [6, 30, 56, 82],
+ [6, 30, 58, 86],
+ [6, 34, 62, 90],
+ [6, 28, 50, 72, 94],
+ [6, 26, 50, 74, 98],
+ [6, 30, 54, 78, 102],
+ [6, 28, 54, 80, 106],
+ [6, 32, 58, 84, 110],
+ [6, 30, 58, 86, 114],
+ [6, 34, 62, 90, 118],
+ [6, 26, 50, 74, 98, 122],
+ [6, 30, 54, 78, 102, 126],
+ [6, 26, 52, 78, 104, 130],
+ [6, 30, 56, 82, 108, 134],
+ [6, 34, 60, 86, 112, 138],
+ [6, 30, 58, 86, 114, 142],
+ [6, 34, 62, 90, 118, 146],
+ [6, 30, 54, 78, 102, 126, 150],
+ [6, 24, 50, 76, 102, 128, 154],
+ [6, 28, 54, 80, 106, 132, 158],
+ [6, 32, 58, 84, 110, 136, 162],
+ [6, 26, 54, 82, 110, 138, 166],
+ [6, 30, 58, 86, 114, 142, 170]
+ ];
+ var G15 = (1 << 10) | (1 << 8) | (1 << 5) | (1 << 4) | (1 << 2) | (1 << 1) | (1 << 0);
+ var G18 = (1 << 12) | (1 << 11) | (1 << 10) | (1 << 9) | (1 << 8) | (1 << 5) | (1 << 2) | (1 << 0);
+ var G15_MASK = (1 << 14) | (1 << 12) | (1 << 10) | (1 << 4) | (1 << 1);
+
+ var _this = {};
+
+ var getBCHDigit = function(data) {
+ var digit = 0;
+ while (data != 0) {
+ digit += 1;
+ data >>>= 1;
+ }
+ return digit;
+ };
+
+ _this.getBCHTypeInfo = function(data) {
+ var d = data << 10;
+ while (getBCHDigit(d) - getBCHDigit(G15) >= 0) {
+ d ^= (G15 << (getBCHDigit(d) - getBCHDigit(G15) ) );
+ }
+ return ( (data << 10) | d) ^ G15_MASK;
+ };
+
+ _this.getBCHTypeNumber = function(data) {
+ var d = data << 12;
+ while (getBCHDigit(d) - getBCHDigit(G18) >= 0) {
+ d ^= (G18 << (getBCHDigit(d) - getBCHDigit(G18) ) );
+ }
+ return (data << 12) | d;
+ };
+
+ _this.getPatternPosition = function(typeNumber) {
+ return PATTERN_POSITION_TABLE[typeNumber - 1];
+ };
+
+ _this.getMaskFunction = function(maskPattern) {
+
+ switch (maskPattern) {
+
+ case QRMaskPattern.PATTERN000 :
+ return function(i, j) { return (i + j) % 2 == 0; };
+ case QRMaskPattern.PATTERN001 :
+ return function(i, j) { return i % 2 == 0; };
+ case QRMaskPattern.PATTERN010 :
+ return function(i, j) { return j % 3 == 0; };
+ case QRMaskPattern.PATTERN011 :
+ return function(i, j) { return (i + j) % 3 == 0; };
+ case QRMaskPattern.PATTERN100 :
+ return function(i, j) { return (Math.floor(i / 2) + Math.floor(j / 3) ) % 2 == 0; };
+ case QRMaskPattern.PATTERN101 :
+ return function(i, j) { return (i * j) % 2 + (i * j) % 3 == 0; };
+ case QRMaskPattern.PATTERN110 :
+ return function(i, j) { return ( (i * j) % 2 + (i * j) % 3) % 2 == 0; };
+ case QRMaskPattern.PATTERN111 :
+ return function(i, j) { return ( (i * j) % 3 + (i + j) % 2) % 2 == 0; };
+
+ default :
+ throw new Error('bad maskPattern:' + maskPattern);
+ }
+ };
+
+ _this.getErrorCorrectPolynomial = function(errorCorrectLength) {
+ var a = qrPolynomial([1], 0);
+ for (var i = 0; i < errorCorrectLength; i += 1) {
+ a = a.multiply(qrPolynomial([1, QRMath.gexp(i)], 0) );
+ }
+ return a;
+ };
+
+ _this.getLengthInBits = function(mode, type) {
+
+ if (1 <= type && type < 10) {
+
+ // 1 - 9
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 10;
+ case QRMode.MODE_ALPHA_NUM : return 9;
+ case QRMode.MODE_8BIT_BYTE : return 8;
+ case QRMode.MODE_KANJI : return 8;
+ default :
+ throw new Error('mode:' + mode);
+ }
+
+ } else if (type < 27) {
+
+ // 10 - 26
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 12;
+ case QRMode.MODE_ALPHA_NUM : return 11;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 10;
+ default :
+ throw new Error('mode:' + mode);
+ }
+
+ } else if (type < 41) {
+
+ // 27 - 40
+
+ switch(mode) {
+ case QRMode.MODE_NUMBER : return 14;
+ case QRMode.MODE_ALPHA_NUM : return 13;
+ case QRMode.MODE_8BIT_BYTE : return 16;
+ case QRMode.MODE_KANJI : return 12;
+ default :
+ throw new Error('mode:' + mode);
+ }
+
+ } else {
+ throw new Error('type:' + type);
+ }
+ };
+
+ _this.getLostPoint = function(qrcode) {
+
+ var moduleCount = qrcode.getModuleCount();
+
+ var lostPoint = 0;
+
+ // LEVEL1
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount; col += 1) {
+
+ var sameCount = 0;
+ var dark = qrcode.isDark(row, col);
+
+ for (var r = -1; r <= 1; r += 1) {
+
+ if (row + r < 0 || moduleCount <= row + r) {
+ continue;
+ }
+
+ for (var c = -1; c <= 1; c += 1) {
+
+ if (col + c < 0 || moduleCount <= col + c) {
+ continue;
+ }
+
+ if (r == 0 && c == 0) {
+ continue;
+ }
+
+ if (dark == qrcode.isDark(row + r, col + c) ) {
+ sameCount += 1;
+ }
+ }
+ }
+
+ if (sameCount > 5) {
+ lostPoint += (3 + sameCount - 5);
+ }
+ }
+ };
+
+ // LEVEL2
+
+ for (var row = 0; row < moduleCount - 1; row += 1) {
+ for (var col = 0; col < moduleCount - 1; col += 1) {
+ var count = 0;
+ if (qrcode.isDark(row, col) ) count += 1;
+ if (qrcode.isDark(row + 1, col) ) count += 1;
+ if (qrcode.isDark(row, col + 1) ) count += 1;
+ if (qrcode.isDark(row + 1, col + 1) ) count += 1;
+ if (count == 0 || count == 4) {
+ lostPoint += 3;
+ }
+ }
+ }
+
+ // LEVEL3
+
+ for (var row = 0; row < moduleCount; row += 1) {
+ for (var col = 0; col < moduleCount - 6; col += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row, col + 1)
+ && qrcode.isDark(row, col + 2)
+ && qrcode.isDark(row, col + 3)
+ && qrcode.isDark(row, col + 4)
+ && !qrcode.isDark(row, col + 5)
+ && qrcode.isDark(row, col + 6) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount - 6; row += 1) {
+ if (qrcode.isDark(row, col)
+ && !qrcode.isDark(row + 1, col)
+ && qrcode.isDark(row + 2, col)
+ && qrcode.isDark(row + 3, col)
+ && qrcode.isDark(row + 4, col)
+ && !qrcode.isDark(row + 5, col)
+ && qrcode.isDark(row + 6, col) ) {
+ lostPoint += 40;
+ }
+ }
+ }
+
+ // LEVEL4
+
+ var darkCount = 0;
+
+ for (var col = 0; col < moduleCount; col += 1) {
+ for (var row = 0; row < moduleCount; row += 1) {
+ if (qrcode.isDark(row, col) ) {
+ darkCount += 1;
+ }
+ }
+ }
+
+ var ratio = Math.abs(100 * darkCount / moduleCount / moduleCount - 50) / 5;
+ lostPoint += ratio * 10;
+
+ return lostPoint;
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // QRMath
+ //---------------------------------------------------------------------
+
+ var QRMath = function() {
+
+ var EXP_TABLE = new Array(256);
+ var LOG_TABLE = new Array(256);
+
+ // initialize tables
+ for (var i = 0; i < 8; i += 1) {
+ EXP_TABLE[i] = 1 << i;
+ }
+ for (var i = 8; i < 256; i += 1) {
+ EXP_TABLE[i] = EXP_TABLE[i - 4]
+ ^ EXP_TABLE[i - 5]
+ ^ EXP_TABLE[i - 6]
+ ^ EXP_TABLE[i - 8];
+ }
+ for (var i = 0; i < 255; i += 1) {
+ LOG_TABLE[EXP_TABLE[i] ] = i;
+ }
+
+ var _this = {};
+
+ _this.glog = function(n) {
+
+ if (n < 1) {
+ throw new Error('glog(' + n + ')');
+ }
+
+ return LOG_TABLE[n];
+ };
+
+ _this.gexp = function(n) {
+
+ while (n < 0) {
+ n += 255;
+ }
+
+ while (n >= 256) {
+ n -= 255;
+ }
+
+ return EXP_TABLE[n];
+ };
+
+ return _this;
+ }();
+
+ //---------------------------------------------------------------------
+ // qrPolynomial
+ //---------------------------------------------------------------------
+
+ function qrPolynomial(num, shift) {
+
+ if (typeof num.length == 'undefined') {
+ throw new Error(num.length + '/' + shift);
+ }
+
+ var _num = function() {
+ var offset = 0;
+ while (offset < num.length && num[offset] == 0) {
+ offset += 1;
+ }
+ var _num = new Array(num.length - offset + shift);
+ for (var i = 0; i < num.length - offset; i += 1) {
+ _num[i] = num[i + offset];
+ }
+ return _num;
+ }();
+
+ var _this = {};
+
+ _this.getAt = function(index) {
+ return _num[index];
+ };
+
+ _this.getLength = function() {
+ return _num.length;
+ };
+
+ _this.multiply = function(e) {
+
+ var num = new Array(_this.getLength() + e.getLength() - 1);
+
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ for (var j = 0; j < e.getLength(); j += 1) {
+ num[i + j] ^= QRMath.gexp(QRMath.glog(_this.getAt(i) ) + QRMath.glog(e.getAt(j) ) );
+ }
+ }
+
+ return qrPolynomial(num, 0);
+ };
+
+ _this.mod = function(e) {
+
+ if (_this.getLength() - e.getLength() < 0) {
+ return _this;
+ }
+
+ var ratio = QRMath.glog(_this.getAt(0) ) - QRMath.glog(e.getAt(0) );
+
+ var num = new Array(_this.getLength() );
+ for (var i = 0; i < _this.getLength(); i += 1) {
+ num[i] = _this.getAt(i);
+ }
+
+ for (var i = 0; i < e.getLength(); i += 1) {
+ num[i] ^= QRMath.gexp(QRMath.glog(e.getAt(i) ) + ratio);
+ }
+
+ // recursive call
+ return qrPolynomial(num, 0).mod(e);
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // QRRSBlock
+ //---------------------------------------------------------------------
+
+ var QRRSBlock = function() {
+
+ var RS_BLOCK_TABLE = [
+
+ // L
+ // M
+ // Q
+ // H
+
+ // 1
+ [1, 26, 19],
+ [1, 26, 16],
+ [1, 26, 13],
+ [1, 26, 9],
+
+ // 2
+ [1, 44, 34],
+ [1, 44, 28],
+ [1, 44, 22],
+ [1, 44, 16],
+
+ // 3
+ [1, 70, 55],
+ [1, 70, 44],
+ [2, 35, 17],
+ [2, 35, 13],
+
+ // 4
+ [1, 100, 80],
+ [2, 50, 32],
+ [2, 50, 24],
+ [4, 25, 9],
+
+ // 5
+ [1, 134, 108],
+ [2, 67, 43],
+ [2, 33, 15, 2, 34, 16],
+ [2, 33, 11, 2, 34, 12],
+
+ // 6
+ [2, 86, 68],
+ [4, 43, 27],
+ [4, 43, 19],
+ [4, 43, 15],
+
+ // 7
+ [2, 98, 78],
+ [4, 49, 31],
+ [2, 32, 14, 4, 33, 15],
+ [4, 39, 13, 1, 40, 14],
+
+ // 8
+ [2, 121, 97],
+ [2, 60, 38, 2, 61, 39],
+ [4, 40, 18, 2, 41, 19],
+ [4, 40, 14, 2, 41, 15],
+
+ // 9
+ [2, 146, 116],
+ [3, 58, 36, 2, 59, 37],
+ [4, 36, 16, 4, 37, 17],
+ [4, 36, 12, 4, 37, 13],
+
+ // 10
+ [2, 86, 68, 2, 87, 69],
+ [4, 69, 43, 1, 70, 44],
+ [6, 43, 19, 2, 44, 20],
+ [6, 43, 15, 2, 44, 16]
+ ];
+
+ var qrRSBlock = function(totalCount, dataCount) {
+ var _this = {};
+ _this.totalCount = totalCount;
+ _this.dataCount = dataCount;
+ return _this;
+ };
+
+ var _this = {};
+
+ var getRsBlockTable = function(typeNumber, errorCorrectLevel) {
+
+ switch(errorCorrectLevel) {
+ case QRErrorCorrectLevel.L :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 0];
+ case QRErrorCorrectLevel.M :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 1];
+ case QRErrorCorrectLevel.Q :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 2];
+ case QRErrorCorrectLevel.H :
+ return RS_BLOCK_TABLE[(typeNumber - 1) * 4 + 3];
+ default :
+ return undefined;
+ }
+ };
+
+ _this.getRSBlocks = function(typeNumber, errorCorrectLevel) {
+
+ var rsBlock = getRsBlockTable(typeNumber, errorCorrectLevel);
+
+ if (typeof rsBlock == 'undefined') {
+ throw new Error('bad rs block @ typeNumber:' + typeNumber +
+ '/errorCorrectLevel:' + errorCorrectLevel);
+ }
+
+ var length = rsBlock.length / 3;
+
+ var list = new Array();
+
+ for (var i = 0; i < length; i += 1) {
+
+ var count = rsBlock[i * 3 + 0];
+ var totalCount = rsBlock[i * 3 + 1];
+ var dataCount = rsBlock[i * 3 + 2];
+
+ for (var j = 0; j < count; j += 1) {
+ list.push(qrRSBlock(totalCount, dataCount) );
+ }
+ }
+
+ return list;
+ };
+
+ return _this;
+ }();
+
+ // mozilla: Add module support
+ exports.QRRSBlock = QRRSBlock;
+
+ //---------------------------------------------------------------------
+ // qrBitBuffer
+ //---------------------------------------------------------------------
+
+ var qrBitBuffer = function() {
+
+ var _buffer = new Array();
+ var _length = 0;
+
+ var _this = {};
+
+ _this.getBuffer = function() {
+ return _buffer;
+ };
+
+ _this.getAt = function(index) {
+ var bufIndex = Math.floor(index / 8);
+ return ( (_buffer[bufIndex] >>> (7 - index % 8) ) & 1) == 1;
+ };
+
+ _this.put = function(num, length) {
+ for (var i = 0; i < length; i += 1) {
+ _this.putBit( ( (num >>> (length - i - 1) ) & 1) == 1);
+ }
+ };
+
+ _this.getLengthInBits = function() {
+ return _length;
+ };
+
+ _this.putBit = function(bit) {
+
+ var bufIndex = Math.floor(_length / 8);
+ if (_buffer.length <= bufIndex) {
+ _buffer.push(0);
+ }
+
+ if (bit) {
+ _buffer[bufIndex] |= (0x80 >>> (_length % 8) );
+ }
+
+ _length += 1;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // qr8BitByte
+ //---------------------------------------------------------------------
+
+ var qr8BitByte = function(data) {
+
+ var _mode = QRMode.MODE_8BIT_BYTE;
+ var _data = data;
+ var _bytes = qrcode.stringToBytes(data);
+
+ var _this = {};
+
+ _this.getMode = function() {
+ return _mode;
+ };
+
+ _this.getLength = function(buffer) {
+ return _bytes.length;
+ };
+
+ _this.write = function(buffer) {
+ for (var i = 0; i < _bytes.length; i += 1) {
+ buffer.put(_bytes[i], 8);
+ }
+ };
+
+ return _this;
+ };
+
+ //=====================================================================
+ // GIF Support etc.
+ //
+
+ //---------------------------------------------------------------------
+ // byteArrayOutputStream
+ //---------------------------------------------------------------------
+
+ var byteArrayOutputStream = function() {
+
+ var _bytes = new Array();
+
+ var _this = {};
+
+ _this.writeByte = function(b) {
+ _bytes.push(b & 0xff);
+ };
+
+ _this.writeShort = function(i) {
+ _this.writeByte(i);
+ _this.writeByte(i >>> 8);
+ };
+
+ _this.writeBytes = function(b, off, len) {
+ off = off || 0;
+ len = len || b.length;
+ for (var i = 0; i < len; i += 1) {
+ _this.writeByte(b[i + off]);
+ }
+ };
+
+ _this.writeString = function(s) {
+ for (var i = 0; i < s.length; i += 1) {
+ _this.writeByte(s.charCodeAt(i) );
+ }
+ };
+
+ _this.toByteArray = function() {
+ return _bytes;
+ };
+
+ _this.toString = function() {
+ var s = '';
+ s += '[';
+ for (var i = 0; i < _bytes.length; i += 1) {
+ if (i > 0) {
+ s += ',';
+ }
+ s += _bytes[i];
+ }
+ s += ']';
+ return s;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64EncodeOutputStream
+ //---------------------------------------------------------------------
+
+ var base64EncodeOutputStream = function() {
+
+ var _buffer = 0;
+ var _buflen = 0;
+ var _length = 0;
+ var _base64 = '';
+
+ var _this = {};
+
+ var writeEncoded = function(b) {
+ _base64 += String.fromCharCode(encode(b & 0x3f) );
+ };
+
+ var encode = function(n) {
+ if (n < 0) {
+ // error.
+ } else if (n < 26) {
+ return 0x41 + n;
+ } else if (n < 52) {
+ return 0x61 + (n - 26);
+ } else if (n < 62) {
+ return 0x30 + (n - 52);
+ } else if (n == 62) {
+ return 0x2b;
+ } else if (n == 63) {
+ return 0x2f;
+ }
+ throw new Error('n:' + n);
+ };
+
+ _this.writeByte = function(n) {
+
+ _buffer = (_buffer << 8) | (n & 0xff);
+ _buflen += 8;
+ _length += 1;
+
+ while (_buflen >= 6) {
+ writeEncoded(_buffer >>> (_buflen - 6) );
+ _buflen -= 6;
+ }
+ };
+
+ _this.flush = function() {
+
+ if (_buflen > 0) {
+ writeEncoded(_buffer << (6 - _buflen) );
+ _buffer = 0;
+ _buflen = 0;
+ }
+
+ if (_length % 3 != 0) {
+ // padding
+ var padlen = 3 - _length % 3;
+ for (var i = 0; i < padlen; i += 1) {
+ _base64 += '=';
+ }
+ }
+ };
+
+ _this.toString = function() {
+ return _base64;
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // base64DecodeInputStream
+ //---------------------------------------------------------------------
+
+ var base64DecodeInputStream = function(str) {
+
+ var _str = str;
+ var _pos = 0;
+ var _buffer = 0;
+ var _buflen = 0;
+
+ var _this = {};
+
+ _this.read = function() {
+
+ while (_buflen < 8) {
+
+ if (_pos >= _str.length) {
+ if (_buflen == 0) {
+ return -1;
+ }
+ throw new Error('unexpected end of file./' + _buflen);
+ }
+
+ var c = _str.charAt(_pos);
+ _pos += 1;
+
+ if (c == '=') {
+ _buflen = 0;
+ return -1;
+ } else if (c.match(/^\s$/) ) {
+ // ignore if whitespace.
+ continue;
+ }
+
+ _buffer = (_buffer << 6) | decode(c.charCodeAt(0) );
+ _buflen += 6;
+ }
+
+ var n = (_buffer >>> (_buflen - 8) ) & 0xff;
+ _buflen -= 8;
+ return n;
+ };
+
+ var decode = function(c) {
+ if (0x41 <= c && c <= 0x5a) {
+ return c - 0x41;
+ } else if (0x61 <= c && c <= 0x7a) {
+ return c - 0x61 + 26;
+ } else if (0x30 <= c && c <= 0x39) {
+ return c - 0x30 + 52;
+ } else if (c == 0x2b) {
+ return 62;
+ } else if (c == 0x2f) {
+ return 63;
+ } else {
+ throw new Error('c:' + c);
+ }
+ };
+
+ return _this;
+ };
+
+ //---------------------------------------------------------------------
+ // gifImage (B/W)
+ //---------------------------------------------------------------------
+
+ var gifImage = function(width, height) {
+
+ var _width = width;
+ var _height = height;
+ var _data = new Array(width * height);
+
+ var _this = {};
+
+ _this.setPixel = function(x, y, pixel) {
+ _data[y * _width + x] = pixel;
+ };
+
+ _this.write = function(out) {
+
+ //---------------------------------
+ // GIF Signature
+
+ out.writeString('GIF87a');
+
+ //---------------------------------
+ // Screen Descriptor
+
+ out.writeShort(_width);
+ out.writeShort(_height);
+
+ out.writeByte(0x80); // 2bit
+ out.writeByte(0);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Global Color Map
+
+ // black
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+ out.writeByte(0x00);
+
+ // white
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+ out.writeByte(0xff);
+
+ //---------------------------------
+ // Image Descriptor
+
+ out.writeString(',');
+ out.writeShort(0);
+ out.writeShort(0);
+ out.writeShort(_width);
+ out.writeShort(_height);
+ out.writeByte(0);
+
+ //---------------------------------
+ // Local Color Map
+
+ //---------------------------------
+ // Raster Data
+
+ var lzwMinCodeSize = 2;
+ var raster = getLZWRaster(lzwMinCodeSize);
+
+ out.writeByte(lzwMinCodeSize);
+
+ var offset = 0;
+
+ while (raster.length - offset > 255) {
+ out.writeByte(255);
+ out.writeBytes(raster, offset, 255);
+ offset += 255;
+ }
+
+ out.writeByte(raster.length - offset);
+ out.writeBytes(raster, offset, raster.length - offset);
+ out.writeByte(0x00);
+
+ //---------------------------------
+ // GIF Terminator
+ out.writeString(';');
+ };
+
+ var bitOutputStream = function(out) {
+
+ var _out = out;
+ var _bitLength = 0;
+ var _bitBuffer = 0;
+
+ var _this = {};
+
+ _this.write = function(data, length) {
+
+ if ( (data >>> length) != 0) {
+ throw new Error('length over');
+ }
+
+ while (_bitLength + length >= 8) {
+ _out.writeByte(0xff & ( (data << _bitLength) | _bitBuffer) );
+ length -= (8 - _bitLength);
+ data >>>= (8 - _bitLength);
+ _bitBuffer = 0;
+ _bitLength = 0;
+ }
+
+ _bitBuffer = (data << _bitLength) | _bitBuffer;
+ _bitLength = _bitLength + length;
+ };
+
+ _this.flush = function() {
+ if (_bitLength > 0) {
+ _out.writeByte(_bitBuffer);
+ }
+ };
+
+ return _this;
+ };
+
+ var getLZWRaster = function(lzwMinCodeSize) {
+
+ var clearCode = 1 << lzwMinCodeSize;
+ var endCode = (1 << lzwMinCodeSize) + 1;
+ var bitLength = lzwMinCodeSize + 1;
+
+ // Setup LZWTable
+ var table = lzwTable();
+
+ for (var i = 0; i < clearCode; i += 1) {
+ table.add(String.fromCharCode(i) );
+ }
+ table.add(String.fromCharCode(clearCode) );
+ table.add(String.fromCharCode(endCode) );
+
+ var byteOut = byteArrayOutputStream();
+ var bitOut = bitOutputStream(byteOut);
+
+ // clear code
+ bitOut.write(clearCode, bitLength);
+
+ var dataIndex = 0;
+
+ var s = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ while (dataIndex < _data.length) {
+
+ var c = String.fromCharCode(_data[dataIndex]);
+ dataIndex += 1;
+
+ if (table.contains(s + c) ) {
+
+ s = s + c;
+
+ } else {
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ if (table.size() < 0xfff) {
+
+ if (table.size() == (1 << bitLength) ) {
+ bitLength += 1;
+ }
+
+ table.add(s + c);
+ }
+
+ s = c;
+ }
+ }
+
+ bitOut.write(table.indexOf(s), bitLength);
+
+ // end code
+ bitOut.write(endCode, bitLength);
+
+ bitOut.flush();
+
+ return byteOut.toByteArray();
+ };
+
+ var lzwTable = function() {
+
+ var _map = {};
+ var _size = 0;
+
+ var _this = {};
+
+ _this.add = function(key) {
+ if (_this.contains(key) ) {
+ throw new Error('dup key:' + key);
+ }
+ _map[key] = _size;
+ _size += 1;
+ };
+
+ _this.size = function() {
+ return _size;
+ };
+
+ _this.indexOf = function(key) {
+ return _map[key];
+ };
+
+ _this.contains = function(key) {
+ return typeof _map[key] != 'undefined';
+ };
+
+ return _this;
+ };
+
+ return _this;
+ };
+
+ var createGifData = function(width, height, getPixel) {
+
+ var gif = gifImage(width, height);
+ for (var y = 0; y < height; y += 1) {
+ for (var x = 0; x < width; x += 1) {
+ gif.setPixel(x, y, getPixel(x, y) );
+ }
+ }
+
+ var b = byteArrayOutputStream();
+ gif.write(b);
+
+ var base64 = base64EncodeOutputStream();
+ var bytes = b.toByteArray();
+ for (var i = 0; i < bytes.length; i += 1) {
+ base64.writeByte(bytes[i]);
+ }
+ base64.flush();
+ return base64;
+
+ };
+
+ var createImgTag = function(width, height, getPixel, alt) {
+
+ var base64 = createGifData(width, height, getPixel);
+ var img = '';
+ img += '<img';
+ img += '\u0020src="';
+ img += 'data:image/gif;base64,';
+ img += base64;
+ img += '"';
+ img += '\u0020width="';
+ img += width;
+ img += '"';
+ img += '\u0020height="';
+ img += height;
+ img += '"';
+ if (alt) {
+ img += '\u0020alt="';
+ img += alt;
+ img += '"';
+ }
+ img += '/>';
+
+ return img;
+ };
+
+ //---------------------------------------------------------------------
+ // returns qrcode function.
+
+ return qrcode;
+}();
+
+// mozilla: Add module support
+exports.Encoder = qrcode;
diff --git a/devtools/shared/qrcode/encoder/moz.build b/devtools/shared/qrcode/encoder/moz.build
new file mode 100644
index 000000000..4442a2e90
--- /dev/null
+++ b/devtools/shared/qrcode/encoder/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'index.js',
+)
diff --git a/devtools/shared/qrcode/index.js b/devtools/shared/qrcode/index.js
new file mode 100644
index 000000000..ec3442426
--- /dev/null
+++ b/devtools/shared/qrcode/index.js
@@ -0,0 +1,116 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Cu } = require("chrome");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+
+// Lazily require encoder and decoder in case only one is needed
+Object.defineProperty(this, "Encoder", {
+ get: () => require("./encoder/index").Encoder
+});
+Object.defineProperty(this, "QRRSBlock", {
+ get: () => require("./encoder/index").QRRSBlock
+});
+Object.defineProperty(this, "QRErrorCorrectLevel", {
+ get: () => require("./encoder/index").QRErrorCorrectLevel
+});
+Object.defineProperty(this, "decoder", {
+ get: () => {
+ // Some applications don't ship the decoder, see moz.build
+ try {
+ return require("./decoder/index");
+ } catch (e) {
+ return null;
+ }
+ }
+});
+
+/**
+ * There are many "versions" of QR codes, which describes how many dots appear
+ * in the resulting image, thus limiting the amount of data that can be
+ * represented.
+ *
+ * The encoder used here allows for versions 1 - 10 (more dots for larger
+ * versions).
+ *
+ * It expects you to pick a version large enough to contain your message. Here
+ * we search for the mimimum version based on the message length.
+ * @param string message
+ * Text to encode
+ * @param string quality
+ * Quality level: L, M, Q, H
+ * @return integer
+ */
+exports.findMinimumVersion = function (message, quality) {
+ let msgLength = message.length;
+ let qualityLevel = QRErrorCorrectLevel[quality];
+ for (let version = 1; version <= 10; version++) {
+ let rsBlocks = QRRSBlock.getRSBlocks(version, qualityLevel);
+ let maxLength = rsBlocks.reduce((prev, block) => {
+ return prev + block.dataCount;
+ }, 0);
+ // Remove two bytes to fit header info
+ maxLength -= 2;
+ if (msgLength <= maxLength) {
+ return version;
+ }
+ }
+ throw new Error("Message too large");
+};
+
+/**
+ * Simple wrapper around the underlying encoder's API.
+ * @param string message
+ * Text to encode
+ * @param string quality (optional)
+ Quality level: L, M, Q, H
+ * @param integer version (optional)
+ * QR code "version" large enough to contain the message
+ * @return object with the following fields:
+ * * src: an image encoded a data URI
+ * * height: image height
+ * * width: image width
+ */
+exports.encodeToDataURI = function (message, quality, version) {
+ quality = quality || "H";
+ version = version || exports.findMinimumVersion(message, quality);
+ let encoder = new Encoder(version, quality);
+ encoder.addData(message);
+ encoder.make();
+ return encoder.createImgData();
+};
+
+/**
+ * Simple wrapper around the underlying decoder's API.
+ * @param string URI
+ * URI of an image of a QR code
+ * @return Promise
+ * The promise will be resolved with a string, which is the data inside
+ * the QR code.
+ */
+exports.decodeFromURI = function (URI) {
+ if (!decoder) {
+ return promise.reject();
+ }
+ let deferred = defer();
+ decoder.decodeFromURI(URI, deferred.resolve, deferred.reject);
+ return deferred.promise;
+};
+
+/**
+ * Decode a QR code that has been drawn to a canvas element.
+ * @param Canvas canvas
+ * <canvas> element to read from
+ * @return string
+ * The data inside the QR code
+ */
+exports.decodeFromCanvas = function (canvas) {
+ if (!decoder) {
+ throw new Error("Decoder not available");
+ }
+ return decoder.decodeFromCanvas(canvas);
+};
diff --git a/devtools/shared/qrcode/moz.build b/devtools/shared/qrcode/moz.build
new file mode 100644
index 000000000..68a093b1c
--- /dev/null
+++ b/devtools/shared/qrcode/moz.build
@@ -0,0 +1,22 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DIRS += [
+ 'encoder'
+]
+
+# Save file size on Fennec until there are active plans to use the decoder there
+if CONFIG['MOZ_BUILD_APP'] != 'mobile/android':
+ DIRS += [
+ 'decoder'
+ ]
+
+DevToolsModules(
+ 'index.js',
+)
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
diff --git a/devtools/shared/qrcode/tests/mochitest/chrome.ini b/devtools/shared/qrcode/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..f73f845e7
--- /dev/null
+++ b/devtools/shared/qrcode/tests/mochitest/chrome.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+skip-if = os == 'android'
+tags = devtools
+
+[test_decode.html]
diff --git a/devtools/shared/qrcode/tests/mochitest/test_decode.html b/devtools/shared/qrcode/tests/mochitest/test_decode.html
new file mode 100644
index 000000000..d7bf4dfb1
--- /dev/null
+++ b/devtools/shared/qrcode/tests/mochitest/test_decode.html
@@ -0,0 +1,68 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test decoding a simple message
+-->
+<head>
+ <meta charset="utf-8">
+ <title>Test decoding a simple message</title>
+
+ <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ <script type="application/javascript;version=1.8">
+window.onload = function() {
+ const { utils: Cu } = Components;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const { Task } = require("devtools/shared/task");
+ const promise = require("promise");
+ const defer = require("devtools/shared/defer");
+
+ const QR = require("devtools/shared/qrcode/index");
+
+ SimpleTest.waitForExplicitFinish();
+
+ const testImage =
+ "data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC" +
+ "/4yPqcvtD6OctNqLs968+w+G4gKU5nkaKKquLuW+QVy2tAkDTj3rfQts8CRDko" +
+ "+HPPoYRUgy9YsyldDm44mLWhHYZM6W7WaDqyCRGkZDySxpRGw2sqvLt1q5w/fo" +
+ "XyE6vnUQOJUHBlinMGh046V1F5PDqNcoqcgBOWKBKbK2N+aY+Ih49VkmqMcl2l" +
+ "dkhZUK1umE6jZXJ2ZJaujZaRqH4bpb2uZrJxvIt4Ebe9qoYYrJOsw8apz2bCut" +
+ "m9kqDcw52uuImyr5Oh1KXH1jrn2anuunywtODU/o2c6teceW39ZcLFg/fNMo1b" +
+ "t3jVw2dwTPwJq1KYG3gAklCgu37yGxeScYKyiCc+7DR34hPVQiuQ7UhJMagyEb" +
+ "lymmzJk0a9q8iTOnzp0NCgAAOw==";
+
+ Task.spawn(function() {
+ let result = yield QR.decodeFromURI(testImage);
+ is(result, "HELLO", "Decoded data URI result matches");
+ let canvas = yield drawToCanvas(testImage);
+ result = QR.decodeFromCanvas(canvas);
+ is(result, "HELLO", "Decoded canvas result matches");
+ }).then(SimpleTest.finish, ok.bind(null, false));
+
+ function drawToCanvas(src) {
+ let deferred = defer();
+ let canvas = document.createElement("canvas");
+ let context = canvas.getContext("2d");
+ let image = new Image();
+
+ image.onload = () => {
+ canvas.width = image.width;
+ canvas.height = image.height;
+ context.drawImage(image, 0, 0);
+ deferred.resolve(canvas);
+ };
+ image.src = src;
+
+ return deferred.promise;
+ }
+};
+ </script>
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
diff --git a/devtools/shared/qrcode/tests/unit/test_encode.js b/devtools/shared/qrcode/tests/unit/test_encode.js
new file mode 100644
index 000000000..92455eb6a
--- /dev/null
+++ b/devtools/shared/qrcode/tests/unit/test_encode.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+/**
+ * Test encoding a simple message.
+ */
+
+var { utils: Cu } = Components;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+const QR = require("devtools/shared/qrcode/index");
+
+function run_test() {
+ let imgData = QR.encodeToDataURI("HELLO", "L");
+ do_check_eq(imgData.src,
+ "data:image/gif;base64,R0lGODdhOgA6AIAAAAAAAP///ywAAAAAOgA6AAAC" +
+ "/4yPqcvtD6OctNqLs968+w+G4gKU5nkaKKquLuW+QVy2tAkDTj3rfQts8CRDko" +
+ "+HPPoYRUgy9YsyldDm44mLWhHYZM6W7WaDqyCRGkZDySxpRGw2sqvLt1q5w/fo" +
+ "XyE6vnUQOJUHBlinMGh046V1F5PDqNcoqcgBOWKBKbK2N+aY+Ih49VkmqMcl2l" +
+ "dkhZUK1umE6jZXJ2ZJaujZaRqH4bpb2uZrJxvIt4Ebe9qoYYrJOsw8apz2bCut" +
+ "m9kqDcw52uuImyr5Oh1KXH1jrn2anuunywtODU/o2c6teceW39ZcLFg/fNMo1b" +
+ "t3jVw2dwTPwJq1KYG3gAklCgu37yGxeScYKyiCc+7DR34hPVQiuQ7UhJMagyEb" +
+ "lymmzJk0a9q8iTOnzp0NCgAAOw==");
+ do_check_eq(imgData.width, 58);
+ do_check_eq(imgData.height, 58);
+}
diff --git a/devtools/shared/qrcode/tests/unit/xpcshell.ini b/devtools/shared/qrcode/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..f265ea382
--- /dev/null
+++ b/devtools/shared/qrcode/tests/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+
+[test_encode.js]
diff --git a/devtools/shared/security/auth.js b/devtools/shared/security/auth.js
new file mode 100644
index 000000000..9272f602e
--- /dev/null
+++ b/devtools/shared/security/auth.js
@@ -0,0 +1,653 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Ci, Cc } = require("chrome");
+var Services = require("Services");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var { dumpn, dumpv } = DevToolsUtils;
+loader.lazyRequireGetter(this, "prompt",
+ "devtools/shared/security/prompt");
+loader.lazyRequireGetter(this, "cert",
+ "devtools/shared/security/cert");
+loader.lazyRequireGetter(this, "asyncStorage",
+ "devtools/shared/async-storage");
+const { Task } = require("devtools/shared/task");
+
+/**
+ * A simple enum-like object with keys mirrored to values.
+ * This makes comparison to a specfic value simpler without having to repeat and
+ * mis-type the value.
+ */
+function createEnum(obj) {
+ for (let key in obj) {
+ obj[key] = key;
+ }
+ return obj;
+}
+
+/**
+ * |allowConnection| implementations can return various values as their |result|
+ * field to indicate what action to take. By specifying these, we can
+ * centralize the common actions available, while still allowing embedders to
+ * present their UI in whatever way they choose.
+ */
+var AuthenticationResult = exports.AuthenticationResult = createEnum({
+
+ /**
+ * Close all listening sockets, and disable them from opening again.
+ */
+ DISABLE_ALL: null,
+
+ /**
+ * Deny the current connection.
+ */
+ DENY: null,
+
+ /**
+ * Additional data needs to be exchanged before a result can be determined.
+ */
+ PENDING: null,
+
+ /**
+ * Allow the current connection.
+ */
+ ALLOW: null,
+
+ /**
+ * Allow the current connection, and persist this choice for future
+ * connections from the same client. This requires a trustable mechanism to
+ * identify the client in the future, such as the cert used during OOB_CERT.
+ */
+ ALLOW_PERSIST: null
+
+});
+
+/**
+ * An |Authenticator| implements an authentication mechanism via various hooks
+ * in the client and server debugger socket connection path (see socket.js).
+ *
+ * |Authenticator|s are stateless objects. Each hook method is passed the state
+ * it needs by the client / server code in socket.js.
+ *
+ * Separate instances of the |Authenticator| are created for each use (client
+ * connection, server listener) in case some methods are customized by the
+ * embedder for a given use case.
+ */
+var Authenticators = {};
+
+/**
+ * The Prompt authenticator displays a server-side user prompt that includes
+ * connection details, and asks the user to verify the connection. There are
+ * no cryptographic properties at work here, so it is up to the user to be sure
+ * that the client can be trusted.
+ */
+var Prompt = Authenticators.Prompt = {};
+
+Prompt.mode = "PROMPT";
+
+Prompt.Client = function () {};
+Prompt.Client.prototype = {
+
+ mode: Prompt.mode,
+
+ /**
+ * When client is about to make a new connection, verify that the connection settings
+ * are compatible with this authenticator.
+ * @throws if validation requirements are not met
+ */
+ validateSettings() {},
+
+ /**
+ * When client has just made a new socket connection, validate the connection
+ * to ensure it meets the authenticator's policies.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param cert object (optional)
+ * The server's cert details.
+ * @param s nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ * @return boolean
+ * Whether the connection is valid.
+ */
+ validateConnection() {
+ return true;
+ },
+
+ /**
+ * Work with the server to complete any additional steps required by this
+ * authenticator's policies.
+ *
+ * Debugging commences after this hook completes successfully.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param transport DebuggerTransport
+ * A transport that can be used to communicate with the server.
+ * @return A promise can be used if there is async behavior.
+ */
+ authenticate() {},
+
+};
+
+Prompt.Server = function () {};
+Prompt.Server.prototype = {
+
+ mode: Prompt.mode,
+
+ /**
+ * Verify that listener settings are appropriate for this authentication mode.
+ *
+ * @param listener SocketListener
+ * The socket listener about to be opened.
+ * @throws if validation requirements are not met
+ */
+ validateOptions() {},
+
+ /**
+ * Augment options on the listening socket about to be opened.
+ *
+ * @param listener SocketListener
+ * The socket listener about to be opened.
+ * @param socket nsIServerSocket
+ * The socket that is about to start listening.
+ */
+ augmentSocketOptions() {},
+
+ /**
+ * Augment the service discovery advertisement with any additional data needed
+ * to support this authentication mode.
+ *
+ * @param listener SocketListener
+ * The socket listener that was just opened.
+ * @param advertisement object
+ * The advertisement being built.
+ */
+ augmentAdvertisement(listener, advertisement) {
+ advertisement.authentication = Prompt.mode;
+ },
+
+ /**
+ * Determine whether a connection the server should be allowed or not based on
+ * this authenticator's policies.
+ *
+ * @param session object
+ * In PROMPT mode, the |session| includes:
+ * {
+ * client: {
+ * host,
+ * port
+ * },
+ * server: {
+ * host,
+ * port
+ * },
+ * transport
+ * }
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+ authenticate({ client, server }) {
+ if (!Services.prefs.getBoolPref("devtools.debugger.prompt-connection")) {
+ return AuthenticationResult.ALLOW;
+ }
+ return this.allowConnection({
+ authentication: this.mode,
+ client,
+ server
+ });
+ },
+
+ /**
+ * Prompt the user to accept or decline the incoming connection. The default
+ * implementation is used unless this is overridden on a particular
+ * authenticator instance.
+ *
+ * It is expected that the implementation of |allowConnection| will show a
+ * prompt to the user so that they can allow or deny the connection.
+ *
+ * @param session object
+ * In PROMPT mode, the |session| includes:
+ * {
+ * authentication: "PROMPT",
+ * client: {
+ * host,
+ * port
+ * },
+ * server: {
+ * host,
+ * port
+ * }
+ * }
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+ allowConnection: prompt.Server.defaultAllowConnection,
+
+};
+
+/**
+ * The out-of-band (OOB) cert authenticator is based on self-signed X.509 certs
+ * at both the client and server end.
+ *
+ * The user is first prompted to verify the connection, similar to the prompt
+ * method above. This prompt may display cert fingerprints if desired.
+ *
+ * Assuming the user approves the connection, further UI is used to assist the
+ * user in tranferring out-of-band (OOB) verification of the client's
+ * certificate. For example, this could take the form of a QR code that the
+ * client displays which is then scanned by a camera on the server.
+ *
+ * Since it is assumed that an attacker can't forge the client's X.509 cert, the
+ * user may also choose to always allow a client, which would permit immediate
+ * connections in the future with no user interaction needed.
+ *
+ * See docs/wifi.md for details of the authentication design.
+ */
+var OOBCert = Authenticators.OOBCert = {};
+
+OOBCert.mode = "OOB_CERT";
+
+OOBCert.Client = function () {};
+OOBCert.Client.prototype = {
+
+ mode: OOBCert.mode,
+
+ /**
+ * When client is about to make a new connection, verify that the connection settings
+ * are compatible with this authenticator.
+ * @throws if validation requirements are not met
+ */
+ validateSettings({ encryption }) {
+ if (!encryption) {
+ throw new Error(`${OOBCert.mode} authentication requires encryption.`);
+ }
+ },
+
+ /**
+ * When client has just made a new socket connection, validate the connection
+ * to ensure it meets the authenticator's policies.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param cert object (optional)
+ * The server's cert details.
+ * @param socket nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ * @return boolean
+ * Whether the connection is valid.
+ */
+ validateConnection({ cert, socket }) {
+ // Step B.7
+ // Client verifies that Server's cert matches hash(ServerCert) from the
+ // advertisement
+ dumpv("Validate server cert hash");
+ let serverCert = socket.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus.serverCert;
+ let advertisedCert = cert;
+ if (serverCert.sha256Fingerprint != advertisedCert.sha256) {
+ dumpn("Server cert hash doesn't match advertisement");
+ return false;
+ }
+ return true;
+ },
+
+ /**
+ * Work with the server to complete any additional steps required by this
+ * authenticator's policies.
+ *
+ * Debugging commences after this hook completes successfully.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param cert object (optional)
+ * The server's cert details.
+ * @param transport DebuggerTransport
+ * A transport that can be used to communicate with the server.
+ * @return A promise can be used if there is async behavior.
+ */
+ authenticate({ host, port, cert, transport }) {
+ let deferred = defer();
+ let oobData;
+
+ let activeSendDialog;
+ let closeDialog = () => {
+ // Close any prompts the client may have been showing from previous
+ // authentication steps
+ if (activeSendDialog && activeSendDialog.close) {
+ activeSendDialog.close();
+ activeSendDialog = null;
+ }
+ };
+
+ transport.hooks = {
+ onPacket: Task.async(function* (packet) {
+ closeDialog();
+ let { authResult } = packet;
+ switch (authResult) {
+ case AuthenticationResult.PENDING:
+ // Step B.8
+ // Client creates hash(ClientCert) + K(random 128-bit number)
+ oobData = yield this._createOOB();
+ activeSendDialog = this.sendOOB({
+ host,
+ port,
+ cert,
+ authResult,
+ oob: oobData
+ });
+ break;
+ case AuthenticationResult.ALLOW:
+ // Step B.12
+ // Client verifies received value matches K
+ if (packet.k != oobData.k) {
+ transport.close(new Error("Auth secret mismatch"));
+ return;
+ }
+ // Step B.13
+ // Debugging begins
+ transport.hooks = null;
+ deferred.resolve(transport);
+ break;
+ case AuthenticationResult.ALLOW_PERSIST:
+ // Server previously persisted Client as allowed
+ // Step C.5
+ // Debugging begins
+ transport.hooks = null;
+ deferred.resolve(transport);
+ break;
+ default:
+ transport.close(new Error("Invalid auth result: " + authResult));
+ return;
+ }
+ }.bind(this)),
+ onClosed(reason) {
+ closeDialog();
+ // Transport died before auth completed
+ transport.hooks = null;
+ deferred.reject(reason);
+ }
+ };
+ transport.ready();
+ return deferred.promise;
+ },
+
+ /**
+ * Create the package of data that needs to be transferred across the OOB
+ * channel.
+ */
+ _createOOB: Task.async(function* () {
+ let clientCert = yield cert.local.getOrCreate();
+ return {
+ sha256: clientCert.sha256Fingerprint,
+ k: this._createRandom()
+ };
+ }),
+
+ _createRandom() {
+ const length = 16; // 16 bytes / 128 bits
+ let rng = Cc["@mozilla.org/security/random-generator;1"]
+ .createInstance(Ci.nsIRandomGenerator);
+ let bytes = rng.generateRandomBytes(length);
+ return bytes.map(byte => byte.toString(16)).join("");
+ },
+
+ /**
+ * Send data across the OOB channel to the server to authenticate the devices.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param cert object (optional)
+ * The server's cert details.
+ * @param authResult AuthenticationResult
+ * Authentication result sent from the server.
+ * @param oob object (optional)
+ * The token data to be transferred during OOB_CERT step 8:
+ * * sha256: hash(ClientCert)
+ * * k : K(random 128-bit number)
+ * @return object containing:
+ * * close: Function to hide the notification
+ */
+ sendOOB: prompt.Client.defaultSendOOB,
+
+};
+
+OOBCert.Server = function () {};
+OOBCert.Server.prototype = {
+
+ mode: OOBCert.mode,
+
+ /**
+ * Verify that listener settings are appropriate for this authentication mode.
+ *
+ * @param listener SocketListener
+ * The socket listener about to be opened.
+ * @throws if validation requirements are not met
+ */
+ validateOptions(listener) {
+ if (!listener.encryption) {
+ throw new Error(OOBCert.mode + " authentication requires encryption.");
+ }
+ },
+
+ /**
+ * Augment options on the listening socket about to be opened.
+ *
+ * @param listener SocketListener
+ * The socket listener about to be opened.
+ * @param socket nsIServerSocket
+ * The socket that is about to start listening.
+ */
+ augmentSocketOptions(listener, socket) {
+ let requestCert = Ci.nsITLSServerSocket.REQUIRE_ALWAYS;
+ socket.setRequestClientCertificate(requestCert);
+ },
+
+ /**
+ * Augment the service discovery advertisement with any additional data needed
+ * to support this authentication mode.
+ *
+ * @param listener SocketListener
+ * The socket listener that was just opened.
+ * @param advertisement object
+ * The advertisement being built.
+ */
+ augmentAdvertisement(listener, advertisement) {
+ advertisement.authentication = OOBCert.mode;
+ // Step A.4
+ // Server announces itself via service discovery
+ // Announcement contains hash(ServerCert) as additional data
+ advertisement.cert = listener.cert;
+ },
+
+ /**
+ * Determine whether a connection the server should be allowed or not based on
+ * this authenticator's policies.
+ *
+ * @param session object
+ * In OOB_CERT mode, the |session| includes:
+ * {
+ * client: {
+ * host,
+ * port,
+ * cert: {
+ * sha256
+ * },
+ * },
+ * server: {
+ * host,
+ * port,
+ * cert: {
+ * sha256
+ * }
+ * },
+ * transport
+ * }
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+ authenticate: Task.async(function* ({ client, server, transport }) {
+ // Step B.3 / C.3
+ // TLS connection established, authentication begins
+ const storageKey = `devtools.auth.${this.mode}.approved-clients`;
+ let approvedClients = (yield asyncStorage.getItem(storageKey)) || {};
+ // Step C.4
+ // Server sees that ClientCert is from a known client via hash(ClientCert)
+ if (approvedClients[client.cert.sha256]) {
+ let authResult = AuthenticationResult.ALLOW_PERSIST;
+ transport.send({ authResult });
+ // Step C.5
+ // Debugging begins
+ return authResult;
+ }
+
+ // Step B.4
+ // Server sees that ClientCert is from a unknown client
+ // Tell client they are unknown and should display OOB client UX
+ transport.send({
+ authResult: AuthenticationResult.PENDING
+ });
+
+ // Step B.5
+ // User is shown a Allow / Deny / Always Allow prompt on the Server
+ // with Client name and hash(ClientCert)
+ let authResult = yield this.allowConnection({
+ authentication: this.mode,
+ client,
+ server
+ });
+
+ switch (authResult) {
+ case AuthenticationResult.ALLOW_PERSIST:
+ case AuthenticationResult.ALLOW:
+ break; // Further processing
+ default:
+ return authResult; // Abort for any negative results
+ }
+
+ // Examine additional data for authentication
+ let oob = yield this.receiveOOB();
+ if (!oob) {
+ dumpn("Invalid OOB data received");
+ return AuthenticationResult.DENY;
+ }
+
+ let { sha256, k } = oob;
+ // The OOB auth prompt should have transferred:
+ // hash(ClientCert) + K(random 128-bit number)
+ // from the client.
+ if (!sha256 || !k) {
+ dumpn("Invalid OOB data received");
+ return AuthenticationResult.DENY;
+ }
+
+ // Step B.10
+ // Server verifies that Client's cert matches hash(ClientCert) from
+ // out-of-band channel
+ if (client.cert.sha256 != sha256) {
+ dumpn("Client cert hash doesn't match OOB data");
+ return AuthenticationResult.DENY;
+ }
+
+ // Step B.11
+ // Server sends K to Client over TLS connection
+ transport.send({ authResult, k });
+
+ // Persist Client if we want to always allow in the future
+ if (authResult === AuthenticationResult.ALLOW_PERSIST) {
+ approvedClients[client.cert.sha256] = true;
+ yield asyncStorage.setItem(storageKey, approvedClients);
+ }
+
+ // Client may decide to abort if K does not match.
+ // Server's portion of authentication is now complete.
+
+ // Step B.13
+ // Debugging begins
+ return authResult;
+ }),
+
+ /**
+ * Prompt the user to accept or decline the incoming connection. The default
+ * implementation is used unless this is overridden on a particular
+ * authenticator instance.
+ *
+ * It is expected that the implementation of |allowConnection| will show a
+ * prompt to the user so that they can allow or deny the connection.
+ *
+ * @param session object
+ * In OOB_CERT mode, the |session| includes:
+ * {
+ * authentication: "OOB_CERT",
+ * client: {
+ * host,
+ * port,
+ * cert: {
+ * sha256
+ * },
+ * },
+ * server: {
+ * host,
+ * port,
+ * cert: {
+ * sha256
+ * }
+ * }
+ * }
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+ allowConnection: prompt.Server.defaultAllowConnection,
+
+ /**
+ * The user must transfer some data through some out of band mechanism from
+ * the client to the server to authenticate the devices.
+ *
+ * @return An object containing:
+ * * sha256: hash(ClientCert)
+ * * k : K(random 128-bit number)
+ * A promise that will be resolved to the above is also allowed.
+ */
+ receiveOOB: prompt.Server.defaultReceiveOOB,
+
+};
+
+exports.Authenticators = {
+ get(mode) {
+ if (!mode) {
+ mode = Prompt.mode;
+ }
+ for (let key in Authenticators) {
+ let auth = Authenticators[key];
+ if (auth.mode === mode) {
+ return auth;
+ }
+ }
+ throw new Error("Unknown authenticator mode: " + mode);
+ }
+};
diff --git a/devtools/shared/security/cert.js b/devtools/shared/security/cert.js
new file mode 100644
index 000000000..7dbeded63
--- /dev/null
+++ b/devtools/shared/security/cert.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Ci, Cc } = require("chrome");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+DevToolsUtils.defineLazyGetter(this, "localCertService", () => {
+ // Ensure PSM is initialized to support TLS sockets
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ return Cc["@mozilla.org/security/local-cert-service;1"]
+ .getService(Ci.nsILocalCertService);
+});
+
+const localCertName = "devtools";
+
+exports.local = {
+
+ /**
+ * Get or create a new self-signed X.509 cert to represent this device for
+ * DevTools purposes over a secure transport, like TLS.
+ *
+ * The cert is stored permanently in the profile's key store after first use,
+ * and is valid for 1 year. If an expired or otherwise invalid cert is found,
+ * it is removed and a new one is made.
+ *
+ * @return promise
+ */
+ getOrCreate() {
+ let deferred = defer();
+ localCertService.getOrCreateCert(localCertName, {
+ handleCert: function (cert, rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve(cert);
+ }
+ });
+ return deferred.promise;
+ },
+
+ /**
+ * Remove the DevTools self-signed X.509 cert for this device.
+ *
+ * @return promise
+ */
+ remove() {
+ let deferred = defer();
+ localCertService.removeCert(localCertName, {
+ handleCert: function (rv) {
+ if (rv) {
+ deferred.reject(rv);
+ return;
+ }
+ deferred.resolve();
+ }
+ });
+ return deferred.promise;
+ }
+
+};
diff --git a/devtools/shared/security/docs/wifi.md b/devtools/shared/security/docs/wifi.md
new file mode 100644
index 000000000..9ba94fc82
--- /dev/null
+++ b/devtools/shared/security/docs/wifi.md
@@ -0,0 +1,154 @@
+Overview
+--------
+
+### Remote Debugging Today
+
+Connecting to the Dev Tools debugging server on a remote device (like
+B2G) via USB (which requires ADB) is too complex to setup and use.
+Dealing with ADB is confusing, especially on Windows and Linux where
+there are driver issues / udev rules to set up first. We have made
+various attempts to simplify this and probably will continue to try our
+best, but it doesn't seem like the UX will ever be great with ADB
+involved.
+
+### Wi-Fi
+
+We're interested in making the debugging server available over Wi-Fi,
+mainly in an attempt to simplify the UX. This of course presents new
+security challenges to address, but we must also keep in mind that **if
+our plan to address security results in a complex UX, then it may not be
+a net gain over the USB & ADB route**.
+
+To be clear, we are not trying to expose ADB over Wi-Fi at all, only the
+Dev Tools debugging server.
+
+### Security
+
+TLS is used to provide encryption of the data in transit. Both parties
+use self-signed certs to identify themselves. There is a one-time setup
+process to authenticate a new device. This is explained in many more
+details later on in this document.
+
+Definitions
+-----------
+
+- **Device / Server**: Firefox OS phone (or Fennec, remote Firefox,
+ etc.)
+- **Computer / Client**: Machine running desktop Firefox w/ WebIDE
+
+Proposal
+--------
+
+This proposal uses TLS with self-signed certs to allow Clients to
+connect to Servers through an encrypted, authenticated channel. After the
+first connection from a new Client, the Client is saved on the Server
+(if the user wants to always allow) and can connect freely in the future
+(assuming Wi-Fi debugging is still enabled).
+
+### Default State
+
+The device does not listen over Wi-Fi at all by default.
+
+### Part A: Enabling Wi-Fi Debugging
+
+1. User goes to Developer menu on Device
+2. User checks "DevTools over Wi-Fi" to enable the feature
+ - Persistent notification displayed in notification bar reminding
+ user that this is enabled
+
+3. Device begins listening on random TCP socket via Wi-Fi only
+4. Device announces itself via service discovery
+ - Announcements only go to the local LAN / same subnet
+ - The announcement contains hash(DeviceCert) as additional data
+
+The Device remains listening as long as the feature is enabled.
+
+### Part B: Using Wi-Fi Debugging (new computer)
+
+Here are the details of connecting from a new computer to the device:
+
+1. Computer detects Device as available for connection via service
+ discovery
+2. User chooses device to start connection on Computer
+3. TLS connection established, authentication begins
+4. Device sees that ComputerCert is from an unknown client (since it is
+ new)
+5. User is shown an Allow / Deny / Always Allow prompt on the Device
+ with Computer name and hash(ComputerCert)
+ - If Deny is chosen, the connection is terminated and exponential
+ backoff begins (larger with each successive Deny)
+ - If Allow is chosen, the connection proceeds, but nothing is
+ stored for the future
+ - If Always Allow is chosen, the connection proceeds, and
+ hash(ComputerCert) is saved for future attempts
+
+6. Device waits for out-of-band data
+7. Computer verifies that Device's cert matches hash(DeviceCert) from
+ the advertisement
+8. Computer creates hash(ComputerCert) and K(random 128-bit number)
+9. Out-of-band channel is used to move result of step 8 from Computer
+ to Device
+ - For Firefox Desktop -\> Firefox OS, Desktop will make a QR code,
+ and FxOS will scan it
+ - For non-mobile servers, some other approach is likely needed,
+ perhaps a short code form for the user to transfer
+
+10. Device verifies that Computer's cert matches hash(ComputerCert) from
+ out-of-band channel
+11. Device sends K to Computer over the TLS connection
+12. Computer verifies received value matches K
+13. Debugging begins
+
+### Part C: Using Wi-Fi Debugging (known computer)
+
+Here are the details of connecting from a known computer (saved via
+Always Allow) to the device:
+
+1. Computer detects Device as available for connection via service
+ discovery
+2. User choose device to start connection on Computer
+3. TLS connection established, authentication begins
+4. Device sees that ComputerCert is from a known client via
+ hash(ComputerCert)
+5. Debugging begins
+
+### Other Details
+
+- When there is a socket listening for connections, they will only be
+ accepted via Wi-Fi
+ - The socket will only listen on the external, Wi-Fi interface
+ - This is to ensure local apps can't connect to the socket
+- Socket remains listening as long as the feature is enabled
+
+### UX
+
+This design seems convenient and of relatively low burden on the user.
+If they choose to save the Computer for future connections, it becomes a
+one click connection from Computer to Device, as it is over USB today.
+
+### Possible Attacks
+
+Someone could try to DoS the phone via many connection attempts. The
+exponential backoff should mitigate this concern. ([bug
+1022692](https://bugzilla.mozilla.org/show_bug.cgi?id=1022692))
+
+### Comparison to ADB
+
+While it would be nice if we could instead leverage ADB here, that
+doesn’t seem viable because:
+
+- ADB comes with a lot of setup / troubleshooting pain
+ - Users don’t understand it or why it is needed for us
+ - Each OS has several UX issues with ADB that make it annoying to
+ use
+- ADB has a much larger attack surface area, simply because it has
+ many more lower level functions than the Developer Tools protocol we
+ are exposing here
+
+Acknowledgments
+---------------
+
+- J. Ryan Stinnett started this project from the DevTools team
+- Brian Warner created many of the specific details of the authentication
+ protocol
+- Trevor Perrin helped vet the authentication protocol
diff --git a/devtools/shared/security/moz.build b/devtools/shared/security/moz.build
new file mode 100644
index 000000000..f4fd1ca1a
--- /dev/null
+++ b/devtools/shared/security/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'auth.js',
+ 'cert.js',
+ 'prompt.js',
+ 'socket.js',
+)
diff --git a/devtools/shared/security/prompt.js b/devtools/shared/security/prompt.js
new file mode 100644
index 000000000..aad9b7211
--- /dev/null
+++ b/devtools/shared/security/prompt.js
@@ -0,0 +1,179 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Ci } = require("chrome");
+var Services = require("Services");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "DebuggerSocket",
+ "devtools/shared/security/socket", true);
+loader.lazyRequireGetter(this, "AuthenticationResult",
+ "devtools/shared/security/auth", true);
+
+const {LocalizationHelper} = require("devtools/shared/l10n");
+const L10N = new LocalizationHelper("devtools/shared/locales/debugger.properties");
+
+var Client = exports.Client = {};
+var Server = exports.Server = {};
+
+/**
+ * During OOB_CERT authentication, a notification dialog like this is used to
+ * to display a token which the user must transfer through some mechanism to the
+ * server to authenticate the devices.
+ *
+ * This implementation presents the token as text for the user to transfer
+ * manually. For a mobile device, you should override this implementation with
+ * something more convenient, such as displaying a QR code.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param cert object (optional)
+ * The server's cert details.
+ * @param authResult AuthenticationResult
+ * Authentication result sent from the server.
+ * @param oob object (optional)
+ * The token data to be transferred during OOB_CERT step 8:
+ * * sha256: hash(ClientCert)
+ * * k : K(random 128-bit number)
+ * @return object containing:
+ * * close: Function to hide the notification
+ */
+Client.defaultSendOOB = ({ authResult, oob }) => {
+ // Only show in the PENDING state
+ if (authResult != AuthenticationResult.PENDING) {
+ throw new Error("Expected PENDING result, got " + authResult);
+ }
+ let title = L10N.getStr("clientSendOOBTitle");
+ let header = L10N.getStr("clientSendOOBHeader");
+ let hashMsg = L10N.getFormatStr("clientSendOOBHash", oob.sha256);
+ let token = oob.sha256.replace(/:/g, "").toLowerCase() + oob.k;
+ let tokenMsg = L10N.getFormatStr("clientSendOOBToken", token);
+ let msg = `${header}\n\n${hashMsg}\n${tokenMsg}`;
+ let prompt = Services.prompt;
+ let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_CANCEL;
+
+ // Listen for the window our prompt opens, so we can close it programatically
+ let promptWindow;
+ let windowListener = {
+ onOpenWindow(xulWindow) {
+ let win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindow);
+ win.addEventListener("load", function listener() {
+ win.removeEventListener("load", listener, false);
+ if (win.document.documentElement.getAttribute("id") != "commonDialog") {
+ return;
+ }
+ // Found the window
+ promptWindow = win;
+ Services.wm.removeListener(windowListener);
+ }, false);
+ },
+ onCloseWindow() {},
+ onWindowTitleChange() {}
+ };
+ Services.wm.addListener(windowListener);
+
+ // nsIPrompt is typically a blocking API, so |executeSoon| to get around this
+ DevToolsUtils.executeSoon(() => {
+ prompt.confirmEx(null, title, msg, flags, null, null, null, null,
+ { value: false });
+ });
+
+ return {
+ close() {
+ if (!promptWindow) {
+ return;
+ }
+ promptWindow.document.documentElement.acceptDialog();
+ promptWindow = null;
+ }
+ };
+};
+
+/**
+ * Prompt the user to accept or decline the incoming connection. This is the
+ * default implementation that products embedding the debugger server may
+ * choose to override. This can be overridden via |allowConnection| on the
+ * socket's authenticator instance.
+ *
+ * @param session object
+ * The session object will contain at least the following fields:
+ * {
+ * authentication,
+ * client: {
+ * host,
+ * port
+ * },
+ * server: {
+ * host,
+ * port
+ * }
+ * }
+ * Specific authentication modes may include additional fields. Check
+ * the different |allowConnection| methods in ./auth.js.
+ * @return An AuthenticationResult value.
+ * A promise that will be resolved to the above is also allowed.
+ */
+Server.defaultAllowConnection = ({ client, server }) => {
+ let title = L10N.getStr("remoteIncomingPromptTitle");
+ let header = L10N.getStr("remoteIncomingPromptHeader");
+ let clientEndpoint = `${client.host}:${client.port}`;
+ let clientMsg = L10N.getFormatStr("remoteIncomingPromptClientEndpoint", clientEndpoint);
+ let serverEndpoint = `${server.host}:${server.port}`;
+ let serverMsg = L10N.getFormatStr("remoteIncomingPromptServerEndpoint", serverEndpoint);
+ let footer = L10N.getStr("remoteIncomingPromptFooter");
+ let msg = `${header}\n\n${clientMsg}\n${serverMsg}\n\n${footer}`;
+ let disableButton = L10N.getStr("remoteIncomingPromptDisable");
+ let prompt = Services.prompt;
+ let flags = prompt.BUTTON_POS_0 * prompt.BUTTON_TITLE_OK +
+ prompt.BUTTON_POS_1 * prompt.BUTTON_TITLE_CANCEL +
+ prompt.BUTTON_POS_2 * prompt.BUTTON_TITLE_IS_STRING +
+ prompt.BUTTON_POS_1_DEFAULT;
+ let result = prompt.confirmEx(null, title, msg, flags, null, null,
+ disableButton, null, { value: false });
+ if (result === 0) {
+ return AuthenticationResult.ALLOW;
+ }
+ if (result === 2) {
+ return AuthenticationResult.DISABLE_ALL;
+ }
+ return AuthenticationResult.DENY;
+};
+
+/**
+ * During OOB_CERT authentication, the user must transfer some data through some
+ * out of band mechanism from the client to the server to authenticate the
+ * devices.
+ *
+ * This implementation prompts the user for a token as constructed by
+ * |Client.defaultSendOOB| that the user needs to transfer manually. For a
+ * mobile device, you should override this implementation with something more
+ * convenient, such as reading a QR code.
+ *
+ * @return An object containing:
+ * * sha256: hash(ClientCert)
+ * * k : K(random 128-bit number)
+ * A promise that will be resolved to the above is also allowed.
+ */
+Server.defaultReceiveOOB = () => {
+ let title = L10N.getStr("serverReceiveOOBTitle");
+ let msg = L10N.getStr("serverReceiveOOBBody");
+ let input = { value: null };
+ let prompt = Services.prompt;
+ let result = prompt.prompt(null, title, msg, input, null, { value: false });
+ if (!result) {
+ return null;
+ }
+ // Re-create original object from token
+ input = input.value.trim();
+ let sha256 = input.substring(0, 64);
+ sha256 = sha256.replace(/\w{2}/g, "$&:").slice(0, -1).toUpperCase();
+ let k = input.substring(64);
+ return { sha256, k };
+};
diff --git a/devtools/shared/security/socket.js b/devtools/shared/security/socket.js
new file mode 100644
index 000000000..068a8ea81
--- /dev/null
+++ b/devtools/shared/security/socket.js
@@ -0,0 +1,793 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var { Ci, Cc, CC, Cr, Cu } = require("chrome");
+
+// Ensure PSM is initialized to support TLS sockets
+Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+
+var Services = require("Services");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var { dumpn, dumpv } = DevToolsUtils;
+loader.lazyRequireGetter(this, "WebSocketServer",
+ "devtools/server/websocket-server");
+loader.lazyRequireGetter(this, "DebuggerTransport",
+ "devtools/shared/transport/transport", true);
+loader.lazyRequireGetter(this, "WebSocketDebuggerTransport",
+ "devtools/shared/transport/websocket-transport");
+loader.lazyRequireGetter(this, "DebuggerServer",
+ "devtools/server/main", true);
+loader.lazyRequireGetter(this, "discovery",
+ "devtools/shared/discovery/discovery");
+loader.lazyRequireGetter(this, "cert",
+ "devtools/shared/security/cert");
+loader.lazyRequireGetter(this, "Authenticators",
+ "devtools/shared/security/auth", true);
+loader.lazyRequireGetter(this, "AuthenticationResult",
+ "devtools/shared/security/auth", true);
+
+DevToolsUtils.defineLazyGetter(this, "nsFile", () => {
+ return CC("@mozilla.org/file/local;1", "nsIFile", "initWithPath");
+});
+
+DevToolsUtils.defineLazyGetter(this, "socketTransportService", () => {
+ return Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+});
+
+DevToolsUtils.defineLazyGetter(this, "certOverrideService", () => {
+ return Cc["@mozilla.org/security/certoverride;1"]
+ .getService(Ci.nsICertOverrideService);
+});
+
+DevToolsUtils.defineLazyGetter(this, "nssErrorsService", () => {
+ return Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+});
+
+const { Task } = require("devtools/shared/task");
+
+var DebuggerSocket = {};
+
+/**
+ * Connects to a debugger server socket.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param webSocket boolean (optional)
+ * Whether to use WebSocket protocol to connect. Defaults to false.
+ * @param authenticator Authenticator (optional)
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return promise
+ * Resolved to a DebuggerTransport instance.
+ */
+DebuggerSocket.connect = Task.async(function* (settings) {
+ // Default to PROMPT |Authenticator| instance if not supplied
+ if (!settings.authenticator) {
+ settings.authenticator = new (Authenticators.get().Client)();
+ }
+ _validateSettings(settings);
+ let { host, port, encryption, authenticator, cert } = settings;
+ let transport = yield _getTransport(settings);
+ yield authenticator.authenticate({
+ host,
+ port,
+ encryption,
+ cert,
+ transport
+ });
+ transport.connectionSettings = settings;
+ return transport;
+});
+
+/**
+ * Validate that the connection settings have been set to a supported configuration.
+ */
+function _validateSettings(settings) {
+ let { encryption, webSocket, authenticator } = settings;
+
+ if (webSocket && encryption) {
+ throw new Error("Encryption not supported on WebSocket transport");
+ }
+ authenticator.validateSettings(settings);
+}
+
+/**
+ * Try very hard to create a DevTools transport, potentially making several
+ * connect attempts in the process.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param webSocket boolean (optional)
+ * Whether to use WebSocket protocol to connect to the server. Defaults to false.
+ * @param authenticator Authenticator
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return transport DebuggerTransport
+ * A possible DevTools transport (if connection succeeded and streams
+ * are actually alive and working)
+ */
+var _getTransport = Task.async(function* (settings) {
+ let { host, port, encryption, webSocket } = settings;
+
+ if (webSocket) {
+ // Establish a connection and wait until the WebSocket is ready to send and receive
+ let socket = yield new Promise((resolve, reject) => {
+ let s = new WebSocket(`ws://${host}:${port}`);
+ s.onopen = () => resolve(s);
+ s.onerror = err => reject(err);
+ });
+
+ return new WebSocketDebuggerTransport(socket);
+ }
+
+ let attempt = yield _attemptTransport(settings);
+ if (attempt.transport) {
+ return attempt.transport; // Success
+ }
+
+ // If the server cert failed validation, store a temporary override and make
+ // a second attempt.
+ if (encryption && attempt.certError) {
+ _storeCertOverride(attempt.s, host, port);
+ } else {
+ throw new Error("Connection failed");
+ }
+
+ attempt = yield _attemptTransport(settings);
+ if (attempt.transport) {
+ return attempt.transport; // Success
+ }
+
+ throw new Error("Connection failed even after cert override");
+});
+
+/**
+ * Make a single attempt to connect and create a DevTools transport. This could
+ * fail if the remote host is unreachable, for example. If there is security
+ * error due to the use of self-signed certs, you should make another attempt
+ * after storing a cert override.
+ *
+ * @param host string
+ * The host name or IP address of the debugger server.
+ * @param port number
+ * The port number of the debugger server.
+ * @param encryption boolean (optional)
+ * Whether the server requires encryption. Defaults to false.
+ * @param authenticator Authenticator
+ * |Authenticator| instance matching the mode in use by the server.
+ * Defaults to a PROMPT instance if not supplied.
+ * @param cert object (optional)
+ * The server's cert details. Used with OOB_CERT authentication.
+ * @return transport DebuggerTransport
+ * A possible DevTools transport (if connection succeeded and streams
+ * are actually alive and working)
+ * @return certError boolean
+ * Flag noting if cert trouble caused the streams to fail
+ * @return s nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ */
+var _attemptTransport = Task.async(function* (settings) {
+ let { authenticator } = settings;
+ // _attemptConnect only opens the streams. Any failures at that stage
+ // aborts the connection process immedidately.
+ let { s, input, output } = yield _attemptConnect(settings);
+
+ // Check if the input stream is alive. If encryption is enabled, we need to
+ // watch out for cert errors by testing the input stream.
+ let alive, certError;
+ try {
+ let results = yield _isInputAlive(input);
+ alive = results.alive;
+ certError = results.certError;
+ } catch (e) {
+ // For other unexpected errors, like NS_ERROR_CONNECTION_REFUSED, we reach
+ // this block.
+ input.close();
+ output.close();
+ throw e;
+ }
+ dumpv("Server cert accepted? " + !certError);
+
+ // The |Authenticator| examines the connection as well and may determine it
+ // should be dropped.
+ alive = alive && authenticator.validateConnection({
+ host: settings.host,
+ port: settings.port,
+ encryption: settings.encryption,
+ cert: settings.cert,
+ socket: s
+ });
+
+ let transport;
+ if (alive) {
+ transport = new DebuggerTransport(input, output);
+ } else {
+ // Something went wrong, close the streams.
+ input.close();
+ output.close();
+ }
+
+ return { transport, certError, s };
+});
+
+/**
+ * Try to connect to a remote server socket.
+ *
+ * If successsful, the socket transport and its opened streams are returned.
+ * Typically, this will only fail if the host / port is unreachable. Other
+ * problems, such as security errors, will allow this stage to succeed, but then
+ * fail later when the streams are actually used.
+ * @return s nsISocketTransport
+ * Underlying socket transport, in case more details are needed.
+ * @return input nsIAsyncInputStream
+ * The socket's input stream.
+ * @return output nsIAsyncOutputStream
+ * The socket's output stream.
+ */
+var _attemptConnect = Task.async(function* ({ host, port, encryption }) {
+ let s;
+ if (encryption) {
+ s = socketTransportService.createTransport(["ssl"], 1, host, port, null);
+ } else {
+ s = socketTransportService.createTransport(null, 0, host, port, null);
+ }
+ // By default the CONNECT socket timeout is very long, 65535 seconds,
+ // so that if we race to be in CONNECT state while the server socket is still
+ // initializing, the connection is stuck in connecting state for 18.20 hours!
+ s.setTimeout(Ci.nsISocketTransport.TIMEOUT_CONNECT, 2);
+
+ // If encrypting, load the client cert now, so we can deliver it at just the
+ // right time.
+ let clientCert;
+ if (encryption) {
+ clientCert = yield cert.local.getOrCreate();
+ }
+
+ let deferred = defer();
+ let input;
+ let output;
+ // Delay opening the input stream until the transport has fully connected.
+ // The goal is to avoid showing the user a client cert UI prompt when
+ // encryption is used. This prompt is shown when the client opens the input
+ // stream and does not know which client cert to present to the server. To
+ // specify a client cert programmatically, we need to access the transport's
+ // nsISSLSocketControl interface, which is not accessible until the transport
+ // has connected.
+ s.setEventSink({
+ onTransportStatus(transport, status) {
+ if (status != Ci.nsISocketTransport.STATUS_CONNECTING_TO) {
+ return;
+ }
+ if (encryption) {
+ let sslSocketControl =
+ transport.securityInfo.QueryInterface(Ci.nsISSLSocketControl);
+ sslSocketControl.clientCert = clientCert;
+ }
+ try {
+ input = s.openInputStream(0, 0, 0);
+ } catch (e) {
+ deferred.reject(e);
+ }
+ deferred.resolve({ s, input, output });
+ }
+ }, Services.tm.currentThread);
+
+ // openOutputStream may throw NS_ERROR_NOT_INITIALIZED if we hit some race
+ // where the nsISocketTransport gets shutdown in between its instantiation and
+ // the call to this method.
+ try {
+ output = s.openOutputStream(0, 0, 0);
+ } catch (e) {
+ deferred.reject(e);
+ }
+
+ deferred.promise.catch(e => {
+ if (input) {
+ input.close();
+ }
+ if (output) {
+ output.close();
+ }
+ DevToolsUtils.reportException("_attemptConnect", e);
+ });
+
+ return deferred.promise;
+});
+
+/**
+ * Check if the input stream is alive. For an encrypted connection, it may not
+ * be if the client refuses the server's cert. A cert error is expected on
+ * first connection to a new host because the cert is self-signed.
+ */
+function _isInputAlive(input) {
+ let deferred = defer();
+ input.asyncWait({
+ onInputStreamReady(stream) {
+ try {
+ stream.available();
+ deferred.resolve({ alive: true });
+ } catch (e) {
+ try {
+ // getErrorClass may throw if you pass a non-NSS error
+ let errorClass = nssErrorsService.getErrorClass(e.result);
+ if (errorClass === Ci.nsINSSErrorsService.ERROR_CLASS_BAD_CERT) {
+ deferred.resolve({ certError: true });
+ } else {
+ deferred.reject(e);
+ }
+ } catch (nssErr) {
+ deferred.reject(e);
+ }
+ }
+ }
+ }, 0, 0, Services.tm.currentThread);
+ return deferred.promise;
+}
+
+/**
+ * To allow the connection to proceed with self-signed cert, we store a cert
+ * override. This implies that we take on the burden of authentication for
+ * these connections.
+ */
+function _storeCertOverride(s, host, port) {
+ let cert = s.securityInfo.QueryInterface(Ci.nsISSLStatusProvider)
+ .SSLStatus.serverCert;
+ let overrideBits = Ci.nsICertOverrideService.ERROR_UNTRUSTED |
+ Ci.nsICertOverrideService.ERROR_MISMATCH;
+ certOverrideService.rememberValidityOverride(host, port, cert, overrideBits,
+ true /* temporary */);
+}
+
+/**
+ * Creates a new socket listener for remote connections to the DebuggerServer.
+ * This helps contain and organize the parts of the server that may differ or
+ * are particular to one given listener mechanism vs. another.
+ */
+function SocketListener() {}
+
+SocketListener.prototype = {
+
+ /* Socket Options */
+
+ /**
+ * The port or path to listen on.
+ *
+ * If given an integer, the port to listen on. Use -1 to choose any available
+ * port. Otherwise, the path to the unix socket domain file to listen on.
+ */
+ portOrPath: null,
+
+ /**
+ * Controls whether this listener is announced via the service discovery
+ * mechanism.
+ */
+ discoverable: false,
+
+ /**
+ * Controls whether this listener's transport uses encryption.
+ */
+ encryption: false,
+
+ /**
+ * Controls the |Authenticator| used, which hooks various socket steps to
+ * implement an authentication policy. It is expected that different use
+ * cases may override pieces of the |Authenticator|. See auth.js.
+ *
+ * Here we set the default |Authenticator|, which is |Prompt|.
+ */
+ authenticator: new (Authenticators.get().Server)(),
+
+ /**
+ * Validate that all options have been set to a supported configuration.
+ */
+ _validateOptions: function () {
+ if (this.portOrPath === null) {
+ throw new Error("Must set a port / path to listen on.");
+ }
+ if (this.discoverable && !Number(this.portOrPath)) {
+ throw new Error("Discovery only supported for TCP sockets.");
+ }
+ if (this.encryption && this.webSocket) {
+ throw new Error("Encryption not supported on WebSocket transport");
+ }
+ this.authenticator.validateOptions(this);
+ },
+
+ /**
+ * Listens on the given port or socket file for remote debugger connections.
+ */
+ open: function () {
+ this._validateOptions();
+ DebuggerServer._addListener(this);
+
+ let flags = Ci.nsIServerSocket.KeepWhenOffline;
+ // A preference setting can force binding on the loopback interface.
+ if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
+ flags |= Ci.nsIServerSocket.LoopbackOnly;
+ }
+
+ let self = this;
+ return Task.spawn(function* () {
+ let backlog = 4;
+ self._socket = self._createSocketInstance();
+ if (self.isPortBased) {
+ let port = Number(self.portOrPath);
+ self._socket.initSpecialConnection(port, flags, backlog);
+ } else {
+ let file = nsFile(self.portOrPath);
+ if (file.exists()) {
+ file.remove(false);
+ }
+ self._socket.initWithFilename(file, parseInt("666", 8), backlog);
+ }
+ yield self._setAdditionalSocketOptions();
+ self._socket.asyncListen(self);
+ dumpn("Socket listening on: " + (self.port || self.portOrPath));
+ }).then(() => {
+ this._advertise();
+ }).catch(e => {
+ dumpn("Could not start debugging listener on '" + this.portOrPath +
+ "': " + e);
+ this.close();
+ });
+ },
+
+ _advertise: function () {
+ if (!this.discoverable || !this.port) {
+ return;
+ }
+
+ let advertisement = {
+ port: this.port,
+ encryption: this.encryption,
+ };
+
+ this.authenticator.augmentAdvertisement(this, advertisement);
+
+ discovery.addService("devtools", advertisement);
+ },
+
+ _createSocketInstance: function () {
+ if (this.encryption) {
+ return Cc["@mozilla.org/network/tls-server-socket;1"]
+ .createInstance(Ci.nsITLSServerSocket);
+ }
+ return Cc["@mozilla.org/network/server-socket;1"]
+ .createInstance(Ci.nsIServerSocket);
+ },
+
+ _setAdditionalSocketOptions: Task.async(function* () {
+ if (this.encryption) {
+ this._socket.serverCert = yield cert.local.getOrCreate();
+ this._socket.setSessionCache(false);
+ this._socket.setSessionTickets(false);
+ let requestCert = Ci.nsITLSServerSocket.REQUEST_NEVER;
+ this._socket.setRequestClientCertificate(requestCert);
+ }
+ this.authenticator.augmentSocketOptions(this, this._socket);
+ }),
+
+ /**
+ * Closes the SocketListener. Notifies the server to remove the listener from
+ * the set of active SocketListeners.
+ */
+ close: function () {
+ if (this.discoverable && this.port) {
+ discovery.removeService("devtools");
+ }
+ if (this._socket) {
+ this._socket.close();
+ this._socket = null;
+ }
+ DebuggerServer._removeListener(this);
+ },
+
+ get host() {
+ if (!this._socket) {
+ return null;
+ }
+ if (Services.prefs.getBoolPref("devtools.debugger.force-local")) {
+ return "127.0.0.1";
+ }
+ return "0.0.0.0";
+ },
+
+ /**
+ * Gets whether this listener uses a port number vs. a path.
+ */
+ get isPortBased() {
+ return !!Number(this.portOrPath);
+ },
+
+ /**
+ * Gets the port that a TCP socket listener is listening on, or null if this
+ * is not a TCP socket (so there is no port).
+ */
+ get port() {
+ if (!this.isPortBased || !this._socket) {
+ return null;
+ }
+ return this._socket.port;
+ },
+
+ get cert() {
+ if (!this._socket || !this._socket.serverCert) {
+ return null;
+ }
+ return {
+ sha256: this._socket.serverCert.sha256Fingerprint
+ };
+ },
+
+ // nsIServerSocketListener implementation
+
+ onSocketAccepted:
+ DevToolsUtils.makeInfallible(function (socket, socketTransport) {
+ new ServerSocketConnection(this, socketTransport);
+ }, "SocketListener.onSocketAccepted"),
+
+ onStopListening: function (socket, status) {
+ dumpn("onStopListening, status: " + status);
+ }
+
+};
+
+// Client must complete TLS handshake within this window (ms)
+loader.lazyGetter(this, "HANDSHAKE_TIMEOUT", () => {
+ return Services.prefs.getIntPref("devtools.remote.tls-handshake-timeout");
+});
+
+/**
+ * A |ServerSocketConnection| is created by a |SocketListener| for each accepted
+ * incoming socket. This is a short-lived object used to implement
+ * authentication and verify encryption prior to handing off the connection to
+ * the |DebuggerServer|.
+ */
+function ServerSocketConnection(listener, socketTransport) {
+ this._listener = listener;
+ this._socketTransport = socketTransport;
+ this._handle();
+}
+
+ServerSocketConnection.prototype = {
+
+ get authentication() {
+ return this._listener.authenticator.mode;
+ },
+
+ get host() {
+ return this._socketTransport.host;
+ },
+
+ get port() {
+ return this._socketTransport.port;
+ },
+
+ get cert() {
+ if (!this._clientCert) {
+ return null;
+ }
+ return {
+ sha256: this._clientCert.sha256Fingerprint
+ };
+ },
+
+ get address() {
+ return this.host + ":" + this.port;
+ },
+
+ get client() {
+ let client = {
+ host: this.host,
+ port: this.port
+ };
+ if (this.cert) {
+ client.cert = this.cert;
+ }
+ return client;
+ },
+
+ get server() {
+ let server = {
+ host: this._listener.host,
+ port: this._listener.port
+ };
+ if (this._listener.cert) {
+ server.cert = this._listener.cert;
+ }
+ return server;
+ },
+
+ /**
+ * This is the main authentication workflow. If any pieces reject a promise,
+ * the connection is denied. If the entire process resolves successfully,
+ * the connection is finally handed off to the |DebuggerServer|.
+ */
+ _handle() {
+ dumpn("Debugging connection starting authentication on " + this.address);
+ let self = this;
+ Task.spawn(function* () {
+ self._listenForTLSHandshake();
+ yield self._createTransport();
+ yield self._awaitTLSHandshake();
+ yield self._authenticate();
+ }).then(() => this.allow()).catch(e => this.deny(e));
+ },
+
+ /**
+ * We need to open the streams early on, as that is required in the case of
+ * TLS sockets to keep the handshake moving.
+ */
+ _createTransport: Task.async(function* () {
+ let input = this._socketTransport.openInputStream(0, 0, 0);
+ let output = this._socketTransport.openOutputStream(0, 0, 0);
+
+ if (this._listener.webSocket) {
+ let socket = yield WebSocketServer.accept(this._socketTransport, input, output);
+ this._transport = new WebSocketDebuggerTransport(socket);
+ } else {
+ this._transport = new DebuggerTransport(input, output);
+ }
+
+ // Start up the transport to observe the streams in case they are closed
+ // early. This allows us to clean up our state as well.
+ this._transport.hooks = {
+ onClosed: reason => {
+ this.deny(reason);
+ }
+ };
+ this._transport.ready();
+ }),
+
+ /**
+ * Set the socket's security observer, which receives an event via the
+ * |onHandshakeDone| callback when the TLS handshake completes.
+ */
+ _setSecurityObserver(observer) {
+ if (!this._socketTransport || !this._socketTransport.securityInfo) {
+ return;
+ }
+ let connectionInfo = this._socketTransport.securityInfo
+ .QueryInterface(Ci.nsITLSServerConnectionInfo);
+ connectionInfo.setSecurityObserver(observer);
+ },
+
+ /**
+ * When encryption is used, we wait for the client to complete the TLS
+ * handshake before proceeding. The handshake details are validated in
+ * |onHandshakeDone|.
+ */
+ _listenForTLSHandshake() {
+ this._handshakeDeferred = defer();
+ if (!this._listener.encryption) {
+ this._handshakeDeferred.resolve();
+ return;
+ }
+ this._setSecurityObserver(this);
+ this._handshakeTimeout = setTimeout(this._onHandshakeTimeout.bind(this),
+ HANDSHAKE_TIMEOUT);
+ },
+
+ _awaitTLSHandshake() {
+ return this._handshakeDeferred.promise;
+ },
+
+ _onHandshakeTimeout() {
+ dumpv("Client failed to complete TLS handshake");
+ this._handshakeDeferred.reject(Cr.NS_ERROR_NET_TIMEOUT);
+ },
+
+ // nsITLSServerSecurityObserver implementation
+ onHandshakeDone(socket, clientStatus) {
+ clearTimeout(this._handshakeTimeout);
+ this._setSecurityObserver(null);
+ dumpv("TLS version: " + clientStatus.tlsVersionUsed.toString(16));
+ dumpv("TLS cipher: " + clientStatus.cipherName);
+ dumpv("TLS key length: " + clientStatus.keyLength);
+ dumpv("TLS MAC length: " + clientStatus.macLength);
+ this._clientCert = clientStatus.peerCert;
+ /*
+ * TODO: These rules should be really be set on the TLS socket directly, but
+ * this would need more platform work to expose it via XPCOM.
+ *
+ * Enforcing cipher suites here would be a bad idea, as we want TLS
+ * cipher negotiation to work correctly. The server already allows only
+ * Gecko's normal set of cipher suites.
+ */
+ if (clientStatus.tlsVersionUsed < Ci.nsITLSClientStatus.TLS_VERSION_1_2) {
+ this._handshakeDeferred.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ return;
+ }
+
+ this._handshakeDeferred.resolve();
+ },
+
+ _authenticate: Task.async(function* () {
+ let result = yield this._listener.authenticator.authenticate({
+ client: this.client,
+ server: this.server,
+ transport: this._transport
+ });
+ switch (result) {
+ case AuthenticationResult.DISABLE_ALL:
+ DebuggerServer.closeAllListeners();
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", false);
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ case AuthenticationResult.DENY:
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ case AuthenticationResult.ALLOW:
+ case AuthenticationResult.ALLOW_PERSIST:
+ return promise.resolve();
+ default:
+ return promise.reject(Cr.NS_ERROR_CONNECTION_REFUSED);
+ }
+ }),
+
+ deny(result) {
+ if (this._destroyed) {
+ return;
+ }
+ let errorName = result;
+ for (let name in Cr) {
+ if (Cr[name] === result) {
+ errorName = name;
+ break;
+ }
+ }
+ dumpn("Debugging connection denied on " + this.address +
+ " (" + errorName + ")");
+ if (this._transport) {
+ this._transport.hooks = null;
+ this._transport.close(result);
+ }
+ this._socketTransport.close(result);
+ this.destroy();
+ },
+
+ allow() {
+ if (this._destroyed) {
+ return;
+ }
+ dumpn("Debugging connection allowed on " + this.address);
+ DebuggerServer._onConnection(this._transport);
+ this.destroy();
+ },
+
+ destroy() {
+ this._destroyed = true;
+ clearTimeout(this._handshakeTimeout);
+ this._setSecurityObserver(null);
+ this._listener = null;
+ this._socketTransport = null;
+ this._transport = null;
+ this._clientCert = null;
+ }
+
+};
+
+DebuggerSocket.createListener = function () {
+ return new SocketListener();
+};
+
+exports.DebuggerSocket = DebuggerSocket;
diff --git a/devtools/shared/security/tests/chrome/chrome.ini b/devtools/shared/security/tests/chrome/chrome.ini
new file mode 100644
index 000000000..581251103
--- /dev/null
+++ b/devtools/shared/security/tests/chrome/chrome.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+tags = devtools
+
+[test_websocket-transport.html]
diff --git a/devtools/shared/security/tests/chrome/test_websocket-transport.html b/devtools/shared/security/tests/chrome/test_websocket-transport.html
new file mode 100644
index 000000000..542206ecf
--- /dev/null
+++ b/devtools/shared/security/tests/chrome/test_websocket-transport.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test the WebSocket debugger transport</title>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+ <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+window.onload = function() {
+ const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+ const Services = require("Services");
+ const {DebuggerClient} = require("devtools/shared/client/main");
+ const {DebuggerServer} = require("devtools/server/main");
+
+ Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+ Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
+
+ SimpleTest.registerCleanupFunction(() => {
+ Services.prefs.clearUserPref("devtools.debugger.remote-enabled");
+ Services.prefs.clearUserPref("devtools.debugger.prompt-connection");
+ });
+
+ add_task(function* () {
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+
+ is(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.webSocket = true;
+ yield listener.open();
+ is(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ webSocket: true
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ let onUnexpectedClose = () => {
+ do_throw("Closed unexpectedly");
+ };
+ client.addListener("closed", onUnexpectedClose);
+
+ yield client.connect();
+ yield client.listTabs();
+
+ // Send a message the server that will echo back
+ let message = "message";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ is(reply.message, message, "Echo message matches");
+
+ client.removeListener("closed", onUnexpectedClose);
+ transport.close();
+ listener.close();
+ is(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ DebuggerServer.destroy();
+ });
+}
+</script>
+</body>
+</html>
diff --git a/devtools/shared/security/tests/unit/.eslintrc.js b/devtools/shared/security/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/security/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/security/tests/unit/head_dbg.js b/devtools/shared/security/tests/unit/head_dbg.js
new file mode 100644
index 000000000..f3b2a9a97
--- /dev/null
+++ b/devtools/shared/security/tests/unit/head_dbg.js
@@ -0,0 +1,96 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const xpcInspector = require("xpcInspector");
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/main");
+
+// We do not want to log packets by default, because in some tests,
+// we can be sending large amounts of data. The test harness has
+// trouble dealing with logging all the data, and we end up with
+// intermittent time outs (e.g. bug 775924).
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+// Fast timeout for TLS tests
+Services.prefs.setIntPref("devtools.remote.tls-handshake-timeout", 1000);
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (xpcInspector.eventLoopNestLevel > 0) {
+ xpcInspector.exitNestedEventLoop();
+ }
+
+ // Print in most cases, but ignore the "strict" messages
+ if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+ do_print("head_dbg.js got console message: " + string + "\n");
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer() {
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ DebuggerServer.init();
+}
diff --git a/devtools/shared/security/tests/unit/test_encryption.js b/devtools/shared/security/tests/unit/test_encryption.js
new file mode 100644
index 000000000..e7fc80f5d
--- /dev/null
+++ b/devtools/shared/security/tests/unit/test_encryption.js
@@ -0,0 +1,110 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test basic functionality of DevTools client and server TLS encryption mode
+function run_test() {
+ // Need profile dir to store the key / cert
+ do_get_profile();
+ // Ensure PSM is initialized
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ run_next_test();
+}
+
+function connectClient(client) {
+ let deferred = defer();
+ client.connect(() => {
+ client.listTabs(deferred.resolve);
+ });
+ return deferred.promise;
+}
+
+add_task(function* () {
+ initTestDebuggerServer();
+});
+
+// Client w/ encryption connects successfully to server w/ encryption
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ let onUnexpectedClose = () => {
+ do_throw("Closed unexpectedly");
+ };
+ client.addListener("closed", onUnexpectedClose);
+ yield connectClient(client);
+
+ // Send a message the server will echo back
+ let message = "secrets";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ equal(reply.message, message, "Encrypted echo matches");
+
+ client.removeListener("closed", onUnexpectedClose);
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+});
+
+// Client w/o encryption fails to connect to server w/ encryption
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.encryption = true;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ // encryption: false is the default
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+add_task(function* () {
+ DebuggerServer.destroy();
+});
diff --git a/devtools/shared/security/tests/unit/test_oob_cert_auth.js b/devtools/shared/security/tests/unit/test_oob_cert_auth.js
new file mode 100644
index 000000000..1e820af52
--- /dev/null
+++ b/devtools/shared/security/tests/unit/test_oob_cert_auth.js
@@ -0,0 +1,261 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var cert = require("devtools/shared/security/cert");
+
+// Test basic functionality of DevTools client and server OOB_CERT auth (used
+// with WiFi debugging)
+function run_test() {
+ // Need profile dir to store the key / cert
+ do_get_profile();
+ // Ensure PSM is initialized
+ Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
+ run_next_test();
+}
+
+function connectClient(client) {
+ return client.connect(() => {
+ return client.listTabs();
+ });
+}
+
+add_task(function* () {
+ initTestDebuggerServer();
+});
+
+// Client w/ OOB_CERT auth connects successfully to server w/ OOB_CERT auth
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ // Pass to server, skipping prompt for tests
+ oobData.resolve(oob);
+ };
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ ok(transport, "Client transport created");
+
+ let client = new DebuggerClient(transport);
+ let onUnexpectedClose = () => {
+ do_throw("Closed unexpectedly");
+ };
+ client.addListener("closed", onUnexpectedClose);
+ yield connectClient(client);
+
+ // Send a message the server will echo back
+ let message = "secrets";
+ let reply = yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ equal(reply.message, message, "Encrypted echo matches");
+
+ client.removeListener("closed", onUnexpectedClose);
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+});
+
+// Client w/o OOB_CERT auth fails to connect to server w/ OOB_CERT auth
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ // This will succeed, but leaves the client in confused state, and no data is
+ // actually accessible
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true
+ // authenticator: PROMPT is the default
+ });
+
+ // Attempt to use the transport
+ let deferred = defer();
+ let client = new DebuggerClient(transport);
+ client.onPacket = packet => {
+ // Client did not authenticate, so it ends up seeing the server's auth data
+ // which is effectively malformed data from the client's perspective
+ ok(!packet.from && packet.authResult, "Got auth packet instead of data");
+ deferred.resolve();
+ };
+ client.connect();
+ yield deferred.promise;
+
+ // Try to send a message the server will echo back
+ let message = "secrets";
+ try {
+ yield client.request({
+ to: "root",
+ type: "echo",
+ message
+ });
+ } catch (e) {
+ ok(true, "Sending a message failed");
+ transport.close();
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+// Client w/ invalid K value fails to connect
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ do_print("Modifying K value, should fail");
+ // Pass to server, skipping prompt for tests
+ oobData.resolve({
+ k: oob.k + 1,
+ sha256: oob.sha256
+ });
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+// Client w/ invalid cert hash fails to connect
+add_task(function* () {
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+ // Grab our cert, instead of relying on a discovery advertisement
+ let serverCert = yield cert.local.getOrCreate();
+
+ let oobData = defer();
+ let AuthenticatorType = DebuggerServer.Authenticators.get("OOB_CERT");
+ let serverAuth = new AuthenticatorType.Server();
+ serverAuth.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ serverAuth.receiveOOB = () => oobData.promise; // Skip prompt for tests
+
+ let clientAuth = new AuthenticatorType.Client();
+ clientAuth.sendOOB = ({ oob }) => {
+ do_print(oob);
+ do_print("Modifying cert hash, should fail");
+ // Pass to server, skipping prompt for tests
+ oobData.resolve({
+ k: oob.k,
+ sha256: oob.sha256 + 1
+ });
+ };
+
+ let listener = DebuggerServer.createListener();
+ ok(listener, "Socket listener created");
+ listener.portOrPath = -1;
+ listener.encryption = true;
+ listener.authenticator = serverAuth;
+ yield listener.open();
+ equal(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+ try {
+ yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port,
+ encryption: true,
+ authenticator: clientAuth,
+ cert: {
+ sha256: serverCert.sha256Fingerprint
+ }
+ });
+ } catch (e) {
+ ok(true, "Client failed to connect as expected");
+ listener.close();
+ equal(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+ return;
+ }
+
+ do_throw("Connection unexpectedly succeeded");
+});
+
+add_task(function* () {
+ DebuggerServer.destroy();
+});
diff --git a/devtools/shared/security/tests/unit/testactors.js b/devtools/shared/security/tests/unit/testactors.js
new file mode 100644
index 000000000..80d5d4e18
--- /dev/null
+++ b/devtools/shared/security/tests/unit/testactors.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } =
+ require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function (aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return promise.resolve([...this._tabActors]);
+ }
+};
+
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal) {
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this._threadActor);
+ this._attached = false;
+ this._extraActors = {};
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return { wrappedJSObject: this._global };
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ form: function () {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function (aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function (aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach
+};
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/security/tests/unit/xpcshell.ini b/devtools/shared/security/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..f2b3e7151
--- /dev/null
+++ b/devtools/shared/security/tests/unit/xpcshell.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+tags = devtools
+head = head_dbg.js
+tail =
+firefox-appdir = browser
+
+support-files=
+ testactors.js
+
+[test_encryption.js]
+[test_oob_cert_auth.js]
+skip-if = (toolkit == 'android' && !debug) # Bug 1141544: Re-enable when buildbot tests are gone
diff --git a/devtools/shared/shims/Console.jsm b/devtools/shared/shims/Console.jsm
new file mode 100644
index 000000000..0bfdcb867
--- /dev/null
+++ b/devtools/shared/shims/Console.jsm
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file only exists to support add-ons which import this module at a
+ * specific path.
+ */
+
+const Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const WARNING_PREF = "devtools.migration.warnings";
+if (Services.prefs.getBoolPref(WARNING_PREF)) {
+ const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("This path to Console.jsm is deprecated. Please use " +
+ "Cu.import(\"resource://gre/modules/Console.jsm\") " +
+ "to load this module.",
+ "https://bugzil.la/912121");
+}
+
+this.EXPORTED_SYMBOLS = [
+ "console",
+ "ConsoleAPI"
+];
+
+const module =
+ Cu.import("resource://gre/modules/Console.jsm", {});
+
+for (let symbol of this.EXPORTED_SYMBOLS) {
+ this[symbol] = module[symbol];
+}
diff --git a/devtools/shared/shims/Loader.jsm b/devtools/shared/shims/Loader.jsm
new file mode 100644
index 000000000..8504e08d0
--- /dev/null
+++ b/devtools/shared/shims/Loader.jsm
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file only exists to support add-ons which import this module at a
+ * specific path.
+ */
+
+const Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const WARNING_PREF = "devtools.migration.warnings";
+if (Services.prefs.getBoolPref(WARNING_PREF)) {
+ const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("This path to Loader.jsm is deprecated. Please use " +
+ "Cu.import(\"resource://devtools/shared/" +
+ "Loader.jsm\") to load this module.",
+ "https://bugzil.la/912121");
+}
+
+this.EXPORTED_SYMBOLS = [
+ "DevToolsLoader",
+ "devtools",
+ "BuiltinProvider",
+ "require",
+ "loader"
+];
+
+const module =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+for (let symbol of this.EXPORTED_SYMBOLS) {
+ this[symbol] = module[symbol];
+}
diff --git a/devtools/shared/shims/Simulator.jsm b/devtools/shared/shims/Simulator.jsm
new file mode 100644
index 000000000..7fd4fef53
--- /dev/null
+++ b/devtools/shared/shims/Simulator.jsm
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file only exists to support add-ons which import this module at a
+ * specific path.
+ */
+
+const Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const WARNING_PREF = "devtools.migration.warnings";
+if (Services.prefs.getBoolPref(WARNING_PREF)) {
+ const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("This path to Simulator.jsm is deprecated. Please use " +
+ "Cu.import(\"resource://devtools/shared/" +
+ "apps/Simulator.jsm\") to load this module.",
+ "https://bugzil.la/912121");
+}
+
+this.EXPORTED_SYMBOLS = [
+ "Simulator",
+];
+
+const module =
+ Cu.import("resource://devtools/shared/apps/Simulator.jsm", {});
+
+for (let symbol of this.EXPORTED_SYMBOLS) {
+ this[symbol] = module[symbol];
+}
diff --git a/devtools/shared/shims/dbg-client.jsm b/devtools/shared/shims/dbg-client.jsm
new file mode 100644
index 000000000..ec3d500cd
--- /dev/null
+++ b/devtools/shared/shims/dbg-client.jsm
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file only exists to support add-ons which import this module at a
+ * specific path.
+ */
+
+const Cu = Components.utils;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+const WARNING_PREF = "devtools.migration.warnings";
+if (Services.prefs.getBoolPref(WARNING_PREF)) {
+ const { Deprecated } = Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("dbg-client.jsm is deprecated. Please use " +
+ "require(\"devtools/shared/client/main\") to load this " +
+ "module.", "https://bugzil.la/912121");
+}
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+this.EXPORTED_SYMBOLS = ["DebuggerTransport",
+ "DebuggerClient",
+ "RootClient",
+ "LongStringClient",
+ "EnvironmentClient",
+ "ObjectClient"];
+
+var client = require("devtools/shared/client/main");
+
+this.DebuggerClient = client.DebuggerClient;
+this.RootClient = client.RootClient;
+this.LongStringClient = client.LongStringClient;
+this.EnvironmentClient = client.EnvironmentClient;
+this.ObjectClient = client.ObjectClient;
+
+this.DebuggerTransport =
+ require("devtools/shared/transport/transport").DebuggerTransport;
diff --git a/devtools/shared/shims/event-emitter.js b/devtools/shared/shims/event-emitter.js
new file mode 100644
index 000000000..6b3672162
--- /dev/null
+++ b/devtools/shared/shims/event-emitter.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This file only exists to support add-ons which import this module at a
+ * specific path.
+ */
+
+(function (factory) { // Module boilerplate
+ if (this.module && module.id.indexOf("event-emitter") >= 0) { // require
+ factory.call(this, require, exports, module);
+ } else { // Cu.import
+ const Cu = Components.utils;
+ const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+ this.isWorker = false;
+ this.promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+ factory.call(this, require, this, { exports: this });
+ this.EXPORTED_SYMBOLS = ["EventEmitter"];
+ }
+}).call(this, function (require, exports, module) {
+ const { Cu } = require("chrome");
+ const Services = require("Services");
+ const WARNING_PREF = "devtools.migration.warnings";
+ // Cu and Services aren't accessible from workers
+ if (Cu && Services && Services.prefs &&
+ Services.prefs.getBoolPref(WARNING_PREF)) {
+ const { Deprecated } =
+ Cu.import("resource://gre/modules/Deprecated.jsm", {});
+ Deprecated.warning("This path to event-emitter.js is deprecated. Please " +
+ "use require(\"devtools/shared/event-emitter\") to " +
+ "load this module.",
+ "https://bugzil.la/912121");
+ }
+
+ const EventEmitter = require("devtools/shared/event-emitter");
+ this.EventEmitter = EventEmitter;
+ module.exports = EventEmitter;
+});
diff --git a/devtools/shared/shims/moz.build b/devtools/shared/shims/moz.build
new file mode 100644
index 000000000..bee1c82ac
--- /dev/null
+++ b/devtools/shared/shims/moz.build
@@ -0,0 +1,31 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+# Unlike most DevTools build files, this file does not use DevToolsModules
+# because these files are here for add-on compatibility, and so they must be
+# installed to previously defined locations.
+
+# These shims for legacy paths expect to be installed as if they were part of
+# /toolkit modules. Disable any DIST_SUBDIR from parent files here.
+DIST_SUBDIR = ''
+
+EXTRA_JS_MODULES.devtools += [
+ 'Console.jsm',
+ 'dbg-client.jsm',
+ 'event-emitter.js',
+ 'Loader.jsm',
+ 'Simulator.jsm',
+]
+
+# Extra compatibility layer for transitional URLs used for part of 44 cycle
+EXTRA_JS_MODULES.devtools.shared += [
+ 'Console.jsm',
+ 'Loader.jsm',
+]
+
+EXTRA_JS_MODULES.devtools.shared.apps += [
+ 'Simulator.jsm',
+]
diff --git a/devtools/shared/sourcemap/UPGRADING.md b/devtools/shared/sourcemap/UPGRADING.md
new file mode 100644
index 000000000..5a257f7e4
--- /dev/null
+++ b/devtools/shared/sourcemap/UPGRADING.md
@@ -0,0 +1,13 @@
+Rather than make changes to the built files here, make them upstream and then
+upgrade our tree's copy of the built files.
+
+To upgrade the source-map library:
+
+ $ git clone https://github.com/mozilla/source-map.git
+ $ cd source-map
+ $ git checkout <latest-tagged-version>
+ $ npm install
+ $ npm run-script build -or- nodejs Makefile.dryice.js (if you have issues with npm)
+ $ cp dist/source-map.js /path/to/mozilla-central/devtools/shared/sourcemap/source-map.js
+ $ cp dist/test/* /path/to/mozilla-central/devtools/shared/sourcemap/tests/unit/
+
diff --git a/devtools/shared/sourcemap/moz.build b/devtools/shared/sourcemap/moz.build
new file mode 100644
index 000000000..459749645
--- /dev/null
+++ b/devtools/shared/sourcemap/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'source-map.js'
+)
diff --git a/devtools/shared/sourcemap/source-map.js b/devtools/shared/sourcemap/source-map.js
new file mode 100644
index 000000000..45400893c
--- /dev/null
+++ b/devtools/shared/sourcemap/source-map.js
@@ -0,0 +1,3006 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+ if(typeof exports === 'object' && typeof module === 'object')
+ module.exports = factory();
+ else if(typeof define === 'function' && define.amd)
+ define([], factory);
+ else if(typeof exports === 'object')
+ exports["sourceMap"] = factory();
+ else
+ root["sourceMap"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+
+
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ exports.SourceMapGenerator = __webpack_require__(1).SourceMapGenerator;
+ exports.SourceMapConsumer = __webpack_require__(7).SourceMapConsumer;
+ exports.SourceNode = __webpack_require__(10).SourceNode;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(2);
+ var util = __webpack_require__(4);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var MappingList = __webpack_require__(6).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(3);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+ var binarySearch = __webpack_require__(8);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var base64VLQ = __webpack_require__(2);
+ var quickSort = __webpack_require__(9).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var SourceMapGenerator = __webpack_require__(1).SourceMapGenerator;
+ var util = __webpack_require__(4);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ var code = "";
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+ }
+
+
+/***/ }
+/******/ ])
+});
+; \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/head_sourcemap.js b/devtools/shared/sourcemap/tests/unit/head_sourcemap.js
new file mode 100644
index 000000000..12d4c078e
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/head_sourcemap.js
@@ -0,0 +1,18 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+function doesNotThrow(f) {
+ try {
+ f();
+ } catch(e) {
+ ok(false, e + e.stack);
+ }
+}
+
+var assert = this;
diff --git a/devtools/shared/sourcemap/tests/unit/test_api.js b/devtools/shared/sourcemap/tests/unit/test_api.js
new file mode 100644
index 000000000..16520e429
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_api.js
@@ -0,0 +1,3026 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2012 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var sourceMap = __webpack_require__(1);
+
+ exports['test that the api is properly exposed in the top level'] = function (assert) {
+ assert.equal(typeof sourceMap.SourceMapGenerator, "function");
+ assert.equal(typeof sourceMap.SourceMapConsumer, "function");
+ assert.equal(typeof sourceMap.SourceNode, "function");
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /*
+ * Copyright 2009-2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE.txt or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ exports.SourceMapGenerator = __webpack_require__(2).SourceMapGenerator;
+ exports.SourceMapConsumer = __webpack_require__(8).SourceMapConsumer;
+ exports.SourceNode = __webpack_require__(11).SourceNode;
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(3);
+ var util = __webpack_require__(5);
+ var ArraySet = __webpack_require__(6).ArraySet;
+ var MappingList = __webpack_require__(7).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(4);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(5);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(5);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(5);
+ var binarySearch = __webpack_require__(9);
+ var ArraySet = __webpack_require__(6).ArraySet;
+ var base64VLQ = __webpack_require__(3);
+ var quickSort = __webpack_require__(10).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var SourceMapGenerator = __webpack_require__(2).SourceMapGenerator;
+ var util = __webpack_require__(5);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ var code = "";
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgOGQzOTg5ZWJhY2JjMWE2ZTVjNzUiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWFwaS5qcyIsIndlYnBhY2s6Ly8vLi9zb3VyY2UtbWFwLmpzIiwid2VicGFjazovLy8uL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvci5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmFzZTY0LXZscS5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmFzZTY0LmpzIiwid2VicGFjazovLy8uL2xpYi91dGlsLmpzIiwid2VicGFjazovLy8uL2xpYi9hcnJheS1zZXQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL21hcHBpbmctbGlzdC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW1hcC1jb25zdW1lci5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmluYXJ5LXNlYXJjaC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvcXVpY2stc29ydC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW5vZGUuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1QkFBZTtBQUNmO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdENBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDZEE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQ1BBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsUUFBTztBQUNQO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw2Q0FBNEMsU0FBUztBQUNyRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5QkFBd0I7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQzNZQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0REFBMkQ7QUFDM0QscUJBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDNUlBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixzQkFBcUI7QUFDckIsdUJBQXNCOztBQUV0QixtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixtQkFBa0I7QUFDbEIsb0JBQW1COztBQUVuQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbkVBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxpREFBZ0QsUUFBUTtBQUN4RDtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNoWEEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDLFNBQVM7QUFDakQ7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN2R0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEI7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUMvRUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQjtBQUNyQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYTs7QUFFYjtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFhOztBQUViO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBNkIsTUFBTTtBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBLHlEQUF3RCxZQUFZO0FBQ3BFO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0Esc0NBQXFDO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBMkIsY0FBYztBQUN6QztBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMEJBQXlCLHdDQUF3QztBQUNqRTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWlELG1CQUFtQixFQUFFO0FBQ3RFOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQixvQkFBb0I7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUErQixNQUFNO0FBQ3JDO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseURBQXdEO0FBQ3hEOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQsd0JBQXVCLCtDQUErQztBQUN0RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLDJCQUEyQjtBQUNoRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hEOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQ7QUFDQTtBQUNBLHdCQUF1Qiw0QkFBNEI7QUFDbkQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN6akNBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7Ozs7O0FDL0dBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQixPQUFPO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbEhBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPOztBQUVQOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQ0FBbUMsUUFBUTtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnREFBK0MsU0FBUztBQUN4RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBc0I7QUFDdEI7QUFDQTtBQUNBLHlDQUF3QztBQUN4QztBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBaUIsV0FBVztBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWlELFNBQVM7QUFDMUQ7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw0Q0FBMkMsU0FBUztBQUNwRDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQSxjQUFhO0FBQ2I7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsK0NBQThDLGNBQWM7QUFDNUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFlO0FBQ2Y7QUFDQTtBQUNBO0FBQ0EsZ0JBQWU7QUFDZjtBQUNBLGNBQWE7QUFDYjtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBLE1BQUs7O0FBRUwsYUFBWTtBQUNaOztBQUVBO0FBQ0EiLCJmaWxlIjoidGVzdF9hcGkuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSlcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcblxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0ZXhwb3J0czoge30sXG4gXHRcdFx0aWQ6IG1vZHVsZUlkLFxuIFx0XHRcdGxvYWRlZDogZmFsc2VcbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubG9hZGVkID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXygwKTtcblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIHdlYnBhY2svYm9vdHN0cmFwIDhkMzk4OWViYWNiYzFhNmU1Yzc1XG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDEyIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgc291cmNlTWFwID0gcmVxdWlyZSgnLi4vc291cmNlLW1hcCcpO1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhhdCB0aGUgYXBpIGlzIHByb3Blcmx5IGV4cG9zZWQgaW4gdGhlIHRvcCBsZXZlbCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGFzc2VydC5lcXVhbCh0eXBlb2Ygc291cmNlTWFwLlNvdXJjZU1hcEdlbmVyYXRvciwgXCJmdW5jdGlvblwiKTtcbiAgICBhc3NlcnQuZXF1YWwodHlwZW9mIHNvdXJjZU1hcC5Tb3VyY2VNYXBDb25zdW1lciwgXCJmdW5jdGlvblwiKTtcbiAgICBhc3NlcnQuZXF1YWwodHlwZW9mIHNvdXJjZU1hcC5Tb3VyY2VOb2RlLCBcImZ1bmN0aW9uXCIpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1hcGkuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKlxuICogQ29weXJpZ2h0IDIwMDktMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0UudHh0IG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG5leHBvcnRzLlNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4vbGliL3NvdXJjZS1tYXAtZ2VuZXJhdG9yJykuU291cmNlTWFwR2VuZXJhdG9yO1xuZXhwb3J0cy5Tb3VyY2VNYXBDb25zdW1lciA9IHJlcXVpcmUoJy4vbGliL3NvdXJjZS1tYXAtY29uc3VtZXInKS5Tb3VyY2VNYXBDb25zdW1lcjtcbmV4cG9ydHMuU291cmNlTm9kZSA9IHJlcXVpcmUoJy4vbGliL3NvdXJjZS1ub2RlJykuU291cmNlTm9kZTtcblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9zb3VyY2UtbWFwLmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgYmFzZTY0VkxRID0gcmVxdWlyZSgnLi9iYXNlNjQtdmxxJyk7XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG4gIHZhciBBcnJheVNldCA9IHJlcXVpcmUoJy4vYXJyYXktc2V0JykuQXJyYXlTZXQ7XG4gIHZhciBNYXBwaW5nTGlzdCA9IHJlcXVpcmUoJy4vbWFwcGluZy1saXN0JykuTWFwcGluZ0xpc3Q7XG5cbiAgLyoqXG4gICAqIEFuIGluc3RhbmNlIG9mIHRoZSBTb3VyY2VNYXBHZW5lcmF0b3IgcmVwcmVzZW50cyBhIHNvdXJjZSBtYXAgd2hpY2ggaXNcbiAgICogYmVpbmcgYnVpbHQgaW5jcmVtZW50YWxseS4gWW91IG1heSBwYXNzIGFuIG9iamVjdCB3aXRoIHRoZSBmb2xsb3dpbmdcbiAgICogcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGZpbGU6IFRoZSBmaWxlbmFtZSBvZiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIHNvdXJjZVJvb3Q6IEEgcm9vdCBmb3IgYWxsIHJlbGF0aXZlIFVSTHMgaW4gdGhpcyBzb3VyY2UgbWFwLlxuICAgKi9cbiAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yKGFBcmdzKSB7XG4gICAgaWYgKCFhQXJncykge1xuICAgICAgYUFyZ3MgPSB7fTtcbiAgICB9XG4gICAgdGhpcy5fZmlsZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnZmlsZScsIG51bGwpO1xuICAgIHRoaXMuX3NvdXJjZVJvb3QgPSB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZVJvb3QnLCBudWxsKTtcbiAgICB0aGlzLl9za2lwVmFsaWRhdGlvbiA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc2tpcFZhbGlkYXRpb24nLCBmYWxzZSk7XG4gICAgdGhpcy5fc291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgIHRoaXMuX25hbWVzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbWFwcGluZ3MgPSBuZXcgTWFwcGluZ0xpc3QoKTtcbiAgICB0aGlzLl9zb3VyY2VzQ29udGVudHMgPSBudWxsO1xuICB9XG5cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBuZXcgU291cmNlTWFwR2VuZXJhdG9yIGJhc2VkIG9uIGEgU291cmNlTWFwQ29uc3VtZXJcbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VNYXBDb25zdW1lciBUaGUgU291cmNlTWFwLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLmZyb21Tb3VyY2VNYXAgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9mcm9tU291cmNlTWFwKGFTb3VyY2VNYXBDb25zdW1lcikge1xuICAgICAgdmFyIHNvdXJjZVJvb3QgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlUm9vdDtcbiAgICAgIHZhciBnZW5lcmF0b3IgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgICAgZmlsZTogYVNvdXJjZU1hcENvbnN1bWVyLmZpbGUsXG4gICAgICAgIHNvdXJjZVJvb3Q6IHNvdXJjZVJvb3RcbiAgICAgIH0pO1xuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLmVhY2hNYXBwaW5nKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICAgIHZhciBuZXdNYXBwaW5nID0ge1xuICAgICAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5nZW5lcmF0ZWRMaW5lLFxuICAgICAgICAgICAgY29sdW1uOiBtYXBwaW5nLmdlbmVyYXRlZENvbHVtblxuICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgIT0gbnVsbCkge1xuICAgICAgICAgIG5ld01hcHBpbmcuc291cmNlID0gbWFwcGluZy5zb3VyY2U7XG4gICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgbmV3TWFwcGluZy5zb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIG5ld01hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBuZXdNYXBwaW5nLm9yaWdpbmFsID0ge1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW5cbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgaWYgKG1hcHBpbmcubmFtZSAhPSBudWxsKSB7XG4gICAgICAgICAgICBuZXdNYXBwaW5nLm5hbWUgPSBtYXBwaW5nLm5hbWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgZ2VuZXJhdG9yLmFkZE1hcHBpbmcobmV3TWFwcGluZyk7XG4gICAgICB9KTtcbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VzLmZvckVhY2goZnVuY3Rpb24gKHNvdXJjZUZpbGUpIHtcbiAgICAgICAgdmFyIGNvbnRlbnQgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlQ29udGVudEZvcihzb3VyY2VGaWxlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQgIT0gbnVsbCkge1xuICAgICAgICAgIGdlbmVyYXRvci5zZXRTb3VyY2VDb250ZW50KHNvdXJjZUZpbGUsIGNvbnRlbnQpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHJldHVybiBnZW5lcmF0b3I7XG4gICAgfTtcblxuICAvKipcbiAgICogQWRkIGEgc2luZ2xlIG1hcHBpbmcgZnJvbSBvcmlnaW5hbCBzb3VyY2UgbGluZSBhbmQgY29sdW1uIHRvIHRoZSBnZW5lcmF0ZWRcbiAgICogc291cmNlJ3MgbGluZSBhbmQgY29sdW1uIGZvciB0aGlzIHNvdXJjZSBtYXAgYmVpbmcgY3JlYXRlZC4gVGhlIG1hcHBpbmdcbiAgICogb2JqZWN0IHNob3VsZCBoYXZlIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGdlbmVyYXRlZDogQW4gb2JqZWN0IHdpdGggdGhlIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zLlxuICAgKiAgIC0gb3JpZ2luYWw6IEFuIG9iamVjdCB3aXRoIHRoZSBvcmlnaW5hbCBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zLlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUgKHJlbGF0aXZlIHRvIHRoZSBzb3VyY2VSb290KS5cbiAgICogICAtIG5hbWU6IEFuIG9wdGlvbmFsIG9yaWdpbmFsIHRva2VuIG5hbWUgZm9yIHRoaXMgbWFwcGluZy5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYWRkTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2FkZE1hcHBpbmcoYUFyZ3MpIHtcbiAgICAgIHZhciBnZW5lcmF0ZWQgPSB1dGlsLmdldEFyZyhhQXJncywgJ2dlbmVyYXRlZCcpO1xuICAgICAgdmFyIG9yaWdpbmFsID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdvcmlnaW5hbCcsIG51bGwpO1xuICAgICAgdmFyIHNvdXJjZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJywgbnVsbCk7XG4gICAgICB2YXIgbmFtZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbmFtZScsIG51bGwpO1xuXG4gICAgICBpZiAoIXRoaXMuX3NraXBWYWxpZGF0aW9uKSB7XG4gICAgICAgIHRoaXMuX3ZhbGlkYXRlTWFwcGluZyhnZW5lcmF0ZWQsIG9yaWdpbmFsLCBzb3VyY2UsIG5hbWUpO1xuICAgICAgfVxuXG4gICAgICBpZiAoc291cmNlICE9IG51bGwgJiYgIXRoaXMuX3NvdXJjZXMuaGFzKHNvdXJjZSkpIHtcbiAgICAgICAgdGhpcy5fc291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKG5hbWUgIT0gbnVsbCAmJiAhdGhpcy5fbmFtZXMuaGFzKG5hbWUpKSB7XG4gICAgICAgIHRoaXMuX25hbWVzLmFkZChuYW1lKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5fbWFwcGluZ3MuYWRkKHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogZ2VuZXJhdGVkLmxpbmUsXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogZ2VuZXJhdGVkLmNvbHVtbixcbiAgICAgICAgb3JpZ2luYWxMaW5lOiBvcmlnaW5hbCAhPSBudWxsICYmIG9yaWdpbmFsLmxpbmUsXG4gICAgICAgIG9yaWdpbmFsQ29sdW1uOiBvcmlnaW5hbCAhPSBudWxsICYmIG9yaWdpbmFsLmNvbHVtbixcbiAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgIG5hbWU6IG5hbWVcbiAgICAgIH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGEgc291cmNlIGZpbGUuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLnNldFNvdXJjZUNvbnRlbnQgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9zZXRTb3VyY2VDb250ZW50KGFTb3VyY2VGaWxlLCBhU291cmNlQ29udGVudCkge1xuICAgICAgdmFyIHNvdXJjZSA9IGFTb3VyY2VGaWxlO1xuICAgICAgaWYgKHRoaXMuX3NvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuX3NvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChhU291cmNlQ29udGVudCAhPSBudWxsKSB7XG4gICAgICAgIC8vIEFkZCB0aGUgc291cmNlIGNvbnRlbnQgdG8gdGhlIF9zb3VyY2VzQ29udGVudHMgbWFwLlxuICAgICAgICAvLyBDcmVhdGUgYSBuZXcgX3NvdXJjZXNDb250ZW50cyBtYXAgaWYgdGhlIHByb3BlcnR5IGlzIG51bGwuXG4gICAgICAgIGlmICghdGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzID0ge307XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzW3V0aWwudG9TZXRTdHJpbmcoc291cmNlKV0gPSBhU291cmNlQ29udGVudDtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgIC8vIFJlbW92ZSB0aGUgc291cmNlIGZpbGUgZnJvbSB0aGUgX3NvdXJjZXNDb250ZW50cyBtYXAuXG4gICAgICAgIC8vIElmIHRoZSBfc291cmNlc0NvbnRlbnRzIG1hcCBpcyBlbXB0eSwgc2V0IHRoZSBwcm9wZXJ0eSB0byBudWxsLlxuICAgICAgICBkZWxldGUgdGhpcy5fc291cmNlc0NvbnRlbnRzW3V0aWwudG9TZXRTdHJpbmcoc291cmNlKV07XG4gICAgICAgIGlmIChPYmplY3Qua2V5cyh0aGlzLl9zb3VyY2VzQ29udGVudHMpLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIHRoaXMuX3NvdXJjZXNDb250ZW50cyA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBcHBsaWVzIHRoZSBtYXBwaW5ncyBvZiBhIHN1Yi1zb3VyY2UtbWFwIGZvciBhIHNwZWNpZmljIHNvdXJjZSBmaWxlIHRvIHRoZVxuICAgKiBzb3VyY2UgbWFwIGJlaW5nIGdlbmVyYXRlZC4gRWFjaCBtYXBwaW5nIHRvIHRoZSBzdXBwbGllZCBzb3VyY2UgZmlsZSBpc1xuICAgKiByZXdyaXR0ZW4gdXNpbmcgdGhlIHN1cHBsaWVkIHNvdXJjZSBtYXAuIE5vdGU6IFRoZSByZXNvbHV0aW9uIGZvciB0aGVcbiAgICogcmVzdWx0aW5nIG1hcHBpbmdzIGlzIHRoZSBtaW5pbWl1bSBvZiB0aGlzIG1hcCBhbmQgdGhlIHN1cHBsaWVkIG1hcC5cbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VNYXBDb25zdW1lciBUaGUgc291cmNlIG1hcCB0byBiZSBhcHBsaWVkLlxuICAgKiBAcGFyYW0gYVNvdXJjZUZpbGUgT3B0aW9uYWwuIFRoZSBmaWxlbmFtZSBvZiB0aGUgc291cmNlIGZpbGUuXG4gICAqICAgICAgICBJZiBvbWl0dGVkLCBTb3VyY2VNYXBDb25zdW1lcidzIGZpbGUgcHJvcGVydHkgd2lsbCBiZSB1c2VkLlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcFBhdGggT3B0aW9uYWwuIFRoZSBkaXJuYW1lIG9mIHRoZSBwYXRoIHRvIHRoZSBzb3VyY2UgbWFwXG4gICAqICAgICAgICB0byBiZSBhcHBsaWVkLiBJZiByZWxhdGl2ZSwgaXQgaXMgcmVsYXRpdmUgdG8gdGhlIFNvdXJjZU1hcENvbnN1bWVyLlxuICAgKiAgICAgICAgVGhpcyBwYXJhbWV0ZXIgaXMgbmVlZGVkIHdoZW4gdGhlIHR3byBzb3VyY2UgbWFwcyBhcmVuJ3QgaW4gdGhlIHNhbWVcbiAgICogICAgICAgIGRpcmVjdG9yeSwgYW5kIHRoZSBzb3VyY2UgbWFwIHRvIGJlIGFwcGxpZWQgY29udGFpbnMgcmVsYXRpdmUgc291cmNlXG4gICAqICAgICAgICBwYXRocy4gSWYgc28sIHRob3NlIHJlbGF0aXZlIHNvdXJjZSBwYXRocyBuZWVkIHRvIGJlIHJld3JpdHRlblxuICAgKiAgICAgICAgcmVsYXRpdmUgdG8gdGhlIFNvdXJjZU1hcEdlbmVyYXRvci5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYXBwbHlTb3VyY2VNYXAgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9hcHBseVNvdXJjZU1hcChhU291cmNlTWFwQ29uc3VtZXIsIGFTb3VyY2VGaWxlLCBhU291cmNlTWFwUGF0aCkge1xuICAgICAgdmFyIHNvdXJjZUZpbGUgPSBhU291cmNlRmlsZTtcbiAgICAgIC8vIElmIGFTb3VyY2VGaWxlIGlzIG9taXR0ZWQsIHdlIHdpbGwgdXNlIHRoZSBmaWxlIHByb3BlcnR5IG9mIHRoZSBTb3VyY2VNYXBcbiAgICAgIGlmIChhU291cmNlRmlsZSA9PSBudWxsKSB7XG4gICAgICAgIGlmIChhU291cmNlTWFwQ29uc3VtZXIuZmlsZSA9PSBudWxsKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgJ1NvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYXBwbHlTb3VyY2VNYXAgcmVxdWlyZXMgZWl0aGVyIGFuIGV4cGxpY2l0IHNvdXJjZSBmaWxlLCAnICtcbiAgICAgICAgICAgICdvciB0aGUgc291cmNlIG1hcFxcJ3MgXCJmaWxlXCIgcHJvcGVydHkuIEJvdGggd2VyZSBvbWl0dGVkLidcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHNvdXJjZUZpbGUgPSBhU291cmNlTWFwQ29uc3VtZXIuZmlsZTtcbiAgICAgIH1cbiAgICAgIHZhciBzb3VyY2VSb290ID0gdGhpcy5fc291cmNlUm9vdDtcbiAgICAgIC8vIE1ha2UgXCJzb3VyY2VGaWxlXCIgcmVsYXRpdmUgaWYgYW4gYWJzb2x1dGUgVXJsIGlzIHBhc3NlZC5cbiAgICAgIGlmIChzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgc291cmNlRmlsZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlRmlsZSk7XG4gICAgICB9XG4gICAgICAvLyBBcHBseWluZyB0aGUgU291cmNlTWFwIGNhbiBhZGQgYW5kIHJlbW92ZSBpdGVtcyBmcm9tIHRoZSBzb3VyY2VzIGFuZFxuICAgICAgLy8gdGhlIG5hbWVzIGFycmF5LlxuICAgICAgdmFyIG5ld1NvdXJjZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICAgIHZhciBuZXdOYW1lcyA9IG5ldyBBcnJheVNldCgpO1xuXG4gICAgICAvLyBGaW5kIG1hcHBpbmdzIGZvciB0aGUgXCJzb3VyY2VGaWxlXCJcbiAgICAgIHRoaXMuX21hcHBpbmdzLnVuc29ydGVkRm9yRWFjaChmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgPT09IHNvdXJjZUZpbGUgJiYgbWFwcGluZy5vcmlnaW5hbExpbmUgIT0gbnVsbCkge1xuICAgICAgICAgIC8vIENoZWNrIGlmIGl0IGNhbiBiZSBtYXBwZWQgYnkgdGhlIHNvdXJjZSBtYXAsIHRoZW4gdXBkYXRlIHRoZSBtYXBwaW5nLlxuICAgICAgICAgIHZhciBvcmlnaW5hbCA9IGFTb3VyY2VNYXBDb25zdW1lci5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgICAgICAgIGxpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgY29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKG9yaWdpbmFsLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgICAvLyBDb3B5IG1hcHBpbmdcbiAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gb3JpZ2luYWwuc291cmNlO1xuICAgICAgICAgICAgaWYgKGFTb3VyY2VNYXBQYXRoICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSB1dGlsLmpvaW4oYVNvdXJjZU1hcFBhdGgsIG1hcHBpbmcuc291cmNlKVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgbWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgPSBvcmlnaW5hbC5saW5lO1xuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IG9yaWdpbmFsLmNvbHVtbjtcbiAgICAgICAgICAgIGlmIChvcmlnaW5hbC5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgbWFwcGluZy5uYW1lID0gb3JpZ2luYWwubmFtZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc291cmNlID0gbWFwcGluZy5zb3VyY2U7XG4gICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiAhbmV3U291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICAgIG5ld1NvdXJjZXMuYWRkKHNvdXJjZSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgbmFtZSA9IG1hcHBpbmcubmFtZTtcbiAgICAgICAgaWYgKG5hbWUgIT0gbnVsbCAmJiAhbmV3TmFtZXMuaGFzKG5hbWUpKSB7XG4gICAgICAgICAgbmV3TmFtZXMuYWRkKG5hbWUpO1xuICAgICAgICB9XG5cbiAgICAgIH0sIHRoaXMpO1xuICAgICAgdGhpcy5fc291cmNlcyA9IG5ld1NvdXJjZXM7XG4gICAgICB0aGlzLl9uYW1lcyA9IG5ld05hbWVzO1xuXG4gICAgICAvLyBDb3B5IHNvdXJjZXNDb250ZW50cyBvZiBhcHBsaWVkIG1hcC5cbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VzLmZvckVhY2goZnVuY3Rpb24gKHNvdXJjZUZpbGUpIHtcbiAgICAgICAgdmFyIGNvbnRlbnQgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlQ29udGVudEZvcihzb3VyY2VGaWxlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQgIT0gbnVsbCkge1xuICAgICAgICAgIGlmIChhU291cmNlTWFwUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5qb2luKGFTb3VyY2VNYXBQYXRoLCBzb3VyY2VGaWxlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlRmlsZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlRmlsZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMuc2V0U291cmNlQ29udGVudChzb3VyY2VGaWxlLCBjb250ZW50KTtcbiAgICAgICAgfVxuICAgICAgfSwgdGhpcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogQSBtYXBwaW5nIGNhbiBoYXZlIG9uZSBvZiB0aGUgdGhyZWUgbGV2ZWxzIG9mIGRhdGE6XG4gICAqXG4gICAqICAgMS4gSnVzdCB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9uLlxuICAgKiAgIDIuIFRoZSBHZW5lcmF0ZWQgcG9zaXRpb24sIG9yaWdpbmFsIHBvc2l0aW9uLCBhbmQgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIDMuIEdlbmVyYXRlZCBhbmQgb3JpZ2luYWwgcG9zaXRpb24sIG9yaWdpbmFsIHNvdXJjZSwgYXMgd2VsbCBhcyBhIG5hbWVcbiAgICogICAgICB0b2tlbi5cbiAgICpcbiAgICogVG8gbWFpbnRhaW4gY29uc2lzdGVuY3ksIHdlIHZhbGlkYXRlIHRoYXQgYW55IG5ldyBtYXBwaW5nIGJlaW5nIGFkZGVkIGZhbGxzXG4gICAqIGluIHRvIG9uZSBvZiB0aGVzZSBjYXRlZ29yaWVzLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fdmFsaWRhdGVNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfdmFsaWRhdGVNYXBwaW5nKGFHZW5lcmF0ZWQsIGFPcmlnaW5hbCwgYVNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFOYW1lKSB7XG4gICAgICBpZiAoYUdlbmVyYXRlZCAmJiAnbGluZScgaW4gYUdlbmVyYXRlZCAmJiAnY29sdW1uJyBpbiBhR2VuZXJhdGVkXG4gICAgICAgICAgJiYgYUdlbmVyYXRlZC5saW5lID4gMCAmJiBhR2VuZXJhdGVkLmNvbHVtbiA+PSAwXG4gICAgICAgICAgJiYgIWFPcmlnaW5hbCAmJiAhYVNvdXJjZSAmJiAhYU5hbWUpIHtcbiAgICAgICAgLy8gQ2FzZSAxLlxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBlbHNlIGlmIChhR2VuZXJhdGVkICYmICdsaW5lJyBpbiBhR2VuZXJhdGVkICYmICdjb2x1bW4nIGluIGFHZW5lcmF0ZWRcbiAgICAgICAgICAgICAgICYmIGFPcmlnaW5hbCAmJiAnbGluZScgaW4gYU9yaWdpbmFsICYmICdjb2x1bW4nIGluIGFPcmlnaW5hbFxuICAgICAgICAgICAgICAgJiYgYUdlbmVyYXRlZC5saW5lID4gMCAmJiBhR2VuZXJhdGVkLmNvbHVtbiA+PSAwXG4gICAgICAgICAgICAgICAmJiBhT3JpZ2luYWwubGluZSA+IDAgJiYgYU9yaWdpbmFsLmNvbHVtbiA+PSAwXG4gICAgICAgICAgICAgICAmJiBhU291cmNlKSB7XG4gICAgICAgIC8vIENhc2VzIDIgYW5kIDMuXG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgbWFwcGluZzogJyArIEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IGFHZW5lcmF0ZWQsXG4gICAgICAgICAgc291cmNlOiBhU291cmNlLFxuICAgICAgICAgIG9yaWdpbmFsOiBhT3JpZ2luYWwsXG4gICAgICAgICAgbmFtZTogYU5hbWVcbiAgICAgICAgfSkpO1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFNlcmlhbGl6ZSB0aGUgYWNjdW11bGF0ZWQgbWFwcGluZ3MgaW4gdG8gdGhlIHN0cmVhbSBvZiBiYXNlIDY0IFZMUXNcbiAgICogc3BlY2lmaWVkIGJ5IHRoZSBzb3VyY2UgbWFwIGZvcm1hdC5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX3NlcmlhbGl6ZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3Jfc2VyaWFsaXplTWFwcGluZ3MoKSB7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzR2VuZXJhdGVkTGluZSA9IDE7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbExpbmUgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzTmFtZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNTb3VyY2UgPSAwO1xuICAgICAgdmFyIHJlc3VsdCA9ICcnO1xuICAgICAgdmFyIG1hcHBpbmc7XG4gICAgICB2YXIgbmFtZUlkeDtcbiAgICAgIHZhciBzb3VyY2VJZHg7XG5cbiAgICAgIHZhciBtYXBwaW5ncyA9IHRoaXMuX21hcHBpbmdzLnRvQXJyYXkoKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBtYXBwaW5ncy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBtYXBwaW5nID0gbWFwcGluZ3NbaV07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSAhPT0gcHJldmlvdXNHZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgICAgIHdoaWxlIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgIT09IHByZXZpb3VzR2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgcmVzdWx0ICs9ICc7JztcbiAgICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkTGluZSsrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBpZiAoaSA+IDApIHtcbiAgICAgICAgICAgIGlmICghdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZChtYXBwaW5nLCBtYXBwaW5nc1tpIC0gMV0pKSB7XG4gICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmVzdWx0ICs9ICcsJztcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShtYXBwaW5nLmdlbmVyYXRlZENvbHVtblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbjtcblxuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgIT0gbnVsbCkge1xuICAgICAgICAgIHNvdXJjZUlkeCA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUoc291cmNlSWR4IC0gcHJldmlvdXNTb3VyY2UpO1xuICAgICAgICAgIHByZXZpb3VzU291cmNlID0gc291cmNlSWR4O1xuXG4gICAgICAgICAgLy8gbGluZXMgYXJlIHN0b3JlZCAwLWJhc2VkIGluIFNvdXJjZU1hcCBzcGVjIHZlcnNpb24gM1xuICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG1hcHBpbmcub3JpZ2luYWxMaW5lIC0gMVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNPcmlnaW5hbExpbmUpO1xuICAgICAgICAgIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gbWFwcGluZy5vcmlnaW5hbExpbmUgLSAxO1xuXG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobWFwcGluZy5vcmlnaW5hbENvbHVtblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNPcmlnaW5hbENvbHVtbik7XG4gICAgICAgICAgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgIG5hbWVJZHggPSB0aGlzLl9uYW1lcy5pbmRleE9mKG1hcHBpbmcubmFtZSk7XG4gICAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShuYW1lSWR4IC0gcHJldmlvdXNOYW1lKTtcbiAgICAgICAgICAgIHByZXZpb3VzTmFtZSA9IG5hbWVJZHg7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfTtcblxuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50ID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfZ2VuZXJhdGVTb3VyY2VzQ29udGVudChhU291cmNlcywgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHJldHVybiBhU291cmNlcy5tYXAoZnVuY3Rpb24gKHNvdXJjZSkge1xuICAgICAgICBpZiAoIXRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGlmIChhU291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlID0gdXRpbC5yZWxhdGl2ZShhU291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIga2V5ID0gdXRpbC50b1NldFN0cmluZyhzb3VyY2UpO1xuICAgICAgICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHRoaXMuX3NvdXJjZXNDb250ZW50cyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkpXG4gICAgICAgICAgPyB0aGlzLl9zb3VyY2VzQ29udGVudHNba2V5XVxuICAgICAgICAgIDogbnVsbDtcbiAgICAgIH0sIHRoaXMpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEV4dGVybmFsaXplIHRoZSBzb3VyY2UgbWFwLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS50b0pTT04gPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl90b0pTT04oKSB7XG4gICAgICB2YXIgbWFwID0ge1xuICAgICAgICB2ZXJzaW9uOiB0aGlzLl92ZXJzaW9uLFxuICAgICAgICBzb3VyY2VzOiB0aGlzLl9zb3VyY2VzLnRvQXJyYXkoKSxcbiAgICAgICAgbmFtZXM6IHRoaXMuX25hbWVzLnRvQXJyYXkoKSxcbiAgICAgICAgbWFwcGluZ3M6IHRoaXMuX3NlcmlhbGl6ZU1hcHBpbmdzKClcbiAgICAgIH07XG4gICAgICBpZiAodGhpcy5fZmlsZSAhPSBudWxsKSB7XG4gICAgICAgIG1hcC5maWxlID0gdGhpcy5fZmlsZTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLl9zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgbWFwLnNvdXJjZVJvb3QgPSB0aGlzLl9zb3VyY2VSb290O1xuICAgICAgfVxuICAgICAgaWYgKHRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICBtYXAuc291cmNlc0NvbnRlbnQgPSB0aGlzLl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50KG1hcC5zb3VyY2VzLCBtYXAuc291cmNlUm9vdCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBtYXA7XG4gICAgfTtcblxuICAvKipcbiAgICogUmVuZGVyIHRoZSBzb3VyY2UgbWFwIGJlaW5nIGdlbmVyYXRlZCB0byBhIHN0cmluZy5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUudG9TdHJpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl90b1N0cmluZygpIHtcbiAgICAgIHJldHVybiBKU09OLnN0cmluZ2lmeSh0aGlzLnRvSlNPTigpKTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuU291cmNlTWFwR2VuZXJhdG9yID0gU291cmNlTWFwR2VuZXJhdG9yO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvci5qc1xuICoqIG1vZHVsZSBpZCA9IDJcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKlxuICogQmFzZWQgb24gdGhlIEJhc2UgNjQgVkxRIGltcGxlbWVudGF0aW9uIGluIENsb3N1cmUgQ29tcGlsZXI6XG4gKiBodHRwczovL2NvZGUuZ29vZ2xlLmNvbS9wL2Nsb3N1cmUtY29tcGlsZXIvc291cmNlL2Jyb3dzZS90cnVuay9zcmMvY29tL2dvb2dsZS9kZWJ1Z2dpbmcvc291cmNlbWFwL0Jhc2U2NFZMUS5qYXZhXG4gKlxuICogQ29weXJpZ2h0IDIwMTEgVGhlIENsb3N1cmUgQ29tcGlsZXIgQXV0aG9ycy4gQWxsIHJpZ2h0cyByZXNlcnZlZC5cbiAqIFJlZGlzdHJpYnV0aW9uIGFuZCB1c2UgaW4gc291cmNlIGFuZCBiaW5hcnkgZm9ybXMsIHdpdGggb3Igd2l0aG91dFxuICogbW9kaWZpY2F0aW9uLCBhcmUgcGVybWl0dGVkIHByb3ZpZGVkIHRoYXQgdGhlIGZvbGxvd2luZyBjb25kaXRpb25zIGFyZVxuICogbWV0OlxuICpcbiAqICAqIFJlZGlzdHJpYnV0aW9ucyBvZiBzb3VyY2UgY29kZSBtdXN0IHJldGFpbiB0aGUgYWJvdmUgY29weXJpZ2h0XG4gKiAgICBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nIGRpc2NsYWltZXIuXG4gKiAgKiBSZWRpc3RyaWJ1dGlvbnMgaW4gYmluYXJ5IGZvcm0gbXVzdCByZXByb2R1Y2UgdGhlIGFib3ZlXG4gKiAgICBjb3B5cmlnaHQgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZ1xuICogICAgZGlzY2xhaW1lciBpbiB0aGUgZG9jdW1lbnRhdGlvbiBhbmQvb3Igb3RoZXIgbWF0ZXJpYWxzIHByb3ZpZGVkXG4gKiAgICB3aXRoIHRoZSBkaXN0cmlidXRpb24uXG4gKiAgKiBOZWl0aGVyIHRoZSBuYW1lIG9mIEdvb2dsZSBJbmMuIG5vciB0aGUgbmFtZXMgb2YgaXRzXG4gKiAgICBjb250cmlidXRvcnMgbWF5IGJlIHVzZWQgdG8gZW5kb3JzZSBvciBwcm9tb3RlIHByb2R1Y3RzIGRlcml2ZWRcbiAqICAgIGZyb20gdGhpcyBzb2Z0d2FyZSB3aXRob3V0IHNwZWNpZmljIHByaW9yIHdyaXR0ZW4gcGVybWlzc2lvbi5cbiAqXG4gKiBUSElTIFNPRlRXQVJFIElTIFBST1ZJREVEIEJZIFRIRSBDT1BZUklHSFQgSE9MREVSUyBBTkQgQ09OVFJJQlVUT1JTXG4gKiBcIkFTIElTXCIgQU5EIEFOWSBFWFBSRVNTIE9SIElNUExJRUQgV0FSUkFOVElFUywgSU5DTFVESU5HLCBCVVQgTk9UXG4gKiBMSU1JVEVEIFRPLCBUSEUgSU1QTElFRCBXQVJSQU5USUVTIE9GIE1FUkNIQU5UQUJJTElUWSBBTkQgRklUTkVTUyBGT1JcbiAqIEEgUEFSVElDVUxBUiBQVVJQT1NFIEFSRSBESVNDTEFJTUVELiBJTiBOTyBFVkVOVCBTSEFMTCBUSEUgQ09QWVJJR0hUXG4gKiBPV05FUiBPUiBDT05UUklCVVRPUlMgQkUgTElBQkxFIEZPUiBBTlkgRElSRUNULCBJTkRJUkVDVCwgSU5DSURFTlRBTCxcbiAqIFNQRUNJQUwsIEVYRU1QTEFSWSwgT1IgQ09OU0VRVUVOVElBTCBEQU1BR0VTIChJTkNMVURJTkcsIEJVVCBOT1RcbiAqIExJTUlURUQgVE8sIFBST0NVUkVNRU5UIE9GIFNVQlNUSVRVVEUgR09PRFMgT1IgU0VSVklDRVM7IExPU1MgT0YgVVNFLFxuICogREFUQSwgT1IgUFJPRklUUzsgT1IgQlVTSU5FU1MgSU5URVJSVVBUSU9OKSBIT1dFVkVSIENBVVNFRCBBTkQgT04gQU5ZXG4gKiBUSEVPUlkgT0YgTElBQklMSVRZLCBXSEVUSEVSIElOIENPTlRSQUNULCBTVFJJQ1QgTElBQklMSVRZLCBPUiBUT1JUXG4gKiAoSU5DTFVESU5HIE5FR0xJR0VOQ0UgT1IgT1RIRVJXSVNFKSBBUklTSU5HIElOIEFOWSBXQVkgT1VUIE9GIFRIRSBVU0VcbiAqIE9GIFRISVMgU09GVFdBUkUsIEVWRU4gSUYgQURWSVNFRCBPRiBUSEUgUE9TU0lCSUxJVFkgT0YgU1VDSCBEQU1BR0UuXG4gKi9cbntcbiAgdmFyIGJhc2U2NCA9IHJlcXVpcmUoJy4vYmFzZTY0Jyk7XG5cbiAgLy8gQSBzaW5nbGUgYmFzZSA2NCBkaWdpdCBjYW4gY29udGFpbiA2IGJpdHMgb2YgZGF0YS4gRm9yIHRoZSBiYXNlIDY0IHZhcmlhYmxlXG4gIC8vIGxlbmd0aCBxdWFudGl0aWVzIHdlIHVzZSBpbiB0aGUgc291cmNlIG1hcCBzcGVjLCB0aGUgZmlyc3QgYml0IGlzIHRoZSBzaWduLFxuICAvLyB0aGUgbmV4dCBmb3VyIGJpdHMgYXJlIHRoZSBhY3R1YWwgdmFsdWUsIGFuZCB0aGUgNnRoIGJpdCBpcyB0aGVcbiAgLy8gY29udGludWF0aW9uIGJpdC4gVGhlIGNvbnRpbnVhdGlvbiBiaXQgdGVsbHMgdXMgd2hldGhlciB0aGVyZSBhcmUgbW9yZVxuICAvLyBkaWdpdHMgaW4gdGhpcyB2YWx1ZSBmb2xsb3dpbmcgdGhpcyBkaWdpdC5cbiAgLy9cbiAgLy8gICBDb250aW51YXRpb25cbiAgLy8gICB8ICAgIFNpZ25cbiAgLy8gICB8ICAgIHxcbiAgLy8gICBWICAgIFZcbiAgLy8gICAxMDEwMTFcblxuICB2YXIgVkxRX0JBU0VfU0hJRlQgPSA1O1xuXG4gIC8vIGJpbmFyeTogMTAwMDAwXG4gIHZhciBWTFFfQkFTRSA9IDEgPDwgVkxRX0JBU0VfU0hJRlQ7XG5cbiAgLy8gYmluYXJ5OiAwMTExMTFcbiAgdmFyIFZMUV9CQVNFX01BU0sgPSBWTFFfQkFTRSAtIDE7XG5cbiAgLy8gYmluYXJ5OiAxMDAwMDBcbiAgdmFyIFZMUV9DT05USU5VQVRJT05fQklUID0gVkxRX0JBU0U7XG5cbiAgLyoqXG4gICAqIENvbnZlcnRzIGZyb20gYSB0d28tY29tcGxlbWVudCB2YWx1ZSB0byBhIHZhbHVlIHdoZXJlIHRoZSBzaWduIGJpdCBpc1xuICAgKiBwbGFjZWQgaW4gdGhlIGxlYXN0IHNpZ25pZmljYW50IGJpdC4gIEZvciBleGFtcGxlLCBhcyBkZWNpbWFsczpcbiAgICogICAxIGJlY29tZXMgMiAoMTAgYmluYXJ5KSwgLTEgYmVjb21lcyAzICgxMSBiaW5hcnkpXG4gICAqICAgMiBiZWNvbWVzIDQgKDEwMCBiaW5hcnkpLCAtMiBiZWNvbWVzIDUgKDEwMSBiaW5hcnkpXG4gICAqL1xuICBmdW5jdGlvbiB0b1ZMUVNpZ25lZChhVmFsdWUpIHtcbiAgICByZXR1cm4gYVZhbHVlIDwgMFxuICAgICAgPyAoKC1hVmFsdWUpIDw8IDEpICsgMVxuICAgICAgOiAoYVZhbHVlIDw8IDEpICsgMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb252ZXJ0cyB0byBhIHR3by1jb21wbGVtZW50IHZhbHVlIGZyb20gYSB2YWx1ZSB3aGVyZSB0aGUgc2lnbiBiaXQgaXNcbiAgICogcGxhY2VkIGluIHRoZSBsZWFzdCBzaWduaWZpY2FudCBiaXQuICBGb3IgZXhhbXBsZSwgYXMgZGVjaW1hbHM6XG4gICAqICAgMiAoMTAgYmluYXJ5KSBiZWNvbWVzIDEsIDMgKDExIGJpbmFyeSkgYmVjb21lcyAtMVxuICAgKiAgIDQgKDEwMCBiaW5hcnkpIGJlY29tZXMgMiwgNSAoMTAxIGJpbmFyeSkgYmVjb21lcyAtMlxuICAgKi9cbiAgZnVuY3Rpb24gZnJvbVZMUVNpZ25lZChhVmFsdWUpIHtcbiAgICB2YXIgaXNOZWdhdGl2ZSA9IChhVmFsdWUgJiAxKSA9PT0gMTtcbiAgICB2YXIgc2hpZnRlZCA9IGFWYWx1ZSA+PiAxO1xuICAgIHJldHVybiBpc05lZ2F0aXZlXG4gICAgICA/IC1zaGlmdGVkXG4gICAgICA6IHNoaWZ0ZWQ7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYmFzZSA2NCBWTFEgZW5jb2RlZCB2YWx1ZS5cbiAgICovXG4gIGV4cG9ydHMuZW5jb2RlID0gZnVuY3Rpb24gYmFzZTY0VkxRX2VuY29kZShhVmFsdWUpIHtcbiAgICB2YXIgZW5jb2RlZCA9IFwiXCI7XG4gICAgdmFyIGRpZ2l0O1xuXG4gICAgdmFyIHZscSA9IHRvVkxRU2lnbmVkKGFWYWx1ZSk7XG5cbiAgICBkbyB7XG4gICAgICBkaWdpdCA9IHZscSAmIFZMUV9CQVNFX01BU0s7XG4gICAgICB2bHEgPj4+PSBWTFFfQkFTRV9TSElGVDtcbiAgICAgIGlmICh2bHEgPiAwKSB7XG4gICAgICAgIC8vIFRoZXJlIGFyZSBzdGlsbCBtb3JlIGRpZ2l0cyBpbiB0aGlzIHZhbHVlLCBzbyB3ZSBtdXN0IG1ha2Ugc3VyZSB0aGVcbiAgICAgICAgLy8gY29udGludWF0aW9uIGJpdCBpcyBtYXJrZWQuXG4gICAgICAgIGRpZ2l0IHw9IFZMUV9DT05USU5VQVRJT05fQklUO1xuICAgICAgfVxuICAgICAgZW5jb2RlZCArPSBiYXNlNjQuZW5jb2RlKGRpZ2l0KTtcbiAgICB9IHdoaWxlICh2bHEgPiAwKTtcblxuICAgIHJldHVybiBlbmNvZGVkO1xuICB9O1xuXG4gIC8qKlxuICAgKiBEZWNvZGVzIHRoZSBuZXh0IGJhc2UgNjQgVkxRIHZhbHVlIGZyb20gdGhlIGdpdmVuIHN0cmluZyBhbmQgcmV0dXJucyB0aGVcbiAgICogdmFsdWUgYW5kIHRoZSByZXN0IG9mIHRoZSBzdHJpbmcgdmlhIHRoZSBvdXQgcGFyYW1ldGVyLlxuICAgKi9cbiAgZXhwb3J0cy5kZWNvZGUgPSBmdW5jdGlvbiBiYXNlNjRWTFFfZGVjb2RlKGFTdHIsIGFJbmRleCwgYU91dFBhcmFtKSB7XG4gICAgdmFyIHN0ckxlbiA9IGFTdHIubGVuZ3RoO1xuICAgIHZhciByZXN1bHQgPSAwO1xuICAgIHZhciBzaGlmdCA9IDA7XG4gICAgdmFyIGNvbnRpbnVhdGlvbiwgZGlnaXQ7XG5cbiAgICBkbyB7XG4gICAgICBpZiAoYUluZGV4ID49IHN0ckxlbikge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJFeHBlY3RlZCBtb3JlIGRpZ2l0cyBpbiBiYXNlIDY0IFZMUSB2YWx1ZS5cIik7XG4gICAgICB9XG5cbiAgICAgIGRpZ2l0ID0gYmFzZTY0LmRlY29kZShhU3RyLmNoYXJDb2RlQXQoYUluZGV4KyspKTtcbiAgICAgIGlmIChkaWdpdCA9PT0gLTEpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiSW52YWxpZCBiYXNlNjQgZGlnaXQ6IFwiICsgYVN0ci5jaGFyQXQoYUluZGV4IC0gMSkpO1xuICAgICAgfVxuXG4gICAgICBjb250aW51YXRpb24gPSAhIShkaWdpdCAmIFZMUV9DT05USU5VQVRJT05fQklUKTtcbiAgICAgIGRpZ2l0ICY9IFZMUV9CQVNFX01BU0s7XG4gICAgICByZXN1bHQgPSByZXN1bHQgKyAoZGlnaXQgPDwgc2hpZnQpO1xuICAgICAgc2hpZnQgKz0gVkxRX0JBU0VfU0hJRlQ7XG4gICAgfSB3aGlsZSAoY29udGludWF0aW9uKTtcblxuICAgIGFPdXRQYXJhbS52YWx1ZSA9IGZyb21WTFFTaWduZWQocmVzdWx0KTtcbiAgICBhT3V0UGFyYW0ucmVzdCA9IGFJbmRleDtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmFzZTY0LXZscS5qc1xuICoqIG1vZHVsZSBpZCA9IDNcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIGludFRvQ2hhck1hcCA9ICdBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWmFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6MDEyMzQ1Njc4OSsvJy5zcGxpdCgnJyk7XG5cbiAgLyoqXG4gICAqIEVuY29kZSBhbiBpbnRlZ2VyIGluIHRoZSByYW5nZSBvZiAwIHRvIDYzIHRvIGEgc2luZ2xlIGJhc2UgNjQgZGlnaXQuXG4gICAqL1xuICBleHBvcnRzLmVuY29kZSA9IGZ1bmN0aW9uIChudW1iZXIpIHtcbiAgICBpZiAoMCA8PSBudW1iZXIgJiYgbnVtYmVyIDwgaW50VG9DaGFyTWFwLmxlbmd0aCkge1xuICAgICAgcmV0dXJuIGludFRvQ2hhck1hcFtudW1iZXJdO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgVHlwZUVycm9yKFwiTXVzdCBiZSBiZXR3ZWVuIDAgYW5kIDYzOiBcIiArIG51bWJlcik7XG4gIH07XG5cbiAgLyoqXG4gICAqIERlY29kZSBhIHNpbmdsZSBiYXNlIDY0IGNoYXJhY3RlciBjb2RlIGRpZ2l0IHRvIGFuIGludGVnZXIuIFJldHVybnMgLTEgb25cbiAgICogZmFpbHVyZS5cbiAgICovXG4gIGV4cG9ydHMuZGVjb2RlID0gZnVuY3Rpb24gKGNoYXJDb2RlKSB7XG4gICAgdmFyIGJpZ0EgPSA2NTsgICAgIC8vICdBJ1xuICAgIHZhciBiaWdaID0gOTA7ICAgICAvLyAnWidcblxuICAgIHZhciBsaXR0bGVBID0gOTc7ICAvLyAnYSdcbiAgICB2YXIgbGl0dGxlWiA9IDEyMjsgLy8gJ3onXG5cbiAgICB2YXIgemVybyA9IDQ4OyAgICAgLy8gJzAnXG4gICAgdmFyIG5pbmUgPSA1NzsgICAgIC8vICc5J1xuXG4gICAgdmFyIHBsdXMgPSA0MzsgICAgIC8vICcrJ1xuICAgIHZhciBzbGFzaCA9IDQ3OyAgICAvLyAnLydcblxuICAgIHZhciBsaXR0bGVPZmZzZXQgPSAyNjtcbiAgICB2YXIgbnVtYmVyT2Zmc2V0ID0gNTI7XG5cbiAgICAvLyAwIC0gMjU6IEFCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaXG4gICAgaWYgKGJpZ0EgPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gYmlnWikge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIGJpZ0EpO1xuICAgIH1cblxuICAgIC8vIDI2IC0gNTE6IGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6XG4gICAgaWYgKGxpdHRsZUEgPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gbGl0dGxlWikge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIGxpdHRsZUEgKyBsaXR0bGVPZmZzZXQpO1xuICAgIH1cblxuICAgIC8vIDUyIC0gNjE6IDAxMjM0NTY3ODlcbiAgICBpZiAoemVybyA8PSBjaGFyQ29kZSAmJiBjaGFyQ29kZSA8PSBuaW5lKSB7XG4gICAgICByZXR1cm4gKGNoYXJDb2RlIC0gemVybyArIG51bWJlck9mZnNldCk7XG4gICAgfVxuXG4gICAgLy8gNjI6ICtcbiAgICBpZiAoY2hhckNvZGUgPT0gcGx1cykge1xuICAgICAgcmV0dXJuIDYyO1xuICAgIH1cblxuICAgIC8vIDYzOiAvXG4gICAgaWYgKGNoYXJDb2RlID09IHNsYXNoKSB7XG4gICAgICByZXR1cm4gNjM7XG4gICAgfVxuXG4gICAgLy8gSW52YWxpZCBiYXNlNjQgZGlnaXQuXG4gICAgcmV0dXJuIC0xO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9iYXNlNjQuanNcbiAqKiBtb2R1bGUgaWQgPSA0XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIC8qKlxuICAgKiBUaGlzIGlzIGEgaGVscGVyIGZ1bmN0aW9uIGZvciBnZXR0aW5nIHZhbHVlcyBmcm9tIHBhcmFtZXRlci9vcHRpb25zXG4gICAqIG9iamVjdHMuXG4gICAqXG4gICAqIEBwYXJhbSBhcmdzIFRoZSBvYmplY3Qgd2UgYXJlIGV4dHJhY3RpbmcgdmFsdWVzIGZyb21cbiAgICogQHBhcmFtIG5hbWUgVGhlIG5hbWUgb2YgdGhlIHByb3BlcnR5IHdlIGFyZSBnZXR0aW5nLlxuICAgKiBAcGFyYW0gZGVmYXVsdFZhbHVlIEFuIG9wdGlvbmFsIHZhbHVlIHRvIHJldHVybiBpZiB0aGUgcHJvcGVydHkgaXMgbWlzc2luZ1xuICAgKiBmcm9tIHRoZSBvYmplY3QuIElmIHRoaXMgaXMgbm90IHNwZWNpZmllZCBhbmQgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmcsIGFuXG4gICAqIGVycm9yIHdpbGwgYmUgdGhyb3duLlxuICAgKi9cbiAgZnVuY3Rpb24gZ2V0QXJnKGFBcmdzLCBhTmFtZSwgYURlZmF1bHRWYWx1ZSkge1xuICAgIGlmIChhTmFtZSBpbiBhQXJncykge1xuICAgICAgcmV0dXJuIGFBcmdzW2FOYW1lXTtcbiAgICB9IGVsc2UgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDMpIHtcbiAgICAgIHJldHVybiBhRGVmYXVsdFZhbHVlO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFOYW1lICsgJ1wiIGlzIGEgcmVxdWlyZWQgYXJndW1lbnQuJyk7XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuZ2V0QXJnID0gZ2V0QXJnO1xuXG4gIHZhciB1cmxSZWdleHAgPSAvXig/OihbXFx3K1xcLS5dKyk6KT9cXC9cXC8oPzooXFx3KzpcXHcrKUApPyhbXFx3Ll0qKSg/OjooXFxkKykpPyhcXFMqKSQvO1xuICB2YXIgZGF0YVVybFJlZ2V4cCA9IC9eZGF0YTouK1xcLC4rJC87XG5cbiAgZnVuY3Rpb24gdXJsUGFyc2UoYVVybCkge1xuICAgIHZhciBtYXRjaCA9IGFVcmwubWF0Y2godXJsUmVnZXhwKTtcbiAgICBpZiAoIW1hdGNoKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIHNjaGVtZTogbWF0Y2hbMV0sXG4gICAgICBhdXRoOiBtYXRjaFsyXSxcbiAgICAgIGhvc3Q6IG1hdGNoWzNdLFxuICAgICAgcG9ydDogbWF0Y2hbNF0sXG4gICAgICBwYXRoOiBtYXRjaFs1XVxuICAgIH07XG4gIH1cbiAgZXhwb3J0cy51cmxQYXJzZSA9IHVybFBhcnNlO1xuXG4gIGZ1bmN0aW9uIHVybEdlbmVyYXRlKGFQYXJzZWRVcmwpIHtcbiAgICB2YXIgdXJsID0gJyc7XG4gICAgaWYgKGFQYXJzZWRVcmwuc2NoZW1lKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5zY2hlbWUgKyAnOic7XG4gICAgfVxuICAgIHVybCArPSAnLy8nO1xuICAgIGlmIChhUGFyc2VkVXJsLmF1dGgpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLmF1dGggKyAnQCc7XG4gICAgfVxuICAgIGlmIChhUGFyc2VkVXJsLmhvc3QpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLmhvc3Q7XG4gICAgfVxuICAgIGlmIChhUGFyc2VkVXJsLnBvcnQpIHtcbiAgICAgIHVybCArPSBcIjpcIiArIGFQYXJzZWRVcmwucG9ydFxuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wYXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5wYXRoO1xuICAgIH1cbiAgICByZXR1cm4gdXJsO1xuICB9XG4gIGV4cG9ydHMudXJsR2VuZXJhdGUgPSB1cmxHZW5lcmF0ZTtcblxuICAvKipcbiAgICogTm9ybWFsaXplcyBhIHBhdGgsIG9yIHRoZSBwYXRoIHBvcnRpb24gb2YgYSBVUkw6XG4gICAqXG4gICAqIC0gUmVwbGFjZXMgY29uc2VxdXRpdmUgc2xhc2hlcyB3aXRoIG9uZSBzbGFzaC5cbiAgICogLSBSZW1vdmVzIHVubmVjZXNzYXJ5ICcuJyBwYXJ0cy5cbiAgICogLSBSZW1vdmVzIHVubmVjZXNzYXJ5ICc8ZGlyPi8uLicgcGFydHMuXG4gICAqXG4gICAqIEJhc2VkIG9uIGNvZGUgaW4gdGhlIE5vZGUuanMgJ3BhdGgnIGNvcmUgbW9kdWxlLlxuICAgKlxuICAgKiBAcGFyYW0gYVBhdGggVGhlIHBhdGggb3IgdXJsIHRvIG5vcm1hbGl6ZS5cbiAgICovXG4gIGZ1bmN0aW9uIG5vcm1hbGl6ZShhUGF0aCkge1xuICAgIHZhciBwYXRoID0gYVBhdGg7XG4gICAgdmFyIHVybCA9IHVybFBhcnNlKGFQYXRoKTtcbiAgICBpZiAodXJsKSB7XG4gICAgICBpZiAoIXVybC5wYXRoKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cbiAgICAgIHBhdGggPSB1cmwucGF0aDtcbiAgICB9XG4gICAgdmFyIGlzQWJzb2x1dGUgPSBleHBvcnRzLmlzQWJzb2x1dGUocGF0aCk7XG5cbiAgICB2YXIgcGFydHMgPSBwYXRoLnNwbGl0KC9cXC8rLyk7XG4gICAgZm9yICh2YXIgcGFydCwgdXAgPSAwLCBpID0gcGFydHMubGVuZ3RoIC0gMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgIHBhcnQgPSBwYXJ0c1tpXTtcbiAgICAgIGlmIChwYXJ0ID09PSAnLicpIHtcbiAgICAgICAgcGFydHMuc3BsaWNlKGksIDEpO1xuICAgICAgfSBlbHNlIGlmIChwYXJ0ID09PSAnLi4nKSB7XG4gICAgICAgIHVwKys7XG4gICAgICB9IGVsc2UgaWYgKHVwID4gMCkge1xuICAgICAgICBpZiAocGFydCA9PT0gJycpIHtcbiAgICAgICAgICAvLyBUaGUgZmlyc3QgcGFydCBpcyBibGFuayBpZiB0aGUgcGF0aCBpcyBhYnNvbHV0ZS4gVHJ5aW5nIHRvIGdvXG4gICAgICAgICAgLy8gYWJvdmUgdGhlIHJvb3QgaXMgYSBuby1vcC4gVGhlcmVmb3JlIHdlIGNhbiByZW1vdmUgYWxsICcuLicgcGFydHNcbiAgICAgICAgICAvLyBkaXJlY3RseSBhZnRlciB0aGUgcm9vdC5cbiAgICAgICAgICBwYXJ0cy5zcGxpY2UoaSArIDEsIHVwKTtcbiAgICAgICAgICB1cCA9IDA7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcGFydHMuc3BsaWNlKGksIDIpO1xuICAgICAgICAgIHVwLS07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcGF0aCA9IHBhcnRzLmpvaW4oJy8nKTtcblxuICAgIGlmIChwYXRoID09PSAnJykge1xuICAgICAgcGF0aCA9IGlzQWJzb2x1dGUgPyAnLycgOiAnLic7XG4gICAgfVxuXG4gICAgaWYgKHVybCkge1xuICAgICAgdXJsLnBhdGggPSBwYXRoO1xuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKHVybCk7XG4gICAgfVxuICAgIHJldHVybiBwYXRoO1xuICB9XG4gIGV4cG9ydHMubm9ybWFsaXplID0gbm9ybWFsaXplO1xuXG4gIC8qKlxuICAgKiBKb2lucyB0d28gcGF0aHMvVVJMcy5cbiAgICpcbiAgICogQHBhcmFtIGFSb290IFRoZSByb290IHBhdGggb3IgVVJMLlxuICAgKiBAcGFyYW0gYVBhdGggVGhlIHBhdGggb3IgVVJMIHRvIGJlIGpvaW5lZCB3aXRoIHRoZSByb290LlxuICAgKlxuICAgKiAtIElmIGFQYXRoIGlzIGEgVVJMIG9yIGEgZGF0YSBVUkksIGFQYXRoIGlzIHJldHVybmVkLCB1bmxlc3MgYVBhdGggaXMgYVxuICAgKiAgIHNjaGVtZS1yZWxhdGl2ZSBVUkw6IFRoZW4gdGhlIHNjaGVtZSBvZiBhUm9vdCwgaWYgYW55LCBpcyBwcmVwZW5kZWRcbiAgICogICBmaXJzdC5cbiAgICogLSBPdGhlcndpc2UgYVBhdGggaXMgYSBwYXRoLiBJZiBhUm9vdCBpcyBhIFVSTCwgdGhlbiBpdHMgcGF0aCBwb3J0aW9uXG4gICAqICAgaXMgdXBkYXRlZCB3aXRoIHRoZSByZXN1bHQgYW5kIGFSb290IGlzIHJldHVybmVkLiBPdGhlcndpc2UgdGhlIHJlc3VsdFxuICAgKiAgIGlzIHJldHVybmVkLlxuICAgKiAgIC0gSWYgYVBhdGggaXMgYWJzb2x1dGUsIHRoZSByZXN1bHQgaXMgYVBhdGguXG4gICAqICAgLSBPdGhlcndpc2UgdGhlIHR3byBwYXRocyBhcmUgam9pbmVkIHdpdGggYSBzbGFzaC5cbiAgICogLSBKb2luaW5nIGZvciBleGFtcGxlICdodHRwOi8vJyBhbmQgJ3d3dy5leGFtcGxlLmNvbScgaXMgYWxzbyBzdXBwb3J0ZWQuXG4gICAqL1xuICBmdW5jdGlvbiBqb2luKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG4gICAgaWYgKGFQYXRoID09PSBcIlwiKSB7XG4gICAgICBhUGF0aCA9IFwiLlwiO1xuICAgIH1cbiAgICB2YXIgYVBhdGhVcmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgdmFyIGFSb290VXJsID0gdXJsUGFyc2UoYVJvb3QpO1xuICAgIGlmIChhUm9vdFVybCkge1xuICAgICAgYVJvb3QgPSBhUm9vdFVybC5wYXRoIHx8ICcvJztcbiAgICB9XG5cbiAgICAvLyBgam9pbihmb28sICcvL3d3dy5leGFtcGxlLm9yZycpYFxuICAgIGlmIChhUGF0aFVybCAmJiAhYVBhdGhVcmwuc2NoZW1lKSB7XG4gICAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgICAgYVBhdGhVcmwuc2NoZW1lID0gYVJvb3RVcmwuc2NoZW1lO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKGFQYXRoVXJsKTtcbiAgICB9XG5cbiAgICBpZiAoYVBhdGhVcmwgfHwgYVBhdGgubWF0Y2goZGF0YVVybFJlZ2V4cCkpIHtcbiAgICAgIHJldHVybiBhUGF0aDtcbiAgICB9XG5cbiAgICAvLyBgam9pbignaHR0cDovLycsICd3d3cuZXhhbXBsZS5jb20nKWBcbiAgICBpZiAoYVJvb3RVcmwgJiYgIWFSb290VXJsLmhvc3QgJiYgIWFSb290VXJsLnBhdGgpIHtcbiAgICAgIGFSb290VXJsLmhvc3QgPSBhUGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUm9vdFVybCk7XG4gICAgfVxuXG4gICAgdmFyIGpvaW5lZCA9IGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nXG4gICAgICA/IGFQYXRoXG4gICAgICA6IG5vcm1hbGl6ZShhUm9vdC5yZXBsYWNlKC9cXC8rJC8sICcnKSArICcvJyArIGFQYXRoKTtcblxuICAgIGlmIChhUm9vdFVybCkge1xuICAgICAgYVJvb3RVcmwucGF0aCA9IGpvaW5lZDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUm9vdFVybCk7XG4gICAgfVxuICAgIHJldHVybiBqb2luZWQ7XG4gIH1cbiAgZXhwb3J0cy5qb2luID0gam9pbjtcblxuICBleHBvcnRzLmlzQWJzb2x1dGUgPSBmdW5jdGlvbiAoYVBhdGgpIHtcbiAgICByZXR1cm4gYVBhdGguY2hhckF0KDApID09PSAnLycgfHwgISFhUGF0aC5tYXRjaCh1cmxSZWdleHApO1xuICB9O1xuXG4gIC8qKlxuICAgKiBNYWtlIGEgcGF0aCByZWxhdGl2ZSB0byBhIFVSTCBvciBhbm90aGVyIHBhdGguXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBtYWRlIHJlbGF0aXZlIHRvIGFSb290LlxuICAgKi9cbiAgZnVuY3Rpb24gcmVsYXRpdmUoYVJvb3QsIGFQYXRoKSB7XG4gICAgaWYgKGFSb290ID09PSBcIlwiKSB7XG4gICAgICBhUm9vdCA9IFwiLlwiO1xuICAgIH1cblxuICAgIGFSb290ID0gYVJvb3QucmVwbGFjZSgvXFwvJC8sICcnKTtcblxuICAgIC8vIEl0IGlzIHBvc3NpYmxlIGZvciB0aGUgcGF0aCB0byBiZSBhYm92ZSB0aGUgcm9vdC4gSW4gdGhpcyBjYXNlLCBzaW1wbHlcbiAgICAvLyBjaGVja2luZyB3aGV0aGVyIHRoZSByb290IGlzIGEgcHJlZml4IG9mIHRoZSBwYXRoIHdvbid0IHdvcmsuIEluc3RlYWQsIHdlXG4gICAgLy8gbmVlZCB0byByZW1vdmUgY29tcG9uZW50cyBmcm9tIHRoZSByb290IG9uZSBieSBvbmUsIHVudGlsIGVpdGhlciB3ZSBmaW5kXG4gICAgLy8gYSBwcmVmaXggdGhhdCBmaXRzLCBvciB3ZSBydW4gb3V0IG9mIGNvbXBvbmVudHMgdG8gcmVtb3ZlLlxuICAgIHZhciBsZXZlbCA9IDA7XG4gICAgd2hpbGUgKGFQYXRoLmluZGV4T2YoYVJvb3QgKyAnLycpICE9PSAwKSB7XG4gICAgICB2YXIgaW5kZXggPSBhUm9vdC5sYXN0SW5kZXhPZihcIi9cIik7XG4gICAgICBpZiAoaW5kZXggPCAwKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cblxuICAgICAgLy8gSWYgdGhlIG9ubHkgcGFydCBvZiB0aGUgcm9vdCB0aGF0IGlzIGxlZnQgaXMgdGhlIHNjaGVtZSAoaS5lLiBodHRwOi8vLFxuICAgICAgLy8gZmlsZTovLy8sIGV0Yy4pLCBvbmUgb3IgbW9yZSBzbGFzaGVzICgvKSwgb3Igc2ltcGx5IG5vdGhpbmcgYXQgYWxsLCB3ZVxuICAgICAgLy8gaGF2ZSBleGhhdXN0ZWQgYWxsIGNvbXBvbmVudHMsIHNvIHRoZSBwYXRoIGlzIG5vdCByZWxhdGl2ZSB0byB0aGUgcm9vdC5cbiAgICAgIGFSb290ID0gYVJvb3Quc2xpY2UoMCwgaW5kZXgpO1xuICAgICAgaWYgKGFSb290Lm1hdGNoKC9eKFteXFwvXSs6XFwvKT9cXC8qJC8pKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cblxuICAgICAgKytsZXZlbDtcbiAgICB9XG5cbiAgICAvLyBNYWtlIHN1cmUgd2UgYWRkIGEgXCIuLi9cIiBmb3IgZWFjaCBjb21wb25lbnQgd2UgcmVtb3ZlZCBmcm9tIHRoZSByb290LlxuICAgIHJldHVybiBBcnJheShsZXZlbCArIDEpLmpvaW4oXCIuLi9cIikgKyBhUGF0aC5zdWJzdHIoYVJvb3QubGVuZ3RoICsgMSk7XG4gIH1cbiAgZXhwb3J0cy5yZWxhdGl2ZSA9IHJlbGF0aXZlO1xuXG4gIC8qKlxuICAgKiBCZWNhdXNlIGJlaGF2aW9yIGdvZXMgd2Fja3kgd2hlbiB5b3Ugc2V0IGBfX3Byb3RvX19gIG9uIG9iamVjdHMsIHdlXG4gICAqIGhhdmUgdG8gcHJlZml4IGFsbCB0aGUgc3RyaW5ncyBpbiBvdXIgc2V0IHdpdGggYW4gYXJiaXRyYXJ5IGNoYXJhY3Rlci5cbiAgICpcbiAgICogU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvcHVsbC8zMSBhbmRcbiAgICogaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc291cmNlLW1hcC9pc3N1ZXMvMzBcbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBmdW5jdGlvbiB0b1NldFN0cmluZyhhU3RyKSB7XG4gICAgcmV0dXJuICckJyArIGFTdHI7XG4gIH1cbiAgZXhwb3J0cy50b1NldFN0cmluZyA9IHRvU2V0U3RyaW5nO1xuXG4gIGZ1bmN0aW9uIGZyb21TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiBhU3RyLnN1YnN0cigxKTtcbiAgfVxuICBleHBvcnRzLmZyb21TZXRTdHJpbmcgPSBmcm9tU2V0U3RyaW5nO1xuXG4gIC8qKlxuICAgKiBDb21wYXJhdG9yIGJldHdlZW4gdHdvIG1hcHBpbmdzIHdoZXJlIHRoZSBvcmlnaW5hbCBwb3NpdGlvbnMgYXJlIGNvbXBhcmVkLlxuICAgKlxuICAgKiBPcHRpb25hbGx5IHBhc3MgaW4gYHRydWVgIGFzIGBvbmx5Q29tcGFyZUdlbmVyYXRlZGAgdG8gY29uc2lkZXIgdHdvXG4gICAqIG1hcHBpbmdzIHdpdGggdGhlIHNhbWUgb3JpZ2luYWwgc291cmNlL2xpbmUvY29sdW1uLCBidXQgZGlmZmVyZW50IGdlbmVyYXRlZFxuICAgKiBsaW5lIGFuZCBjb2x1bW4gdGhlIHNhbWUuIFVzZWZ1bCB3aGVuIHNlYXJjaGluZyBmb3IgYSBtYXBwaW5nIHdpdGggYVxuICAgKiBzdHViYmVkIG91dCBtYXBwaW5nLlxuICAgKi9cbiAgZnVuY3Rpb24gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMobWFwcGluZ0EsIG1hcHBpbmdCLCBvbmx5Q29tcGFyZU9yaWdpbmFsKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLnNvdXJjZSAtIG1hcHBpbmdCLnNvdXJjZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsTGluZSAtIG1hcHBpbmdCLm9yaWdpbmFsTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsQ29sdW1uIC0gbWFwcGluZ0Iub3JpZ2luYWxDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCB8fCBvbmx5Q29tcGFyZU9yaWdpbmFsKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIG1hcHBpbmdBLm5hbWUgLSBtYXBwaW5nQi5uYW1lO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMgPSBjb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGRlZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBpbmRpY2VzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4sIGJ1dCBkaWZmZXJlbnRcbiAgICogc291cmNlL25hbWUvb3JpZ2luYWwgbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGFcbiAgICogbWFwcGluZyB3aXRoIGEgc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICB2YXIgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZSAtIG1hcHBpbmdCLmdlbmVyYXRlZExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCB8fCBvbmx5Q29tcGFyZUdlbmVyYXRlZCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIG1hcHBpbmdBLm5hbWUgLSBtYXBwaW5nQi5uYW1lO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQgPSBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZDtcblxuICBmdW5jdGlvbiBzdHJjbXAoYVN0cjEsIGFTdHIyKSB7XG4gICAgaWYgKGFTdHIxID09PSBhU3RyMikge1xuICAgICAgcmV0dXJuIDA7XG4gICAgfVxuXG4gICAgaWYgKGFTdHIxID4gYVN0cjIpIHtcbiAgICAgIHJldHVybiAxO1xuICAgIH1cblxuICAgIHJldHVybiAtMTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21wYXJhdG9yIGJldHdlZW4gdHdvIG1hcHBpbmdzIHdpdGggaW5mbGF0ZWQgc291cmNlIGFuZCBuYW1lIHN0cmluZ3Mgd2hlcmVcbiAgICogdGhlIGdlbmVyYXRlZCBwb3NpdGlvbnMgYXJlIGNvbXBhcmVkLlxuICAgKi9cbiAgZnVuY3Rpb24gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQobWFwcGluZ0EsIG1hcHBpbmdCKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gc3RyY21wKG1hcHBpbmdBLnNvdXJjZSwgbWFwcGluZ0Iuc291cmNlKTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsTGluZSAtIG1hcHBpbmdCLm9yaWdpbmFsTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsQ29sdW1uIC0gbWFwcGluZ0Iub3JpZ2luYWxDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICByZXR1cm4gc3RyY21wKG1hcHBpbmdBLm5hbWUsIG1hcHBpbmdCLm5hbWUpO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQgPSBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZDtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvdXRpbC5qc1xuICoqIG1vZHVsZSBpZCA9IDVcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICAvKipcbiAgICogQSBkYXRhIHN0cnVjdHVyZSB3aGljaCBpcyBhIGNvbWJpbmF0aW9uIG9mIGFuIGFycmF5IGFuZCBhIHNldC4gQWRkaW5nIGEgbmV3XG4gICAqIG1lbWJlciBpcyBPKDEpLCB0ZXN0aW5nIGZvciBtZW1iZXJzaGlwIGlzIE8oMSksIGFuZCBmaW5kaW5nIHRoZSBpbmRleCBvZiBhblxuICAgKiBlbGVtZW50IGlzIE8oMSkuIFJlbW92aW5nIGVsZW1lbnRzIGZyb20gdGhlIHNldCBpcyBub3Qgc3VwcG9ydGVkLiBPbmx5XG4gICAqIHN0cmluZ3MgYXJlIHN1cHBvcnRlZCBmb3IgbWVtYmVyc2hpcC5cbiAgICovXG4gIGZ1bmN0aW9uIEFycmF5U2V0KCkge1xuICAgIHRoaXMuX2FycmF5ID0gW107XG4gICAgdGhpcy5fc2V0ID0ge307XG4gIH1cblxuICAvKipcbiAgICogU3RhdGljIG1ldGhvZCBmb3IgY3JlYXRpbmcgQXJyYXlTZXQgaW5zdGFuY2VzIGZyb20gYW4gZXhpc3RpbmcgYXJyYXkuXG4gICAqL1xuICBBcnJheVNldC5mcm9tQXJyYXkgPSBmdW5jdGlvbiBBcnJheVNldF9mcm9tQXJyYXkoYUFycmF5LCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgdmFyIHNldCA9IG5ldyBBcnJheVNldCgpO1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhQXJyYXkubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIHNldC5hZGQoYUFycmF5W2ldLCBhQWxsb3dEdXBsaWNhdGVzKTtcbiAgICB9XG4gICAgcmV0dXJuIHNldDtcbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIGhvdyBtYW55IHVuaXF1ZSBpdGVtcyBhcmUgaW4gdGhpcyBBcnJheVNldC4gSWYgZHVwbGljYXRlcyBoYXZlIGJlZW5cbiAgICogYWRkZWQsIHRoYW4gdGhvc2UgZG8gbm90IGNvdW50IHRvd2FyZHMgdGhlIHNpemUuXG4gICAqXG4gICAqIEByZXR1cm5zIE51bWJlclxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLnNpemUgPSBmdW5jdGlvbiBBcnJheVNldF9zaXplKCkge1xuICAgIHJldHVybiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyh0aGlzLl9zZXQpLmxlbmd0aDtcbiAgfTtcblxuICAvKipcbiAgICogQWRkIHRoZSBnaXZlbiBzdHJpbmcgdG8gdGhpcyBzZXQuXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIEFycmF5U2V0X2FkZChhU3RyLCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIHZhciBpc0R1cGxpY2F0ZSA9IHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKTtcbiAgICB2YXIgaWR4ID0gdGhpcy5fYXJyYXkubGVuZ3RoO1xuICAgIGlmICghaXNEdXBsaWNhdGUgfHwgYUFsbG93RHVwbGljYXRlcykge1xuICAgICAgdGhpcy5fYXJyYXkucHVzaChhU3RyKTtcbiAgICB9XG4gICAgaWYgKCFpc0R1cGxpY2F0ZSkge1xuICAgICAgdGhpcy5fc2V0W3NTdHJdID0gaWR4O1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogSXMgdGhlIGdpdmVuIHN0cmluZyBhIG1lbWJlciBvZiB0aGlzIHNldD9cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuaGFzID0gZnVuY3Rpb24gQXJyYXlTZXRfaGFzKGFTdHIpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgcmV0dXJuIHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKTtcbiAgfTtcblxuICAvKipcbiAgICogV2hhdCBpcyB0aGUgaW5kZXggb2YgdGhlIGdpdmVuIHN0cmluZyBpbiB0aGUgYXJyYXk/XG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmluZGV4T2YgPSBmdW5jdGlvbiBBcnJheVNldF9pbmRleE9mKGFTdHIpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgaWYgKHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKSkge1xuICAgICAgcmV0dXJuIHRoaXMuX3NldFtzU3RyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhU3RyICsgJ1wiIGlzIG5vdCBpbiB0aGUgc2V0LicpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXaGF0IGlzIHRoZSBlbGVtZW50IGF0IHRoZSBnaXZlbiBpbmRleD9cbiAgICpcbiAgICogQHBhcmFtIE51bWJlciBhSWR4XG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuYXQgPSBmdW5jdGlvbiBBcnJheVNldF9hdChhSWR4KSB7XG4gICAgaWYgKGFJZHggPj0gMCAmJiBhSWR4IDwgdGhpcy5fYXJyYXkubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fYXJyYXlbYUlkeF07XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignTm8gZWxlbWVudCBpbmRleGVkIGJ5ICcgKyBhSWR4KTtcbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYXJyYXkgcmVwcmVzZW50YXRpb24gb2YgdGhpcyBzZXQgKHdoaWNoIGhhcyB0aGUgcHJvcGVyIGluZGljZXNcbiAgICogaW5kaWNhdGVkIGJ5IGluZGV4T2YpLiBOb3RlIHRoYXQgdGhpcyBpcyBhIGNvcHkgb2YgdGhlIGludGVybmFsIGFycmF5IHVzZWRcbiAgICogZm9yIHN0b3JpbmcgdGhlIG1lbWJlcnMgc28gdGhhdCBubyBvbmUgY2FuIG1lc3Mgd2l0aCBpbnRlcm5hbCBzdGF0ZS5cbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS50b0FycmF5ID0gZnVuY3Rpb24gQXJyYXlTZXRfdG9BcnJheSgpIHtcbiAgICByZXR1cm4gdGhpcy5fYXJyYXkuc2xpY2UoKTtcbiAgfTtcblxuICBleHBvcnRzLkFycmF5U2V0ID0gQXJyYXlTZXQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2FycmF5LXNldC5qc1xuICoqIG1vZHVsZSBpZCA9IDZcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxNCBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICAvKipcbiAgICogRGV0ZXJtaW5lIHdoZXRoZXIgbWFwcGluZ0IgaXMgYWZ0ZXIgbWFwcGluZ0Egd2l0aCByZXNwZWN0IHRvIGdlbmVyYXRlZFxuICAgKiBwb3NpdGlvbi5cbiAgICovXG4gIGZ1bmN0aW9uIGdlbmVyYXRlZFBvc2l0aW9uQWZ0ZXIobWFwcGluZ0EsIG1hcHBpbmdCKSB7XG4gICAgLy8gT3B0aW1pemVkIGZvciBtb3N0IGNvbW1vbiBjYXNlXG4gICAgdmFyIGxpbmVBID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZTtcbiAgICB2YXIgbGluZUIgPSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIHZhciBjb2x1bW5BID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHZhciBjb2x1bW5CID0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHJldHVybiBsaW5lQiA+IGxpbmVBIHx8IGxpbmVCID09IGxpbmVBICYmIGNvbHVtbkIgPj0gY29sdW1uQSB8fFxuICAgICAgICAgICB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikgPD0gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBIGRhdGEgc3RydWN0dXJlIHRvIHByb3ZpZGUgYSBzb3J0ZWQgdmlldyBvZiBhY2N1bXVsYXRlZCBtYXBwaW5ncyBpbiBhXG4gICAqIHBlcmZvcm1hbmNlIGNvbnNjaW91cyBtYW5uZXIuIEl0IHRyYWRlcyBhIG5lZ2xpYmFibGUgb3ZlcmhlYWQgaW4gZ2VuZXJhbFxuICAgKiBjYXNlIGZvciBhIGxhcmdlIHNwZWVkdXAgaW4gY2FzZSBvZiBtYXBwaW5ncyBiZWluZyBhZGRlZCBpbiBvcmRlci5cbiAgICovXG4gIGZ1bmN0aW9uIE1hcHBpbmdMaXN0KCkge1xuICAgIHRoaXMuX2FycmF5ID0gW107XG4gICAgdGhpcy5fc29ydGVkID0gdHJ1ZTtcbiAgICAvLyBTZXJ2ZXMgYXMgaW5maW11bVxuICAgIHRoaXMuX2xhc3QgPSB7Z2VuZXJhdGVkTGluZTogLTEsIGdlbmVyYXRlZENvbHVtbjogMH07XG4gIH1cblxuICAvKipcbiAgICogSXRlcmF0ZSB0aHJvdWdoIGludGVybmFsIGl0ZW1zLiBUaGlzIG1ldGhvZCB0YWtlcyB0aGUgc2FtZSBhcmd1bWVudHMgdGhhdFxuICAgKiBgQXJyYXkucHJvdG90eXBlLmZvckVhY2hgIHRha2VzLlxuICAgKlxuICAgKiBOT1RFOiBUaGUgb3JkZXIgb2YgdGhlIG1hcHBpbmdzIGlzIE5PVCBndWFyYW50ZWVkLlxuICAgKi9cbiAgTWFwcGluZ0xpc3QucHJvdG90eXBlLnVuc29ydGVkRm9yRWFjaCA9XG4gICAgZnVuY3Rpb24gTWFwcGluZ0xpc3RfZm9yRWFjaChhQ2FsbGJhY2ssIGFUaGlzQXJnKSB7XG4gICAgICB0aGlzLl9hcnJheS5mb3JFYWNoKGFDYWxsYmFjaywgYVRoaXNBcmcpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCB0aGUgZ2l2ZW4gc291cmNlIG1hcHBpbmcuXG4gICAqXG4gICAqIEBwYXJhbSBPYmplY3QgYU1hcHBpbmdcbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBNYXBwaW5nTGlzdF9hZGQoYU1hcHBpbmcpIHtcbiAgICBpZiAoZ2VuZXJhdGVkUG9zaXRpb25BZnRlcih0aGlzLl9sYXN0LCBhTWFwcGluZykpIHtcbiAgICAgIHRoaXMuX2xhc3QgPSBhTWFwcGluZztcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLl9zb3J0ZWQgPSBmYWxzZTtcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZmxhdCwgc29ydGVkIGFycmF5IG9mIG1hcHBpbmdzLiBUaGUgbWFwcGluZ3MgYXJlIHNvcnRlZCBieVxuICAgKiBnZW5lcmF0ZWQgcG9zaXRpb24uXG4gICAqXG4gICAqIFdBUk5JTkc6IFRoaXMgbWV0aG9kIHJldHVybnMgaW50ZXJuYWwgZGF0YSB3aXRob3V0IGNvcHlpbmcsIGZvclxuICAgKiBwZXJmb3JtYW5jZS4gVGhlIHJldHVybiB2YWx1ZSBtdXN0IE5PVCBiZSBtdXRhdGVkLCBhbmQgc2hvdWxkIGJlIHRyZWF0ZWQgYXNcbiAgICogYW4gaW1tdXRhYmxlIGJvcnJvdy4gSWYgeW91IHdhbnQgdG8gdGFrZSBvd25lcnNoaXAsIHlvdSBtdXN0IG1ha2UgeW91ciBvd25cbiAgICogY29weS5cbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS50b0FycmF5ID0gZnVuY3Rpb24gTWFwcGluZ0xpc3RfdG9BcnJheSgpIHtcbiAgICBpZiAoIXRoaXMuX3NvcnRlZCkge1xuICAgICAgdGhpcy5fYXJyYXkuc29ydCh1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKTtcbiAgICAgIHRoaXMuX3NvcnRlZCA9IHRydWU7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLl9hcnJheTtcbiAgfTtcblxuICBleHBvcnRzLk1hcHBpbmdMaXN0ID0gTWFwcGluZ0xpc3Q7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL21hcHBpbmctbGlzdC5qc1xuICoqIG1vZHVsZSBpZCA9IDdcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcbiAgdmFyIGJpbmFyeVNlYXJjaCA9IHJlcXVpcmUoJy4vYmluYXJ5LXNlYXJjaCcpO1xuICB2YXIgQXJyYXlTZXQgPSByZXF1aXJlKCcuL2FycmF5LXNldCcpLkFycmF5U2V0O1xuICB2YXIgYmFzZTY0VkxRID0gcmVxdWlyZSgnLi9iYXNlNjQtdmxxJyk7XG4gIHZhciBxdWlja1NvcnQgPSByZXF1aXJlKCcuL3F1aWNrLXNvcnQnKS5xdWlja1NvcnQ7XG5cbiAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXIoYVNvdXJjZU1hcCkge1xuICAgIHZhciBzb3VyY2VNYXAgPSBhU291cmNlTWFwO1xuICAgIGlmICh0eXBlb2YgYVNvdXJjZU1hcCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHNvdXJjZU1hcCA9IEpTT04ucGFyc2UoYVNvdXJjZU1hcC5yZXBsYWNlKC9eXFwpXFxdXFx9Jy8sICcnKSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHNvdXJjZU1hcC5zZWN0aW9ucyAhPSBudWxsXG4gICAgICA/IG5ldyBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIoc291cmNlTWFwKVxuICAgICAgOiBuZXcgQmFzaWNTb3VyY2VNYXBDb25zdW1lcihzb3VyY2VNYXApO1xuICB9XG5cbiAgU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcCA9IGZ1bmN0aW9uKGFTb3VyY2VNYXApIHtcbiAgICByZXR1cm4gQmFzaWNTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwKGFTb3VyY2VNYXApO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLy8gYF9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZCBgX19vcmlnaW5hbE1hcHBpbmdzYCBhcmUgYXJyYXlzIHRoYXQgaG9sZCB0aGVcbiAgLy8gcGFyc2VkIG1hcHBpbmcgY29vcmRpbmF0ZXMgZnJvbSB0aGUgc291cmNlIG1hcCdzIFwibWFwcGluZ3NcIiBhdHRyaWJ1dGUuIFRoZXlcbiAgLy8gYXJlIGxhemlseSBpbnN0YW50aWF0ZWQsIGFjY2Vzc2VkIHZpYSB0aGUgYF9nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gIC8vIGBfb3JpZ2luYWxNYXBwaW5nc2AgZ2V0dGVycyByZXNwZWN0aXZlbHksIGFuZCB3ZSBvbmx5IHBhcnNlIHRoZSBtYXBwaW5nc1xuICAvLyBhbmQgY3JlYXRlIHRoZXNlIGFycmF5cyBvbmNlIHF1ZXJpZWQgZm9yIGEgc291cmNlIGxvY2F0aW9uLiBXZSBqdW1wIHRocm91Z2hcbiAgLy8gdGhlc2UgaG9vcHMgYmVjYXVzZSB0aGVyZSBjYW4gYmUgbWFueSB0aG91c2FuZHMgb2YgbWFwcGluZ3MsIGFuZCBwYXJzaW5nXG4gIC8vIHRoZW0gaXMgZXhwZW5zaXZlLCBzbyB3ZSBvbmx5IHdhbnQgdG8gZG8gaXQgaWYgd2UgbXVzdC5cbiAgLy9cbiAgLy8gRWFjaCBvYmplY3QgaW4gdGhlIGFycmF5cyBpcyBvZiB0aGUgZm9ybTpcbiAgLy9cbiAgLy8gICAgIHtcbiAgLy8gICAgICAgZ2VuZXJhdGVkTGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIHNvdXJjZTogVGhlIHBhdGggdG8gdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlIHRoYXQgZ2VuZXJhdGVkIHRoaXNcbiAgLy8gICAgICAgICAgICAgICBjaHVuayBvZiBjb2RlLFxuICAvLyAgICAgICBvcmlnaW5hbExpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlIHRoYXRcbiAgLy8gICAgICAgICAgICAgICAgICAgICBjb3JyZXNwb25kcyB0byB0aGlzIGNodW5rIG9mIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBvcmlnaW5hbENvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSB0aGF0XG4gIC8vICAgICAgICAgICAgICAgICAgICAgICBjb3JyZXNwb25kcyB0byB0aGlzIGNodW5rIG9mIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBuYW1lOiBUaGUgbmFtZSBvZiB0aGUgb3JpZ2luYWwgc3ltYm9sIHdoaWNoIGdlbmVyYXRlZCB0aGlzIGNodW5rIG9mXG4gIC8vICAgICAgICAgICAgIGNvZGUuXG4gIC8vICAgICB9XG4gIC8vXG4gIC8vIEFsbCBwcm9wZXJ0aWVzIGV4Y2VwdCBmb3IgYGdlbmVyYXRlZExpbmVgIGFuZCBgZ2VuZXJhdGVkQ29sdW1uYCBjYW4gYmVcbiAgLy8gYG51bGxgLlxuICAvL1xuICAvLyBgX2dlbmVyYXRlZE1hcHBpbmdzYCBpcyBvcmRlcmVkIGJ5IHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zLlxuICAvL1xuICAvLyBgX29yaWdpbmFsTWFwcGluZ3NgIGlzIG9yZGVyZWQgYnkgdGhlIG9yaWdpbmFsIHBvc2l0aW9ucy5cblxuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IG51bGw7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdfZ2VuZXJhdGVkTWFwcGluZ3MnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICBpZiAoIXRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncykge1xuICAgICAgICB0aGlzLl9wYXJzZU1hcHBpbmdzKHRoaXMuX21hcHBpbmdzLCB0aGlzLnNvdXJjZVJvb3QpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzO1xuICAgIH1cbiAgfSk7XG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9fb3JpZ2luYWxNYXBwaW5ncyA9IG51bGw7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdfb3JpZ2luYWxNYXBwaW5ncycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIGlmICghdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MpIHtcbiAgICAgICAgdGhpcy5fcGFyc2VNYXBwaW5ncyh0aGlzLl9tYXBwaW5ncywgdGhpcy5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzO1xuICAgIH1cbiAgfSk7XG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9jaGFySXNNYXBwaW5nU2VwYXJhdG9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9jaGFySXNNYXBwaW5nU2VwYXJhdG9yKGFTdHIsIGluZGV4KSB7XG4gICAgICB2YXIgYyA9IGFTdHIuY2hhckF0KGluZGV4KTtcbiAgICAgIHJldHVybiBjID09PSBcIjtcIiB8fCBjID09PSBcIixcIjtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBQYXJzZSB0aGUgbWFwcGluZ3MgaW4gYSBzdHJpbmcgaW4gdG8gYSBkYXRhIHN0cnVjdHVyZSB3aGljaCB3ZSBjYW4gZWFzaWx5XG4gICAqIHF1ZXJ5ICh0aGUgb3JkZXJlZCBhcnJheXMgaW4gdGhlIGB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAgKiBgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3NgIHByb3BlcnRpZXMpLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9wYXJzZU1hcHBpbmdzKGFTdHIsIGFTb3VyY2VSb290KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTdWJjbGFzc2VzIG11c3QgaW1wbGVtZW50IF9wYXJzZU1hcHBpbmdzXCIpO1xuICAgIH07XG5cbiAgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSID0gMTtcbiAgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVIgPSAyO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EID0gMTtcbiAgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQgPSAyO1xuXG4gIC8qKlxuICAgKiBJdGVyYXRlIG92ZXIgZWFjaCBtYXBwaW5nIGJldHdlZW4gYW4gb3JpZ2luYWwgc291cmNlL2xpbmUvY29sdW1uIGFuZCBhXG4gICAqIGdlbmVyYXRlZCBsaW5lL2NvbHVtbiBpbiB0aGlzIHNvdXJjZSBtYXAuXG4gICAqXG4gICAqIEBwYXJhbSBGdW5jdGlvbiBhQ2FsbGJhY2tcbiAgICogICAgICAgIFRoZSBmdW5jdGlvbiB0aGF0IGlzIGNhbGxlZCB3aXRoIGVhY2ggbWFwcGluZy5cbiAgICogQHBhcmFtIE9iamVjdCBhQ29udGV4dFxuICAgKiAgICAgICAgT3B0aW9uYWwuIElmIHNwZWNpZmllZCwgdGhpcyBvYmplY3Qgd2lsbCBiZSB0aGUgdmFsdWUgb2YgYHRoaXNgIGV2ZXJ5XG4gICAqICAgICAgICB0aW1lIHRoYXQgYGFDYWxsYmFja2AgaXMgY2FsbGVkLlxuICAgKiBAcGFyYW0gYU9yZGVyXG4gICAqICAgICAgICBFaXRoZXIgYFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUmAgb3JcbiAgICogICAgICAgIGBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUmAuIFNwZWNpZmllcyB3aGV0aGVyIHlvdSB3YW50IHRvXG4gICAqICAgICAgICBpdGVyYXRlIG92ZXIgdGhlIG1hcHBpbmdzIHNvcnRlZCBieSB0aGUgZ2VuZXJhdGVkIGZpbGUncyBsaW5lL2NvbHVtblxuICAgKiAgICAgICAgb3JkZXIgb3IgdGhlIG9yaWdpbmFsJ3Mgc291cmNlL2xpbmUvY29sdW1uIG9yZGVyLCByZXNwZWN0aXZlbHkuIERlZmF1bHRzIHRvXG4gICAqICAgICAgICBgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSYC5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5lYWNoTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZWFjaE1hcHBpbmcoYUNhbGxiYWNrLCBhQ29udGV4dCwgYU9yZGVyKSB7XG4gICAgICB2YXIgY29udGV4dCA9IGFDb250ZXh0IHx8IG51bGw7XG4gICAgICB2YXIgb3JkZXIgPSBhT3JkZXIgfHwgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSO1xuXG4gICAgICB2YXIgbWFwcGluZ3M7XG4gICAgICBzd2l0Y2ggKG9yZGVyKSB7XG4gICAgICBjYXNlIFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUjpcbiAgICAgICAgbWFwcGluZ3MgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncztcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIFNvdXJjZU1hcENvbnN1bWVyLk9SSUdJTkFMX09SREVSOlxuICAgICAgICBtYXBwaW5ncyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3M7XG4gICAgICAgIGJyZWFrO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVW5rbm93biBvcmRlciBvZiBpdGVyYXRpb24uXCIpO1xuICAgICAgfVxuXG4gICAgICB2YXIgc291cmNlUm9vdCA9IHRoaXMuc291cmNlUm9vdDtcbiAgICAgIG1hcHBpbmdzLm1hcChmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICB2YXIgc291cmNlID0gbWFwcGluZy5zb3VyY2UgPT09IG51bGwgPyBudWxsIDogdGhpcy5fc291cmNlcy5hdChtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiBzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICBzb3VyY2UgPSB1dGlsLmpvaW4oc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSxcbiAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICAgIG9yaWdpbmFsTGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgb3JpZ2luYWxDb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgbmFtZTogbWFwcGluZy5uYW1lID09PSBudWxsID8gbnVsbCA6IHRoaXMuX25hbWVzLmF0KG1hcHBpbmcubmFtZSlcbiAgICAgICAgfTtcbiAgICAgIH0sIHRoaXMpLmZvckVhY2goYUNhbGxiYWNrLCBjb250ZXh0KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFsbCBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgb3JpZ2luYWwgc291cmNlLFxuICAgKiBsaW5lLCBhbmQgY29sdW1uIHByb3ZpZGVkLiBJZiBubyBjb2x1bW4gaXMgcHJvdmlkZWQsIHJldHVybnMgYWxsIG1hcHBpbmdzXG4gICAqIGNvcnJlc3BvbmRpbmcgdG8gYSBlaXRoZXIgdGhlIGxpbmUgd2UgYXJlIHNlYXJjaGluZyBmb3Igb3IgdGhlIG5leHRcbiAgICogY2xvc2VzdCBsaW5lIHRoYXQgaGFzIGFueSBtYXBwaW5ncy4gT3RoZXJ3aXNlLCByZXR1cm5zIGFsbCBtYXBwaW5nc1xuICAgKiBjb3JyZXNwb25kaW5nIHRvIHRoZSBnaXZlbiBsaW5lIGFuZCBlaXRoZXIgdGhlIGNvbHVtbiB3ZSBhcmUgc2VhcmNoaW5nIGZvclxuICAgKiBvciB0aGUgbmV4dCBjbG9zZXN0IGNvbHVtbiB0aGF0IGhhcyBhbnkgb2Zmc2V0cy5cbiAgICpcbiAgICogVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0IHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBPcHRpb25hbC4gdGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIGFycmF5IG9mIG9iamVjdHMgaXMgcmV0dXJuZWQsIGVhY2ggd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0ZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgbGluZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpO1xuXG4gICAgICAvLyBXaGVuIHRoZXJlIGlzIG5vIGV4YWN0IG1hdGNoLCBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fZmluZE1hcHBpbmdcbiAgICAgIC8vIHJldHVybnMgdGhlIGluZGV4IG9mIHRoZSBjbG9zZXN0IG1hcHBpbmcgbGVzcyB0aGFuIHRoZSBuZWVkbGUuIEJ5XG4gICAgICAvLyBzZXR0aW5nIG5lZWRsZS5vcmlnaW5hbENvbHVtbiB0byAwLCB3ZSB0aHVzIGZpbmQgdGhlIGxhc3QgbWFwcGluZyBmb3JcbiAgICAgIC8vIHRoZSBnaXZlbiBsaW5lLCBwcm92aWRlZCBzdWNoIGEgbWFwcGluZyBleGlzdHMuXG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBzb3VyY2U6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJyksXG4gICAgICAgIG9yaWdpbmFsTGluZTogbGluZSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJywgMClcbiAgICAgIH07XG5cbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBuZWVkbGUuc291cmNlID0gdXRpbC5yZWxhdGl2ZSh0aGlzLnNvdXJjZVJvb3QsIG5lZWRsZS5zb3VyY2UpO1xuICAgICAgfVxuICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzLmhhcyhuZWVkbGUuc291cmNlKSkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICAgICBuZWVkbGUuc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKG5lZWRsZS5zb3VyY2UpO1xuXG4gICAgICB2YXIgbWFwcGluZ3MgPSBbXTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcobmVlZGxlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5fb3JpZ2luYWxNYXBwaW5ncyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwib3JpZ2luYWxMaW5lXCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIm9yaWdpbmFsQ29sdW1uXCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmluYXJ5U2VhcmNoLkxFQVNUX1VQUEVSX0JPVU5EKTtcbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgaWYgKGFBcmdzLmNvbHVtbiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgdmFyIG9yaWdpbmFsTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lO1xuXG4gICAgICAgICAgLy8gSXRlcmF0ZSB1bnRpbCBlaXRoZXIgd2UgcnVuIG91dCBvZiBtYXBwaW5ncywgb3Igd2UgcnVuIGludG9cbiAgICAgICAgICAvLyBhIG1hcHBpbmcgZm9yIGEgZGlmZmVyZW50IGxpbmUgdGhhbiB0aGUgb25lIHdlIGZvdW5kLiBTaW5jZVxuICAgICAgICAgIC8vIG1hcHBpbmdzIGFyZSBzb3J0ZWQsIHRoaXMgaXMgZ3VhcmFudGVlZCB0byBmaW5kIGFsbCBtYXBwaW5ncyBmb3JcbiAgICAgICAgICAvLyB0aGUgbGluZSB3ZSBmb3VuZC5cbiAgICAgICAgICB3aGlsZSAobWFwcGluZyAmJiBtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gb3JpZ2luYWxMaW5lKSB7XG4gICAgICAgICAgICBtYXBwaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1srK2luZGV4XTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdmFyIG9yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgIC8vIEl0ZXJhdGUgdW50aWwgZWl0aGVyIHdlIHJ1biBvdXQgb2YgbWFwcGluZ3MsIG9yIHdlIHJ1biBpbnRvXG4gICAgICAgICAgLy8gYSBtYXBwaW5nIGZvciBhIGRpZmZlcmVudCBsaW5lIHRoYW4gdGhlIG9uZSB3ZSB3ZXJlIHNlYXJjaGluZyBmb3IuXG4gICAgICAgICAgLy8gU2luY2UgbWFwcGluZ3MgYXJlIHNvcnRlZCwgdGhpcyBpcyBndWFyYW50ZWVkIHRvIGZpbmQgYWxsIG1hcHBpbmdzIGZvclxuICAgICAgICAgIC8vIHRoZSBsaW5lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLlxuICAgICAgICAgIHdoaWxlIChtYXBwaW5nICYmXG4gICAgICAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSBsaW5lICYmXG4gICAgICAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPT0gb3JpZ2luYWxDb2x1bW4pIHtcbiAgICAgICAgICAgIG1hcHBpbmdzLnB1c2goe1xuICAgICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkTGluZScsIG51bGwpLFxuICAgICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgICAgbGFzdENvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2xhc3RHZW5lcmF0ZWRDb2x1bW4nLCBudWxsKVxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIG1hcHBpbmcgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzWysraW5kZXhdO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gbWFwcGluZ3M7XG4gICAgfTtcblxuICBleHBvcnRzLlNvdXJjZU1hcENvbnN1bWVyID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIEEgQmFzaWNTb3VyY2VNYXBDb25zdW1lciBpbnN0YW5jZSByZXByZXNlbnRzIGEgcGFyc2VkIHNvdXJjZSBtYXAgd2hpY2ggd2UgY2FuXG4gICAqIHF1ZXJ5IGZvciBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3JpZ2luYWwgZmlsZSBwb3NpdGlvbnMgYnkgZ2l2aW5nIGl0IGEgZmlsZVxuICAgKiBwb3NpdGlvbiBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICpcbiAgICogVGhlIG9ubHkgcGFyYW1ldGVyIGlzIHRoZSByYXcgc291cmNlIG1hcCAoZWl0aGVyIGFzIGEgSlNPTiBzdHJpbmcsIG9yXG4gICAqIGFscmVhZHkgcGFyc2VkIHRvIGFuIG9iamVjdCkuIEFjY29yZGluZyB0byB0aGUgc3BlYywgc291cmNlIG1hcHMgaGF2ZSB0aGVcbiAgICogZm9sbG93aW5nIGF0dHJpYnV0ZXM6XG4gICAqXG4gICAqICAgLSB2ZXJzaW9uOiBXaGljaCB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwIHNwZWMgdGhpcyBtYXAgaXMgZm9sbG93aW5nLlxuICAgKiAgIC0gc291cmNlczogQW4gYXJyYXkgb2YgVVJMcyB0byB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGVzLlxuICAgKiAgIC0gbmFtZXM6IEFuIGFycmF5IG9mIGlkZW50aWZpZXJzIHdoaWNoIGNhbiBiZSByZWZlcnJlbmNlZCBieSBpbmRpdmlkdWFsIG1hcHBpbmdzLlxuICAgKiAgIC0gc291cmNlUm9vdDogT3B0aW9uYWwuIFRoZSBVUkwgcm9vdCBmcm9tIHdoaWNoIGFsbCBzb3VyY2VzIGFyZSByZWxhdGl2ZS5cbiAgICogICAtIHNvdXJjZXNDb250ZW50OiBPcHRpb25hbC4gQW4gYXJyYXkgb2YgY29udGVudHMgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlcy5cbiAgICogICAtIG1hcHBpbmdzOiBBIHN0cmluZyBvZiBiYXNlNjQgVkxRcyB3aGljaCBjb250YWluIHRoZSBhY3R1YWwgbWFwcGluZ3MuXG4gICAqICAgLSBmaWxlOiBPcHRpb25hbC4gVGhlIGdlbmVyYXRlZCBmaWxlIHRoaXMgc291cmNlIG1hcCBpcyBhc3NvY2lhdGVkIHdpdGguXG4gICAqXG4gICAqIEhlcmUgaXMgYW4gZXhhbXBsZSBzb3VyY2UgbWFwLCB0YWtlbiBmcm9tIHRoZSBzb3VyY2UgbWFwIHNwZWNbMF06XG4gICAqXG4gICAqICAgICB7XG4gICAqICAgICAgIHZlcnNpb24gOiAzLFxuICAgKiAgICAgICBmaWxlOiBcIm91dC5qc1wiLFxuICAgKiAgICAgICBzb3VyY2VSb290IDogXCJcIixcbiAgICogICAgICAgc291cmNlczogW1wiZm9vLmpzXCIsIFwiYmFyLmpzXCJdLFxuICAgKiAgICAgICBuYW1lczogW1wic3JjXCIsIFwibWFwc1wiLCBcImFyZVwiLCBcImZ1blwiXSxcbiAgICogICAgICAgbWFwcGluZ3M6IFwiQUEsQUI7O0FCQ0RFO1wiXG4gICAqICAgICB9XG4gICAqXG4gICAqIFswXTogaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vZG9jdW1lbnQvZC8xVTFSR0FlaFF3UnlwVVRvdkYxS1JscGlPRnplMGItXzJnYzZmQUgwS1kway9lZGl0P3BsaT0xI1xuICAgKi9cbiAgZnVuY3Rpb24gQmFzaWNTb3VyY2VNYXBDb25zdW1lcihhU291cmNlTWFwKSB7XG4gICAgdmFyIHNvdXJjZU1hcCA9IGFTb3VyY2VNYXA7XG4gICAgaWYgKHR5cGVvZiBhU291cmNlTWFwID09PSAnc3RyaW5nJykge1xuICAgICAgc291cmNlTWFwID0gSlNPTi5wYXJzZShhU291cmNlTWFwLnJlcGxhY2UoL15cXClcXF1cXH0nLywgJycpKTtcbiAgICB9XG5cbiAgICB2YXIgdmVyc2lvbiA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3ZlcnNpb24nKTtcbiAgICB2YXIgc291cmNlcyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NvdXJjZXMnKTtcbiAgICAvLyBTYXNzIDMuMyBsZWF2ZXMgb3V0IHRoZSAnbmFtZXMnIGFycmF5LCBzbyB3ZSBkZXZpYXRlIGZyb20gdGhlIHNwZWMgKHdoaWNoXG4gICAgLy8gcmVxdWlyZXMgdGhlIGFycmF5KSB0byBwbGF5IG5pY2UgaGVyZS5cbiAgICB2YXIgbmFtZXMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICduYW1lcycsIFtdKTtcbiAgICB2YXIgc291cmNlUm9vdCA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NvdXJjZVJvb3QnLCBudWxsKTtcbiAgICB2YXIgc291cmNlc0NvbnRlbnQgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VzQ29udGVudCcsIG51bGwpO1xuICAgIHZhciBtYXBwaW5ncyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ21hcHBpbmdzJyk7XG4gICAgdmFyIGZpbGUgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdmaWxlJywgbnVsbCk7XG5cbiAgICAvLyBPbmNlIGFnYWluLCBTYXNzIGRldmlhdGVzIGZyb20gdGhlIHNwZWMgYW5kIHN1cHBsaWVzIHRoZSB2ZXJzaW9uIGFzIGFcbiAgICAvLyBzdHJpbmcgcmF0aGVyIHRoYW4gYSBudW1iZXIsIHNvIHdlIHVzZSBsb29zZSBlcXVhbGl0eSBjaGVja2luZyBoZXJlLlxuICAgIGlmICh2ZXJzaW9uICE9IHRoaXMuX3ZlcnNpb24pIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVW5zdXBwb3J0ZWQgdmVyc2lvbjogJyArIHZlcnNpb24pO1xuICAgIH1cblxuICAgIHNvdXJjZXMgPSBzb3VyY2VzXG4gICAgICAvLyBTb21lIHNvdXJjZSBtYXBzIHByb2R1Y2UgcmVsYXRpdmUgc291cmNlIHBhdGhzIGxpa2UgXCIuL2Zvby5qc1wiIGluc3RlYWQgb2ZcbiAgICAgIC8vIFwiZm9vLmpzXCIuICBOb3JtYWxpemUgdGhlc2UgZmlyc3Qgc28gdGhhdCBmdXR1cmUgY29tcGFyaXNvbnMgd2lsbCBzdWNjZWVkLlxuICAgICAgLy8gU2VlIGJ1Z3ppbC5sYS8xMDkwNzY4LlxuICAgICAgLm1hcCh1dGlsLm5vcm1hbGl6ZSlcbiAgICAgIC8vIEFsd2F5cyBlbnN1cmUgdGhhdCBhYnNvbHV0ZSBzb3VyY2VzIGFyZSBpbnRlcm5hbGx5IHN0b3JlZCByZWxhdGl2ZSB0b1xuICAgICAgLy8gdGhlIHNvdXJjZSByb290LCBpZiB0aGUgc291cmNlIHJvb3QgaXMgYWJzb2x1dGUuIE5vdCBkb2luZyB0aGlzIHdvdWxkXG4gICAgICAvLyBiZSBwYXJ0aWN1bGFybHkgcHJvYmxlbWF0aWMgd2hlbiB0aGUgc291cmNlIHJvb3QgaXMgYSBwcmVmaXggb2YgdGhlXG4gICAgICAvLyBzb3VyY2UgKHZhbGlkLCBidXQgd2h5Pz8pLiBTZWUgZ2l0aHViIGlzc3VlICMxOTkgYW5kIGJ1Z3ppbC5sYS8xMTg4OTgyLlxuICAgICAgLm1hcChmdW5jdGlvbiAoc291cmNlKSB7XG4gICAgICAgIHJldHVybiBzb3VyY2VSb290ICYmIHV0aWwuaXNBYnNvbHV0ZShzb3VyY2VSb290KSAmJiB1dGlsLmlzQWJzb2x1dGUoc291cmNlKVxuICAgICAgICAgID8gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2UpXG4gICAgICAgICAgOiBzb3VyY2U7XG4gICAgICB9KTtcblxuICAgIC8vIFBhc3MgYHRydWVgIGJlbG93IHRvIGFsbG93IGR1cGxpY2F0ZSBuYW1lcyBhbmQgc291cmNlcy4gV2hpbGUgc291cmNlIG1hcHNcbiAgICAvLyBhcmUgaW50ZW5kZWQgdG8gYmUgY29tcHJlc3NlZCBhbmQgZGVkdXBsaWNhdGVkLCB0aGUgVHlwZVNjcmlwdCBjb21waWxlclxuICAgIC8vIHNvbWV0aW1lcyBnZW5lcmF0ZXMgc291cmNlIG1hcHMgd2l0aCBkdXBsaWNhdGVzIGluIHRoZW0uIFNlZSBHaXRodWIgaXNzdWVcbiAgICAvLyAjNzIgYW5kIGJ1Z3ppbC5sYS84ODk0OTIuXG4gICAgdGhpcy5fbmFtZXMgPSBBcnJheVNldC5mcm9tQXJyYXkobmFtZXMsIHRydWUpO1xuICAgIHRoaXMuX3NvdXJjZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoc291cmNlcywgdHJ1ZSk7XG5cbiAgICB0aGlzLnNvdXJjZVJvb3QgPSBzb3VyY2VSb290O1xuICAgIHRoaXMuc291cmNlc0NvbnRlbnQgPSBzb3VyY2VzQ29udGVudDtcbiAgICB0aGlzLl9tYXBwaW5ncyA9IG1hcHBpbmdzO1xuICAgIHRoaXMuZmlsZSA9IGZpbGU7XG4gIH1cblxuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlKTtcbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29uc3VtZXIgPSBTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogQ3JlYXRlIGEgQmFzaWNTb3VyY2VNYXBDb25zdW1lciBmcm9tIGEgU291cmNlTWFwR2VuZXJhdG9yLlxuICAgKlxuICAgKiBAcGFyYW0gU291cmNlTWFwR2VuZXJhdG9yIGFTb3VyY2VNYXBcbiAgICogICAgICAgIFRoZSBzb3VyY2UgbWFwIHRoYXQgd2lsbCBiZSBjb25zdW1lZC5cbiAgICogQHJldHVybnMgQmFzaWNTb3VyY2VNYXBDb25zdW1lclxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9mcm9tU291cmNlTWFwKGFTb3VyY2VNYXApIHtcbiAgICAgIHZhciBzbWMgPSBPYmplY3QuY3JlYXRlKEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlKTtcblxuICAgICAgdmFyIG5hbWVzID0gc21jLl9uYW1lcyA9IEFycmF5U2V0LmZyb21BcnJheShhU291cmNlTWFwLl9uYW1lcy50b0FycmF5KCksIHRydWUpO1xuICAgICAgdmFyIHNvdXJjZXMgPSBzbWMuX3NvdXJjZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoYVNvdXJjZU1hcC5fc291cmNlcy50b0FycmF5KCksIHRydWUpO1xuICAgICAgc21jLnNvdXJjZVJvb3QgPSBhU291cmNlTWFwLl9zb3VyY2VSb290O1xuICAgICAgc21jLnNvdXJjZXNDb250ZW50ID0gYVNvdXJjZU1hcC5fZ2VuZXJhdGVTb3VyY2VzQ29udGVudChzbWMuX3NvdXJjZXMudG9BcnJheSgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbWMuc291cmNlUm9vdCk7XG4gICAgICBzbWMuZmlsZSA9IGFTb3VyY2VNYXAuX2ZpbGU7XG5cbiAgICAgIC8vIEJlY2F1c2Ugd2UgYXJlIG1vZGlmeWluZyB0aGUgZW50cmllcyAoYnkgY29udmVydGluZyBzdHJpbmcgc291cmNlcyBhbmRcbiAgICAgIC8vIG5hbWVzIHRvIGluZGljZXMgaW50byB0aGUgc291cmNlcyBhbmQgbmFtZXMgQXJyYXlTZXRzKSwgd2UgaGF2ZSB0byBtYWtlXG4gICAgICAvLyBhIGNvcHkgb2YgdGhlIGVudHJ5IG9yIGVsc2UgYmFkIHRoaW5ncyBoYXBwZW4uIFNoYXJlZCBtdXRhYmxlIHN0YXRlXG4gICAgICAvLyBzdHJpa2VzIGFnYWluISBTZWUgZ2l0aHViIGlzc3VlICMxOTEuXG5cbiAgICAgIHZhciBnZW5lcmF0ZWRNYXBwaW5ncyA9IGFTb3VyY2VNYXAuX21hcHBpbmdzLnRvQXJyYXkoKS5zbGljZSgpO1xuICAgICAgdmFyIGRlc3RHZW5lcmF0ZWRNYXBwaW5ncyA9IHNtYy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB2YXIgZGVzdE9yaWdpbmFsTWFwcGluZ3MgPSBzbWMuX19vcmlnaW5hbE1hcHBpbmdzID0gW107XG5cbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBnZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc3JjTWFwcGluZyA9IGdlbmVyYXRlZE1hcHBpbmdzW2ldO1xuICAgICAgICB2YXIgZGVzdE1hcHBpbmcgPSBuZXcgTWFwcGluZztcbiAgICAgICAgZGVzdE1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9IHNyY01hcHBpbmcuZ2VuZXJhdGVkTGluZTtcbiAgICAgICAgZGVzdE1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uID0gc3JjTWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG5cbiAgICAgICAgaWYgKHNyY01hcHBpbmcuc291cmNlKSB7XG4gICAgICAgICAgZGVzdE1hcHBpbmcuc291cmNlID0gc291cmNlcy5pbmRleE9mKHNyY01hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICBkZXN0TWFwcGluZy5vcmlnaW5hbExpbmUgPSBzcmNNYXBwaW5nLm9yaWdpbmFsTGluZTtcbiAgICAgICAgICBkZXN0TWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IHNyY01hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICBpZiAoc3JjTWFwcGluZy5uYW1lKSB7XG4gICAgICAgICAgICBkZXN0TWFwcGluZy5uYW1lID0gbmFtZXMuaW5kZXhPZihzcmNNYXBwaW5nLm5hbWUpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGRlc3RPcmlnaW5hbE1hcHBpbmdzLnB1c2goZGVzdE1hcHBpbmcpO1xuICAgICAgICB9XG5cbiAgICAgICAgZGVzdEdlbmVyYXRlZE1hcHBpbmdzLnB1c2goZGVzdE1hcHBpbmcpO1xuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQoc21jLl9fb3JpZ2luYWxNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyk7XG5cbiAgICAgIHJldHVybiBzbWM7XG4gICAgfTtcblxuICAvKipcbiAgICogVGhlIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXBwaW5nIHNwZWMgdGhhdCB3ZSBhcmUgY29uc3VtaW5nLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8qKlxuICAgKiBUaGUgbGlzdCBvZiBvcmlnaW5hbCBzb3VyY2VzLlxuICAgKi9cbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnc291cmNlcycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIHJldHVybiB0aGlzLl9zb3VyY2VzLnRvQXJyYXkoKS5tYXAoZnVuY3Rpb24gKHMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlUm9vdCAhPSBudWxsID8gdXRpbC5qb2luKHRoaXMuc291cmNlUm9vdCwgcykgOiBzO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfVxuICB9KTtcblxuICAvKipcbiAgICogUHJvdmlkZSB0aGUgSklUIHdpdGggYSBuaWNlIHNoYXBlIC8gaGlkZGVuIGNsYXNzLlxuICAgKi9cbiAgZnVuY3Rpb24gTWFwcGluZygpIHtcbiAgICB0aGlzLmdlbmVyYXRlZExpbmUgPSAwO1xuICAgIHRoaXMuZ2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICB0aGlzLnNvdXJjZSA9IG51bGw7XG4gICAgdGhpcy5vcmlnaW5hbExpbmUgPSBudWxsO1xuICAgIHRoaXMub3JpZ2luYWxDb2x1bW4gPSBudWxsO1xuICAgIHRoaXMubmFtZSA9IG51bGw7XG4gIH1cblxuICAvKipcbiAgICogUGFyc2UgdGhlIG1hcHBpbmdzIGluIGEgc3RyaW5nIGluIHRvIGEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggd2UgY2FuIGVhc2lseVxuICAgKiBxdWVyeSAodGhlIG9yZGVyZWQgYXJyYXlzIGluIHRoZSBgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgICogYHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzYCBwcm9wZXJ0aWVzKS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9wYXJzZU1hcHBpbmdzKGFTdHIsIGFTb3VyY2VSb290KSB7XG4gICAgICB2YXIgZ2VuZXJhdGVkTGluZSA9IDE7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c1NvdXJjZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNOYW1lID0gMDtcbiAgICAgIHZhciBsZW5ndGggPSBhU3RyLmxlbmd0aDtcbiAgICAgIHZhciBpbmRleCA9IDA7XG4gICAgICB2YXIgY2FjaGVkU2VnbWVudHMgPSB7fTtcbiAgICAgIHZhciB0ZW1wID0ge307XG4gICAgICB2YXIgb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIGdlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB2YXIgbWFwcGluZywgc3RyLCBzZWdtZW50LCBlbmQsIHZhbHVlO1xuXG4gICAgICB3aGlsZSAoaW5kZXggPCBsZW5ndGgpIHtcbiAgICAgICAgaWYgKGFTdHIuY2hhckF0KGluZGV4KSA9PT0gJzsnKSB7XG4gICAgICAgICAgZ2VuZXJhdGVkTGluZSsrO1xuICAgICAgICAgIGluZGV4Kys7XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGFTdHIuY2hhckF0KGluZGV4KSA9PT0gJywnKSB7XG4gICAgICAgICAgaW5kZXgrKztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBtYXBwaW5nID0gbmV3IE1hcHBpbmcoKTtcbiAgICAgICAgICBtYXBwaW5nLmdlbmVyYXRlZExpbmUgPSBnZW5lcmF0ZWRMaW5lO1xuXG4gICAgICAgICAgLy8gQmVjYXVzZSBlYWNoIG9mZnNldCBpcyBlbmNvZGVkIHJlbGF0aXZlIHRvIHRoZSBwcmV2aW91cyBvbmUsXG4gICAgICAgICAgLy8gbWFueSBzZWdtZW50cyBvZnRlbiBoYXZlIHRoZSBzYW1lIGVuY29kaW5nLiBXZSBjYW4gZXhwbG9pdCB0aGlzXG4gICAgICAgICAgLy8gZmFjdCBieSBjYWNoaW5nIHRoZSBwYXJzZWQgdmFyaWFibGUgbGVuZ3RoIGZpZWxkcyBvZiBlYWNoIHNlZ21lbnQsXG4gICAgICAgICAgLy8gYWxsb3dpbmcgdXMgdG8gYXZvaWQgYSBzZWNvbmQgcGFyc2UgaWYgd2UgZW5jb3VudGVyIHRoZSBzYW1lXG4gICAgICAgICAgLy8gc2VnbWVudCBhZ2Fpbi5cbiAgICAgICAgICBmb3IgKGVuZCA9IGluZGV4OyBlbmQgPCBsZW5ndGg7IGVuZCsrKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5fY2hhcklzTWFwcGluZ1NlcGFyYXRvcihhU3RyLCBlbmQpKSB7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBzdHIgPSBhU3RyLnNsaWNlKGluZGV4LCBlbmQpO1xuXG4gICAgICAgICAgc2VnbWVudCA9IGNhY2hlZFNlZ21lbnRzW3N0cl07XG4gICAgICAgICAgaWYgKHNlZ21lbnQpIHtcbiAgICAgICAgICAgIGluZGV4ICs9IHN0ci5sZW5ndGg7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNlZ21lbnQgPSBbXTtcbiAgICAgICAgICAgIHdoaWxlIChpbmRleCA8IGVuZCkge1xuICAgICAgICAgICAgICBiYXNlNjRWTFEuZGVjb2RlKGFTdHIsIGluZGV4LCB0ZW1wKTtcbiAgICAgICAgICAgICAgdmFsdWUgPSB0ZW1wLnZhbHVlO1xuICAgICAgICAgICAgICBpbmRleCA9IHRlbXAucmVzdDtcbiAgICAgICAgICAgICAgc2VnbWVudC5wdXNoKHZhbHVlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID09PSAyKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignRm91bmQgYSBzb3VyY2UsIGJ1dCBubyBsaW5lIGFuZCBjb2x1bW4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID09PSAzKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignRm91bmQgYSBzb3VyY2UgYW5kIGxpbmUsIGJ1dCBubyBjb2x1bW4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY2FjaGVkU2VnbWVudHNbc3RyXSA9IHNlZ21lbnQ7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gR2VuZXJhdGVkIGNvbHVtbi5cbiAgICAgICAgICBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbiA9IHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uICsgc2VnbWVudFswXTtcbiAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID4gMSkge1xuICAgICAgICAgICAgLy8gT3JpZ2luYWwgc291cmNlLlxuICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSBwcmV2aW91c1NvdXJjZSArIHNlZ21lbnRbMV07XG4gICAgICAgICAgICBwcmV2aW91c1NvdXJjZSArPSBzZWdtZW50WzFdO1xuXG4gICAgICAgICAgICAvLyBPcmlnaW5hbCBsaW5lLlxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgPSBwcmV2aW91c09yaWdpbmFsTGluZSArIHNlZ21lbnRbMl07XG4gICAgICAgICAgICBwcmV2aW91c09yaWdpbmFsTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lO1xuICAgICAgICAgICAgLy8gTGluZXMgYXJlIHN0b3JlZCAwLWJhc2VkXG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSArPSAxO1xuXG4gICAgICAgICAgICAvLyBPcmlnaW5hbCBjb2x1bW4uXG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID0gcHJldmlvdXNPcmlnaW5hbENvbHVtbiArIHNlZ21lbnRbM107XG4gICAgICAgICAgICBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID4gNCkge1xuICAgICAgICAgICAgICAvLyBPcmlnaW5hbCBuYW1lLlxuICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUgPSBwcmV2aW91c05hbWUgKyBzZWdtZW50WzRdO1xuICAgICAgICAgICAgICBwcmV2aW91c05hbWUgKz0gc2VnbWVudFs0XTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG5cbiAgICAgICAgICBnZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKG1hcHBpbmcpO1xuICAgICAgICAgIGlmICh0eXBlb2YgbWFwcGluZy5vcmlnaW5hbExpbmUgPT09ICdudW1iZXInKSB7XG4gICAgICAgICAgICBvcmlnaW5hbE1hcHBpbmdzLnB1c2gobWFwcGluZyk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHF1aWNrU29ydChnZW5lcmF0ZWRNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCk7XG4gICAgICB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBnZW5lcmF0ZWRNYXBwaW5ncztcblxuICAgICAgcXVpY2tTb3J0KG9yaWdpbmFsTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMpO1xuICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MgPSBvcmlnaW5hbE1hcHBpbmdzO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEZpbmQgdGhlIG1hcHBpbmcgdGhhdCBiZXN0IG1hdGNoZXMgdGhlIGh5cG90aGV0aWNhbCBcIm5lZWRsZVwiIG1hcHBpbmcgdGhhdFxuICAgKiB3ZSBhcmUgc2VhcmNoaW5nIGZvciBpbiB0aGUgZ2l2ZW4gXCJoYXlzdGFja1wiIG9mIG1hcHBpbmdzLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX2ZpbmRNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9maW5kTWFwcGluZyhhTmVlZGxlLCBhTWFwcGluZ3MsIGFMaW5lTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhQ29sdW1uTmFtZSwgYUNvbXBhcmF0b3IsIGFCaWFzKSB7XG4gICAgICAvLyBUbyByZXR1cm4gdGhlIHBvc2l0aW9uIHdlIGFyZSBzZWFyY2hpbmcgZm9yLCB3ZSBtdXN0IGZpcnN0IGZpbmQgdGhlXG4gICAgICAvLyBtYXBwaW5nIGZvciB0aGUgZ2l2ZW4gcG9zaXRpb24gYW5kIHRoZW4gcmV0dXJuIHRoZSBvcHBvc2l0ZSBwb3NpdGlvbiBpdFxuICAgICAgLy8gcG9pbnRzIHRvLiBCZWNhdXNlIHRoZSBtYXBwaW5ncyBhcmUgc29ydGVkLCB3ZSBjYW4gdXNlIGJpbmFyeSBzZWFyY2ggdG9cbiAgICAgIC8vIGZpbmQgdGhlIGJlc3QgbWFwcGluZy5cblxuICAgICAgaWYgKGFOZWVkbGVbYUxpbmVOYW1lXSA8PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0xpbmUgbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMSwgZ290ICdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICArIGFOZWVkbGVbYUxpbmVOYW1lXSk7XG4gICAgICB9XG4gICAgICBpZiAoYU5lZWRsZVthQ29sdW1uTmFtZV0gPCAwKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0NvbHVtbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAwLCBnb3QgJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgYU5lZWRsZVthQ29sdW1uTmFtZV0pO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gYmluYXJ5U2VhcmNoLnNlYXJjaChhTmVlZGxlLCBhTWFwcGluZ3MsIGFDb21wYXJhdG9yLCBhQmlhcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogQ29tcHV0ZSB0aGUgbGFzdCBjb2x1bW4gZm9yIGVhY2ggZ2VuZXJhdGVkIG1hcHBpbmcuIFRoZSBsYXN0IGNvbHVtbiBpc1xuICAgKiBpbmNsdXNpdmUuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5jb21wdXRlQ29sdW1uU3BhbnMgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2NvbXB1dGVDb2x1bW5TcGFucygpIHtcbiAgICAgIGZvciAodmFyIGluZGV4ID0gMDsgaW5kZXggPCB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGg7ICsraW5kZXgpIHtcbiAgICAgICAgdmFyIG1hcHBpbmcgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgLy8gTWFwcGluZ3MgZG8gbm90IGNvbnRhaW4gYSBmaWVsZCBmb3IgdGhlIGxhc3QgZ2VuZXJhdGVkIGNvbHVtbnQuIFdlXG4gICAgICAgIC8vIGNhbiBjb21lIHVwIHdpdGggYW4gb3B0aW1pc3RpYyBlc3RpbWF0ZSwgaG93ZXZlciwgYnkgYXNzdW1pbmcgdGhhdFxuICAgICAgICAvLyBtYXBwaW5ncyBhcmUgY29udGlndW91cyAoaS5lLiBnaXZlbiB0d28gY29uc2VjdXRpdmUgbWFwcGluZ3MsIHRoZVxuICAgICAgICAvLyBmaXJzdCBtYXBwaW5nIGVuZHMgd2hlcmUgdGhlIHNlY29uZCBvbmUgc3RhcnRzKS5cbiAgICAgICAgaWYgKGluZGV4ICsgMSA8IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzLmxlbmd0aCkge1xuICAgICAgICAgIHZhciBuZXh0TWFwcGluZyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzW2luZGV4ICsgMV07XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lID09PSBuZXh0TWFwcGluZy5nZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgICBtYXBwaW5nLmxhc3RHZW5lcmF0ZWRDb2x1bW4gPSBuZXh0TWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gLSAxO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gVGhlIGxhc3QgbWFwcGluZyBmb3IgZWFjaCBsaW5lIHNwYW5zIHRoZSBlbnRpcmUgbGluZS5cbiAgICAgICAgbWFwcGluZy5sYXN0R2VuZXJhdGVkQ29sdW1uID0gSW5maW5pdHk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgb3JpZ2luYWwgc291cmNlLCBsaW5lLCBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgZ2VuZXJhdGVkXG4gICAqIHNvdXJjZSdzIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdFxuICAgKiB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBiaWFzOiBFaXRoZXIgJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ1NvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5EJy4gU3BlY2lmaWVzIHdoZXRoZXIgdG8gcmV0dXJuIHRoZVxuICAgKiAgICAgY2xvc2VzdCBlbGVtZW50IHRoYXQgaXMgc21hbGxlciB0aGFuIG9yIGdyZWF0ZXIgdGhhbiB0aGUgb25lIHdlIGFyZVxuICAgKiAgICAgc2VhcmNoaW5nIGZvciwgcmVzcGVjdGl2ZWx5LCBpZiB0aGUgZXhhY3QgZWxlbWVudCBjYW5ub3QgYmUgZm91bmQuXG4gICAqICAgICBEZWZhdWx0cyB0byAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUsIG9yIG51bGwuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIG5hbWU6IFRoZSBvcmlnaW5hbCBpZGVudGlmaWVyLCBvciBudWxsLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUub3JpZ2luYWxQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfb3JpZ2luYWxQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyksXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcoXG4gICAgICAgIG5lZWRsZSxcbiAgICAgICAgdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3MsXG4gICAgICAgIFwiZ2VuZXJhdGVkTGluZVwiLFxuICAgICAgICBcImdlbmVyYXRlZENvbHVtblwiLFxuICAgICAgICB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkLFxuICAgICAgICB1dGlsLmdldEFyZyhhQXJncywgJ2JpYXMnLCBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORClcbiAgICAgICk7XG5cbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgPT09IG5lZWRsZS5nZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgdmFyIHNvdXJjZSA9IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdzb3VyY2UnLCBudWxsKTtcbiAgICAgICAgICBpZiAoc291cmNlICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmF0KHNvdXJjZSk7XG4gICAgICAgICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHRoaXMuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgdmFyIG5hbWUgPSB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbmFtZScsIG51bGwpO1xuICAgICAgICAgIGlmIChuYW1lICE9PSBudWxsKSB7XG4gICAgICAgICAgICBuYW1lID0gdGhpcy5fbmFtZXMuYXQobmFtZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdvcmlnaW5hbExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ29yaWdpbmFsQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICBuYW1lOiBuYW1lXG4gICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBzb3VyY2U6IG51bGwsXG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgbmFtZTogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdHJ1ZSBpZiB3ZSBoYXZlIHRoZSBzb3VyY2UgY29udGVudCBmb3IgZXZlcnkgc291cmNlIGluIHRoZSBzb3VyY2VcbiAgICogbWFwLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5oYXNDb250ZW50c09mQWxsU291cmNlcyA9XG4gICAgZnVuY3Rpb24gQmFzaWNTb3VyY2VNYXBDb25zdW1lcl9oYXNDb250ZW50c09mQWxsU291cmNlcygpIHtcbiAgICAgIGlmICghdGhpcy5zb3VyY2VzQ29udGVudCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudC5sZW5ndGggPj0gdGhpcy5fc291cmNlcy5zaXplKCkgJiZcbiAgICAgICAgIXRoaXMuc291cmNlc0NvbnRlbnQuc29tZShmdW5jdGlvbiAoc2MpIHsgcmV0dXJuIHNjID09IG51bGw7IH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50LiBUaGUgb25seSBhcmd1bWVudCBpcyB0aGUgdXJsIG9mIHRoZVxuICAgKiBvcmlnaW5hbCBzb3VyY2UgZmlsZS4gUmV0dXJucyBudWxsIGlmIG5vIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50IGlzXG4gICAqIGF2YWlsaWJsZS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLnNvdXJjZUNvbnRlbnRGb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX3NvdXJjZUNvbnRlbnRGb3IoYVNvdXJjZSwgbnVsbE9uTWlzc2luZykge1xuICAgICAgaWYgKCF0aGlzLnNvdXJjZXNDb250ZW50KSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuXG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgYVNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBhU291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHRoaXMuX3NvdXJjZXMuaGFzKGFTb3VyY2UpKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihhU291cmNlKV07XG4gICAgICB9XG5cbiAgICAgIHZhciB1cmw7XG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGxcbiAgICAgICAgICAmJiAodXJsID0gdXRpbC51cmxQYXJzZSh0aGlzLnNvdXJjZVJvb3QpKSkge1xuICAgICAgICAvLyBYWFg6IGZpbGU6Ly8gVVJJcyBhbmQgYWJzb2x1dGUgcGF0aHMgbGVhZCB0byB1bmV4cGVjdGVkIGJlaGF2aW9yIGZvclxuICAgICAgICAvLyBtYW55IHVzZXJzLiBXZSBjYW4gaGVscCB0aGVtIG91dCB3aGVuIHRoZXkgZXhwZWN0IGZpbGU6Ly8gVVJJcyB0b1xuICAgICAgICAvLyBiZWhhdmUgbGlrZSBpdCB3b3VsZCBpZiB0aGV5IHdlcmUgcnVubmluZyBhIGxvY2FsIEhUVFAgc2VydmVyLiBTZWVcbiAgICAgICAgLy8gaHR0cHM6Ly9idWd6aWxsYS5tb3ppbGxhLm9yZy9zaG93X2J1Zy5jZ2k/aWQ9ODg1NTk3LlxuICAgICAgICB2YXIgZmlsZVVyaUFic1BhdGggPSBhU291cmNlLnJlcGxhY2UoL15maWxlOlxcL1xcLy8sIFwiXCIpO1xuICAgICAgICBpZiAodXJsLnNjaGVtZSA9PSBcImZpbGVcIlxuICAgICAgICAgICAgJiYgdGhpcy5fc291cmNlcy5oYXMoZmlsZVVyaUFic1BhdGgpKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnRbdGhpcy5fc291cmNlcy5pbmRleE9mKGZpbGVVcmlBYnNQYXRoKV1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICgoIXVybC5wYXRoIHx8IHVybC5wYXRoID09IFwiL1wiKVxuICAgICAgICAgICAgJiYgdGhpcy5fc291cmNlcy5oYXMoXCIvXCIgKyBhU291cmNlKSkge1xuICAgICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihcIi9cIiArIGFTb3VyY2UpXTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBUaGlzIGZ1bmN0aW9uIGlzIHVzZWQgcmVjdXJzaXZlbHkgZnJvbVxuICAgICAgLy8gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5zb3VyY2VDb250ZW50Rm9yLiBJbiB0aGF0IGNhc2UsIHdlXG4gICAgICAvLyBkb24ndCB3YW50IHRvIHRocm93IGlmIHdlIGNhbid0IGZpbmQgdGhlIHNvdXJjZSAtIHdlIGp1c3Qgd2FudCB0b1xuICAgICAgLy8gcmV0dXJuIG51bGwsIHNvIHdlIHByb3ZpZGUgYSBmbGFnIHRvIGV4aXQgZ3JhY2VmdWxseS5cbiAgICAgIGlmIChudWxsT25NaXNzaW5nKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYVNvdXJjZSArICdcIiBpcyBub3QgaW4gdGhlIFNvdXJjZU1hcC4nKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgb3JpZ2luYWwgc291cmNlLFxuICAgKiBsaW5lLCBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0IHdpdGhcbiAgICogdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gYmlhczogRWl0aGVyICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJy5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5nZW5lcmF0ZWRQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBzb3VyY2UgPSB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpO1xuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIHNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgfVxuICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzLmhhcyhzb3VyY2UpKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgICAgbGFzdENvbHVtbjogbnVsbFxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKHNvdXJjZSk7XG5cbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICBvcmlnaW5hbExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBvcmlnaW5hbENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcoXG4gICAgICAgIG5lZWRsZSxcbiAgICAgICAgdGhpcy5fb3JpZ2luYWxNYXBwaW5ncyxcbiAgICAgICAgXCJvcmlnaW5hbExpbmVcIixcbiAgICAgICAgXCJvcmlnaW5hbENvbHVtblwiLFxuICAgICAgICB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zLFxuICAgICAgICB1dGlsLmdldEFyZyhhQXJncywgJ2JpYXMnLCBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORClcbiAgICAgICk7XG5cbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuc291cmNlID09PSBuZWVkbGUuc291cmNlKSB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRMaW5lJywgbnVsbCksXG4gICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgbGFzdENvbHVtbjogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuQmFzaWNTb3VyY2VNYXBDb25zdW1lciA9IEJhc2ljU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIEFuIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lciBpbnN0YW5jZSByZXByZXNlbnRzIGEgcGFyc2VkIHNvdXJjZSBtYXAgd2hpY2hcbiAgICogd2UgY2FuIHF1ZXJ5IGZvciBpbmZvcm1hdGlvbi4gSXQgZGlmZmVycyBmcm9tIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgaW5cbiAgICogdGhhdCBpdCB0YWtlcyBcImluZGV4ZWRcIiBzb3VyY2UgbWFwcyAoaS5lLiBvbmVzIHdpdGggYSBcInNlY3Rpb25zXCIgZmllbGQpIGFzXG4gICAqIGlucHV0LlxuICAgKlxuICAgKiBUaGUgb25seSBwYXJhbWV0ZXIgaXMgYSByYXcgc291cmNlIG1hcCAoZWl0aGVyIGFzIGEgSlNPTiBzdHJpbmcsIG9yIGFscmVhZHlcbiAgICogcGFyc2VkIHRvIGFuIG9iamVjdCkuIEFjY29yZGluZyB0byB0aGUgc3BlYyBmb3IgaW5kZXhlZCBzb3VyY2UgbWFwcywgdGhleVxuICAgKiBoYXZlIHRoZSBmb2xsb3dpbmcgYXR0cmlidXRlczpcbiAgICpcbiAgICogICAtIHZlcnNpb246IFdoaWNoIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXAgc3BlYyB0aGlzIG1hcCBpcyBmb2xsb3dpbmcuXG4gICAqICAgLSBmaWxlOiBPcHRpb25hbC4gVGhlIGdlbmVyYXRlZCBmaWxlIHRoaXMgc291cmNlIG1hcCBpcyBhc3NvY2lhdGVkIHdpdGguXG4gICAqICAgLSBzZWN0aW9uczogQSBsaXN0IG9mIHNlY3Rpb24gZGVmaW5pdGlvbnMuXG4gICAqXG4gICAqIEVhY2ggdmFsdWUgdW5kZXIgdGhlIFwic2VjdGlvbnNcIiBmaWVsZCBoYXMgdHdvIGZpZWxkczpcbiAgICogICAtIG9mZnNldDogVGhlIG9mZnNldCBpbnRvIHRoZSBvcmlnaW5hbCBzcGVjaWZpZWQgYXQgd2hpY2ggdGhpcyBzZWN0aW9uXG4gICAqICAgICAgIGJlZ2lucyB0byBhcHBseSwgZGVmaW5lZCBhcyBhbiBvYmplY3Qgd2l0aCBhIFwibGluZVwiIGFuZCBcImNvbHVtblwiXG4gICAqICAgICAgIGZpZWxkLlxuICAgKiAgIC0gbWFwOiBBIHNvdXJjZSBtYXAgZGVmaW5pdGlvbi4gVGhpcyBzb3VyY2UgbWFwIGNvdWxkIGFsc28gYmUgaW5kZXhlZCxcbiAgICogICAgICAgYnV0IGRvZXNuJ3QgaGF2ZSB0byBiZS5cbiAgICpcbiAgICogSW5zdGVhZCBvZiB0aGUgXCJtYXBcIiBmaWVsZCwgaXQncyBhbHNvIHBvc3NpYmxlIHRvIGhhdmUgYSBcInVybFwiIGZpZWxkXG4gICAqIHNwZWNpZnlpbmcgYSBVUkwgdG8gcmV0cmlldmUgYSBzb3VyY2UgbWFwIGZyb20sIGJ1dCB0aGF0J3MgY3VycmVudGx5XG4gICAqIHVuc3VwcG9ydGVkLlxuICAgKlxuICAgKiBIZXJlJ3MgYW4gZXhhbXBsZSBzb3VyY2UgbWFwLCB0YWtlbiBmcm9tIHRoZSBzb3VyY2UgbWFwIHNwZWNbMF0sIGJ1dFxuICAgKiBtb2RpZmllZCB0byBvbWl0IGEgc2VjdGlvbiB3aGljaCB1c2VzIHRoZSBcInVybFwiIGZpZWxkLlxuICAgKlxuICAgKiAge1xuICAgKiAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgZmlsZTogXCJhcHAuanNcIixcbiAgICogICAgc2VjdGlvbnM6IFt7XG4gICAqICAgICAgb2Zmc2V0OiB7bGluZToxMDAsIGNvbHVtbjoxMH0sXG4gICAqICAgICAgbWFwOiB7XG4gICAqICAgICAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgICAgIGZpbGU6IFwic2VjdGlvbi5qc1wiLFxuICAgKiAgICAgICAgc291cmNlczogW1wiZm9vLmpzXCIsIFwiYmFyLmpzXCJdLFxuICAgKiAgICAgICAgbmFtZXM6IFtcInNyY1wiLCBcIm1hcHNcIiwgXCJhcmVcIiwgXCJmdW5cIl0sXG4gICAqICAgICAgICBtYXBwaW5nczogXCJBQUFBLEU7O0FCQ0RFO1wiXG4gICAqICAgICAgfVxuICAgKiAgICB9XSxcbiAgICogIH1cbiAgICpcbiAgICogWzBdOiBodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9kb2N1bWVudC9kLzFVMVJHQWVoUXdSeXBVVG92RjFLUmxwaU9GemUwYi1fMmdjNmZBSDBLWTBrL2VkaXQjaGVhZGluZz1oLjUzNWVzM3hlcHJndFxuICAgKi9cbiAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyKGFTb3VyY2VNYXApIHtcbiAgICB2YXIgc291cmNlTWFwID0gYVNvdXJjZU1hcDtcbiAgICBpZiAodHlwZW9mIGFTb3VyY2VNYXAgPT09ICdzdHJpbmcnKSB7XG4gICAgICBzb3VyY2VNYXAgPSBKU09OLnBhcnNlKGFTb3VyY2VNYXAucmVwbGFjZSgvXlxcKVxcXVxcfScvLCAnJykpO1xuICAgIH1cblxuICAgIHZhciB2ZXJzaW9uID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAndmVyc2lvbicpO1xuICAgIHZhciBzZWN0aW9ucyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NlY3Rpb25zJyk7XG5cbiAgICBpZiAodmVyc2lvbiAhPSB0aGlzLl92ZXJzaW9uKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIHZlcnNpb246ICcgKyB2ZXJzaW9uKTtcbiAgICB9XG5cbiAgICB0aGlzLl9zb3VyY2VzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbmFtZXMgPSBuZXcgQXJyYXlTZXQoKTtcblxuICAgIHZhciBsYXN0T2Zmc2V0ID0ge1xuICAgICAgbGluZTogLTEsXG4gICAgICBjb2x1bW46IDBcbiAgICB9O1xuICAgIHRoaXMuX3NlY3Rpb25zID0gc2VjdGlvbnMubWFwKGZ1bmN0aW9uIChzKSB7XG4gICAgICBpZiAocy51cmwpIHtcbiAgICAgICAgLy8gVGhlIHVybCBmaWVsZCB3aWxsIHJlcXVpcmUgc3VwcG9ydCBmb3IgYXN5bmNocm9uaWNpdHkuXG4gICAgICAgIC8vIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL2lzc3Vlcy8xNlxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1N1cHBvcnQgZm9yIHVybCBmaWVsZCBpbiBzZWN0aW9ucyBub3QgaW1wbGVtZW50ZWQuJyk7XG4gICAgICB9XG4gICAgICB2YXIgb2Zmc2V0ID0gdXRpbC5nZXRBcmcocywgJ29mZnNldCcpO1xuICAgICAgdmFyIG9mZnNldExpbmUgPSB1dGlsLmdldEFyZyhvZmZzZXQsICdsaW5lJyk7XG4gICAgICB2YXIgb2Zmc2V0Q29sdW1uID0gdXRpbC5nZXRBcmcob2Zmc2V0LCAnY29sdW1uJyk7XG5cbiAgICAgIGlmIChvZmZzZXRMaW5lIDwgbGFzdE9mZnNldC5saW5lIHx8XG4gICAgICAgICAgKG9mZnNldExpbmUgPT09IGxhc3RPZmZzZXQubGluZSAmJiBvZmZzZXRDb2x1bW4gPCBsYXN0T2Zmc2V0LmNvbHVtbikpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTZWN0aW9uIG9mZnNldHMgbXVzdCBiZSBvcmRlcmVkIGFuZCBub24tb3ZlcmxhcHBpbmcuJyk7XG4gICAgICB9XG4gICAgICBsYXN0T2Zmc2V0ID0gb2Zmc2V0O1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBnZW5lcmF0ZWRPZmZzZXQ6IHtcbiAgICAgICAgICAvLyBUaGUgb2Zmc2V0IGZpZWxkcyBhcmUgMC1iYXNlZCwgYnV0IHdlIHVzZSAxLWJhc2VkIGluZGljZXMgd2hlblxuICAgICAgICAgIC8vIGVuY29kaW5nL2RlY29kaW5nIGZyb20gVkxRLlxuICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG9mZnNldExpbmUgKyAxLFxuICAgICAgICAgIGdlbmVyYXRlZENvbHVtbjogb2Zmc2V0Q29sdW1uICsgMVxuICAgICAgICB9LFxuICAgICAgICBjb25zdW1lcjogbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuZ2V0QXJnKHMsICdtYXAnKSlcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogVGhlIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXBwaW5nIHNwZWMgdGhhdCB3ZSBhcmUgY29uc3VtaW5nLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLyoqXG4gICAqIFRoZSBsaXN0IG9mIG9yaWdpbmFsIHNvdXJjZXMuXG4gICAqL1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ3NvdXJjZXMnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICB2YXIgc291cmNlcyA9IFtdO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHRoaXMuX3NlY3Rpb25zW2ldLmNvbnN1bWVyLnNvdXJjZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICBzb3VyY2VzLnB1c2godGhpcy5fc2VjdGlvbnNbaV0uY29uc3VtZXIuc291cmNlc1tqXSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBzb3VyY2VzO1xuICAgIH1cbiAgfSk7XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSwgbGluZSwgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3RcbiAgICogd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUsIG9yIG51bGwuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIG5hbWU6IFRoZSBvcmlnaW5hbCBpZGVudGlmaWVyLCBvciBudWxsLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5vcmlnaW5hbFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfb3JpZ2luYWxQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyksXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgLy8gRmluZCB0aGUgc2VjdGlvbiBjb250YWluaW5nIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb24gd2UncmUgdHJ5aW5nIHRvIG1hcFxuICAgICAgLy8gdG8gYW4gb3JpZ2luYWwgcG9zaXRpb24uXG4gICAgICB2YXIgc2VjdGlvbkluZGV4ID0gYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIHRoaXMuX3NlY3Rpb25zLFxuICAgICAgICBmdW5jdGlvbihuZWVkbGUsIHNlY3Rpb24pIHtcbiAgICAgICAgICB2YXIgY21wID0gbmVlZGxlLmdlbmVyYXRlZExpbmUgLSBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lO1xuICAgICAgICAgIGlmIChjbXApIHtcbiAgICAgICAgICAgIHJldHVybiBjbXA7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmV0dXJuIChuZWVkbGUuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAgICAgICAgIHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbik7XG4gICAgICAgIH0pO1xuICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tzZWN0aW9uSW5kZXhdO1xuXG4gICAgICBpZiAoIXNlY3Rpb24pIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzb3VyY2U6IG51bGwsXG4gICAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgICAgbmFtZTogbnVsbFxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gc2VjdGlvbi5jb25zdW1lci5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgICAgbGluZTogbmVlZGxlLmdlbmVyYXRlZExpbmUgLVxuICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgIGNvbHVtbjogbmVlZGxlLmdlbmVyYXRlZENvbHVtbiAtXG4gICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgPT09IG5lZWRsZS5nZW5lcmF0ZWRMaW5lXG4gICAgICAgICAgID8gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uIC0gMVxuICAgICAgICAgICA6IDApLFxuICAgICAgICBiaWFzOiBhQXJncy5iaWFzXG4gICAgICB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdHJ1ZSBpZiB3ZSBoYXZlIHRoZSBzb3VyY2UgY29udGVudCBmb3IgZXZlcnkgc291cmNlIGluIHRoZSBzb3VyY2VcbiAgICogbWFwLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc2VjdGlvbnMuZXZlcnkoZnVuY3Rpb24gKHMpIHtcbiAgICAgICAgcmV0dXJuIHMuY29uc3VtZXIuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKTtcbiAgICAgIH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50LiBUaGUgb25seSBhcmd1bWVudCBpcyB0aGUgdXJsIG9mIHRoZVxuICAgKiBvcmlnaW5hbCBzb3VyY2UgZmlsZS4gUmV0dXJucyBudWxsIGlmIG5vIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50IGlzXG4gICAqIGF2YWlsYWJsZS5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuc291cmNlQ29udGVudEZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX3NvdXJjZUNvbnRlbnRGb3IoYVNvdXJjZSwgbnVsbE9uTWlzc2luZykge1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW2ldO1xuXG4gICAgICAgIHZhciBjb250ZW50ID0gc2VjdGlvbi5jb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIHRydWUpO1xuICAgICAgICBpZiAoY29udGVudCkge1xuICAgICAgICAgIHJldHVybiBjb250ZW50O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAobnVsbE9uTWlzc2luZykge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTb3VyY2UgKyAnXCIgaXMgbm90IGluIHRoZSBTb3VyY2VNYXAuJyk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoXG4gICAqIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmdlbmVyYXRlZFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcblxuICAgICAgICAvLyBPbmx5IGNvbnNpZGVyIHRoaXMgc2VjdGlvbiBpZiB0aGUgcmVxdWVzdGVkIHNvdXJjZSBpcyBpbiB0aGUgbGlzdCBvZlxuICAgICAgICAvLyBzb3VyY2VzIG9mIHRoZSBjb25zdW1lci5cbiAgICAgICAgaWYgKHNlY3Rpb24uY29uc3VtZXIuc291cmNlcy5pbmRleE9mKHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJykpID09PSAtMSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIHZhciBnZW5lcmF0ZWRQb3NpdGlvbiA9IHNlY3Rpb24uY29uc3VtZXIuZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpO1xuICAgICAgICBpZiAoZ2VuZXJhdGVkUG9zaXRpb24pIHtcbiAgICAgICAgICB2YXIgcmV0ID0ge1xuICAgICAgICAgICAgbGluZTogZ2VuZXJhdGVkUG9zaXRpb24ubGluZSArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgICAgICBjb2x1bW46IGdlbmVyYXRlZFBvc2l0aW9uLmNvbHVtbiArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBnZW5lcmF0ZWRQb3NpdGlvbi5saW5lXG4gICAgICAgICAgICAgICA/IHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbiAtIDFcbiAgICAgICAgICAgICAgIDogMClcbiAgICAgICAgICB9O1xuICAgICAgICAgIHJldHVybiByZXQ7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgY29sdW1uOiBudWxsXG4gICAgICB9O1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFBhcnNlIHRoZSBtYXBwaW5ncyBpbiBhIHN0cmluZyBpbiB0byBhIGRhdGEgc3RydWN0dXJlIHdoaWNoIHdlIGNhbiBlYXNpbHlcbiAgICogcXVlcnkgKHRoZSBvcmRlcmVkIGFycmF5cyBpbiB0aGUgYHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gICAqIGB0aGlzLl9fb3JpZ2luYWxNYXBwaW5nc2AgcHJvcGVydGllcykuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW2ldO1xuICAgICAgICB2YXIgc2VjdGlvbk1hcHBpbmdzID0gc2VjdGlvbi5jb25zdW1lci5fZ2VuZXJhdGVkTWFwcGluZ3M7XG4gICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgc2VjdGlvbk1hcHBpbmdzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgdmFyIG1hcHBpbmcgPSBzZWN0aW9uTWFwcGluZ3NbaV07XG5cbiAgICAgICAgICB2YXIgc291cmNlID0gc2VjdGlvbi5jb25zdW1lci5fc291cmNlcy5hdChtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgaWYgKHNlY3Rpb24uY29uc3VtZXIuc291cmNlUm9vdCAhPT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHNlY3Rpb24uY29uc3VtZXIuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5fc291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgICAgICBzb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmluZGV4T2Yoc291cmNlKTtcblxuICAgICAgICAgIHZhciBuYW1lID0gc2VjdGlvbi5jb25zdW1lci5fbmFtZXMuYXQobWFwcGluZy5uYW1lKTtcbiAgICAgICAgICB0aGlzLl9uYW1lcy5hZGQobmFtZSk7XG4gICAgICAgICAgbmFtZSA9IHRoaXMuX25hbWVzLmluZGV4T2YobmFtZSk7XG5cbiAgICAgICAgICAvLyBUaGUgbWFwcGluZ3MgY29taW5nIGZyb20gdGhlIGNvbnN1bWVyIGZvciB0aGUgc2VjdGlvbiBoYXZlXG4gICAgICAgICAgLy8gZ2VuZXJhdGVkIHBvc2l0aW9ucyByZWxhdGl2ZSB0byB0aGUgc3RhcnQgb2YgdGhlIHNlY3Rpb24sIHNvIHdlXG4gICAgICAgICAgLy8gbmVlZCB0byBvZmZzZXQgdGhlbSB0byBiZSByZWxhdGl2ZSB0byB0aGUgc3RhcnQgb2YgdGhlIGNvbmNhdGVuYXRlZFxuICAgICAgICAgIC8vIGdlbmVyYXRlZCBmaWxlLlxuICAgICAgICAgIHZhciBhZGp1c3RlZE1hcHBpbmcgPSB7XG4gICAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG1hcHBpbmcuY29sdW1uICtcbiAgICAgICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgPT09IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSlcbiAgICAgICAgICAgICAgPyBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4gLSAxXG4gICAgICAgICAgICAgIDogMCxcbiAgICAgICAgICAgIG9yaWdpbmFsTGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBvcmlnaW5hbENvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgIG5hbWU6IG5hbWVcbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzLnB1c2goYWRqdXN0ZWRNYXBwaW5nKTtcbiAgICAgICAgICBpZiAodHlwZW9mIGFkanVzdGVkTWFwcGluZy5vcmlnaW5hbExpbmUgPT09ICdudW1iZXInKSB7XG4gICAgICAgICAgICB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncy5wdXNoKGFkanVzdGVkTWFwcGluZyk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHF1aWNrU29ydCh0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQpO1xuICAgICAgcXVpY2tTb3J0KHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyID0gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyLmpzXG4gKiogbW9kdWxlIGlkID0gOFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EID0gMTtcbiAgZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCA9IDI7XG5cbiAgLyoqXG4gICAqIFJlY3Vyc2l2ZSBpbXBsZW1lbnRhdGlvbiBvZiBiaW5hcnkgc2VhcmNoLlxuICAgKlxuICAgKiBAcGFyYW0gYUxvdyBJbmRpY2VzIGhlcmUgYW5kIGxvd2VyIGRvIG5vdCBjb250YWluIHRoZSBuZWVkbGUuXG4gICAqIEBwYXJhbSBhSGlnaCBJbmRpY2VzIGhlcmUgYW5kIGhpZ2hlciBkbyBub3QgY29udGFpbiB0aGUgbmVlZGxlLlxuICAgKiBAcGFyYW0gYU5lZWRsZSBUaGUgZWxlbWVudCBiZWluZyBzZWFyY2hlZCBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIG5vbi1lbXB0eSBhcnJheSBiZWluZyBzZWFyY2hlZC5cbiAgICogQHBhcmFtIGFDb21wYXJlIEZ1bmN0aW9uIHdoaWNoIHRha2VzIHR3byBlbGVtZW50cyBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKi9cbiAgZnVuY3Rpb24gcmVjdXJzaXZlU2VhcmNoKGFMb3csIGFIaWdoLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIC8vIFRoaXMgZnVuY3Rpb24gdGVybWluYXRlcyB3aGVuIG9uZSBvZiB0aGUgZm9sbG93aW5nIGlzIHRydWU6XG4gICAgLy9cbiAgICAvLyAgIDEuIFdlIGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQgd2UgYXJlIGxvb2tpbmcgZm9yLlxuICAgIC8vXG4gICAgLy8gICAyLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGJ1dCB3ZSBjYW4gcmV0dXJuIHRoZSBpbmRleCBvZlxuICAgIC8vICAgICAgdGhlIG5leHQtY2xvc2VzdCBlbGVtZW50LlxuICAgIC8vXG4gICAgLy8gICAzLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGFuZCB0aGVyZSBpcyBubyBuZXh0LWNsb3Nlc3RcbiAgICAvLyAgICAgIGVsZW1lbnQgdGhhbiB0aGUgb25lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLCBzbyB3ZSByZXR1cm4gLTEuXG4gICAgdmFyIG1pZCA9IE1hdGguZmxvb3IoKGFIaWdoIC0gYUxvdykgLyAyKSArIGFMb3c7XG4gICAgdmFyIGNtcCA9IGFDb21wYXJlKGFOZWVkbGUsIGFIYXlzdGFja1ttaWRdLCB0cnVlKTtcbiAgICBpZiAoY21wID09PSAwKSB7XG4gICAgICAvLyBGb3VuZCB0aGUgZWxlbWVudCB3ZSBhcmUgbG9va2luZyBmb3IuXG4gICAgICByZXR1cm4gbWlkO1xuICAgIH1cbiAgICBlbHNlIGlmIChjbXAgPiAwKSB7XG4gICAgICAvLyBPdXIgbmVlZGxlIGlzIGdyZWF0ZXIgdGhhbiBhSGF5c3RhY2tbbWlkXS5cbiAgICAgIGlmIChhSGlnaCAtIG1pZCA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIHVwcGVyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2gobWlkLCBhSGlnaCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpO1xuICAgICAgfVxuXG4gICAgICAvLyBUaGUgZXhhY3QgbmVlZGxlIGVsZW1lbnQgd2FzIG5vdCBmb3VuZCBpbiB0aGlzIGhheXN0YWNrLiBEZXRlcm1pbmUgaWZcbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gYUhpZ2ggPCBhSGF5c3RhY2subGVuZ3RoID8gYUhpZ2ggOiAtMTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBtaWQ7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgLy8gT3VyIG5lZWRsZSBpcyBsZXNzIHRoYW4gYUhheXN0YWNrW21pZF0uXG4gICAgICBpZiAobWlkIC0gYUxvdyA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIGxvd2VyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2goYUxvdywgbWlkLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcyk7XG4gICAgICB9XG5cbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gbWlkO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGFMb3cgPCAwID8gLTEgOiBhTG93O1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBUaGlzIGlzIGFuIGltcGxlbWVudGF0aW9uIG9mIGJpbmFyeSBzZWFyY2ggd2hpY2ggd2lsbCBhbHdheXMgdHJ5IGFuZCByZXR1cm5cbiAgICogdGhlIGluZGV4IG9mIHRoZSBjbG9zZXN0IGVsZW1lbnQgaWYgdGhlcmUgaXMgbm8gZXhhY3QgaGl0LiBUaGlzIGlzIGJlY2F1c2VcbiAgICogbWFwcGluZ3MgYmV0d2VlbiBvcmlnaW5hbCBhbmQgZ2VuZXJhdGVkIGxpbmUvY29sIHBhaXJzIGFyZSBzaW5nbGUgcG9pbnRzLFxuICAgKiBhbmQgdGhlcmUgaXMgYW4gaW1wbGljaXQgcmVnaW9uIGJldHdlZW4gZWFjaCBvZiB0aGVtLCBzbyBhIG1pc3MganVzdCBtZWFuc1xuICAgKiB0aGF0IHlvdSBhcmVuJ3Qgb24gdGhlIHZlcnkgc3RhcnQgb2YgYSByZWdpb24uXG4gICAqXG4gICAqIEBwYXJhbSBhTmVlZGxlIFRoZSBlbGVtZW50IHlvdSBhcmUgbG9va2luZyBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIGFycmF5IHRoYXQgaXMgYmVpbmcgc2VhcmNoZWQuXG4gICAqIEBwYXJhbSBhQ29tcGFyZSBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIHRoZSBuZWVkbGUgYW5kIGFuIGVsZW1lbnQgaW4gdGhlXG4gICAqICAgICBhcnJheSBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMSBkZXBlbmRpbmcgb24gd2hldGhlciB0aGUgbmVlZGxlIGlzIGxlc3NcbiAgICogICAgIHRoYW4sIGVxdWFsIHRvLCBvciBncmVhdGVyIHRoYW4gdGhlIGVsZW1lbnQsIHJlc3BlY3RpdmVseS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqL1xuICBleHBvcnRzLnNlYXJjaCA9IGZ1bmN0aW9uIHNlYXJjaChhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIGlmIChhSGF5c3RhY2subGVuZ3RoID09PSAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgdmFyIGluZGV4ID0gcmVjdXJzaXZlU2VhcmNoKC0xLCBhSGF5c3RhY2subGVuZ3RoLCBhTmVlZGxlLCBhSGF5c3RhY2ssXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFDb21wYXJlLCBhQmlhcyB8fCBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EKTtcbiAgICBpZiAoaW5kZXggPCAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgLy8gV2UgaGF2ZSBmb3VuZCBlaXRoZXIgdGhlIGV4YWN0IGVsZW1lbnQsIG9yIHRoZSBuZXh0LWNsb3Nlc3QgZWxlbWVudCB0aGFuXG4gICAgLy8gdGhlIG9uZSB3ZSBhcmUgc2VhcmNoaW5nIGZvci4gSG93ZXZlciwgdGhlcmUgbWF5IGJlIG1vcmUgdGhhbiBvbmUgc3VjaFxuICAgIC8vIGVsZW1lbnQuIE1ha2Ugc3VyZSB3ZSBhbHdheXMgcmV0dXJuIHRoZSBzbWFsbGVzdCBvZiB0aGVzZS5cbiAgICB3aGlsZSAoaW5kZXggLSAxID49IDApIHtcbiAgICAgIGlmIChhQ29tcGFyZShhSGF5c3RhY2tbaW5kZXhdLCBhSGF5c3RhY2tbaW5kZXggLSAxXSwgdHJ1ZSkgIT09IDApIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICAtLWluZGV4O1xuICAgIH1cblxuICAgIHJldHVybiBpbmRleDtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmluYXJ5LXNlYXJjaC5qc1xuICoqIG1vZHVsZSBpZCA9IDlcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgLy8gSXQgdHVybnMgb3V0IHRoYXQgc29tZSAobW9zdD8pIEphdmFTY3JpcHQgZW5naW5lcyBkb24ndCBzZWxmLWhvc3RcbiAgLy8gYEFycmF5LnByb3RvdHlwZS5zb3J0YC4gVGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlIEMrKyB3aWxsIGxpa2VseSByZW1haW5cbiAgLy8gZmFzdGVyIHRoYW4gSlMgd2hlbiBkb2luZyByYXcgQ1BVLWludGVuc2l2ZSBzb3J0aW5nLiBIb3dldmVyLCB3aGVuIHVzaW5nIGFcbiAgLy8gY3VzdG9tIGNvbXBhcmF0b3IgZnVuY3Rpb24sIGNhbGxpbmcgYmFjayBhbmQgZm9ydGggYmV0d2VlbiB0aGUgVk0ncyBDKysgYW5kXG4gIC8vIEpJVCdkIEpTIGlzIHJhdGhlciBzbG93ICphbmQqIGxvc2VzIEpJVCB0eXBlIGluZm9ybWF0aW9uLCByZXN1bHRpbmcgaW5cbiAgLy8gd29yc2UgZ2VuZXJhdGVkIGNvZGUgZm9yIHRoZSBjb21wYXJhdG9yIGZ1bmN0aW9uIHRoYW4gd291bGQgYmUgb3B0aW1hbC4gSW5cbiAgLy8gZmFjdCwgd2hlbiBzb3J0aW5nIHdpdGggYSBjb21wYXJhdG9yLCB0aGVzZSBjb3N0cyBvdXR3ZWlnaCB0aGUgYmVuZWZpdHMgb2ZcbiAgLy8gc29ydGluZyBpbiBDKysuIEJ5IHVzaW5nIG91ciBvd24gSlMtaW1wbGVtZW50ZWQgUXVpY2sgU29ydCAoYmVsb3cpLCB3ZSBnZXRcbiAgLy8gYSB+MzUwMG1zIG1lYW4gc3BlZWQtdXAgaW4gYGJlbmNoL2JlbmNoLmh0bWxgLlxuXG4gIC8qKlxuICAgKiBTd2FwIHRoZSBlbGVtZW50cyBpbmRleGVkIGJ5IGB4YCBhbmQgYHlgIGluIHRoZSBhcnJheSBgYXJ5YC5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBUaGUgYXJyYXkuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB4XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIGZpcnN0IGl0ZW0uXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB5XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIHNlY29uZCBpdGVtLlxuICAgKi9cbiAgZnVuY3Rpb24gc3dhcChhcnksIHgsIHkpIHtcbiAgICB2YXIgdGVtcCA9IGFyeVt4XTtcbiAgICBhcnlbeF0gPSBhcnlbeV07XG4gICAgYXJ5W3ldID0gdGVtcDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGEgcmFuZG9tIGludGVnZXIgd2l0aGluIHRoZSByYW5nZSBgbG93IC4uIGhpZ2hgIGluY2x1c2l2ZS5cbiAgICpcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGxvd1xuICAgKiAgICAgICAgVGhlIGxvd2VyIGJvdW5kIG9uIHRoZSByYW5nZS5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IGhpZ2hcbiAgICogICAgICAgIFRoZSB1cHBlciBib3VuZCBvbiB0aGUgcmFuZ2UuXG4gICAqL1xuICBmdW5jdGlvbiByYW5kb21JbnRJblJhbmdlKGxvdywgaGlnaCkge1xuICAgIHJldHVybiBNYXRoLnJvdW5kKGxvdyArIChNYXRoLnJhbmRvbSgpICogKGhpZ2ggLSBsb3cpKSk7XG4gIH1cblxuICAvKipcbiAgICogVGhlIFF1aWNrIFNvcnQgYWxnb3JpdGhtLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHBcbiAgICogICAgICAgIFN0YXJ0IGluZGV4IG9mIHRoZSBhcnJheVxuICAgKiBAcGFyYW0ge051bWJlcn0gclxuICAgKiAgICAgICAgRW5kIGluZGV4IG9mIHRoZSBhcnJheVxuICAgKi9cbiAgZnVuY3Rpb24gZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBwLCByKSB7XG4gICAgLy8gSWYgb3VyIGxvd2VyIGJvdW5kIGlzIGxlc3MgdGhhbiBvdXIgdXBwZXIgYm91bmQsIHdlICgxKSBwYXJ0aXRpb24gdGhlXG4gICAgLy8gYXJyYXkgaW50byB0d28gcGllY2VzIGFuZCAoMikgcmVjdXJzZSBvbiBlYWNoIGhhbGYuIElmIGl0IGlzIG5vdCwgdGhpcyBpc1xuICAgIC8vIHRoZSBlbXB0eSBhcnJheSBhbmQgb3VyIGJhc2UgY2FzZS5cblxuICAgIGlmIChwIDwgcikge1xuICAgICAgLy8gKDEpIFBhcnRpdGlvbmluZy5cbiAgICAgIC8vXG4gICAgICAvLyBUaGUgcGFydGl0aW9uaW5nIGNob29zZXMgYSBwaXZvdCBiZXR3ZWVuIGBwYCBhbmQgYHJgIGFuZCBtb3ZlcyBhbGxcbiAgICAgIC8vIGVsZW1lbnRzIHRoYXQgYXJlIGxlc3MgdGhhbiBvciBlcXVhbCB0byB0aGUgcGl2b3QgdG8gdGhlIGJlZm9yZSBpdCwgYW5kXG4gICAgICAvLyBhbGwgdGhlIGVsZW1lbnRzIHRoYXQgYXJlIGdyZWF0ZXIgdGhhbiBpdCBhZnRlciBpdC4gVGhlIGVmZmVjdCBpcyB0aGF0XG4gICAgICAvLyBvbmNlIHBhcnRpdGlvbiBpcyBkb25lLCB0aGUgcGl2b3QgaXMgaW4gdGhlIGV4YWN0IHBsYWNlIGl0IHdpbGwgYmUgd2hlblxuICAgICAgLy8gdGhlIGFycmF5IGlzIHB1dCBpbiBzb3J0ZWQgb3JkZXIsIGFuZCBpdCB3aWxsIG5vdCBuZWVkIHRvIGJlIG1vdmVkXG4gICAgICAvLyBhZ2Fpbi4gVGhpcyBydW5zIGluIE8obikgdGltZS5cblxuICAgICAgLy8gQWx3YXlzIGNob29zZSBhIHJhbmRvbSBwaXZvdCBzbyB0aGF0IGFuIGlucHV0IGFycmF5IHdoaWNoIGlzIHJldmVyc2VcbiAgICAgIC8vIHNvcnRlZCBkb2VzIG5vdCBjYXVzZSBPKG5eMikgcnVubmluZyB0aW1lLlxuICAgICAgdmFyIHBpdm90SW5kZXggPSByYW5kb21JbnRJblJhbmdlKHAsIHIpO1xuICAgICAgdmFyIGkgPSBwIC0gMTtcblxuICAgICAgc3dhcChhcnksIHBpdm90SW5kZXgsIHIpO1xuICAgICAgdmFyIHBpdm90ID0gYXJ5W3JdO1xuXG4gICAgICAvLyBJbW1lZGlhdGVseSBhZnRlciBgamAgaXMgaW5jcmVtZW50ZWQgaW4gdGhpcyBsb29wLCB0aGUgZm9sbG93aW5nIGhvbGRcbiAgICAgIC8vIHRydWU6XG4gICAgICAvL1xuICAgICAgLy8gICAqIEV2ZXJ5IGVsZW1lbnQgaW4gYGFyeVtwIC4uIGldYCBpcyBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHBpdm90LlxuICAgICAgLy9cbiAgICAgIC8vICAgKiBFdmVyeSBlbGVtZW50IGluIGBhcnlbaSsxIC4uIGotMV1gIGlzIGdyZWF0ZXIgdGhhbiB0aGUgcGl2b3QuXG4gICAgICBmb3IgKHZhciBqID0gcDsgaiA8IHI7IGorKykge1xuICAgICAgICBpZiAoY29tcGFyYXRvcihhcnlbal0sIHBpdm90KSA8PSAwKSB7XG4gICAgICAgICAgaSArPSAxO1xuICAgICAgICAgIHN3YXAoYXJ5LCBpLCBqKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBzd2FwKGFyeSwgaSArIDEsIGopO1xuICAgICAgdmFyIHEgPSBpICsgMTtcblxuICAgICAgLy8gKDIpIFJlY3Vyc2Ugb24gZWFjaCBoYWxmLlxuXG4gICAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHAsIHEgLSAxKTtcbiAgICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcSArIDEsIHIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTb3J0IHRoZSBnaXZlbiBhcnJheSBpbi1wbGFjZSB3aXRoIHRoZSBnaXZlbiBjb21wYXJhdG9yIGZ1bmN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICovXG4gIGV4cG9ydHMucXVpY2tTb3J0ID0gZnVuY3Rpb24gKGFyeSwgY29tcGFyYXRvcikge1xuICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgMCwgYXJ5Lmxlbmd0aCAtIDEpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9xdWljay1zb3J0LmpzXG4gKiogbW9kdWxlIGlkID0gMTBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIFNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4vc291cmNlLW1hcC1nZW5lcmF0b3InKS5Tb3VyY2VNYXBHZW5lcmF0b3I7XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLy8gTWF0Y2hlcyBhIFdpbmRvd3Mtc3R5bGUgYFxcclxcbmAgbmV3bGluZSBvciBhIGBcXG5gIG5ld2xpbmUgdXNlZCBieSBhbGwgb3RoZXJcbiAgLy8gb3BlcmF0aW5nIHN5c3RlbXMgdGhlc2UgZGF5cyAoY2FwdHVyaW5nIHRoZSByZXN1bHQpLlxuICB2YXIgUkVHRVhfTkVXTElORSA9IC8oXFxyP1xcbikvO1xuXG4gIC8vIE5ld2xpbmUgY2hhcmFjdGVyIGNvZGUgZm9yIGNoYXJDb2RlQXQoKSBjb21wYXJpc29uc1xuICB2YXIgTkVXTElORV9DT0RFID0gMTA7XG5cbiAgLy8gUHJpdmF0ZSBzeW1ib2wgZm9yIGlkZW50aWZ5aW5nIGBTb3VyY2VOb2RlYHMgd2hlbiBtdWx0aXBsZSB2ZXJzaW9ucyBvZlxuICAvLyB0aGUgc291cmNlLW1hcCBsaWJyYXJ5IGFyZSBsb2FkZWQuIFRoaXMgTVVTVCBOT1QgQ0hBTkdFIGFjcm9zc1xuICAvLyB2ZXJzaW9ucyFcbiAgdmFyIGlzU291cmNlTm9kZSA9IFwiJCQkaXNTb3VyY2VOb2RlJCQkXCI7XG5cbiAgLyoqXG4gICAqIFNvdXJjZU5vZGVzIHByb3ZpZGUgYSB3YXkgdG8gYWJzdHJhY3Qgb3ZlciBpbnRlcnBvbGF0aW5nL2NvbmNhdGVuYXRpbmdcbiAgICogc25pcHBldHMgb2YgZ2VuZXJhdGVkIEphdmFTY3JpcHQgc291cmNlIGNvZGUgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGxpbmUgYW5kXG4gICAqIGNvbHVtbiBpbmZvcm1hdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhlIG9yaWdpbmFsIHNvdXJjZSBjb2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUxpbmUgVGhlIG9yaWdpbmFsIGxpbmUgbnVtYmVyLlxuICAgKiBAcGFyYW0gYUNvbHVtbiBUaGUgb3JpZ2luYWwgY29sdW1uIG51bWJlci5cbiAgICogQHBhcmFtIGFTb3VyY2UgVGhlIG9yaWdpbmFsIHNvdXJjZSdzIGZpbGVuYW1lLlxuICAgKiBAcGFyYW0gYUNodW5rcyBPcHRpb25hbC4gQW4gYXJyYXkgb2Ygc3RyaW5ncyB3aGljaCBhcmUgc25pcHBldHMgb2ZcbiAgICogICAgICAgIGdlbmVyYXRlZCBKUywgb3Igb3RoZXIgU291cmNlTm9kZXMuXG4gICAqIEBwYXJhbSBhTmFtZSBUaGUgb3JpZ2luYWwgaWRlbnRpZmllci5cbiAgICovXG4gIGZ1bmN0aW9uIFNvdXJjZU5vZGUoYUxpbmUsIGFDb2x1bW4sIGFTb3VyY2UsIGFDaHVua3MsIGFOYW1lKSB7XG4gICAgdGhpcy5jaGlsZHJlbiA9IFtdO1xuICAgIHRoaXMuc291cmNlQ29udGVudHMgPSB7fTtcbiAgICB0aGlzLmxpbmUgPSBhTGluZSA9PSBudWxsID8gbnVsbCA6IGFMaW5lO1xuICAgIHRoaXMuY29sdW1uID0gYUNvbHVtbiA9PSBudWxsID8gbnVsbCA6IGFDb2x1bW47XG4gICAgdGhpcy5zb3VyY2UgPSBhU291cmNlID09IG51bGwgPyBudWxsIDogYVNvdXJjZTtcbiAgICB0aGlzLm5hbWUgPSBhTmFtZSA9PSBudWxsID8gbnVsbCA6IGFOYW1lO1xuICAgIHRoaXNbaXNTb3VyY2VOb2RlXSA9IHRydWU7XG4gICAgaWYgKGFDaHVua3MgIT0gbnVsbCkgdGhpcy5hZGQoYUNodW5rcyk7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyBhIFNvdXJjZU5vZGUgZnJvbSBnZW5lcmF0ZWQgY29kZSBhbmQgYSBTb3VyY2VNYXBDb25zdW1lci5cbiAgICpcbiAgICogQHBhcmFtIGFHZW5lcmF0ZWRDb2RlIFRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBTb3VyY2VNYXAgZm9yIHRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVJlbGF0aXZlUGF0aCBPcHRpb25hbC4gVGhlIHBhdGggdGhhdCByZWxhdGl2ZSBzb3VyY2VzIGluIHRoZVxuICAgKiAgICAgICAgU291cmNlTWFwQ29uc3VtZXIgc2hvdWxkIGJlIHJlbGF0aXZlIHRvLlxuICAgKi9cbiAgU291cmNlTm9kZS5mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcChhR2VuZXJhdGVkQ29kZSwgYVNvdXJjZU1hcENvbnN1bWVyLCBhUmVsYXRpdmVQYXRoKSB7XG4gICAgICAvLyBUaGUgU291cmNlTm9kZSB3ZSB3YW50IHRvIGZpbGwgd2l0aCB0aGUgZ2VuZXJhdGVkIGNvZGVcbiAgICAgIC8vIGFuZCB0aGUgU291cmNlTWFwXG4gICAgICB2YXIgbm9kZSA9IG5ldyBTb3VyY2VOb2RlKCk7XG5cbiAgICAgIC8vIEFsbCBldmVuIGluZGljZXMgb2YgdGhpcyBhcnJheSBhcmUgb25lIGxpbmUgb2YgdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAgICAgLy8gd2hpbGUgYWxsIG9kZCBpbmRpY2VzIGFyZSB0aGUgbmV3bGluZXMgYmV0d2VlbiB0d28gYWRqYWNlbnQgbGluZXNcbiAgICAgIC8vIChzaW5jZSBgUkVHRVhfTkVXTElORWAgY2FwdHVyZXMgaXRzIG1hdGNoKS5cbiAgICAgIC8vIFByb2Nlc3NlZCBmcmFnbWVudHMgYXJlIHJlbW92ZWQgZnJvbSB0aGlzIGFycmF5LCBieSBjYWxsaW5nIGBzaGlmdE5leHRMaW5lYC5cbiAgICAgIHZhciByZW1haW5pbmdMaW5lcyA9IGFHZW5lcmF0ZWRDb2RlLnNwbGl0KFJFR0VYX05FV0xJTkUpO1xuICAgICAgdmFyIHNoaWZ0TmV4dExpbmUgPSBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGxpbmVDb250ZW50cyA9IHJlbWFpbmluZ0xpbmVzLnNoaWZ0KCk7XG4gICAgICAgIC8vIFRoZSBsYXN0IGxpbmUgb2YgYSBmaWxlIG1pZ2h0IG5vdCBoYXZlIGEgbmV3bGluZS5cbiAgICAgICAgdmFyIG5ld0xpbmUgPSByZW1haW5pbmdMaW5lcy5zaGlmdCgpIHx8IFwiXCI7XG4gICAgICAgIHJldHVybiBsaW5lQ29udGVudHMgKyBuZXdMaW5lO1xuICAgICAgfTtcblxuICAgICAgLy8gV2UgbmVlZCB0byByZW1lbWJlciB0aGUgcG9zaXRpb24gb2YgXCJyZW1haW5pbmdMaW5lc1wiXG4gICAgICB2YXIgbGFzdEdlbmVyYXRlZExpbmUgPSAxLCBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcblxuICAgICAgLy8gVGhlIGdlbmVyYXRlIFNvdXJjZU5vZGVzIHdlIG5lZWQgYSBjb2RlIHJhbmdlLlxuICAgICAgLy8gVG8gZXh0cmFjdCBpdCBjdXJyZW50IGFuZCBsYXN0IG1hcHBpbmcgaXMgdXNlZC5cbiAgICAgIC8vIEhlcmUgd2Ugc3RvcmUgdGhlIGxhc3QgbWFwcGluZy5cbiAgICAgIHZhciBsYXN0TWFwcGluZyA9IG51bGw7XG5cbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcgIT09IG51bGwpIHtcbiAgICAgICAgICAvLyBXZSBhZGQgdGhlIGNvZGUgZnJvbSBcImxhc3RNYXBwaW5nXCIgdG8gXCJtYXBwaW5nXCI6XG4gICAgICAgICAgLy8gRmlyc3QgY2hlY2sgaWYgdGhlcmUgaXMgYSBuZXcgbGluZSBpbiBiZXR3ZWVuLlxuICAgICAgICAgIGlmIChsYXN0R2VuZXJhdGVkTGluZSA8IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgdmFyIGNvZGUgPSBcIlwiO1xuICAgICAgICAgICAgLy8gQXNzb2NpYXRlIGZpcnN0IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICAgIGFkZE1hcHBpbmdXaXRoQ29kZShsYXN0TWFwcGluZywgc2hpZnROZXh0TGluZSgpKTtcbiAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgICAgIC8vIFRoZSByZW1haW5pbmcgY29kZSBpcyBhZGRlZCB3aXRob3V0IG1hcHBpbmdcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gVGhlcmUgaXMgbm8gbmV3IGxpbmUgaW4gYmV0d2Vlbi5cbiAgICAgICAgICAgIC8vIEFzc29jaWF0ZSB0aGUgY29kZSBiZXR3ZWVuIFwibGFzdEdlbmVyYXRlZENvbHVtblwiIGFuZFxuICAgICAgICAgICAgLy8gXCJtYXBwaW5nLmdlbmVyYXRlZENvbHVtblwiIHdpdGggXCJsYXN0TWFwcGluZ1wiXG4gICAgICAgICAgICB2YXIgbmV4dExpbmUgPSByZW1haW5pbmdMaW5lc1swXTtcbiAgICAgICAgICAgIHZhciBjb2RlID0gbmV4dExpbmUuc3Vic3RyKDAsIG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICAgICAgcmVtYWluaW5nTGluZXNbMF0gPSBuZXh0TGluZS5zdWJzdHIobWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdEdlbmVyYXRlZENvbHVtbik7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIGNvZGUpO1xuICAgICAgICAgICAgLy8gTm8gbW9yZSByZW1haW5pbmcgY29kZSwgY29udGludWVcbiAgICAgICAgICAgIGxhc3RNYXBwaW5nID0gbWFwcGluZztcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gV2UgYWRkIHRoZSBnZW5lcmF0ZWQgY29kZSB1bnRpbCB0aGUgZmlyc3QgbWFwcGluZ1xuICAgICAgICAvLyB0byB0aGUgU291cmNlTm9kZSB3aXRob3V0IGFueSBtYXBwaW5nLlxuICAgICAgICAvLyBFYWNoIGxpbmUgaXMgYWRkZWQgYXMgc2VwYXJhdGUgc3RyaW5nLlxuICAgICAgICB3aGlsZSAobGFzdEdlbmVyYXRlZExpbmUgPCBtYXBwaW5nLmdlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICBub2RlLmFkZChzaGlmdE5leHRMaW5lKCkpO1xuICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxhc3RHZW5lcmF0ZWRDb2x1bW4gPCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikge1xuICAgICAgICAgIHZhciBuZXh0TGluZSA9IHJlbWFpbmluZ0xpbmVzWzBdO1xuICAgICAgICAgIG5vZGUuYWRkKG5leHRMaW5lLnN1YnN0cigwLCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikpO1xuICAgICAgICAgIHJlbWFpbmluZ0xpbmVzWzBdID0gbmV4dExpbmUuc3Vic3RyKG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgIH1cbiAgICAgICAgbGFzdE1hcHBpbmcgPSBtYXBwaW5nO1xuICAgICAgfSwgdGhpcyk7XG4gICAgICAvLyBXZSBoYXZlIHByb2Nlc3NlZCBhbGwgbWFwcGluZ3MuXG4gICAgICBpZiAocmVtYWluaW5nTGluZXMubGVuZ3RoID4gMCkge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcpIHtcbiAgICAgICAgICAvLyBBc3NvY2lhdGUgdGhlIHJlbWFpbmluZyBjb2RlIGluIHRoZSBjdXJyZW50IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIHNoaWZ0TmV4dExpbmUoKSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gYW5kIGFkZCB0aGUgcmVtYWluaW5nIGxpbmVzIHdpdGhvdXQgYW55IG1hcHBpbmdcbiAgICAgICAgbm9kZS5hZGQocmVtYWluaW5nTGluZXMuam9pbihcIlwiKSk7XG4gICAgICB9XG5cbiAgICAgIC8vIENvcHkgc291cmNlc0NvbnRlbnQgaW50byBTb3VyY2VOb2RlXG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGZ1bmN0aW9uIChzb3VyY2VGaWxlKSB7XG4gICAgICAgIHZhciBjb250ZW50ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlRmlsZSk7XG4gICAgICAgIGlmIChjb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgICBpZiAoYVJlbGF0aXZlUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5qb2luKGFSZWxhdGl2ZVBhdGgsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBub2RlLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4gbm9kZTtcblxuICAgICAgZnVuY3Rpb24gYWRkTWFwcGluZ1dpdGhDb2RlKG1hcHBpbmcsIGNvZGUpIHtcbiAgICAgICAgaWYgKG1hcHBpbmcgPT09IG51bGwgfHwgbWFwcGluZy5zb3VyY2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIG5vZGUuYWRkKGNvZGUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhciBzb3VyY2UgPSBhUmVsYXRpdmVQYXRoXG4gICAgICAgICAgICA/IHV0aWwuam9pbihhUmVsYXRpdmVQYXRoLCBtYXBwaW5nLnNvdXJjZSlcbiAgICAgICAgICAgIDogbWFwcGluZy5zb3VyY2U7XG4gICAgICAgICAgbm9kZS5hZGQobmV3IFNvdXJjZU5vZGUobWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGlzIHNvdXJjZSBub2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUNodW5rIEEgc3RyaW5nIHNuaXBwZXQgb2YgZ2VuZXJhdGVkIEpTIGNvZGUsIGFub3RoZXIgaW5zdGFuY2Ugb2ZcbiAgICogICAgICAgIFNvdXJjZU5vZGUsIG9yIGFuIGFycmF5IHdoZXJlIGVhY2ggbWVtYmVyIGlzIG9uZSBvZiB0aG9zZSB0aGluZ3MuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX2FkZChhQ2h1bmspIHtcbiAgICBpZiAoQXJyYXkuaXNBcnJheShhQ2h1bmspKSB7XG4gICAgICBhQ2h1bmsuZm9yRWFjaChmdW5jdGlvbiAoY2h1bmspIHtcbiAgICAgICAgdGhpcy5hZGQoY2h1bmspO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIGlmIChhQ2h1bmspIHtcbiAgICAgICAgdGhpcy5jaGlsZHJlbi5wdXNoKGFDaHVuayk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgXCJFeHBlY3RlZCBhIFNvdXJjZU5vZGUsIHN0cmluZywgb3IgYW4gYXJyYXkgb2YgU291cmNlTm9kZXMgYW5kIHN0cmluZ3MuIEdvdCBcIiArIGFDaHVua1xuICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGUgYmVnaW5uaW5nIG9mIHRoaXMgc291cmNlIG5vZGUuXG4gICAqXG4gICAqIEBwYXJhbSBhQ2h1bmsgQSBzdHJpbmcgc25pcHBldCBvZiBnZW5lcmF0ZWQgSlMgY29kZSwgYW5vdGhlciBpbnN0YW5jZSBvZlxuICAgKiAgICAgICAgU291cmNlTm9kZSwgb3IgYW4gYXJyYXkgd2hlcmUgZWFjaCBtZW1iZXIgaXMgb25lIG9mIHRob3NlIHRoaW5ncy5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnByZXBlbmQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX3ByZXBlbmQoYUNodW5rKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkoYUNodW5rKSkge1xuICAgICAgZm9yICh2YXIgaSA9IGFDaHVuay5sZW5ndGgtMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgICAgdGhpcy5wcmVwZW5kKGFDaHVua1tpXSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4udW5zaGlmdChhQ2h1bmspO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXG4gICAgICAgIFwiRXhwZWN0ZWQgYSBTb3VyY2VOb2RlLCBzdHJpbmcsIG9yIGFuIGFycmF5IG9mIFNvdXJjZU5vZGVzIGFuZCBzdHJpbmdzLiBHb3QgXCIgKyBhQ2h1bmtcbiAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgSlMgc25pcHBldHMgaW4gdGhpcyBub2RlIGFuZCBpdHMgY2hpbGRyZW4uIFRoZVxuICAgKiB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBvbmNlIGZvciBlYWNoIHNuaXBwZXQgb2YgSlMgYW5kIGlzIHBhc3NlZCB0aGF0XG4gICAqIHNuaXBwZXQgYW5kIHRoZSBpdHMgb3JpZ2luYWwgYXNzb2NpYXRlZCBzb3VyY2UncyBsaW5lL2NvbHVtbiBsb2NhdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2FsayA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfd2FsayhhRm4pIHtcbiAgICB2YXIgY2h1bms7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGNodW5rID0gdGhpcy5jaGlsZHJlbltpXTtcbiAgICAgIGlmIChjaHVua1tpc1NvdXJjZU5vZGVdKSB7XG4gICAgICAgIGNodW5rLndhbGsoYUZuKTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICBpZiAoY2h1bmsgIT09ICcnKSB7XG4gICAgICAgICAgYUZuKGNodW5rLCB7IHNvdXJjZTogdGhpcy5zb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgIGxpbmU6IHRoaXMubGluZSxcbiAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uOiB0aGlzLmNvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogdGhpcy5uYW1lIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBMaWtlIGBTdHJpbmcucHJvdG90eXBlLmpvaW5gIGV4Y2VwdCBmb3IgU291cmNlTm9kZXMuIEluc2VydHMgYGFTdHJgIGJldHdlZW5cbiAgICogZWFjaCBvZiBgdGhpcy5jaGlsZHJlbmAuXG4gICAqXG4gICAqIEBwYXJhbSBhU2VwIFRoZSBzZXBhcmF0b3IuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5qb2luID0gZnVuY3Rpb24gU291cmNlTm9kZV9qb2luKGFTZXApIHtcbiAgICB2YXIgbmV3Q2hpbGRyZW47XG4gICAgdmFyIGk7XG4gICAgdmFyIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoO1xuICAgIGlmIChsZW4gPiAwKSB7XG4gICAgICBuZXdDaGlsZHJlbiA9IFtdO1xuICAgICAgZm9yIChpID0gMDsgaSA8IGxlbi0xOyBpKyspIHtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaCh0aGlzLmNoaWxkcmVuW2ldKTtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaChhU2VwKTtcbiAgICAgIH1cbiAgICAgIG5ld0NoaWxkcmVuLnB1c2godGhpcy5jaGlsZHJlbltpXSk7XG4gICAgICB0aGlzLmNoaWxkcmVuID0gbmV3Q2hpbGRyZW47XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBDYWxsIFN0cmluZy5wcm90b3R5cGUucmVwbGFjZSBvbiB0aGUgdmVyeSByaWdodC1tb3N0IHNvdXJjZSBzbmlwcGV0LiBVc2VmdWxcbiAgICogZm9yIHRyaW1taW5nIHdoaXRlc3BhY2UgZnJvbSB0aGUgZW5kIG9mIGEgc291cmNlIG5vZGUsIGV0Yy5cbiAgICpcbiAgICogQHBhcmFtIGFQYXR0ZXJuIFRoZSBwYXR0ZXJuIHRvIHJlcGxhY2UuXG4gICAqIEBwYXJhbSBhUmVwbGFjZW1lbnQgVGhlIHRoaW5nIHRvIHJlcGxhY2UgdGhlIHBhdHRlcm4gd2l0aC5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnJlcGxhY2VSaWdodCA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfcmVwbGFjZVJpZ2h0KGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpIHtcbiAgICB2YXIgbGFzdENoaWxkID0gdGhpcy5jaGlsZHJlblt0aGlzLmNoaWxkcmVuLmxlbmd0aCAtIDFdO1xuICAgIGlmIChsYXN0Q2hpbGRbaXNTb3VyY2VOb2RlXSkge1xuICAgICAgbGFzdENoaWxkLnJlcGxhY2VSaWdodChhUGF0dGVybiwgYVJlcGxhY2VtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAodHlwZW9mIGxhc3RDaGlsZCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW5bdGhpcy5jaGlsZHJlbi5sZW5ndGggLSAxXSA9IGxhc3RDaGlsZC5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4ucHVzaCgnJy5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGEgc291cmNlIGZpbGUuIFRoaXMgd2lsbCBiZSBhZGRlZCB0byB0aGUgU291cmNlTWFwR2VuZXJhdG9yXG4gICAqIGluIHRoZSBzb3VyY2VzQ29udGVudCBmaWVsZC5cbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VGaWxlIFRoZSBmaWxlbmFtZSBvZiB0aGUgc291cmNlIGZpbGVcbiAgICogQHBhcmFtIGFTb3VyY2VDb250ZW50IFRoZSBjb250ZW50IG9mIHRoZSBzb3VyY2UgZmlsZVxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUuc2V0U291cmNlQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9zZXRTb3VyY2VDb250ZW50KGFTb3VyY2VGaWxlLCBhU291cmNlQ29udGVudCkge1xuICAgICAgdGhpcy5zb3VyY2VDb250ZW50c1t1dGlsLnRvU2V0U3RyaW5nKGFTb3VyY2VGaWxlKV0gPSBhU291cmNlQ29udGVudDtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgU291cmNlTm9kZXMuIFRoZSB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBmb3IgZWFjaFxuICAgKiBzb3VyY2UgZmlsZSBjb250ZW50IGFuZCBpcyBwYXNzZWQgdGhlIGZpbGVuYW1lIGFuZCBzb3VyY2UgY29udGVudC5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2Fsa1NvdXJjZUNvbnRlbnRzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VOb2RlX3dhbGtTb3VyY2VDb250ZW50cyhhRm4pIHtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSB0aGlzLmNoaWxkcmVuLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGlmICh0aGlzLmNoaWxkcmVuW2ldW2lzU291cmNlTm9kZV0pIHtcbiAgICAgICAgICB0aGlzLmNoaWxkcmVuW2ldLndhbGtTb3VyY2VDb250ZW50cyhhRm4pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBzb3VyY2VzID0gT2JqZWN0LmtleXModGhpcy5zb3VyY2VDb250ZW50cyk7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gc291cmNlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBhRm4odXRpbC5mcm9tU2V0U3RyaW5nKHNvdXJjZXNbaV0pLCB0aGlzLnNvdXJjZUNvbnRlbnRzW3NvdXJjZXNbaV1dKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlLiBXYWxrcyBvdmVyIHRoZSB0cmVlXG4gICAqIGFuZCBjb25jYXRlbmF0ZXMgYWxsIHRoZSB2YXJpb3VzIHNuaXBwZXRzIHRvZ2V0aGVyIHRvIG9uZSBzdHJpbmcuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS50b1N0cmluZyA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfdG9TdHJpbmcoKSB7XG4gICAgdmFyIHN0ciA9IFwiXCI7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaykge1xuICAgICAgc3RyICs9IGNodW5rO1xuICAgIH0pO1xuICAgIHJldHVybiBzdHI7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlIGFsb25nIHdpdGggYSBzb3VyY2VcbiAgICogbWFwLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUudG9TdHJpbmdXaXRoU291cmNlTWFwID0gZnVuY3Rpb24gU291cmNlTm9kZV90b1N0cmluZ1dpdGhTb3VyY2VNYXAoYUFyZ3MpIHtcbiAgICB2YXIgZ2VuZXJhdGVkID0ge1xuICAgICAgY29kZTogXCJcIixcbiAgICAgIGxpbmU6IDEsXG4gICAgICBjb2x1bW46IDBcbiAgICB9O1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKGFBcmdzKTtcbiAgICB2YXIgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgIHZhciBsYXN0T3JpZ2luYWxTb3VyY2UgPSBudWxsO1xuICAgIHZhciBsYXN0T3JpZ2luYWxMaW5lID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsQ29sdW1uID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsTmFtZSA9IG51bGw7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaywgb3JpZ2luYWwpIHtcbiAgICAgIGdlbmVyYXRlZC5jb2RlICs9IGNodW5rO1xuICAgICAgaWYgKG9yaWdpbmFsLnNvdXJjZSAhPT0gbnVsbFxuICAgICAgICAgICYmIG9yaWdpbmFsLmxpbmUgIT09IG51bGxcbiAgICAgICAgICAmJiBvcmlnaW5hbC5jb2x1bW4gIT09IG51bGwpIHtcbiAgICAgICAgaWYobGFzdE9yaWdpbmFsU291cmNlICE9PSBvcmlnaW5hbC5zb3VyY2VcbiAgICAgICAgICAgfHwgbGFzdE9yaWdpbmFsTGluZSAhPT0gb3JpZ2luYWwubGluZVxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxDb2x1bW4gIT09IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxOYW1lICE9PSBvcmlnaW5hbC5uYW1lKSB7XG4gICAgICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICAgICAgc291cmNlOiBvcmlnaW5hbC5zb3VyY2UsXG4gICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICBsaW5lOiBvcmlnaW5hbC5saW5lLFxuICAgICAgICAgICAgICBjb2x1bW46IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICAgICAgICBsaW5lOiBnZW5lcmF0ZWQubGluZSxcbiAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbmFtZTogb3JpZ2luYWwubmFtZVxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG9yaWdpbmFsLnNvdXJjZTtcbiAgICAgICAgbGFzdE9yaWdpbmFsTGluZSA9IG9yaWdpbmFsLmxpbmU7XG4gICAgICAgIGxhc3RPcmlnaW5hbENvbHVtbiA9IG9yaWdpbmFsLmNvbHVtbjtcbiAgICAgICAgbGFzdE9yaWdpbmFsTmFtZSA9IG9yaWdpbmFsLm5hbWU7XG4gICAgICAgIHNvdXJjZU1hcHBpbmdBY3RpdmUgPSB0cnVlO1xuICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IHtcbiAgICAgICAgICAgIGxpbmU6IGdlbmVyYXRlZC5saW5lLFxuICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgbGFzdE9yaWdpbmFsU291cmNlID0gbnVsbDtcbiAgICAgICAgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgICAgfVxuICAgICAgZm9yICh2YXIgaWR4ID0gMCwgbGVuZ3RoID0gY2h1bmsubGVuZ3RoOyBpZHggPCBsZW5ndGg7IGlkeCsrKSB7XG4gICAgICAgIGlmIChjaHVuay5jaGFyQ29kZUF0KGlkeCkgPT09IE5FV0xJTkVfQ09ERSkge1xuICAgICAgICAgIGdlbmVyYXRlZC5saW5lKys7XG4gICAgICAgICAgZ2VuZXJhdGVkLmNvbHVtbiA9IDA7XG4gICAgICAgICAgLy8gTWFwcGluZ3MgZW5kIGF0IGVvbFxuICAgICAgICAgIGlmIChpZHggKyAxID09PSBsZW5ndGgpIHtcbiAgICAgICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG51bGw7XG4gICAgICAgICAgICBzb3VyY2VNYXBwaW5nQWN0aXZlID0gZmFsc2U7XG4gICAgICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgICAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICAgICAgICAgIHNvdXJjZTogb3JpZ2luYWwuc291cmNlLFxuICAgICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICAgIGxpbmU6IG9yaWdpbmFsLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBvcmlnaW5hbC5jb2x1bW5cbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgZ2VuZXJhdGVkOiB7XG4gICAgICAgICAgICAgICAgbGluZTogZ2VuZXJhdGVkLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIG5hbWU6IG9yaWdpbmFsLm5hbWVcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBnZW5lcmF0ZWQuY29sdW1uKys7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgICB0aGlzLndhbGtTb3VyY2VDb250ZW50cyhmdW5jdGlvbiAoc291cmNlRmlsZSwgc291cmNlQ29udGVudCkge1xuICAgICAgbWFwLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgc291cmNlQ29udGVudCk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4geyBjb2RlOiBnZW5lcmF0ZWQuY29kZSwgbWFwOiBtYXAgfTtcbiAgfTtcblxuICBleHBvcnRzLlNvdXJjZU5vZGUgPSBTb3VyY2VOb2RlO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2Utbm9kZS5qc1xuICoqIG1vZHVsZSBpZCA9IDExXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_array_set.js b/devtools/shared/sourcemap/tests/unit/test_array_set.js
new file mode 100644
index 000000000..c12d3ba49
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_array_set.js
@@ -0,0 +1,683 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var ArraySet = __webpack_require__(1).ArraySet;
+
+ function makeTestSet() {
+ var set = new ArraySet();
+ for (var i = 0; i < 100; i++) {
+ set.add(String(i));
+ }
+ return set;
+ }
+
+ exports['test .has() membership'] = function (assert) {
+ var set = makeTestSet();
+ for (var i = 0; i < 100; i++) {
+ assert.ok(set.has(String(i)));
+ }
+ };
+
+ exports['test .indexOf() elements'] = function (assert) {
+ var set = makeTestSet();
+ for (var i = 0; i < 100; i++) {
+ assert.strictEqual(set.indexOf(String(i)), i);
+ }
+ };
+
+ exports['test .at() indexing'] = function (assert) {
+ var set = makeTestSet();
+ for (var i = 0; i < 100; i++) {
+ assert.strictEqual(set.at(i), String(i));
+ }
+ };
+
+ exports['test creating from an array'] = function (assert) {
+ var set = ArraySet.fromArray(['foo', 'bar', 'baz', 'quux', 'hasOwnProperty']);
+
+ assert.ok(set.has('foo'));
+ assert.ok(set.has('bar'));
+ assert.ok(set.has('baz'));
+ assert.ok(set.has('quux'));
+ assert.ok(set.has('hasOwnProperty'));
+
+ assert.strictEqual(set.indexOf('foo'), 0);
+ assert.strictEqual(set.indexOf('bar'), 1);
+ assert.strictEqual(set.indexOf('baz'), 2);
+ assert.strictEqual(set.indexOf('quux'), 3);
+
+ assert.strictEqual(set.at(0), 'foo');
+ assert.strictEqual(set.at(1), 'bar');
+ assert.strictEqual(set.at(2), 'baz');
+ assert.strictEqual(set.at(3), 'quux');
+ };
+
+ exports['test that you can add __proto__; see github issue #30'] = function (assert) {
+ var set = new ArraySet();
+ set.add('__proto__');
+ assert.ok(set.has('__proto__'));
+ assert.strictEqual(set.at(0), '__proto__');
+ assert.strictEqual(set.indexOf('__proto__'), 0);
+ };
+
+ exports['test .fromArray() with duplicates'] = function (assert) {
+ var set = ArraySet.fromArray(['foo', 'foo']);
+ assert.ok(set.has('foo'));
+ assert.strictEqual(set.at(0), 'foo');
+ assert.strictEqual(set.indexOf('foo'), 0);
+ assert.strictEqual(set.toArray().length, 1);
+
+ set = ArraySet.fromArray(['foo', 'foo'], true);
+ assert.ok(set.has('foo'));
+ assert.strictEqual(set.at(0), 'foo');
+ assert.strictEqual(set.at(1), 'foo');
+ assert.strictEqual(set.indexOf('foo'), 0);
+ assert.strictEqual(set.toArray().length, 2);
+ };
+
+ exports['test .add() with duplicates'] = function (assert) {
+ var set = new ArraySet();
+ set.add('foo');
+
+ set.add('foo');
+ assert.ok(set.has('foo'));
+ assert.strictEqual(set.at(0), 'foo');
+ assert.strictEqual(set.indexOf('foo'), 0);
+ assert.strictEqual(set.toArray().length, 1);
+
+ set.add('foo', true);
+ assert.ok(set.has('foo'));
+ assert.strictEqual(set.at(0), 'foo');
+ assert.strictEqual(set.at(1), 'foo');
+ assert.strictEqual(set.indexOf('foo'), 0);
+ assert.strictEqual(set.toArray().length, 2);
+ };
+
+ exports['test .size()'] = function (assert) {
+ var set = new ArraySet();
+ set.add('foo');
+ set.add('bar');
+ set.add('baz');
+ assert.strictEqual(set.size(), 3);
+ };
+
+ exports['test .size() with disallowed duplicates'] = function (assert) {
+ var set = new ArraySet();
+
+ set.add('foo');
+ set.add('foo');
+
+ set.add('bar');
+ set.add('bar');
+
+ set.add('baz');
+ set.add('baz');
+
+ assert.strictEqual(set.size(), 3);
+ };
+
+ exports['test .size() with allowed duplicates'] = function (assert) {
+ var set = new ArraySet();
+
+ set.add('foo');
+ set.add('foo', true);
+
+ set.add('bar');
+ set.add('bar', true);
+
+ set.add('baz');
+ set.add('baz', true);
+
+ assert.strictEqual(set.size(), 3);
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgZjFlMTE4YWJkNTdmZDlhZDAzNTIiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWFycmF5LXNldC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYXJyYXktc2V0LmpzIiwid2VicGFjazovLy8uL2xpYi91dGlsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsdUJBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQ3RDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxvQkFBbUIsU0FBUztBQUM1QjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0Esb0JBQW1CLFNBQVM7QUFDNUI7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxvQkFBbUIsU0FBUztBQUM1QjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLG9CQUFtQixTQUFTO0FBQzVCO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSw0Q0FBMkM7QUFDM0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7Ozs7Ozs7QUN4SUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDLFNBQVM7QUFDakQ7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN2R0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGlEQUFnRCxRQUFRO0FBQ3hEO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJ0ZXN0X2FycmF5X3NldC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgZjFlMTE4YWJkNTdmZDlhZDAzNTJcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBBcnJheVNldCA9IHJlcXVpcmUoJy4uL2xpYi9hcnJheS1zZXQnKS5BcnJheVNldDtcblxuICBmdW5jdGlvbiBtYWtlVGVzdFNldCgpIHtcbiAgICB2YXIgc2V0ID0gbmV3IEFycmF5U2V0KCk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCAxMDA7IGkrKykge1xuICAgICAgc2V0LmFkZChTdHJpbmcoaSkpO1xuICAgIH1cbiAgICByZXR1cm4gc2V0O1xuICB9XG5cbiAgZXhwb3J0c1sndGVzdCAuaGFzKCkgbWVtYmVyc2hpcCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBzZXQgPSBtYWtlVGVzdFNldCgpO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgMTAwOyBpKyspIHtcbiAgICAgIGFzc2VydC5vayhzZXQuaGFzKFN0cmluZyhpKSkpO1xuICAgIH1cbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IC5pbmRleE9mKCkgZWxlbWVudHMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc2V0ID0gbWFrZVRlc3RTZXQoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IDEwMDsgaSsrKSB7XG4gICAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmluZGV4T2YoU3RyaW5nKGkpKSwgaSk7XG4gICAgfVxuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLmF0KCkgaW5kZXhpbmcnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc2V0ID0gbWFrZVRlc3RTZXQoKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IDEwMDsgaSsrKSB7XG4gICAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KGkpLCBTdHJpbmcoaSkpO1xuICAgIH1cbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGNyZWF0aW5nIGZyb20gYW4gYXJyYXknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc2V0ID0gQXJyYXlTZXQuZnJvbUFycmF5KFsnZm9vJywgJ2JhcicsICdiYXonLCAncXV1eCcsICdoYXNPd25Qcm9wZXJ0eSddKTtcblxuICAgIGFzc2VydC5vayhzZXQuaGFzKCdmb28nKSk7XG4gICAgYXNzZXJ0Lm9rKHNldC5oYXMoJ2JhcicpKTtcbiAgICBhc3NlcnQub2soc2V0LmhhcygnYmF6JykpO1xuICAgIGFzc2VydC5vayhzZXQuaGFzKCdxdXV4JykpO1xuICAgIGFzc2VydC5vayhzZXQuaGFzKCdoYXNPd25Qcm9wZXJ0eScpKTtcblxuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuaW5kZXhPZignZm9vJyksIDApO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuaW5kZXhPZignYmFyJyksIDEpO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuaW5kZXhPZignYmF6JyksIDIpO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuaW5kZXhPZigncXV1eCcpLCAzKTtcblxuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuYXQoMCksICdmb28nKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KDEpLCAnYmFyJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5hdCgyKSwgJ2JheicpO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuYXQoMyksICdxdXV4Jyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHlvdSBjYW4gYWRkIF9fcHJvdG9fXzsgc2VlIGdpdGh1YiBpc3N1ZSAjMzAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc2V0ID0gbmV3IEFycmF5U2V0KCk7XG4gICAgc2V0LmFkZCgnX19wcm90b19fJyk7XG4gICAgYXNzZXJ0Lm9rKHNldC5oYXMoJ19fcHJvdG9fXycpKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KDApLCAnX19wcm90b19fJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5pbmRleE9mKCdfX3Byb3RvX18nKSwgMCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAuZnJvbUFycmF5KCkgd2l0aCBkdXBsaWNhdGVzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHNldCA9IEFycmF5U2V0LmZyb21BcnJheShbJ2ZvbycsICdmb28nXSk7XG4gICAgYXNzZXJ0Lm9rKHNldC5oYXMoJ2ZvbycpKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KDApLCAnZm9vJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5pbmRleE9mKCdmb28nKSwgMCk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC50b0FycmF5KCkubGVuZ3RoLCAxKTtcblxuICAgIHNldCA9IEFycmF5U2V0LmZyb21BcnJheShbJ2ZvbycsICdmb28nXSwgdHJ1ZSk7XG4gICAgYXNzZXJ0Lm9rKHNldC5oYXMoJ2ZvbycpKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KDApLCAnZm9vJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5hdCgxKSwgJ2ZvbycpO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuaW5kZXhPZignZm9vJyksIDApO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQudG9BcnJheSgpLmxlbmd0aCwgMik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAuYWRkKCkgd2l0aCBkdXBsaWNhdGVzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHNldCA9IG5ldyBBcnJheVNldCgpO1xuICAgIHNldC5hZGQoJ2ZvbycpO1xuXG4gICAgc2V0LmFkZCgnZm9vJyk7XG4gICAgYXNzZXJ0Lm9rKHNldC5oYXMoJ2ZvbycpKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmF0KDApLCAnZm9vJyk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5pbmRleE9mKCdmb28nKSwgMCk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC50b0FycmF5KCkubGVuZ3RoLCAxKTtcblxuICAgIHNldC5hZGQoJ2ZvbycsIHRydWUpO1xuICAgIGFzc2VydC5vayhzZXQuaGFzKCdmb28nKSk7XG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5hdCgwKSwgJ2ZvbycpO1xuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuYXQoMSksICdmb28nKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LmluZGV4T2YoJ2ZvbycpLCAwKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LnRvQXJyYXkoKS5sZW5ndGgsIDIpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLnNpemUoKSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBzZXQgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICBzZXQuYWRkKCdmb28nKTtcbiAgICBzZXQuYWRkKCdiYXInKTtcbiAgICBzZXQuYWRkKCdiYXonKTtcbiAgICBhc3NlcnQuc3RyaWN0RXF1YWwoc2V0LnNpemUoKSwgMyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAuc2l6ZSgpIHdpdGggZGlzYWxsb3dlZCBkdXBsaWNhdGVzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHNldCA9IG5ldyBBcnJheVNldCgpO1xuXG4gICAgc2V0LmFkZCgnZm9vJyk7XG4gICAgc2V0LmFkZCgnZm9vJyk7XG5cbiAgICBzZXQuYWRkKCdiYXInKTtcbiAgICBzZXQuYWRkKCdiYXInKTtcblxuICAgIHNldC5hZGQoJ2JheicpO1xuICAgIHNldC5hZGQoJ2JheicpO1xuXG4gICAgYXNzZXJ0LnN0cmljdEVxdWFsKHNldC5zaXplKCksIDMpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLnNpemUoKSB3aXRoIGFsbG93ZWQgZHVwbGljYXRlcyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBzZXQgPSBuZXcgQXJyYXlTZXQoKTtcblxuICAgIHNldC5hZGQoJ2ZvbycpO1xuICAgIHNldC5hZGQoJ2ZvbycsIHRydWUpO1xuXG4gICAgc2V0LmFkZCgnYmFyJyk7XG4gICAgc2V0LmFkZCgnYmFyJywgdHJ1ZSk7XG5cbiAgICBzZXQuYWRkKCdiYXonKTtcbiAgICBzZXQuYWRkKCdiYXonLCB0cnVlKTtcblxuICAgIGFzc2VydC5zdHJpY3RFcXVhbChzZXQuc2l6ZSgpLCAzKTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi90ZXN0L3Rlc3QtYXJyYXktc2V0LmpzXG4gKiogbW9kdWxlIGlkID0gMFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgdXRpbCA9IHJlcXVpcmUoJy4vdXRpbCcpO1xuXG4gIC8qKlxuICAgKiBBIGRhdGEgc3RydWN0dXJlIHdoaWNoIGlzIGEgY29tYmluYXRpb24gb2YgYW4gYXJyYXkgYW5kIGEgc2V0LiBBZGRpbmcgYSBuZXdcbiAgICogbWVtYmVyIGlzIE8oMSksIHRlc3RpbmcgZm9yIG1lbWJlcnNoaXAgaXMgTygxKSwgYW5kIGZpbmRpbmcgdGhlIGluZGV4IG9mIGFuXG4gICAqIGVsZW1lbnQgaXMgTygxKS4gUmVtb3ZpbmcgZWxlbWVudHMgZnJvbSB0aGUgc2V0IGlzIG5vdCBzdXBwb3J0ZWQuIE9ubHlcbiAgICogc3RyaW5ncyBhcmUgc3VwcG9ydGVkIGZvciBtZW1iZXJzaGlwLlxuICAgKi9cbiAgZnVuY3Rpb24gQXJyYXlTZXQoKSB7XG4gICAgdGhpcy5fYXJyYXkgPSBbXTtcbiAgICB0aGlzLl9zZXQgPSB7fTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGF0aWMgbWV0aG9kIGZvciBjcmVhdGluZyBBcnJheVNldCBpbnN0YW5jZXMgZnJvbSBhbiBleGlzdGluZyBhcnJheS5cbiAgICovXG4gIEFycmF5U2V0LmZyb21BcnJheSA9IGZ1bmN0aW9uIEFycmF5U2V0X2Zyb21BcnJheShhQXJyYXksIGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICB2YXIgc2V0ID0gbmV3IEFycmF5U2V0KCk7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGFBcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgc2V0LmFkZChhQXJyYXlbaV0sIGFBbGxvd0R1cGxpY2F0ZXMpO1xuICAgIH1cbiAgICByZXR1cm4gc2V0O1xuICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gaG93IG1hbnkgdW5pcXVlIGl0ZW1zIGFyZSBpbiB0aGlzIEFycmF5U2V0LiBJZiBkdXBsaWNhdGVzIGhhdmUgYmVlblxuICAgKiBhZGRlZCwgdGhhbiB0aG9zZSBkbyBub3QgY291bnQgdG93YXJkcyB0aGUgc2l6ZS5cbiAgICpcbiAgICogQHJldHVybnMgTnVtYmVyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuc2l6ZSA9IGZ1bmN0aW9uIEFycmF5U2V0X3NpemUoKSB7XG4gICAgcmV0dXJuIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKHRoaXMuX3NldCkubGVuZ3RoO1xuICB9O1xuXG4gIC8qKlxuICAgKiBBZGQgdGhlIGdpdmVuIHN0cmluZyB0byB0aGlzIHNldC5cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuYWRkID0gZnVuY3Rpb24gQXJyYXlTZXRfYWRkKGFTdHIsIGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgdmFyIGlzRHVwbGljYXRlID0gdGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpO1xuICAgIHZhciBpZHggPSB0aGlzLl9hcnJheS5sZW5ndGg7XG4gICAgaWYgKCFpc0R1cGxpY2F0ZSB8fCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgICB0aGlzLl9hcnJheS5wdXNoKGFTdHIpO1xuICAgIH1cbiAgICBpZiAoIWlzRHVwbGljYXRlKSB7XG4gICAgICB0aGlzLl9zZXRbc1N0cl0gPSBpZHg7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBJcyB0aGUgZ2l2ZW4gc3RyaW5nIGEgbWVtYmVyIG9mIHRoaXMgc2V0P1xuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5oYXMgPSBmdW5jdGlvbiBBcnJheVNldF9oYXMoYVN0cikge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICByZXR1cm4gdGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXaGF0IGlzIHRoZSBpbmRleCBvZiB0aGUgZ2l2ZW4gc3RyaW5nIGluIHRoZSBhcnJheT9cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuaW5kZXhPZiA9IGZ1bmN0aW9uIEFycmF5U2V0X2luZGV4T2YoYVN0cikge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICBpZiAodGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc2V0W3NTdHJdO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTdHIgKyAnXCIgaXMgbm90IGluIHRoZSBzZXQuJyk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFdoYXQgaXMgdGhlIGVsZW1lbnQgYXQgdGhlIGdpdmVuIGluZGV4P1xuICAgKlxuICAgKiBAcGFyYW0gTnVtYmVyIGFJZHhcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5hdCA9IGZ1bmN0aW9uIEFycmF5U2V0X2F0KGFJZHgpIHtcbiAgICBpZiAoYUlkeCA+PSAwICYmIGFJZHggPCB0aGlzLl9hcnJheS5sZW5ndGgpIHtcbiAgICAgIHJldHVybiB0aGlzLl9hcnJheVthSWR4XTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBlbGVtZW50IGluZGV4ZWQgYnkgJyArIGFJZHgpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBhcnJheSByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNldCAod2hpY2ggaGFzIHRoZSBwcm9wZXIgaW5kaWNlc1xuICAgKiBpbmRpY2F0ZWQgYnkgaW5kZXhPZikuIE5vdGUgdGhhdCB0aGlzIGlzIGEgY29weSBvZiB0aGUgaW50ZXJuYWwgYXJyYXkgdXNlZFxuICAgKiBmb3Igc3RvcmluZyB0aGUgbWVtYmVycyBzbyB0aGF0IG5vIG9uZSBjYW4gbWVzcyB3aXRoIGludGVybmFsIHN0YXRlLlxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLnRvQXJyYXkgPSBmdW5jdGlvbiBBcnJheVNldF90b0FycmF5KCkge1xuICAgIHJldHVybiB0aGlzLl9hcnJheS5zbGljZSgpO1xuICB9O1xuXG4gIGV4cG9ydHMuQXJyYXlTZXQgPSBBcnJheVNldDtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYXJyYXktc2V0LmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICAvKipcbiAgICogVGhpcyBpcyBhIGhlbHBlciBmdW5jdGlvbiBmb3IgZ2V0dGluZyB2YWx1ZXMgZnJvbSBwYXJhbWV0ZXIvb3B0aW9uc1xuICAgKiBvYmplY3RzLlxuICAgKlxuICAgKiBAcGFyYW0gYXJncyBUaGUgb2JqZWN0IHdlIGFyZSBleHRyYWN0aW5nIHZhbHVlcyBmcm9tXG4gICAqIEBwYXJhbSBuYW1lIFRoZSBuYW1lIG9mIHRoZSBwcm9wZXJ0eSB3ZSBhcmUgZ2V0dGluZy5cbiAgICogQHBhcmFtIGRlZmF1bHRWYWx1ZSBBbiBvcHRpb25hbCB2YWx1ZSB0byByZXR1cm4gaWYgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmdcbiAgICogZnJvbSB0aGUgb2JqZWN0LiBJZiB0aGlzIGlzIG5vdCBzcGVjaWZpZWQgYW5kIHRoZSBwcm9wZXJ0eSBpcyBtaXNzaW5nLCBhblxuICAgKiBlcnJvciB3aWxsIGJlIHRocm93bi5cbiAgICovXG4gIGZ1bmN0aW9uIGdldEFyZyhhQXJncywgYU5hbWUsIGFEZWZhdWx0VmFsdWUpIHtcbiAgICBpZiAoYU5hbWUgaW4gYUFyZ3MpIHtcbiAgICAgIHJldHVybiBhQXJnc1thTmFtZV07XG4gICAgfSBlbHNlIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAzKSB7XG4gICAgICByZXR1cm4gYURlZmF1bHRWYWx1ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhTmFtZSArICdcIiBpcyBhIHJlcXVpcmVkIGFyZ3VtZW50LicpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmdldEFyZyA9IGdldEFyZztcblxuICB2YXIgdXJsUmVnZXhwID0gL14oPzooW1xcdytcXC0uXSspOik/XFwvXFwvKD86KFxcdys6XFx3KylAKT8oW1xcdy5dKikoPzo6KFxcZCspKT8oXFxTKikkLztcbiAgdmFyIGRhdGFVcmxSZWdleHAgPSAvXmRhdGE6LitcXCwuKyQvO1xuXG4gIGZ1bmN0aW9uIHVybFBhcnNlKGFVcmwpIHtcbiAgICB2YXIgbWF0Y2ggPSBhVXJsLm1hdGNoKHVybFJlZ2V4cCk7XG4gICAgaWYgKCFtYXRjaCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBzY2hlbWU6IG1hdGNoWzFdLFxuICAgICAgYXV0aDogbWF0Y2hbMl0sXG4gICAgICBob3N0OiBtYXRjaFszXSxcbiAgICAgIHBvcnQ6IG1hdGNoWzRdLFxuICAgICAgcGF0aDogbWF0Y2hbNV1cbiAgICB9O1xuICB9XG4gIGV4cG9ydHMudXJsUGFyc2UgPSB1cmxQYXJzZTtcblxuICBmdW5jdGlvbiB1cmxHZW5lcmF0ZShhUGFyc2VkVXJsKSB7XG4gICAgdmFyIHVybCA9ICcnO1xuICAgIGlmIChhUGFyc2VkVXJsLnNjaGVtZSkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuc2NoZW1lICsgJzonO1xuICAgIH1cbiAgICB1cmwgKz0gJy8vJztcbiAgICBpZiAoYVBhcnNlZFVybC5hdXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5hdXRoICsgJ0AnO1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5ob3N0KSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5ob3N0O1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wb3J0KSB7XG4gICAgICB1cmwgKz0gXCI6XCIgKyBhUGFyc2VkVXJsLnBvcnRcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwucGF0aCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwucGF0aDtcbiAgICB9XG4gICAgcmV0dXJuIHVybDtcbiAgfVxuICBleHBvcnRzLnVybEdlbmVyYXRlID0gdXJsR2VuZXJhdGU7XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBwYXRoLCBvciB0aGUgcGF0aCBwb3J0aW9uIG9mIGEgVVJMOlxuICAgKlxuICAgKiAtIFJlcGxhY2VzIGNvbnNlcXV0aXZlIHNsYXNoZXMgd2l0aCBvbmUgc2xhc2guXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnLicgcGFydHMuXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnPGRpcj4vLi4nIHBhcnRzLlxuICAgKlxuICAgKiBCYXNlZCBvbiBjb2RlIGluIHRoZSBOb2RlLmpzICdwYXRoJyBjb3JlIG1vZHVsZS5cbiAgICpcbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIHVybCB0byBub3JtYWxpemUuXG4gICAqL1xuICBmdW5jdGlvbiBub3JtYWxpemUoYVBhdGgpIHtcbiAgICB2YXIgcGF0aCA9IGFQYXRoO1xuICAgIHZhciB1cmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgaWYgKHVybCkge1xuICAgICAgaWYgKCF1cmwucGF0aCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG4gICAgICBwYXRoID0gdXJsLnBhdGg7XG4gICAgfVxuICAgIHZhciBpc0Fic29sdXRlID0gZXhwb3J0cy5pc0Fic29sdXRlKHBhdGgpO1xuXG4gICAgdmFyIHBhcnRzID0gcGF0aC5zcGxpdCgvXFwvKy8pO1xuICAgIGZvciAodmFyIHBhcnQsIHVwID0gMCwgaSA9IHBhcnRzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICBwYXJ0ID0gcGFydHNbaV07XG4gICAgICBpZiAocGFydCA9PT0gJy4nKSB7XG4gICAgICAgIHBhcnRzLnNwbGljZShpLCAxKTtcbiAgICAgIH0gZWxzZSBpZiAocGFydCA9PT0gJy4uJykge1xuICAgICAgICB1cCsrO1xuICAgICAgfSBlbHNlIGlmICh1cCA+IDApIHtcbiAgICAgICAgaWYgKHBhcnQgPT09ICcnKSB7XG4gICAgICAgICAgLy8gVGhlIGZpcnN0IHBhcnQgaXMgYmxhbmsgaWYgdGhlIHBhdGggaXMgYWJzb2x1dGUuIFRyeWluZyB0byBnb1xuICAgICAgICAgIC8vIGFib3ZlIHRoZSByb290IGlzIGEgbm8tb3AuIFRoZXJlZm9yZSB3ZSBjYW4gcmVtb3ZlIGFsbCAnLi4nIHBhcnRzXG4gICAgICAgICAgLy8gZGlyZWN0bHkgYWZ0ZXIgdGhlIHJvb3QuXG4gICAgICAgICAgcGFydHMuc3BsaWNlKGkgKyAxLCB1cCk7XG4gICAgICAgICAgdXAgPSAwO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHBhcnRzLnNwbGljZShpLCAyKTtcbiAgICAgICAgICB1cC0tO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHBhdGggPSBwYXJ0cy5qb2luKCcvJyk7XG5cbiAgICBpZiAocGF0aCA9PT0gJycpIHtcbiAgICAgIHBhdGggPSBpc0Fic29sdXRlID8gJy8nIDogJy4nO1xuICAgIH1cblxuICAgIGlmICh1cmwpIHtcbiAgICAgIHVybC5wYXRoID0gcGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZSh1cmwpO1xuICAgIH1cbiAgICByZXR1cm4gcGF0aDtcbiAgfVxuICBleHBvcnRzLm5vcm1hbGl6ZSA9IG5vcm1hbGl6ZTtcblxuICAvKipcbiAgICogSm9pbnMgdHdvIHBhdGhzL1VSTHMuXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBqb2luZWQgd2l0aCB0aGUgcm9vdC5cbiAgICpcbiAgICogLSBJZiBhUGF0aCBpcyBhIFVSTCBvciBhIGRhdGEgVVJJLCBhUGF0aCBpcyByZXR1cm5lZCwgdW5sZXNzIGFQYXRoIGlzIGFcbiAgICogICBzY2hlbWUtcmVsYXRpdmUgVVJMOiBUaGVuIHRoZSBzY2hlbWUgb2YgYVJvb3QsIGlmIGFueSwgaXMgcHJlcGVuZGVkXG4gICAqICAgZmlyc3QuXG4gICAqIC0gT3RoZXJ3aXNlIGFQYXRoIGlzIGEgcGF0aC4gSWYgYVJvb3QgaXMgYSBVUkwsIHRoZW4gaXRzIHBhdGggcG9ydGlvblxuICAgKiAgIGlzIHVwZGF0ZWQgd2l0aCB0aGUgcmVzdWx0IGFuZCBhUm9vdCBpcyByZXR1cm5lZC4gT3RoZXJ3aXNlIHRoZSByZXN1bHRcbiAgICogICBpcyByZXR1cm5lZC5cbiAgICogICAtIElmIGFQYXRoIGlzIGFic29sdXRlLCB0aGUgcmVzdWx0IGlzIGFQYXRoLlxuICAgKiAgIC0gT3RoZXJ3aXNlIHRoZSB0d28gcGF0aHMgYXJlIGpvaW5lZCB3aXRoIGEgc2xhc2guXG4gICAqIC0gSm9pbmluZyBmb3IgZXhhbXBsZSAnaHR0cDovLycgYW5kICd3d3cuZXhhbXBsZS5jb20nIGlzIGFsc28gc3VwcG9ydGVkLlxuICAgKi9cbiAgZnVuY3Rpb24gam9pbihhUm9vdCwgYVBhdGgpIHtcbiAgICBpZiAoYVJvb3QgPT09IFwiXCIpIHtcbiAgICAgIGFSb290ID0gXCIuXCI7XG4gICAgfVxuICAgIGlmIChhUGF0aCA9PT0gXCJcIikge1xuICAgICAgYVBhdGggPSBcIi5cIjtcbiAgICB9XG4gICAgdmFyIGFQYXRoVXJsID0gdXJsUGFyc2UoYVBhdGgpO1xuICAgIHZhciBhUm9vdFVybCA9IHVybFBhcnNlKGFSb290KTtcbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290ID0gYVJvb3RVcmwucGF0aCB8fCAnLyc7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oZm9vLCAnLy93d3cuZXhhbXBsZS5vcmcnKWBcbiAgICBpZiAoYVBhdGhVcmwgJiYgIWFQYXRoVXJsLnNjaGVtZSkge1xuICAgICAgaWYgKGFSb290VXJsKSB7XG4gICAgICAgIGFQYXRoVXJsLnNjaGVtZSA9IGFSb290VXJsLnNjaGVtZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUGF0aFVybCk7XG4gICAgfVxuXG4gICAgaWYgKGFQYXRoVXJsIHx8IGFQYXRoLm1hdGNoKGRhdGFVcmxSZWdleHApKSB7XG4gICAgICByZXR1cm4gYVBhdGg7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oJ2h0dHA6Ly8nLCAnd3d3LmV4YW1wbGUuY29tJylgXG4gICAgaWYgKGFSb290VXJsICYmICFhUm9vdFVybC5ob3N0ICYmICFhUm9vdFVybC5wYXRoKSB7XG4gICAgICBhUm9vdFVybC5ob3N0ID0gYVBhdGg7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cblxuICAgIHZhciBqb2luZWQgPSBhUGF0aC5jaGFyQXQoMCkgPT09ICcvJ1xuICAgICAgPyBhUGF0aFxuICAgICAgOiBub3JtYWxpemUoYVJvb3QucmVwbGFjZSgvXFwvKyQvLCAnJykgKyAnLycgKyBhUGF0aCk7XG5cbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290VXJsLnBhdGggPSBqb2luZWQ7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cbiAgICByZXR1cm4gam9pbmVkO1xuICB9XG4gIGV4cG9ydHMuam9pbiA9IGpvaW47XG5cbiAgZXhwb3J0cy5pc0Fic29sdXRlID0gZnVuY3Rpb24gKGFQYXRoKSB7XG4gICAgcmV0dXJuIGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nIHx8ICEhYVBhdGgubWF0Y2godXJsUmVnZXhwKTtcbiAgfTtcblxuICAvKipcbiAgICogTWFrZSBhIHBhdGggcmVsYXRpdmUgdG8gYSBVUkwgb3IgYW5vdGhlciBwYXRoLlxuICAgKlxuICAgKiBAcGFyYW0gYVJvb3QgVGhlIHJvb3QgcGF0aCBvciBVUkwuXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciBVUkwgdG8gYmUgbWFkZSByZWxhdGl2ZSB0byBhUm9vdC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlbGF0aXZlKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG5cbiAgICBhUm9vdCA9IGFSb290LnJlcGxhY2UoL1xcLyQvLCAnJyk7XG5cbiAgICAvLyBJdCBpcyBwb3NzaWJsZSBmb3IgdGhlIHBhdGggdG8gYmUgYWJvdmUgdGhlIHJvb3QuIEluIHRoaXMgY2FzZSwgc2ltcGx5XG4gICAgLy8gY2hlY2tpbmcgd2hldGhlciB0aGUgcm9vdCBpcyBhIHByZWZpeCBvZiB0aGUgcGF0aCB3b24ndCB3b3JrLiBJbnN0ZWFkLCB3ZVxuICAgIC8vIG5lZWQgdG8gcmVtb3ZlIGNvbXBvbmVudHMgZnJvbSB0aGUgcm9vdCBvbmUgYnkgb25lLCB1bnRpbCBlaXRoZXIgd2UgZmluZFxuICAgIC8vIGEgcHJlZml4IHRoYXQgZml0cywgb3Igd2UgcnVuIG91dCBvZiBjb21wb25lbnRzIHRvIHJlbW92ZS5cbiAgICB2YXIgbGV2ZWwgPSAwO1xuICAgIHdoaWxlIChhUGF0aC5pbmRleE9mKGFSb290ICsgJy8nKSAhPT0gMCkge1xuICAgICAgdmFyIGluZGV4ID0gYVJvb3QubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgIC8vIElmIHRoZSBvbmx5IHBhcnQgb2YgdGhlIHJvb3QgdGhhdCBpcyBsZWZ0IGlzIHRoZSBzY2hlbWUgKGkuZS4gaHR0cDovLyxcbiAgICAgIC8vIGZpbGU6Ly8vLCBldGMuKSwgb25lIG9yIG1vcmUgc2xhc2hlcyAoLyksIG9yIHNpbXBseSBub3RoaW5nIGF0IGFsbCwgd2VcbiAgICAgIC8vIGhhdmUgZXhoYXVzdGVkIGFsbCBjb21wb25lbnRzLCBzbyB0aGUgcGF0aCBpcyBub3QgcmVsYXRpdmUgdG8gdGhlIHJvb3QuXG4gICAgICBhUm9vdCA9IGFSb290LnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIGlmIChhUm9vdC5tYXRjaCgvXihbXlxcL10rOlxcLyk/XFwvKiQvKSkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgICsrbGV2ZWw7XG4gICAgfVxuXG4gICAgLy8gTWFrZSBzdXJlIHdlIGFkZCBhIFwiLi4vXCIgZm9yIGVhY2ggY29tcG9uZW50IHdlIHJlbW92ZWQgZnJvbSB0aGUgcm9vdC5cbiAgICByZXR1cm4gQXJyYXkobGV2ZWwgKyAxKS5qb2luKFwiLi4vXCIpICsgYVBhdGguc3Vic3RyKGFSb290Lmxlbmd0aCArIDEpO1xuICB9XG4gIGV4cG9ydHMucmVsYXRpdmUgPSByZWxhdGl2ZTtcblxuICAvKipcbiAgICogQmVjYXVzZSBiZWhhdmlvciBnb2VzIHdhY2t5IHdoZW4geW91IHNldCBgX19wcm90b19fYCBvbiBvYmplY3RzLCB3ZVxuICAgKiBoYXZlIHRvIHByZWZpeCBhbGwgdGhlIHN0cmluZ3MgaW4gb3VyIHNldCB3aXRoIGFuIGFyYml0cmFyeSBjaGFyYWN0ZXIuXG4gICAqXG4gICAqIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL3B1bGwvMzEgYW5kXG4gICAqIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvaXNzdWVzLzMwXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgZnVuY3Rpb24gdG9TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiAnJCcgKyBhU3RyO1xuICB9XG4gIGV4cG9ydHMudG9TZXRTdHJpbmcgPSB0b1NldFN0cmluZztcblxuICBmdW5jdGlvbiBmcm9tU2V0U3RyaW5nKGFTdHIpIHtcbiAgICByZXR1cm4gYVN0ci5zdWJzdHIoMSk7XG4gIH1cbiAgZXhwb3J0cy5mcm9tU2V0U3RyaW5nID0gZnJvbVNldFN0cmluZztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aGVyZSB0aGUgb3JpZ2luYWwgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiwgYnV0IGRpZmZlcmVudCBnZW5lcmF0ZWRcbiAgICogbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGEgbWFwcGluZyB3aXRoIGFcbiAgICogc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zID0gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnM7XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2l0aCBkZWZsYXRlZCBzb3VyY2UgYW5kIG5hbWUgaW5kaWNlcyB3aGVyZVxuICAgKiB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqXG4gICAqIE9wdGlvbmFsbHkgcGFzcyBpbiBgdHJ1ZWAgYXMgYG9ubHlDb21wYXJlR2VuZXJhdGVkYCB0byBjb25zaWRlciB0d29cbiAgICogbWFwcGluZ3Mgd2l0aCB0aGUgc2FtZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uLCBidXQgZGlmZmVyZW50XG4gICAqIHNvdXJjZS9uYW1lL29yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiB0aGUgc2FtZS4gVXNlZnVsIHdoZW4gc2VhcmNoaW5nIGZvciBhXG4gICAqIG1hcHBpbmcgd2l0aCBhIHN0dWJiZWQgb3V0IG1hcHBpbmcuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IsIG9ubHlDb21wYXJlR2VuZXJhdGVkKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Euc291cmNlIC0gbWFwcGluZ0Iuc291cmNlO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQ7XG5cbiAgZnVuY3Rpb24gc3RyY21wKGFTdHIxLCBhU3RyMikge1xuICAgIGlmIChhU3RyMSA9PT0gYVN0cjIpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cblxuICAgIGlmIChhU3RyMSA+IGFTdHIyKSB7XG4gICAgICByZXR1cm4gMTtcbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG4gIH1cblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGluZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBzdHJpbmdzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IHN0cmNtcChtYXBwaW5nQS5zb3VyY2UsIG1hcHBpbmdCLnNvdXJjZSk7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHN0cmNtcChtYXBwaW5nQS5uYW1lLCBtYXBwaW5nQi5uYW1lKTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3V0aWwuanNcbiAqKiBtb2R1bGUgaWQgPSAyXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_base64.js b/devtools/shared/sourcemap/tests/unit/test_base64.js
new file mode 100644
index 000000000..f87a81b8f
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_base64.js
@@ -0,0 +1,163 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64 = __webpack_require__(1);
+
+ exports['test out of range encoding'] = function (assert) {
+ assert.throws(function () {
+ base64.encode(-1);
+ });
+ assert.throws(function () {
+ base64.encode(64);
+ });
+ };
+
+ exports['test out of range decoding'] = function (assert) {
+ assert.equal(base64.decode('='.charCodeAt(0)), -1);
+ };
+
+ exports['test normal encoding and decoding'] = function (assert) {
+ for (var i = 0; i < 64; i++) {
+ assert.equal(base64.decode(base64.encode(i).charCodeAt(0)), i);
+ }
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgMjJlMTk3NDMwNTJlZjViYWNjZDQiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWJhc2U2NC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmFzZTY0LmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsdUJBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQ3RDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLG9CQUFtQixRQUFRO0FBQzNCO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDM0JBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixzQkFBcUI7QUFDckIsdUJBQXNCOztBQUV0QixtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixtQkFBa0I7QUFDbEIsb0JBQW1COztBQUVuQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InRlc3RfYmFzZTY0LmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pXG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG5cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGV4cG9ydHM6IHt9LFxuIFx0XHRcdGlkOiBtb2R1bGVJZCxcbiBcdFx0XHRsb2FkZWQ6IGZhbHNlXG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmxvYWRlZCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oMCk7XG5cblxuXG4vKiogV0VCUEFDSyBGT09URVIgKipcbiAqKiB3ZWJwYWNrL2Jvb3RzdHJhcCAyMmUxOTc0MzA1MmVmNWJhY2NkNFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIGJhc2U2NCA9IHJlcXVpcmUoJy4uL2xpYi9iYXNlNjQnKTtcblxuICBleHBvcnRzWyd0ZXN0IG91dCBvZiByYW5nZSBlbmNvZGluZyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgYmFzZTY0LmVuY29kZSgtMSk7XG4gICAgfSk7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBiYXNlNjQuZW5jb2RlKDY0KTtcbiAgICB9KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IG91dCBvZiByYW5nZSBkZWNvZGluZyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGFzc2VydC5lcXVhbChiYXNlNjQuZGVjb2RlKCc9Jy5jaGFyQ29kZUF0KDApKSwgLTEpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgbm9ybWFsIGVuY29kaW5nIGFuZCBkZWNvZGluZyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgNjQ7IGkrKykge1xuICAgICAgYXNzZXJ0LmVxdWFsKGJhc2U2NC5kZWNvZGUoYmFzZTY0LmVuY29kZShpKS5jaGFyQ29kZUF0KDApKSwgaSk7XG4gICAgfVxuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1iYXNlNjQuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBpbnRUb0NoYXJNYXAgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLycuc3BsaXQoJycpO1xuXG4gIC8qKlxuICAgKiBFbmNvZGUgYW4gaW50ZWdlciBpbiB0aGUgcmFuZ2Ugb2YgMCB0byA2MyB0byBhIHNpbmdsZSBiYXNlIDY0IGRpZ2l0LlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiAobnVtYmVyKSB7XG4gICAgaWYgKDAgPD0gbnVtYmVyICYmIG51bWJlciA8IGludFRvQ2hhck1hcC5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBpbnRUb0NoYXJNYXBbbnVtYmVyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIk11c3QgYmUgYmV0d2VlbiAwIGFuZCA2MzogXCIgKyBudW1iZXIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBEZWNvZGUgYSBzaW5nbGUgYmFzZSA2NCBjaGFyYWN0ZXIgY29kZSBkaWdpdCB0byBhbiBpbnRlZ2VyLiBSZXR1cm5zIC0xIG9uXG4gICAqIGZhaWx1cmUuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIChjaGFyQ29kZSkge1xuICAgIHZhciBiaWdBID0gNjU7ICAgICAvLyAnQSdcbiAgICB2YXIgYmlnWiA9IDkwOyAgICAgLy8gJ1onXG5cbiAgICB2YXIgbGl0dGxlQSA9IDk3OyAgLy8gJ2EnXG4gICAgdmFyIGxpdHRsZVogPSAxMjI7IC8vICd6J1xuXG4gICAgdmFyIHplcm8gPSA0ODsgICAgIC8vICcwJ1xuICAgIHZhciBuaW5lID0gNTc7ICAgICAvLyAnOSdcblxuICAgIHZhciBwbHVzID0gNDM7ICAgICAvLyAnKydcbiAgICB2YXIgc2xhc2ggPSA0NzsgICAgLy8gJy8nXG5cbiAgICB2YXIgbGl0dGxlT2Zmc2V0ID0gMjY7XG4gICAgdmFyIG51bWJlck9mZnNldCA9IDUyO1xuXG4gICAgLy8gMCAtIDI1OiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWlxuICAgIGlmIChiaWdBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGJpZ1opIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBiaWdBKTtcbiAgICB9XG5cbiAgICAvLyAyNiAtIDUxOiBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5elxuICAgIGlmIChsaXR0bGVBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGxpdHRsZVopIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBsaXR0bGVBICsgbGl0dGxlT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA1MiAtIDYxOiAwMTIzNDU2Nzg5XG4gICAgaWYgKHplcm8gPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gbmluZSkge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIHplcm8gKyBudW1iZXJPZmZzZXQpO1xuICAgIH1cblxuICAgIC8vIDYyOiArXG4gICAgaWYgKGNoYXJDb2RlID09IHBsdXMpIHtcbiAgICAgIHJldHVybiA2MjtcbiAgICB9XG5cbiAgICAvLyA2MzogL1xuICAgIGlmIChjaGFyQ29kZSA9PSBzbGFzaCkge1xuICAgICAgcmV0dXJuIDYzO1xuICAgIH1cblxuICAgIC8vIEludmFsaWQgYmFzZTY0IGRpZ2l0LlxuICAgIHJldHVybiAtMTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmFzZTY0LmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_base64_vlq.js b/devtools/shared/sourcemap/tests/unit/test_base64_vlq.js
new file mode 100644
index 000000000..b7b38cc88
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_base64_vlq.js
@@ -0,0 +1,301 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(1);
+
+ exports['test normal encoding and decoding'] = function (assert) {
+ var result = {};
+ for (var i = -255; i < 256; i++) {
+ var str = base64VLQ.encode(i);
+ base64VLQ.decode(str, 0, result);
+ assert.equal(result.value, i);
+ assert.equal(result.rest, str.length);
+ }
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(2);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgODE2ZmNlY2MyY2FkYmFiMjRjOGIiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWJhc2U2NC12bHEuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC12bHEuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHVCQUFlO0FBQ2Y7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN0Q0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsdUJBQXNCLFNBQVM7QUFDL0I7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNsQkEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNERBQTJEO0FBQzNELHFCQUFvQjtBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQzVJQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCO0FBQ2xCLG1CQUFrQjs7QUFFbEIsc0JBQXFCO0FBQ3JCLHVCQUFzQjs7QUFFdEIsbUJBQWtCO0FBQ2xCLG1CQUFrQjs7QUFFbEIsbUJBQWtCO0FBQ2xCLG9CQUFtQjs7QUFFbkI7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJ0ZXN0X2Jhc2U2NF92bHEuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSlcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcblxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0ZXhwb3J0czoge30sXG4gXHRcdFx0aWQ6IG1vZHVsZUlkLFxuIFx0XHRcdGxvYWRlZDogZmFsc2VcbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubG9hZGVkID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXygwKTtcblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIHdlYnBhY2svYm9vdHN0cmFwIDgxNmZjZWNjMmNhZGJhYjI0YzhiXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgYmFzZTY0VkxRID0gcmVxdWlyZSgnLi4vbGliL2Jhc2U2NC12bHEnKTtcblxuICBleHBvcnRzWyd0ZXN0IG5vcm1hbCBlbmNvZGluZyBhbmQgZGVjb2RpbmcnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgcmVzdWx0ID0ge307XG4gICAgZm9yICh2YXIgaSA9IC0yNTU7IGkgPCAyNTY7IGkrKykge1xuICAgICAgdmFyIHN0ciA9IGJhc2U2NFZMUS5lbmNvZGUoaSk7XG4gICAgICBiYXNlNjRWTFEuZGVjb2RlKHN0ciwgMCwgcmVzdWx0KTtcbiAgICAgIGFzc2VydC5lcXVhbChyZXN1bHQudmFsdWUsIGkpO1xuICAgICAgYXNzZXJ0LmVxdWFsKHJlc3VsdC5yZXN0LCBzdHIubGVuZ3RoKTtcbiAgICB9XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC90ZXN0LWJhc2U2NC12bHEuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICpcbiAqIEJhc2VkIG9uIHRoZSBCYXNlIDY0IFZMUSBpbXBsZW1lbnRhdGlvbiBpbiBDbG9zdXJlIENvbXBpbGVyOlxuICogaHR0cHM6Ly9jb2RlLmdvb2dsZS5jb20vcC9jbG9zdXJlLWNvbXBpbGVyL3NvdXJjZS9icm93c2UvdHJ1bmsvc3JjL2NvbS9nb29nbGUvZGVidWdnaW5nL3NvdXJjZW1hcC9CYXNlNjRWTFEuamF2YVxuICpcbiAqIENvcHlyaWdodCAyMDExIFRoZSBDbG9zdXJlIENvbXBpbGVyIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXRcbiAqIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmVcbiAqIG1ldDpcbiAqXG4gKiAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodFxuICogICAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLlxuICogICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZVxuICogICAgY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmdcbiAqICAgIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZFxuICogICAgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuICogICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0c1xuICogICAgY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkXG4gKiAgICBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uXG4gKlxuICogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SU1xuICogXCJBUyBJU1wiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVFxuICogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SXG4gKiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVFxuICogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsXG4gKiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UXG4gKiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSxcbiAqIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWVxuICogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVFxuICogKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFXG4gKiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLlxuICovXG57XG4gIHZhciBiYXNlNjQgPSByZXF1aXJlKCcuL2Jhc2U2NCcpO1xuXG4gIC8vIEEgc2luZ2xlIGJhc2UgNjQgZGlnaXQgY2FuIGNvbnRhaW4gNiBiaXRzIG9mIGRhdGEuIEZvciB0aGUgYmFzZSA2NCB2YXJpYWJsZVxuICAvLyBsZW5ndGggcXVhbnRpdGllcyB3ZSB1c2UgaW4gdGhlIHNvdXJjZSBtYXAgc3BlYywgdGhlIGZpcnN0IGJpdCBpcyB0aGUgc2lnbixcbiAgLy8gdGhlIG5leHQgZm91ciBiaXRzIGFyZSB0aGUgYWN0dWFsIHZhbHVlLCBhbmQgdGhlIDZ0aCBiaXQgaXMgdGhlXG4gIC8vIGNvbnRpbnVhdGlvbiBiaXQuIFRoZSBjb250aW51YXRpb24gYml0IHRlbGxzIHVzIHdoZXRoZXIgdGhlcmUgYXJlIG1vcmVcbiAgLy8gZGlnaXRzIGluIHRoaXMgdmFsdWUgZm9sbG93aW5nIHRoaXMgZGlnaXQuXG4gIC8vXG4gIC8vICAgQ29udGludWF0aW9uXG4gIC8vICAgfCAgICBTaWduXG4gIC8vICAgfCAgICB8XG4gIC8vICAgViAgICBWXG4gIC8vICAgMTAxMDExXG5cbiAgdmFyIFZMUV9CQVNFX1NISUZUID0gNTtcblxuICAvLyBiaW5hcnk6IDEwMDAwMFxuICB2YXIgVkxRX0JBU0UgPSAxIDw8IFZMUV9CQVNFX1NISUZUO1xuXG4gIC8vIGJpbmFyeTogMDExMTExXG4gIHZhciBWTFFfQkFTRV9NQVNLID0gVkxRX0JBU0UgLSAxO1xuXG4gIC8vIGJpbmFyeTogMTAwMDAwXG4gIHZhciBWTFFfQ09OVElOVUFUSU9OX0JJVCA9IFZMUV9CQVNFO1xuXG4gIC8qKlxuICAgKiBDb252ZXJ0cyBmcm9tIGEgdHdvLWNvbXBsZW1lbnQgdmFsdWUgdG8gYSB2YWx1ZSB3aGVyZSB0aGUgc2lnbiBiaXQgaXNcbiAgICogcGxhY2VkIGluIHRoZSBsZWFzdCBzaWduaWZpY2FudCBiaXQuICBGb3IgZXhhbXBsZSwgYXMgZGVjaW1hbHM6XG4gICAqICAgMSBiZWNvbWVzIDIgKDEwIGJpbmFyeSksIC0xIGJlY29tZXMgMyAoMTEgYmluYXJ5KVxuICAgKiAgIDIgYmVjb21lcyA0ICgxMDAgYmluYXJ5KSwgLTIgYmVjb21lcyA1ICgxMDEgYmluYXJ5KVxuICAgKi9cbiAgZnVuY3Rpb24gdG9WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgcmV0dXJuIGFWYWx1ZSA8IDBcbiAgICAgID8gKCgtYVZhbHVlKSA8PCAxKSArIDFcbiAgICAgIDogKGFWYWx1ZSA8PCAxKSArIDA7XG4gIH1cblxuICAvKipcbiAgICogQ29udmVydHMgdG8gYSB0d28tY29tcGxlbWVudCB2YWx1ZSBmcm9tIGEgdmFsdWUgd2hlcmUgdGhlIHNpZ24gYml0IGlzXG4gICAqIHBsYWNlZCBpbiB0aGUgbGVhc3Qgc2lnbmlmaWNhbnQgYml0LiAgRm9yIGV4YW1wbGUsIGFzIGRlY2ltYWxzOlxuICAgKiAgIDIgKDEwIGJpbmFyeSkgYmVjb21lcyAxLCAzICgxMSBiaW5hcnkpIGJlY29tZXMgLTFcbiAgICogICA0ICgxMDAgYmluYXJ5KSBiZWNvbWVzIDIsIDUgKDEwMSBiaW5hcnkpIGJlY29tZXMgLTJcbiAgICovXG4gIGZ1bmN0aW9uIGZyb21WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgdmFyIGlzTmVnYXRpdmUgPSAoYVZhbHVlICYgMSkgPT09IDE7XG4gICAgdmFyIHNoaWZ0ZWQgPSBhVmFsdWUgPj4gMTtcbiAgICByZXR1cm4gaXNOZWdhdGl2ZVxuICAgICAgPyAtc2hpZnRlZFxuICAgICAgOiBzaGlmdGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGJhc2UgNjQgVkxRIGVuY29kZWQgdmFsdWUuXG4gICAqL1xuICBleHBvcnRzLmVuY29kZSA9IGZ1bmN0aW9uIGJhc2U2NFZMUV9lbmNvZGUoYVZhbHVlKSB7XG4gICAgdmFyIGVuY29kZWQgPSBcIlwiO1xuICAgIHZhciBkaWdpdDtcblxuICAgIHZhciB2bHEgPSB0b1ZMUVNpZ25lZChhVmFsdWUpO1xuXG4gICAgZG8ge1xuICAgICAgZGlnaXQgPSB2bHEgJiBWTFFfQkFTRV9NQVNLO1xuICAgICAgdmxxID4+Pj0gVkxRX0JBU0VfU0hJRlQ7XG4gICAgICBpZiAodmxxID4gMCkge1xuICAgICAgICAvLyBUaGVyZSBhcmUgc3RpbGwgbW9yZSBkaWdpdHMgaW4gdGhpcyB2YWx1ZSwgc28gd2UgbXVzdCBtYWtlIHN1cmUgdGhlXG4gICAgICAgIC8vIGNvbnRpbnVhdGlvbiBiaXQgaXMgbWFya2VkLlxuICAgICAgICBkaWdpdCB8PSBWTFFfQ09OVElOVUFUSU9OX0JJVDtcbiAgICAgIH1cbiAgICAgIGVuY29kZWQgKz0gYmFzZTY0LmVuY29kZShkaWdpdCk7XG4gICAgfSB3aGlsZSAodmxxID4gMCk7XG5cbiAgICByZXR1cm4gZW5jb2RlZDtcbiAgfTtcblxuICAvKipcbiAgICogRGVjb2RlcyB0aGUgbmV4dCBiYXNlIDY0IFZMUSB2YWx1ZSBmcm9tIHRoZSBnaXZlbiBzdHJpbmcgYW5kIHJldHVybnMgdGhlXG4gICAqIHZhbHVlIGFuZCB0aGUgcmVzdCBvZiB0aGUgc3RyaW5nIHZpYSB0aGUgb3V0IHBhcmFtZXRlci5cbiAgICovXG4gIGV4cG9ydHMuZGVjb2RlID0gZnVuY3Rpb24gYmFzZTY0VkxRX2RlY29kZShhU3RyLCBhSW5kZXgsIGFPdXRQYXJhbSkge1xuICAgIHZhciBzdHJMZW4gPSBhU3RyLmxlbmd0aDtcbiAgICB2YXIgcmVzdWx0ID0gMDtcbiAgICB2YXIgc2hpZnQgPSAwO1xuICAgIHZhciBjb250aW51YXRpb24sIGRpZ2l0O1xuXG4gICAgZG8ge1xuICAgICAgaWYgKGFJbmRleCA+PSBzdHJMZW4pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRXhwZWN0ZWQgbW9yZSBkaWdpdHMgaW4gYmFzZSA2NCBWTFEgdmFsdWUuXCIpO1xuICAgICAgfVxuXG4gICAgICBkaWdpdCA9IGJhc2U2NC5kZWNvZGUoYVN0ci5jaGFyQ29kZUF0KGFJbmRleCsrKSk7XG4gICAgICBpZiAoZGlnaXQgPT09IC0xKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkludmFsaWQgYmFzZTY0IGRpZ2l0OiBcIiArIGFTdHIuY2hhckF0KGFJbmRleCAtIDEpKTtcbiAgICAgIH1cblxuICAgICAgY29udGludWF0aW9uID0gISEoZGlnaXQgJiBWTFFfQ09OVElOVUFUSU9OX0JJVCk7XG4gICAgICBkaWdpdCAmPSBWTFFfQkFTRV9NQVNLO1xuICAgICAgcmVzdWx0ID0gcmVzdWx0ICsgKGRpZ2l0IDw8IHNoaWZ0KTtcbiAgICAgIHNoaWZ0ICs9IFZMUV9CQVNFX1NISUZUO1xuICAgIH0gd2hpbGUgKGNvbnRpbnVhdGlvbik7XG5cbiAgICBhT3V0UGFyYW0udmFsdWUgPSBmcm9tVkxRU2lnbmVkKHJlc3VsdCk7XG4gICAgYU91dFBhcmFtLnJlc3QgPSBhSW5kZXg7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2Jhc2U2NC12bHEuanNcbiAqKiBtb2R1bGUgaWQgPSAxXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBpbnRUb0NoYXJNYXAgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLycuc3BsaXQoJycpO1xuXG4gIC8qKlxuICAgKiBFbmNvZGUgYW4gaW50ZWdlciBpbiB0aGUgcmFuZ2Ugb2YgMCB0byA2MyB0byBhIHNpbmdsZSBiYXNlIDY0IGRpZ2l0LlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiAobnVtYmVyKSB7XG4gICAgaWYgKDAgPD0gbnVtYmVyICYmIG51bWJlciA8IGludFRvQ2hhck1hcC5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBpbnRUb0NoYXJNYXBbbnVtYmVyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIk11c3QgYmUgYmV0d2VlbiAwIGFuZCA2MzogXCIgKyBudW1iZXIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBEZWNvZGUgYSBzaW5nbGUgYmFzZSA2NCBjaGFyYWN0ZXIgY29kZSBkaWdpdCB0byBhbiBpbnRlZ2VyLiBSZXR1cm5zIC0xIG9uXG4gICAqIGZhaWx1cmUuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIChjaGFyQ29kZSkge1xuICAgIHZhciBiaWdBID0gNjU7ICAgICAvLyAnQSdcbiAgICB2YXIgYmlnWiA9IDkwOyAgICAgLy8gJ1onXG5cbiAgICB2YXIgbGl0dGxlQSA9IDk3OyAgLy8gJ2EnXG4gICAgdmFyIGxpdHRsZVogPSAxMjI7IC8vICd6J1xuXG4gICAgdmFyIHplcm8gPSA0ODsgICAgIC8vICcwJ1xuICAgIHZhciBuaW5lID0gNTc7ICAgICAvLyAnOSdcblxuICAgIHZhciBwbHVzID0gNDM7ICAgICAvLyAnKydcbiAgICB2YXIgc2xhc2ggPSA0NzsgICAgLy8gJy8nXG5cbiAgICB2YXIgbGl0dGxlT2Zmc2V0ID0gMjY7XG4gICAgdmFyIG51bWJlck9mZnNldCA9IDUyO1xuXG4gICAgLy8gMCAtIDI1OiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWlxuICAgIGlmIChiaWdBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGJpZ1opIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBiaWdBKTtcbiAgICB9XG5cbiAgICAvLyAyNiAtIDUxOiBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5elxuICAgIGlmIChsaXR0bGVBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGxpdHRsZVopIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBsaXR0bGVBICsgbGl0dGxlT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA1MiAtIDYxOiAwMTIzNDU2Nzg5XG4gICAgaWYgKHplcm8gPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gbmluZSkge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIHplcm8gKyBudW1iZXJPZmZzZXQpO1xuICAgIH1cblxuICAgIC8vIDYyOiArXG4gICAgaWYgKGNoYXJDb2RlID09IHBsdXMpIHtcbiAgICAgIHJldHVybiA2MjtcbiAgICB9XG5cbiAgICAvLyA2MzogL1xuICAgIGlmIChjaGFyQ29kZSA9PSBzbGFzaCkge1xuICAgICAgcmV0dXJuIDYzO1xuICAgIH1cblxuICAgIC8vIEludmFsaWQgYmFzZTY0IGRpZ2l0LlxuICAgIHJldHVybiAtMTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmFzZTY0LmpzXG4gKiogbW9kdWxlIGlkID0gMlxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIl0sInNvdXJjZVJvb3QiOiIifQ== \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_binary_search.js b/devtools/shared/sourcemap/tests/unit/test_binary_search.js
new file mode 100644
index 000000000..fcb70c520
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_binary_search.js
@@ -0,0 +1,276 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var binarySearch = __webpack_require__(1);
+
+ function numberCompare(a, b) {
+ return a - b;
+ }
+
+ exports['test too high with default (glb) bias'] = function (assert) {
+ var needle = 30;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.doesNotThrow(function () {
+ binarySearch.search(needle, haystack, numberCompare);
+ });
+
+ assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 20);
+ };
+
+ exports['test too low with default (glb) bias'] = function (assert) {
+ var needle = 1;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.doesNotThrow(function () {
+ binarySearch.search(needle, haystack, numberCompare);
+ });
+
+ assert.equal(binarySearch.search(needle, haystack, numberCompare), -1);
+ };
+
+ exports['test too high with lub bias'] = function (assert) {
+ var needle = 30;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.doesNotThrow(function () {
+ binarySearch.search(needle, haystack, numberCompare);
+ });
+
+ assert.equal(binarySearch.search(needle, haystack, numberCompare,
+ binarySearch.LEAST_UPPER_BOUND), -1);
+ };
+
+ exports['test too low with lub bias'] = function (assert) {
+ var needle = 1;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.doesNotThrow(function () {
+ binarySearch.search(needle, haystack, numberCompare);
+ });
+
+ assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare,
+ binarySearch.LEAST_UPPER_BOUND)], 2);
+ };
+
+ exports['test exact search'] = function (assert) {
+ var needle = 4;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 4);
+ };
+
+ exports['test fuzzy search with default (glb) bias'] = function (assert) {
+ var needle = 19;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare)], 18);
+ };
+
+ exports['test fuzzy search with lub bias'] = function (assert) {
+ var needle = 19;
+ var haystack = [2,4,6,8,10,12,14,16,18,20];
+
+ assert.equal(haystack[binarySearch.search(needle, haystack, numberCompare,
+ binarySearch.LEAST_UPPER_BOUND)], 20);
+ };
+
+ exports['test multiple matches'] = function (assert) {
+ var needle = 5;
+ var haystack = [1, 1, 2, 5, 5, 5, 13, 21];
+
+ assert.equal(binarySearch.search(needle, haystack, numberCompare,
+ binarySearch.LEAST_UPPER_BOUND), 3);
+ };
+
+ exports['test multiple matches at the beginning'] = function (assert) {
+ var needle = 1;
+ var haystack = [1, 1, 2, 5, 5, 5, 13, 21];
+
+ assert.equal(binarySearch.search(needle, haystack, numberCompare,
+ binarySearch.LEAST_UPPER_BOUND), 0);
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgYmI3MjVjOTVmZTk3YzY1OTQ3OGMiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWJpbmFyeS1zZWFyY2guanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2JpbmFyeS1zZWFyY2guanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1QkFBZTtBQUNmO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdENBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDaEdBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBIiwiZmlsZSI6InRlc3RfYmluYXJ5X3NlYXJjaC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgYmI3MjVjOTVmZTk3YzY1OTQ3OGNcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBiaW5hcnlTZWFyY2ggPSByZXF1aXJlKCcuLi9saWIvYmluYXJ5LXNlYXJjaCcpO1xuXG4gIGZ1bmN0aW9uIG51bWJlckNvbXBhcmUoYSwgYikge1xuICAgIHJldHVybiBhIC0gYjtcbiAgfVxuXG4gIGV4cG9ydHNbJ3Rlc3QgdG9vIGhpZ2ggd2l0aCBkZWZhdWx0IChnbGIpIGJpYXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbmVlZGxlID0gMzA7XG4gICAgdmFyIGhheXN0YWNrID0gWzIsNCw2LDgsMTAsMTIsMTQsMTYsMTgsMjBdO1xuXG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbiAoKSB7XG4gICAgICBiaW5hcnlTZWFyY2guc2VhcmNoKG5lZWRsZSwgaGF5c3RhY2ssIG51bWJlckNvbXBhcmUpO1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGhheXN0YWNrW2JpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCBoYXlzdGFjaywgbnVtYmVyQ29tcGFyZSldLCAyMCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0b28gbG93IHdpdGggZGVmYXVsdCAoZ2xiKSBiaWFzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5lZWRsZSA9IDE7XG4gICAgdmFyIGhheXN0YWNrID0gWzIsNCw2LDgsMTAsMTIsMTQsMTYsMTgsMjBdO1xuXG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbiAoKSB7XG4gICAgICBiaW5hcnlTZWFyY2guc2VhcmNoKG5lZWRsZSwgaGF5c3RhY2ssIG51bWJlckNvbXBhcmUpO1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGJpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCBoYXlzdGFjaywgbnVtYmVyQ29tcGFyZSksIC0xKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IHRvbyBoaWdoIHdpdGggbHViIGJpYXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbmVlZGxlID0gMzA7XG4gICAgdmFyIGhheXN0YWNrID0gWzIsNCw2LDgsMTAsMTIsMTQsMTYsMTgsMjBdO1xuXG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbiAoKSB7XG4gICAgICBiaW5hcnlTZWFyY2guc2VhcmNoKG5lZWRsZSwgaGF5c3RhY2ssIG51bWJlckNvbXBhcmUpO1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGJpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCBoYXlzdGFjaywgbnVtYmVyQ29tcGFyZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQpLCAtMSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0b28gbG93IHdpdGggbHViIGJpYXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbmVlZGxlID0gMTtcbiAgICB2YXIgaGF5c3RhY2sgPSBbMiw0LDYsOCwxMCwxMiwxNCwxNiwxOCwyMF07XG5cbiAgICBhc3NlcnQuZG9lc05vdFRocm93KGZ1bmN0aW9uICgpIHtcbiAgICAgIGJpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCBoYXlzdGFjaywgbnVtYmVyQ29tcGFyZSk7XG4gICAgfSk7XG5cbiAgICBhc3NlcnQuZXF1YWwoaGF5c3RhY2tbYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIGhheXN0YWNrLCBudW1iZXJDb21wYXJlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCldLCAyKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGV4YWN0IHNlYXJjaCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBuZWVkbGUgPSA0O1xuICAgIHZhciBoYXlzdGFjayA9IFsyLDQsNiw4LDEwLDEyLDE0LDE2LDE4LDIwXTtcblxuICAgIGFzc2VydC5lcXVhbChoYXlzdGFja1tiaW5hcnlTZWFyY2guc2VhcmNoKG5lZWRsZSwgaGF5c3RhY2ssIG51bWJlckNvbXBhcmUpXSwgNCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBmdXp6eSBzZWFyY2ggd2l0aCBkZWZhdWx0IChnbGIpIGJpYXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbmVlZGxlID0gMTk7XG4gICAgdmFyIGhheXN0YWNrID0gWzIsNCw2LDgsMTAsMTIsMTQsMTYsMTgsMjBdO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGhheXN0YWNrW2JpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCBoYXlzdGFjaywgbnVtYmVyQ29tcGFyZSldLCAxOCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBmdXp6eSBzZWFyY2ggd2l0aCBsdWIgYmlhcyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBuZWVkbGUgPSAxOTtcbiAgICB2YXIgaGF5c3RhY2sgPSBbMiw0LDYsOCwxMCwxMiwxNCwxNiwxOCwyMF07XG5cbiAgICBhc3NlcnQuZXF1YWwoaGF5c3RhY2tbYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIGhheXN0YWNrLCBudW1iZXJDb21wYXJlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCldLCAyMCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBtdWx0aXBsZSBtYXRjaGVzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5lZWRsZSA9IDU7XG4gICAgdmFyIGhheXN0YWNrID0gWzEsIDEsIDIsIDUsIDUsIDUsIDEzLCAyMV07XG5cbiAgICBhc3NlcnQuZXF1YWwoYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIGhheXN0YWNrLCBudW1iZXJDb21wYXJlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCksIDMpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgbXVsdGlwbGUgbWF0Y2hlcyBhdCB0aGUgYmVnaW5uaW5nJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5lZWRsZSA9IDE7XG4gICAgdmFyIGhheXN0YWNrID0gWzEsIDEsIDIsIDUsIDUsIDUsIDEzLCAyMV07XG5cbiAgICBhc3NlcnQuZXF1YWwoYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIGhheXN0YWNrLCBudW1iZXJDb21wYXJlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCksIDApO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1iaW5hcnktc2VhcmNoLmpzXG4gKiogbW9kdWxlIGlkID0gMFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EID0gMTtcbiAgZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCA9IDI7XG5cbiAgLyoqXG4gICAqIFJlY3Vyc2l2ZSBpbXBsZW1lbnRhdGlvbiBvZiBiaW5hcnkgc2VhcmNoLlxuICAgKlxuICAgKiBAcGFyYW0gYUxvdyBJbmRpY2VzIGhlcmUgYW5kIGxvd2VyIGRvIG5vdCBjb250YWluIHRoZSBuZWVkbGUuXG4gICAqIEBwYXJhbSBhSGlnaCBJbmRpY2VzIGhlcmUgYW5kIGhpZ2hlciBkbyBub3QgY29udGFpbiB0aGUgbmVlZGxlLlxuICAgKiBAcGFyYW0gYU5lZWRsZSBUaGUgZWxlbWVudCBiZWluZyBzZWFyY2hlZCBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIG5vbi1lbXB0eSBhcnJheSBiZWluZyBzZWFyY2hlZC5cbiAgICogQHBhcmFtIGFDb21wYXJlIEZ1bmN0aW9uIHdoaWNoIHRha2VzIHR3byBlbGVtZW50cyBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKi9cbiAgZnVuY3Rpb24gcmVjdXJzaXZlU2VhcmNoKGFMb3csIGFIaWdoLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIC8vIFRoaXMgZnVuY3Rpb24gdGVybWluYXRlcyB3aGVuIG9uZSBvZiB0aGUgZm9sbG93aW5nIGlzIHRydWU6XG4gICAgLy9cbiAgICAvLyAgIDEuIFdlIGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQgd2UgYXJlIGxvb2tpbmcgZm9yLlxuICAgIC8vXG4gICAgLy8gICAyLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGJ1dCB3ZSBjYW4gcmV0dXJuIHRoZSBpbmRleCBvZlxuICAgIC8vICAgICAgdGhlIG5leHQtY2xvc2VzdCBlbGVtZW50LlxuICAgIC8vXG4gICAgLy8gICAzLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGFuZCB0aGVyZSBpcyBubyBuZXh0LWNsb3Nlc3RcbiAgICAvLyAgICAgIGVsZW1lbnQgdGhhbiB0aGUgb25lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLCBzbyB3ZSByZXR1cm4gLTEuXG4gICAgdmFyIG1pZCA9IE1hdGguZmxvb3IoKGFIaWdoIC0gYUxvdykgLyAyKSArIGFMb3c7XG4gICAgdmFyIGNtcCA9IGFDb21wYXJlKGFOZWVkbGUsIGFIYXlzdGFja1ttaWRdLCB0cnVlKTtcbiAgICBpZiAoY21wID09PSAwKSB7XG4gICAgICAvLyBGb3VuZCB0aGUgZWxlbWVudCB3ZSBhcmUgbG9va2luZyBmb3IuXG4gICAgICByZXR1cm4gbWlkO1xuICAgIH1cbiAgICBlbHNlIGlmIChjbXAgPiAwKSB7XG4gICAgICAvLyBPdXIgbmVlZGxlIGlzIGdyZWF0ZXIgdGhhbiBhSGF5c3RhY2tbbWlkXS5cbiAgICAgIGlmIChhSGlnaCAtIG1pZCA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIHVwcGVyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2gobWlkLCBhSGlnaCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpO1xuICAgICAgfVxuXG4gICAgICAvLyBUaGUgZXhhY3QgbmVlZGxlIGVsZW1lbnQgd2FzIG5vdCBmb3VuZCBpbiB0aGlzIGhheXN0YWNrLiBEZXRlcm1pbmUgaWZcbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gYUhpZ2ggPCBhSGF5c3RhY2subGVuZ3RoID8gYUhpZ2ggOiAtMTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBtaWQ7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgLy8gT3VyIG5lZWRsZSBpcyBsZXNzIHRoYW4gYUhheXN0YWNrW21pZF0uXG4gICAgICBpZiAobWlkIC0gYUxvdyA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIGxvd2VyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2goYUxvdywgbWlkLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcyk7XG4gICAgICB9XG5cbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gbWlkO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGFMb3cgPCAwID8gLTEgOiBhTG93O1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBUaGlzIGlzIGFuIGltcGxlbWVudGF0aW9uIG9mIGJpbmFyeSBzZWFyY2ggd2hpY2ggd2lsbCBhbHdheXMgdHJ5IGFuZCByZXR1cm5cbiAgICogdGhlIGluZGV4IG9mIHRoZSBjbG9zZXN0IGVsZW1lbnQgaWYgdGhlcmUgaXMgbm8gZXhhY3QgaGl0LiBUaGlzIGlzIGJlY2F1c2VcbiAgICogbWFwcGluZ3MgYmV0d2VlbiBvcmlnaW5hbCBhbmQgZ2VuZXJhdGVkIGxpbmUvY29sIHBhaXJzIGFyZSBzaW5nbGUgcG9pbnRzLFxuICAgKiBhbmQgdGhlcmUgaXMgYW4gaW1wbGljaXQgcmVnaW9uIGJldHdlZW4gZWFjaCBvZiB0aGVtLCBzbyBhIG1pc3MganVzdCBtZWFuc1xuICAgKiB0aGF0IHlvdSBhcmVuJ3Qgb24gdGhlIHZlcnkgc3RhcnQgb2YgYSByZWdpb24uXG4gICAqXG4gICAqIEBwYXJhbSBhTmVlZGxlIFRoZSBlbGVtZW50IHlvdSBhcmUgbG9va2luZyBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIGFycmF5IHRoYXQgaXMgYmVpbmcgc2VhcmNoZWQuXG4gICAqIEBwYXJhbSBhQ29tcGFyZSBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIHRoZSBuZWVkbGUgYW5kIGFuIGVsZW1lbnQgaW4gdGhlXG4gICAqICAgICBhcnJheSBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMSBkZXBlbmRpbmcgb24gd2hldGhlciB0aGUgbmVlZGxlIGlzIGxlc3NcbiAgICogICAgIHRoYW4sIGVxdWFsIHRvLCBvciBncmVhdGVyIHRoYW4gdGhlIGVsZW1lbnQsIHJlc3BlY3RpdmVseS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqL1xuICBleHBvcnRzLnNlYXJjaCA9IGZ1bmN0aW9uIHNlYXJjaChhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIGlmIChhSGF5c3RhY2subGVuZ3RoID09PSAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgdmFyIGluZGV4ID0gcmVjdXJzaXZlU2VhcmNoKC0xLCBhSGF5c3RhY2subGVuZ3RoLCBhTmVlZGxlLCBhSGF5c3RhY2ssXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFDb21wYXJlLCBhQmlhcyB8fCBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EKTtcbiAgICBpZiAoaW5kZXggPCAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgLy8gV2UgaGF2ZSBmb3VuZCBlaXRoZXIgdGhlIGV4YWN0IGVsZW1lbnQsIG9yIHRoZSBuZXh0LWNsb3Nlc3QgZWxlbWVudCB0aGFuXG4gICAgLy8gdGhlIG9uZSB3ZSBhcmUgc2VhcmNoaW5nIGZvci4gSG93ZXZlciwgdGhlcmUgbWF5IGJlIG1vcmUgdGhhbiBvbmUgc3VjaFxuICAgIC8vIGVsZW1lbnQuIE1ha2Ugc3VyZSB3ZSBhbHdheXMgcmV0dXJuIHRoZSBzbWFsbGVzdCBvZiB0aGVzZS5cbiAgICB3aGlsZSAoaW5kZXggLSAxID49IDApIHtcbiAgICAgIGlmIChhQ29tcGFyZShhSGF5c3RhY2tbaW5kZXhdLCBhSGF5c3RhY2tbaW5kZXggLSAxXSwgdHJ1ZSkgIT09IDApIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICAtLWluZGV4O1xuICAgIH1cblxuICAgIHJldHVybiBpbmRleDtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmluYXJ5LXNlYXJjaC5qc1xuICoqIG1vZHVsZSBpZCA9IDFcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_dog_fooding.js b/devtools/shared/sourcemap/tests/unit/test_dog_fooding.js
new file mode 100644
index 000000000..ae94a0cd8
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_dog_fooding.js
@@ -0,0 +1,2985 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(1);
+ var SourceMapConsumer = __webpack_require__(3).SourceMapConsumer;
+ var SourceMapGenerator = __webpack_require__(9).SourceMapGenerator;
+
+ exports['test eating our own dog food'] = function (assert) {
+ var smg = new SourceMapGenerator({
+ file: 'testing.js',
+ sourceRoot: '/wu/tang'
+ });
+
+ smg.addMapping({
+ source: 'gza.coffee',
+ original: { line: 1, column: 0 },
+ generated: { line: 2, column: 2 }
+ });
+
+ smg.addMapping({
+ source: 'gza.coffee',
+ original: { line: 2, column: 0 },
+ generated: { line: 3, column: 2 }
+ });
+
+ smg.addMapping({
+ source: 'gza.coffee',
+ original: { line: 3, column: 0 },
+ generated: { line: 4, column: 2 }
+ });
+
+ smg.addMapping({
+ source: 'gza.coffee',
+ original: { line: 4, column: 0 },
+ generated: { line: 5, column: 2 }
+ });
+
+ smg.addMapping({
+ source: 'gza.coffee',
+ original: { line: 5, column: 10 },
+ generated: { line: 6, column: 12 }
+ });
+
+ var smc = new SourceMapConsumer(smg.toString());
+
+ // Exact
+ util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert);
+ util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert);
+ util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert);
+ util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert);
+ util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert);
+
+ // Fuzzy
+
+ // Generated to original with default (glb) bias.
+ util.assertMapping(2, 0, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, null, smc, assert, true);
+ util.assertMapping(3, 0, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, null, smc, assert, true);
+ util.assertMapping(4, 0, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, null, smc, assert, true);
+ util.assertMapping(5, 0, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, null, smc, assert, true);
+ util.assertMapping(6, 0, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(6, 9, null, null, null, null, null, smc, assert, true);
+ util.assertMapping(6, 13, '/wu/tang/gza.coffee', 5, 10, null, null, smc, assert, true);
+
+ // Generated to original with lub bias.
+ util.assertMapping(2, 0, '/wu/tang/gza.coffee', 1, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(2, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(3, 0, '/wu/tang/gza.coffee', 2, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(3, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(4, 0, '/wu/tang/gza.coffee', 3, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(4, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(5, 0, '/wu/tang/gza.coffee', 4, 0, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(5, 9, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(6, 0, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(6, 9, '/wu/tang/gza.coffee', 5, 10, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+ util.assertMapping(6, 13, null, null, null, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, true);
+
+ // Original to generated with default (glb) bias
+ util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, null, smc, assert, null, true);
+ util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, null, smc, assert, null, true);
+ util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, null, smc, assert, null, true);
+ util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, null, smc, assert, null, true);
+ util.assertMapping(5, 2, '/wu/tang/gza.coffee', 5, 9, null, null, smc, assert, null, true);
+ util.assertMapping(6, 12, '/wu/tang/gza.coffee', 6, 19, null, null, smc, assert, null, true);
+
+ // Original to generated with lub bias.
+ util.assertMapping(3, 2, '/wu/tang/gza.coffee', 1, 1, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ util.assertMapping(4, 2, '/wu/tang/gza.coffee', 2, 3, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ util.assertMapping(5, 2, '/wu/tang/gza.coffee', 3, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ util.assertMapping(6, 12, '/wu/tang/gza.coffee', 4, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ util.assertMapping(6, 12, '/wu/tang/gza.coffee', 5, 9, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ util.assertMapping(null, null, '/wu/tang/gza.coffee', 6, 19, null, SourceMapConsumer.LEAST_UPPER_BOUND, smc, assert, null, true);
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ // This is a test mapping which maps functions from two different files
+ // (one.js and two.js) to a minified generated source.
+ //
+ // Here is one.js:
+ //
+ // ONE.foo = function (bar) {
+ // return baz(bar);
+ // };
+ //
+ // Here is two.js:
+ //
+ // TWO.inc = function (n) {
+ // return n + 1;
+ // };
+ //
+ // And here is the generated code (min.js):
+ //
+ // ONE.foo=function(a){return baz(a);};
+ // TWO.inc=function(a){return a+1;};
+ exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
+ " TWO.inc=function(a){return a+1;};";
+ exports.testMap = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapNoSourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapEmptySourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ // This mapping is identical to above, but uses the indexed format instead.
+ exports.indexedTestMap = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ }
+ ]
+ };
+ exports.indexedTestMapDifferentSourceRoots = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/different/root"
+ }
+ }
+ ]
+ };
+ exports.testMapWithSourcesContent = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapRelativeSources = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['./one.js', './two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.emptyMap = {
+ version: 3,
+ file: 'min.js',
+ names: [],
+ sources: [],
+ mappings: ''
+ };
+
+
+ function assertMapping(generatedLine, generatedColumn, originalSource,
+ originalLine, originalColumn, name, bias, map, assert,
+ dontTestGenerated, dontTestOriginal) {
+ if (!dontTestOriginal) {
+ var origMapping = map.originalPositionFor({
+ line: generatedLine,
+ column: generatedColumn,
+ bias: bias
+ });
+ assert.equal(origMapping.name, name,
+ 'Incorrect name, expected ' + JSON.stringify(name)
+ + ', got ' + JSON.stringify(origMapping.name));
+ assert.equal(origMapping.line, originalLine,
+ 'Incorrect line, expected ' + JSON.stringify(originalLine)
+ + ', got ' + JSON.stringify(origMapping.line));
+ assert.equal(origMapping.column, originalColumn,
+ 'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ + ', got ' + JSON.stringify(origMapping.column));
+
+ var expectedSource;
+
+ if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
+ expectedSource = originalSource;
+ } else if (originalSource) {
+ expectedSource = map.sourceRoot
+ ? util.join(map.sourceRoot, originalSource)
+ : originalSource;
+ } else {
+ expectedSource = null;
+ }
+
+ assert.equal(origMapping.source, expectedSource,
+ 'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ + ', got ' + JSON.stringify(origMapping.source));
+ }
+
+ if (!dontTestGenerated) {
+ var genMapping = map.generatedPositionFor({
+ source: originalSource,
+ line: originalLine,
+ column: originalColumn,
+ bias: bias
+ });
+ assert.equal(genMapping.line, generatedLine,
+ 'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ + ', got ' + JSON.stringify(genMapping.line));
+ assert.equal(genMapping.column, generatedColumn,
+ 'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ + ', got ' + JSON.stringify(genMapping.column));
+ }
+ }
+ exports.assertMapping = assertMapping;
+
+ function assertEqualMaps(assert, actualMap, expectedMap) {
+ assert.equal(actualMap.version, expectedMap.version, "version mismatch");
+ assert.equal(actualMap.file, expectedMap.file, "file mismatch");
+ assert.equal(actualMap.names.length,
+ expectedMap.names.length,
+ "names length mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ for (var i = 0; i < actualMap.names.length; i++) {
+ assert.equal(actualMap.names[i],
+ expectedMap.names[i],
+ "names[" + i + "] mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ }
+ assert.equal(actualMap.sources.length,
+ expectedMap.sources.length,
+ "sources length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ for (var i = 0; i < actualMap.sources.length; i++) {
+ assert.equal(actualMap.sources[i],
+ expectedMap.sources[i],
+ "sources[" + i + "] length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ }
+ assert.equal(actualMap.sourceRoot,
+ expectedMap.sourceRoot,
+ "sourceRoot mismatch: " +
+ actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
+ assert.equal(actualMap.mappings, expectedMap.mappings,
+ "mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
+ if (actualMap.sourcesContent) {
+ assert.equal(actualMap.sourcesContent.length,
+ expectedMap.sourcesContent.length,
+ "sourcesContent length mismatch");
+ for (var i = 0; i < actualMap.sourcesContent.length; i++) {
+ assert.equal(actualMap.sourcesContent[i],
+ expectedMap.sourcesContent[i],
+ "sourcesContent[" + i + "] mismatch");
+ }
+ }
+ }
+ exports.assertEqualMaps = assertEqualMaps;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+ var binarySearch = __webpack_require__(4);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var base64VLQ = __webpack_require__(6);
+ var quickSort = __webpack_require__(8).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(7);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(6);
+ var util = __webpack_require__(2);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var MappingList = __webpack_require__(10).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgZjg0ZTU2YjRjYmQ3YWQ2NzQ5MjAiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LWRvZy1mb29kaW5nLmpzIiwid2VicGFjazovLy8uL3Rlc3QvdXRpbC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvdXRpbC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW1hcC1jb25zdW1lci5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmluYXJ5LXNlYXJjaC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYXJyYXktc2V0LmpzIiwid2VicGFjazovLy8uL2xpYi9iYXNlNjQtdmxxLmpzIiwid2VicGFjazovLy8uL2xpYi9iYXNlNjQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3F1aWNrLXNvcnQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3NvdXJjZS1tYXAtZ2VuZXJhdG9yLmpzIiwid2VicGFjazovLy8uL2xpYi9tYXBwaW5nLWxpc3QuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1QkFBZTtBQUNmO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdENBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0Esa0JBQWlCLHFCQUFxQjtBQUN0QyxtQkFBa0I7QUFDbEIsTUFBSzs7QUFFTDtBQUNBO0FBQ0Esa0JBQWlCLHFCQUFxQjtBQUN0QyxtQkFBa0I7QUFDbEIsTUFBSzs7QUFFTDtBQUNBO0FBQ0Esa0JBQWlCLHFCQUFxQjtBQUN0QyxtQkFBa0I7QUFDbEIsTUFBSzs7QUFFTDtBQUNBO0FBQ0Esa0JBQWlCLHFCQUFxQjtBQUN0QyxtQkFBa0I7QUFDbEIsTUFBSzs7QUFFTDtBQUNBO0FBQ0Esa0JBQWlCLHNCQUFzQjtBQUN2QyxtQkFBa0I7QUFDbEIsTUFBSzs7QUFFTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNwR0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNEJBQTJCO0FBQzNCLDRCQUEyQjtBQUMzQixxREFBb0QsZ0JBQWdCO0FBQ3BFLHFEQUFvRCxhQUFhO0FBQ2pFO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDO0FBQ3hDLGlDQUFnQztBQUNoQyxpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVDQUFzQztBQUN0Qyw4QkFBNkI7QUFDN0IsaUJBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF3QztBQUN4QyxpQ0FBZ0M7QUFDaEMsaUJBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBc0M7QUFDdEMsOEJBQTZCO0FBQzdCLGlCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUNBQWtDO0FBQ2xDLDJCQUEwQjtBQUMxQixXQUFVO0FBQ1YsaUNBQWdDO0FBQ2hDLHdCQUF1QjtBQUN2QixXQUFVO0FBQ1Y7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFrQztBQUNsQywyQkFBMEI7QUFDMUIsV0FBVTtBQUNWLGlDQUFnQztBQUNoQyx3QkFBdUI7QUFDdkIsV0FBVTtBQUNWO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW1CLDRCQUE0QjtBQUMvQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBbUIsOEJBQThCO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQixxQ0FBcUM7QUFDMUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQ3ZTQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsaURBQWdELFFBQVE7QUFDeEQ7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDaFhBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5REFBd0Q7QUFDeEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQSxzQkFBcUI7QUFDckI7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWE7O0FBRWI7QUFDQTtBQUNBLFVBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYTs7QUFFYjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOEJBQTZCLE1BQU07QUFDbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5REFBd0Q7QUFDeEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPOztBQUVQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSx5REFBd0QsWUFBWTtBQUNwRTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLHNDQUFxQztBQUNyQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNEJBQTJCLGNBQWM7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUF5Qix3Q0FBd0M7QUFDakU7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtEQUFpRCxtQkFBbUIsRUFBRTtBQUN0RTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBbUIsb0JBQW9CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQ0FBK0IsTUFBTTtBQUNyQztBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hELHdCQUF1QiwrQ0FBK0M7QUFDdEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLFVBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLDJCQUEyQjtBQUNoRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hEO0FBQ0E7QUFDQSx3QkFBdUIsNEJBQTRCO0FBQ25EOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDempDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7Ozs7OztBQy9HQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBd0MsU0FBUztBQUNqRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQ3ZHQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0REFBMkQ7QUFDM0QscUJBQW9CO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDNUlBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixzQkFBcUI7QUFDckIsdUJBQXNCOztBQUV0QixtQkFBa0I7QUFDbEIsbUJBQWtCOztBQUVsQixtQkFBa0I7QUFDbEIsb0JBQW1COztBQUVuQjtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbkVBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQixPQUFPO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbEhBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUEsUUFBTztBQUNQO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw2Q0FBNEMsU0FBUztBQUNyRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5QkFBd0I7QUFDeEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQzNZQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFrQjtBQUNsQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSIsImZpbGUiOiJ0ZXN0X2RvZ19mb29kaW5nLmpzIiwic291cmNlc0NvbnRlbnQiOlsiIFx0Ly8gVGhlIG1vZHVsZSBjYWNoZVxuIFx0dmFyIGluc3RhbGxlZE1vZHVsZXMgPSB7fTtcblxuIFx0Ly8gVGhlIHJlcXVpcmUgZnVuY3Rpb25cbiBcdGZ1bmN0aW9uIF9fd2VicGFja19yZXF1aXJlX18obW9kdWxlSWQpIHtcblxuIFx0XHQvLyBDaGVjayBpZiBtb2R1bGUgaXMgaW4gY2FjaGVcbiBcdFx0aWYoaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0pXG4gXHRcdFx0cmV0dXJuIGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdLmV4cG9ydHM7XG5cbiBcdFx0Ly8gQ3JlYXRlIGEgbmV3IG1vZHVsZSAoYW5kIHB1dCBpdCBpbnRvIHRoZSBjYWNoZSlcbiBcdFx0dmFyIG1vZHVsZSA9IGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdID0ge1xuIFx0XHRcdGV4cG9ydHM6IHt9LFxuIFx0XHRcdGlkOiBtb2R1bGVJZCxcbiBcdFx0XHRsb2FkZWQ6IGZhbHNlXG4gXHRcdH07XG5cbiBcdFx0Ly8gRXhlY3V0ZSB0aGUgbW9kdWxlIGZ1bmN0aW9uXG4gXHRcdG1vZHVsZXNbbW9kdWxlSWRdLmNhbGwobW9kdWxlLmV4cG9ydHMsIG1vZHVsZSwgbW9kdWxlLmV4cG9ydHMsIF9fd2VicGFja19yZXF1aXJlX18pO1xuXG4gXHRcdC8vIEZsYWcgdGhlIG1vZHVsZSBhcyBsb2FkZWRcbiBcdFx0bW9kdWxlLmxvYWRlZCA9IHRydWU7XG5cbiBcdFx0Ly8gUmV0dXJuIHRoZSBleHBvcnRzIG9mIHRoZSBtb2R1bGVcbiBcdFx0cmV0dXJuIG1vZHVsZS5leHBvcnRzO1xuIFx0fVxuXG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlcyBvYmplY3QgKF9fd2VicGFja19tb2R1bGVzX18pXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLm0gPSBtb2R1bGVzO1xuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZSBjYWNoZVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5jID0gaW5zdGFsbGVkTW9kdWxlcztcblxuIFx0Ly8gX193ZWJwYWNrX3B1YmxpY19wYXRoX19cbiBcdF9fd2VicGFja19yZXF1aXJlX18ucCA9IFwiXCI7XG5cbiBcdC8vIExvYWQgZW50cnkgbW9kdWxlIGFuZCByZXR1cm4gZXhwb3J0c1xuIFx0cmV0dXJuIF9fd2VicGFja19yZXF1aXJlX18oMCk7XG5cblxuXG4vKiogV0VCUEFDSyBGT09URVIgKipcbiAqKiB3ZWJwYWNrL2Jvb3RzdHJhcCBmODRlNTZiNGNiZDdhZDY3NDkyMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKFwiLi91dGlsXCIpO1xuICB2YXIgU291cmNlTWFwQ29uc3VtZXIgPSByZXF1aXJlKCcuLi9saWIvc291cmNlLW1hcC1jb25zdW1lcicpLlNvdXJjZU1hcENvbnN1bWVyO1xuICB2YXIgU291cmNlTWFwR2VuZXJhdG9yID0gcmVxdWlyZSgnLi4vbGliL3NvdXJjZS1tYXAtZ2VuZXJhdG9yJykuU291cmNlTWFwR2VuZXJhdG9yO1xuXG4gIGV4cG9ydHNbJ3Rlc3QgZWF0aW5nIG91ciBvd24gZG9nIGZvb2QnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc21nID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAndGVzdGluZy5qcycsXG4gICAgICBzb3VyY2VSb290OiAnL3d1L3RhbmcnXG4gICAgfSk7XG5cbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdnemEuY29mZmVlJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9XG4gICAgfSk7XG5cbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdnemEuY29mZmVlJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMCB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDMsIGNvbHVtbjogMiB9XG4gICAgfSk7XG5cbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdnemEuY29mZmVlJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDMsIGNvbHVtbjogMCB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDQsIGNvbHVtbjogMiB9XG4gICAgfSk7XG5cbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdnemEuY29mZmVlJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDQsIGNvbHVtbjogMCB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDUsIGNvbHVtbjogMiB9XG4gICAgfSk7XG5cbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdnemEuY29mZmVlJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDUsIGNvbHVtbjogMTAgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiA2LCBjb2x1bW46IDEyIH1cbiAgICB9KTtcblxuICAgIHZhciBzbWMgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIoc21nLnRvU3RyaW5nKCkpO1xuXG4gICAgLy8gRXhhY3RcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCAxLCAwLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDMsIDIsICcvd3UvdGFuZy9nemEuY29mZmVlJywgMiwgMCwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg0LCAyLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDMsIDAsIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNSwgMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCA0LCAwLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDYsIDEyLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDUsIDEwLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCk7XG5cbiAgICAvLyBGdXp6eVxuXG4gICAgLy8gR2VuZXJhdGVkIHRvIG9yaWdpbmFsIHdpdGggZGVmYXVsdCAoZ2xiKSBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAwLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDksICcvd3UvdGFuZy9nemEuY29mZmVlJywgMSwgMCwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygzLCAwLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDMsIDksICcvd3UvdGFuZy9nemEuY29mZmVlJywgMiwgMCwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg0LCAwLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDQsIDksICcvd3UvdGFuZy9nemEuY29mZmVlJywgMywgMCwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg1LCAwLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDUsIDksICcvd3UvdGFuZy9nemEuY29mZmVlJywgNCwgMCwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg2LCAwLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDYsIDksIG51bGwsIG51bGwsIG51bGwsIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNiwgMTMsICcvd3UvdGFuZy9nemEuY29mZmVlJywgNSwgMTAsIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0LCB0cnVlKTtcblxuICAgIC8vIEdlbmVyYXRlZCB0byBvcmlnaW5hbCB3aXRoIGx1YiBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAwLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDEsIDAsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDksIG51bGwsIG51bGwsIG51bGwsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDMsIDAsICcvd3UvdGFuZy9nemEuY29mZmVlJywgMiwgMCwgbnVsbCwgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQsIHNtYywgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMywgOSwgbnVsbCwgbnVsbCwgbnVsbCwgbnVsbCwgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQsIHNtYywgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNCwgMCwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCAzLCAwLCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg0LCA5LCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg1LCAwLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDQsIDAsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDUsIDksIG51bGwsIG51bGwsIG51bGwsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDYsIDAsICcvd3UvdGFuZy9nemEuY29mZmVlJywgNSwgMTAsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDYsIDksICcvd3UvdGFuZy9nemEuY29mZmVlJywgNSwgMTAsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDYsIDEzLCBudWxsLCBudWxsLCBudWxsLCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIHRydWUpO1xuXG4gICAgLy8gT3JpZ2luYWwgdG8gZ2VuZXJhdGVkIHdpdGggZGVmYXVsdCAoZ2xiKSBiaWFzXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDIsICcvd3UvdGFuZy9nemEuY29mZmVlJywgMSwgMSwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygzLCAyLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDIsIDMsIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0LCBudWxsLCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNCwgMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCAzLCA2LCBudWxsLCBudWxsLCBzbWMsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDUsIDIsICcvd3UvdGFuZy9nemEuY29mZmVlJywgNCwgOSwgbnVsbCwgbnVsbCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg1LCAyLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDUsIDksIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0LCBudWxsLCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNiwgMTIsICcvd3UvdGFuZy9nemEuY29mZmVlJywgNiwgMTksIG51bGwsIG51bGwsIHNtYywgYXNzZXJ0LCBudWxsLCB0cnVlKTtcblxuICAgIC8vIE9yaWdpbmFsIHRvIGdlbmVyYXRlZCB3aXRoIGx1YiBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygzLCAyLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDEsIDEsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBzbWMsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDQsIDIsICcvd3UvdGFuZy9nemEuY29mZmVlJywgMiwgMywgbnVsbCwgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQsIHNtYywgYXNzZXJ0LCBudWxsLCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoNSwgMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCAzLCA2LCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg2LCAxMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCA0LCA5LCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyg2LCAxMiwgJy93dS90YW5nL2d6YS5jb2ZmZWUnLCA1LCA5LCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZyhudWxsLCBudWxsLCAnL3d1L3RhbmcvZ3phLmNvZmZlZScsIDYsIDE5LCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgc21jLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1kb2ctZm9vZGluZy5qc1xuICoqIG1vZHVsZSBpZCA9IDBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuLi9saWIvdXRpbCcpO1xuXG4gIC8vIFRoaXMgaXMgYSB0ZXN0IG1hcHBpbmcgd2hpY2ggbWFwcyBmdW5jdGlvbnMgZnJvbSB0d28gZGlmZmVyZW50IGZpbGVzXG4gIC8vIChvbmUuanMgYW5kIHR3by5qcykgdG8gYSBtaW5pZmllZCBnZW5lcmF0ZWQgc291cmNlLlxuICAvL1xuICAvLyBIZXJlIGlzIG9uZS5qczpcbiAgLy9cbiAgLy8gICBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xuICAvLyAgICAgcmV0dXJuIGJheihiYXIpO1xuICAvLyAgIH07XG4gIC8vXG4gIC8vIEhlcmUgaXMgdHdvLmpzOlxuICAvL1xuICAvLyAgIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xuICAvLyAgICAgcmV0dXJuIG4gKyAxO1xuICAvLyAgIH07XG4gIC8vXG4gIC8vIEFuZCBoZXJlIGlzIHRoZSBnZW5lcmF0ZWQgY29kZSAobWluLmpzKTpcbiAgLy9cbiAgLy8gICBPTkUuZm9vPWZ1bmN0aW9uKGEpe3JldHVybiBiYXooYSk7fTtcbiAgLy8gICBUV08uaW5jPWZ1bmN0aW9uKGEpe3JldHVybiBhKzE7fTtcbiAgZXhwb3J0cy50ZXN0R2VuZXJhdGVkQ29kZSA9IFwiIE9ORS5mb289ZnVuY3Rpb24oYSl7cmV0dXJuIGJheihhKTt9O1xcblwiK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCIgVFdPLmluYz1mdW5jdGlvbihhKXtyZXR1cm4gYSsxO307XCI7XG4gIGV4cG9ydHMudGVzdE1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcE5vU291cmNlUm9vdCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcEVtcHR5U291cmNlUm9vdCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIHNvdXJjZVJvb3Q6ICcnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIC8vIFRoaXMgbWFwcGluZyBpcyBpZGVudGljYWwgdG8gYWJvdmUsIGJ1dCB1c2VzIHRoZSBpbmRleGVkIGZvcm1hdCBpbnN0ZWFkLlxuICBleHBvcnRzLmluZGV4ZWRUZXN0TWFwID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgc2VjdGlvbnM6IFtcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMCxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcIm9uZS5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICAgICAgICcgfTsnLFxuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiYmFyXCIsXG4gICAgICAgICAgICBcImJhelwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRFwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvdGhlL3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAge1xuICAgICAgICBvZmZzZXQ6IHtcbiAgICAgICAgICBsaW5lOiAxLFxuICAgICAgICAgIGNvbHVtbjogMFxuICAgICAgICB9LFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICB2ZXJzaW9uOiAzLFxuICAgICAgICAgIHNvdXJjZXM6IFtcbiAgICAgICAgICAgIFwidHdvLmpzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAgICAgICAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbicgK1xuICAgICAgICAgICAgJyAgIHJldHVybiBuICsgMTtcXG4nICtcbiAgICAgICAgICAgICcgfTsnXG4gICAgICAgICAgXSxcbiAgICAgICAgICBuYW1lczogW1xuICAgICAgICAgICAgXCJuXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIG1hcHBpbmdzOiBcIkNBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9BXCIsXG4gICAgICAgICAgZmlsZTogXCJtaW4uanNcIixcbiAgICAgICAgICBzb3VyY2VSb290OiBcIi90aGUvcm9vdFwiXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICBdXG4gIH07XG4gIGV4cG9ydHMuaW5kZXhlZFRlc3RNYXBEaWZmZXJlbnRTb3VyY2VSb290cyA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIHNlY3Rpb25zOiBbXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDAsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJvbmUuanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAgICAgICAnIH07JyxcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcImJhclwiLFxuICAgICAgICAgICAgXCJiYXpcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSURcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL3RoZS9yb290XCJcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMSxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcInR3by5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAgICAgICAnIH07J1xuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiblwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQVwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvZGlmZmVyZW50L3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgXVxuICB9O1xuICBleHBvcnRzLnRlc3RNYXBXaXRoU291cmNlc0NvbnRlbnQgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICcgfTsnLFxuICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAnIH07J1xuICAgIF0sXG4gICAgc291cmNlUm9vdDogJy90aGUvcm9vdCcsXG4gICAgbWFwcGluZ3M6ICdDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRDtDQ0RiLElBQUksSUFBTSxTQUFVRSxHQUNsQixPQUFPQSdcbiAgfTtcbiAgZXhwb3J0cy50ZXN0TWFwUmVsYXRpdmVTb3VyY2VzID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFsnYmFyJywgJ2JheicsICduJ10sXG4gICAgc291cmNlczogWycuL29uZS5qcycsICcuL3R3by5qcyddLFxuICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuJyArXG4gICAgICAnICAgcmV0dXJuIGJheihiYXIpO1xcbicgK1xuICAgICAgJyB9OycsXG4gICAgICAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBuICsgMTtcXG4nICtcbiAgICAgICcgfTsnXG4gICAgXSxcbiAgICBzb3VyY2VSb290OiAnL3RoZS9yb290JyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLmVtcHR5TWFwID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFtdLFxuICAgIHNvdXJjZXM6IFtdLFxuICAgIG1hcHBpbmdzOiAnJ1xuICB9O1xuXG5cbiAgZnVuY3Rpb24gYXNzZXJ0TWFwcGluZyhnZW5lcmF0ZWRMaW5lLCBnZW5lcmF0ZWRDb2x1bW4sIG9yaWdpbmFsU291cmNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsTGluZSwgb3JpZ2luYWxDb2x1bW4sIG5hbWUsIGJpYXMsIG1hcCwgYXNzZXJ0LFxuICAgICAgICAgICAgICAgICAgICAgICAgIGRvbnRUZXN0R2VuZXJhdGVkLCBkb250VGVzdE9yaWdpbmFsKSB7XG4gICAgaWYgKCFkb250VGVzdE9yaWdpbmFsKSB7XG4gICAgICB2YXIgb3JpZ01hcHBpbmcgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICAgIGxpbmU6IGdlbmVyYXRlZExpbmUsXG4gICAgICAgIGNvbHVtbjogZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICBiaWFzOiBiaWFzXG4gICAgICB9KTtcbiAgICAgIGFzc2VydC5lcXVhbChvcmlnTWFwcGluZy5uYW1lLCBuYW1lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbmFtZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG5hbWUpXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdNYXBwaW5nLm5hbWUpKTtcbiAgICAgIGFzc2VydC5lcXVhbChvcmlnTWFwcGluZy5saW5lLCBvcmlnaW5hbExpbmUsXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBsaW5lLCBleHBlY3RlZCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ2luYWxMaW5lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5saW5lKSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcuY29sdW1uLCBvcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IGNvbHVtbiwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdpbmFsQ29sdW1uKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5jb2x1bW4pKTtcblxuICAgICAgdmFyIGV4cGVjdGVkU291cmNlO1xuXG4gICAgICBpZiAob3JpZ2luYWxTb3VyY2UgJiYgbWFwLnNvdXJjZVJvb3QgJiYgb3JpZ2luYWxTb3VyY2UuaW5kZXhPZihtYXAuc291cmNlUm9vdCkgPT09IDApIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBvcmlnaW5hbFNvdXJjZTtcbiAgICAgIH0gZWxzZSBpZiAob3JpZ2luYWxTb3VyY2UpIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBtYXAuc291cmNlUm9vdFxuICAgICAgICAgID8gdXRpbC5qb2luKG1hcC5zb3VyY2VSb290LCBvcmlnaW5hbFNvdXJjZSlcbiAgICAgICAgICA6IG9yaWdpbmFsU291cmNlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBudWxsO1xuICAgICAgfVxuXG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcuc291cmNlLCBleHBlY3RlZFNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IHNvdXJjZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KGV4cGVjdGVkU291cmNlKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5zb3VyY2UpKTtcbiAgICB9XG5cbiAgICBpZiAoIWRvbnRUZXN0R2VuZXJhdGVkKSB7XG4gICAgICB2YXIgZ2VuTWFwcGluZyA9IG1hcC5nZW5lcmF0ZWRQb3NpdGlvbkZvcih7XG4gICAgICAgIHNvdXJjZTogb3JpZ2luYWxTb3VyY2UsXG4gICAgICAgIGxpbmU6IG9yaWdpbmFsTGluZSxcbiAgICAgICAgY29sdW1uOiBvcmlnaW5hbENvbHVtbixcbiAgICAgICAgYmlhczogYmlhc1xuICAgICAgfSk7XG4gICAgICBhc3NlcnQuZXF1YWwoZ2VuTWFwcGluZy5saW5lLCBnZW5lcmF0ZWRMaW5lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbGluZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KGdlbmVyYXRlZExpbmUpXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KGdlbk1hcHBpbmcubGluZSkpO1xuICAgICAgYXNzZXJ0LmVxdWFsKGdlbk1hcHBpbmcuY29sdW1uLCBnZW5lcmF0ZWRDb2x1bW4sXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBjb2x1bW4sIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShnZW5lcmF0ZWRDb2x1bW4pXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KGdlbk1hcHBpbmcuY29sdW1uKSk7XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuYXNzZXJ0TWFwcGluZyA9IGFzc2VydE1hcHBpbmc7XG5cbiAgZnVuY3Rpb24gYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgYWN0dWFsTWFwLCBleHBlY3RlZE1hcCkge1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAudmVyc2lvbiwgZXhwZWN0ZWRNYXAudmVyc2lvbiwgXCJ2ZXJzaW9uIG1pc21hdGNoXCIpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAuZmlsZSwgZXhwZWN0ZWRNYXAuZmlsZSwgXCJmaWxlIG1pc21hdGNoXCIpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAubmFtZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5uYW1lcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIFwibmFtZXMgbGVuZ3RoIG1pc21hdGNoOiBcIiArXG4gICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAubmFtZXMuam9pbihcIiwgXCIpKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5uYW1lcy5sZW5ndGg7IGkrKykge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5uYW1lc1tpXSxcbiAgICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5uYW1lc1tpXSxcbiAgICAgICAgICAgICAgICAgICBcIm5hbWVzW1wiICsgaSArIFwiXSBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAubmFtZXMuam9pbihcIiwgXCIpKTtcbiAgICB9XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzLmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIFwic291cmNlcyBsZW5ndGggbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5zb3VyY2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc1tpXSxcbiAgICAgICAgICAgICAgICAgICBcInNvdXJjZXNbXCIgKyBpICsgXCJdIGxlbmd0aCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5zb3VyY2VzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAuc291cmNlcy5qb2luKFwiLCBcIikpO1xuICAgIH1cbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZVJvb3QsXG4gICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZVJvb3QsXG4gICAgICAgICAgICAgICAgIFwic291cmNlUm9vdCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5zb3VyY2VSb290ICsgXCIgIT0gXCIgKyBleHBlY3RlZE1hcC5zb3VyY2VSb290KTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLm1hcHBpbmdzLCBleHBlY3RlZE1hcC5tYXBwaW5ncyxcbiAgICAgICAgICAgICAgICAgXCJtYXBwaW5ncyBtaXNtYXRjaDpcXG5BY3R1YWw6ICAgXCIgKyBhY3R1YWxNYXAubWFwcGluZ3MgKyBcIlxcbkV4cGVjdGVkOiBcIiArIGV4cGVjdGVkTWFwLm1hcHBpbmdzKTtcbiAgICBpZiAoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50KSB7XG4gICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50Lmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzQ29udGVudCBsZW5ndGggbWlzbWF0Y2hcIik7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGg7IGkrKykge1xuICAgICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50W2ldLFxuICAgICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc0NvbnRlbnRbaV0sXG4gICAgICAgICAgICAgICAgICAgICBcInNvdXJjZXNDb250ZW50W1wiICsgaSArIFwiXSBtaXNtYXRjaFwiKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZXhwb3J0cy5hc3NlcnRFcXVhbE1hcHMgPSBhc3NlcnRFcXVhbE1hcHM7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC91dGlsLmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICAvKipcbiAgICogVGhpcyBpcyBhIGhlbHBlciBmdW5jdGlvbiBmb3IgZ2V0dGluZyB2YWx1ZXMgZnJvbSBwYXJhbWV0ZXIvb3B0aW9uc1xuICAgKiBvYmplY3RzLlxuICAgKlxuICAgKiBAcGFyYW0gYXJncyBUaGUgb2JqZWN0IHdlIGFyZSBleHRyYWN0aW5nIHZhbHVlcyBmcm9tXG4gICAqIEBwYXJhbSBuYW1lIFRoZSBuYW1lIG9mIHRoZSBwcm9wZXJ0eSB3ZSBhcmUgZ2V0dGluZy5cbiAgICogQHBhcmFtIGRlZmF1bHRWYWx1ZSBBbiBvcHRpb25hbCB2YWx1ZSB0byByZXR1cm4gaWYgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmdcbiAgICogZnJvbSB0aGUgb2JqZWN0LiBJZiB0aGlzIGlzIG5vdCBzcGVjaWZpZWQgYW5kIHRoZSBwcm9wZXJ0eSBpcyBtaXNzaW5nLCBhblxuICAgKiBlcnJvciB3aWxsIGJlIHRocm93bi5cbiAgICovXG4gIGZ1bmN0aW9uIGdldEFyZyhhQXJncywgYU5hbWUsIGFEZWZhdWx0VmFsdWUpIHtcbiAgICBpZiAoYU5hbWUgaW4gYUFyZ3MpIHtcbiAgICAgIHJldHVybiBhQXJnc1thTmFtZV07XG4gICAgfSBlbHNlIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAzKSB7XG4gICAgICByZXR1cm4gYURlZmF1bHRWYWx1ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhTmFtZSArICdcIiBpcyBhIHJlcXVpcmVkIGFyZ3VtZW50LicpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmdldEFyZyA9IGdldEFyZztcblxuICB2YXIgdXJsUmVnZXhwID0gL14oPzooW1xcdytcXC0uXSspOik/XFwvXFwvKD86KFxcdys6XFx3KylAKT8oW1xcdy5dKikoPzo6KFxcZCspKT8oXFxTKikkLztcbiAgdmFyIGRhdGFVcmxSZWdleHAgPSAvXmRhdGE6LitcXCwuKyQvO1xuXG4gIGZ1bmN0aW9uIHVybFBhcnNlKGFVcmwpIHtcbiAgICB2YXIgbWF0Y2ggPSBhVXJsLm1hdGNoKHVybFJlZ2V4cCk7XG4gICAgaWYgKCFtYXRjaCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBzY2hlbWU6IG1hdGNoWzFdLFxuICAgICAgYXV0aDogbWF0Y2hbMl0sXG4gICAgICBob3N0OiBtYXRjaFszXSxcbiAgICAgIHBvcnQ6IG1hdGNoWzRdLFxuICAgICAgcGF0aDogbWF0Y2hbNV1cbiAgICB9O1xuICB9XG4gIGV4cG9ydHMudXJsUGFyc2UgPSB1cmxQYXJzZTtcblxuICBmdW5jdGlvbiB1cmxHZW5lcmF0ZShhUGFyc2VkVXJsKSB7XG4gICAgdmFyIHVybCA9ICcnO1xuICAgIGlmIChhUGFyc2VkVXJsLnNjaGVtZSkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuc2NoZW1lICsgJzonO1xuICAgIH1cbiAgICB1cmwgKz0gJy8vJztcbiAgICBpZiAoYVBhcnNlZFVybC5hdXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5hdXRoICsgJ0AnO1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5ob3N0KSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5ob3N0O1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wb3J0KSB7XG4gICAgICB1cmwgKz0gXCI6XCIgKyBhUGFyc2VkVXJsLnBvcnRcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwucGF0aCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwucGF0aDtcbiAgICB9XG4gICAgcmV0dXJuIHVybDtcbiAgfVxuICBleHBvcnRzLnVybEdlbmVyYXRlID0gdXJsR2VuZXJhdGU7XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBwYXRoLCBvciB0aGUgcGF0aCBwb3J0aW9uIG9mIGEgVVJMOlxuICAgKlxuICAgKiAtIFJlcGxhY2VzIGNvbnNlcXV0aXZlIHNsYXNoZXMgd2l0aCBvbmUgc2xhc2guXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnLicgcGFydHMuXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnPGRpcj4vLi4nIHBhcnRzLlxuICAgKlxuICAgKiBCYXNlZCBvbiBjb2RlIGluIHRoZSBOb2RlLmpzICdwYXRoJyBjb3JlIG1vZHVsZS5cbiAgICpcbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIHVybCB0byBub3JtYWxpemUuXG4gICAqL1xuICBmdW5jdGlvbiBub3JtYWxpemUoYVBhdGgpIHtcbiAgICB2YXIgcGF0aCA9IGFQYXRoO1xuICAgIHZhciB1cmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgaWYgKHVybCkge1xuICAgICAgaWYgKCF1cmwucGF0aCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG4gICAgICBwYXRoID0gdXJsLnBhdGg7XG4gICAgfVxuICAgIHZhciBpc0Fic29sdXRlID0gZXhwb3J0cy5pc0Fic29sdXRlKHBhdGgpO1xuXG4gICAgdmFyIHBhcnRzID0gcGF0aC5zcGxpdCgvXFwvKy8pO1xuICAgIGZvciAodmFyIHBhcnQsIHVwID0gMCwgaSA9IHBhcnRzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICBwYXJ0ID0gcGFydHNbaV07XG4gICAgICBpZiAocGFydCA9PT0gJy4nKSB7XG4gICAgICAgIHBhcnRzLnNwbGljZShpLCAxKTtcbiAgICAgIH0gZWxzZSBpZiAocGFydCA9PT0gJy4uJykge1xuICAgICAgICB1cCsrO1xuICAgICAgfSBlbHNlIGlmICh1cCA+IDApIHtcbiAgICAgICAgaWYgKHBhcnQgPT09ICcnKSB7XG4gICAgICAgICAgLy8gVGhlIGZpcnN0IHBhcnQgaXMgYmxhbmsgaWYgdGhlIHBhdGggaXMgYWJzb2x1dGUuIFRyeWluZyB0byBnb1xuICAgICAgICAgIC8vIGFib3ZlIHRoZSByb290IGlzIGEgbm8tb3AuIFRoZXJlZm9yZSB3ZSBjYW4gcmVtb3ZlIGFsbCAnLi4nIHBhcnRzXG4gICAgICAgICAgLy8gZGlyZWN0bHkgYWZ0ZXIgdGhlIHJvb3QuXG4gICAgICAgICAgcGFydHMuc3BsaWNlKGkgKyAxLCB1cCk7XG4gICAgICAgICAgdXAgPSAwO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHBhcnRzLnNwbGljZShpLCAyKTtcbiAgICAgICAgICB1cC0tO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHBhdGggPSBwYXJ0cy5qb2luKCcvJyk7XG5cbiAgICBpZiAocGF0aCA9PT0gJycpIHtcbiAgICAgIHBhdGggPSBpc0Fic29sdXRlID8gJy8nIDogJy4nO1xuICAgIH1cblxuICAgIGlmICh1cmwpIHtcbiAgICAgIHVybC5wYXRoID0gcGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZSh1cmwpO1xuICAgIH1cbiAgICByZXR1cm4gcGF0aDtcbiAgfVxuICBleHBvcnRzLm5vcm1hbGl6ZSA9IG5vcm1hbGl6ZTtcblxuICAvKipcbiAgICogSm9pbnMgdHdvIHBhdGhzL1VSTHMuXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBqb2luZWQgd2l0aCB0aGUgcm9vdC5cbiAgICpcbiAgICogLSBJZiBhUGF0aCBpcyBhIFVSTCBvciBhIGRhdGEgVVJJLCBhUGF0aCBpcyByZXR1cm5lZCwgdW5sZXNzIGFQYXRoIGlzIGFcbiAgICogICBzY2hlbWUtcmVsYXRpdmUgVVJMOiBUaGVuIHRoZSBzY2hlbWUgb2YgYVJvb3QsIGlmIGFueSwgaXMgcHJlcGVuZGVkXG4gICAqICAgZmlyc3QuXG4gICAqIC0gT3RoZXJ3aXNlIGFQYXRoIGlzIGEgcGF0aC4gSWYgYVJvb3QgaXMgYSBVUkwsIHRoZW4gaXRzIHBhdGggcG9ydGlvblxuICAgKiAgIGlzIHVwZGF0ZWQgd2l0aCB0aGUgcmVzdWx0IGFuZCBhUm9vdCBpcyByZXR1cm5lZC4gT3RoZXJ3aXNlIHRoZSByZXN1bHRcbiAgICogICBpcyByZXR1cm5lZC5cbiAgICogICAtIElmIGFQYXRoIGlzIGFic29sdXRlLCB0aGUgcmVzdWx0IGlzIGFQYXRoLlxuICAgKiAgIC0gT3RoZXJ3aXNlIHRoZSB0d28gcGF0aHMgYXJlIGpvaW5lZCB3aXRoIGEgc2xhc2guXG4gICAqIC0gSm9pbmluZyBmb3IgZXhhbXBsZSAnaHR0cDovLycgYW5kICd3d3cuZXhhbXBsZS5jb20nIGlzIGFsc28gc3VwcG9ydGVkLlxuICAgKi9cbiAgZnVuY3Rpb24gam9pbihhUm9vdCwgYVBhdGgpIHtcbiAgICBpZiAoYVJvb3QgPT09IFwiXCIpIHtcbiAgICAgIGFSb290ID0gXCIuXCI7XG4gICAgfVxuICAgIGlmIChhUGF0aCA9PT0gXCJcIikge1xuICAgICAgYVBhdGggPSBcIi5cIjtcbiAgICB9XG4gICAgdmFyIGFQYXRoVXJsID0gdXJsUGFyc2UoYVBhdGgpO1xuICAgIHZhciBhUm9vdFVybCA9IHVybFBhcnNlKGFSb290KTtcbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290ID0gYVJvb3RVcmwucGF0aCB8fCAnLyc7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oZm9vLCAnLy93d3cuZXhhbXBsZS5vcmcnKWBcbiAgICBpZiAoYVBhdGhVcmwgJiYgIWFQYXRoVXJsLnNjaGVtZSkge1xuICAgICAgaWYgKGFSb290VXJsKSB7XG4gICAgICAgIGFQYXRoVXJsLnNjaGVtZSA9IGFSb290VXJsLnNjaGVtZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUGF0aFVybCk7XG4gICAgfVxuXG4gICAgaWYgKGFQYXRoVXJsIHx8IGFQYXRoLm1hdGNoKGRhdGFVcmxSZWdleHApKSB7XG4gICAgICByZXR1cm4gYVBhdGg7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oJ2h0dHA6Ly8nLCAnd3d3LmV4YW1wbGUuY29tJylgXG4gICAgaWYgKGFSb290VXJsICYmICFhUm9vdFVybC5ob3N0ICYmICFhUm9vdFVybC5wYXRoKSB7XG4gICAgICBhUm9vdFVybC5ob3N0ID0gYVBhdGg7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cblxuICAgIHZhciBqb2luZWQgPSBhUGF0aC5jaGFyQXQoMCkgPT09ICcvJ1xuICAgICAgPyBhUGF0aFxuICAgICAgOiBub3JtYWxpemUoYVJvb3QucmVwbGFjZSgvXFwvKyQvLCAnJykgKyAnLycgKyBhUGF0aCk7XG5cbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290VXJsLnBhdGggPSBqb2luZWQ7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cbiAgICByZXR1cm4gam9pbmVkO1xuICB9XG4gIGV4cG9ydHMuam9pbiA9IGpvaW47XG5cbiAgZXhwb3J0cy5pc0Fic29sdXRlID0gZnVuY3Rpb24gKGFQYXRoKSB7XG4gICAgcmV0dXJuIGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nIHx8ICEhYVBhdGgubWF0Y2godXJsUmVnZXhwKTtcbiAgfTtcblxuICAvKipcbiAgICogTWFrZSBhIHBhdGggcmVsYXRpdmUgdG8gYSBVUkwgb3IgYW5vdGhlciBwYXRoLlxuICAgKlxuICAgKiBAcGFyYW0gYVJvb3QgVGhlIHJvb3QgcGF0aCBvciBVUkwuXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciBVUkwgdG8gYmUgbWFkZSByZWxhdGl2ZSB0byBhUm9vdC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlbGF0aXZlKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG5cbiAgICBhUm9vdCA9IGFSb290LnJlcGxhY2UoL1xcLyQvLCAnJyk7XG5cbiAgICAvLyBJdCBpcyBwb3NzaWJsZSBmb3IgdGhlIHBhdGggdG8gYmUgYWJvdmUgdGhlIHJvb3QuIEluIHRoaXMgY2FzZSwgc2ltcGx5XG4gICAgLy8gY2hlY2tpbmcgd2hldGhlciB0aGUgcm9vdCBpcyBhIHByZWZpeCBvZiB0aGUgcGF0aCB3b24ndCB3b3JrLiBJbnN0ZWFkLCB3ZVxuICAgIC8vIG5lZWQgdG8gcmVtb3ZlIGNvbXBvbmVudHMgZnJvbSB0aGUgcm9vdCBvbmUgYnkgb25lLCB1bnRpbCBlaXRoZXIgd2UgZmluZFxuICAgIC8vIGEgcHJlZml4IHRoYXQgZml0cywgb3Igd2UgcnVuIG91dCBvZiBjb21wb25lbnRzIHRvIHJlbW92ZS5cbiAgICB2YXIgbGV2ZWwgPSAwO1xuICAgIHdoaWxlIChhUGF0aC5pbmRleE9mKGFSb290ICsgJy8nKSAhPT0gMCkge1xuICAgICAgdmFyIGluZGV4ID0gYVJvb3QubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgIC8vIElmIHRoZSBvbmx5IHBhcnQgb2YgdGhlIHJvb3QgdGhhdCBpcyBsZWZ0IGlzIHRoZSBzY2hlbWUgKGkuZS4gaHR0cDovLyxcbiAgICAgIC8vIGZpbGU6Ly8vLCBldGMuKSwgb25lIG9yIG1vcmUgc2xhc2hlcyAoLyksIG9yIHNpbXBseSBub3RoaW5nIGF0IGFsbCwgd2VcbiAgICAgIC8vIGhhdmUgZXhoYXVzdGVkIGFsbCBjb21wb25lbnRzLCBzbyB0aGUgcGF0aCBpcyBub3QgcmVsYXRpdmUgdG8gdGhlIHJvb3QuXG4gICAgICBhUm9vdCA9IGFSb290LnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIGlmIChhUm9vdC5tYXRjaCgvXihbXlxcL10rOlxcLyk/XFwvKiQvKSkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgICsrbGV2ZWw7XG4gICAgfVxuXG4gICAgLy8gTWFrZSBzdXJlIHdlIGFkZCBhIFwiLi4vXCIgZm9yIGVhY2ggY29tcG9uZW50IHdlIHJlbW92ZWQgZnJvbSB0aGUgcm9vdC5cbiAgICByZXR1cm4gQXJyYXkobGV2ZWwgKyAxKS5qb2luKFwiLi4vXCIpICsgYVBhdGguc3Vic3RyKGFSb290Lmxlbmd0aCArIDEpO1xuICB9XG4gIGV4cG9ydHMucmVsYXRpdmUgPSByZWxhdGl2ZTtcblxuICAvKipcbiAgICogQmVjYXVzZSBiZWhhdmlvciBnb2VzIHdhY2t5IHdoZW4geW91IHNldCBgX19wcm90b19fYCBvbiBvYmplY3RzLCB3ZVxuICAgKiBoYXZlIHRvIHByZWZpeCBhbGwgdGhlIHN0cmluZ3MgaW4gb3VyIHNldCB3aXRoIGFuIGFyYml0cmFyeSBjaGFyYWN0ZXIuXG4gICAqXG4gICAqIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL3B1bGwvMzEgYW5kXG4gICAqIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvaXNzdWVzLzMwXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgZnVuY3Rpb24gdG9TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiAnJCcgKyBhU3RyO1xuICB9XG4gIGV4cG9ydHMudG9TZXRTdHJpbmcgPSB0b1NldFN0cmluZztcblxuICBmdW5jdGlvbiBmcm9tU2V0U3RyaW5nKGFTdHIpIHtcbiAgICByZXR1cm4gYVN0ci5zdWJzdHIoMSk7XG4gIH1cbiAgZXhwb3J0cy5mcm9tU2V0U3RyaW5nID0gZnJvbVNldFN0cmluZztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aGVyZSB0aGUgb3JpZ2luYWwgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiwgYnV0IGRpZmZlcmVudCBnZW5lcmF0ZWRcbiAgICogbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGEgbWFwcGluZyB3aXRoIGFcbiAgICogc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zID0gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnM7XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2l0aCBkZWZsYXRlZCBzb3VyY2UgYW5kIG5hbWUgaW5kaWNlcyB3aGVyZVxuICAgKiB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqXG4gICAqIE9wdGlvbmFsbHkgcGFzcyBpbiBgdHJ1ZWAgYXMgYG9ubHlDb21wYXJlR2VuZXJhdGVkYCB0byBjb25zaWRlciB0d29cbiAgICogbWFwcGluZ3Mgd2l0aCB0aGUgc2FtZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uLCBidXQgZGlmZmVyZW50XG4gICAqIHNvdXJjZS9uYW1lL29yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiB0aGUgc2FtZS4gVXNlZnVsIHdoZW4gc2VhcmNoaW5nIGZvciBhXG4gICAqIG1hcHBpbmcgd2l0aCBhIHN0dWJiZWQgb3V0IG1hcHBpbmcuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IsIG9ubHlDb21wYXJlR2VuZXJhdGVkKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Euc291cmNlIC0gbWFwcGluZ0Iuc291cmNlO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQ7XG5cbiAgZnVuY3Rpb24gc3RyY21wKGFTdHIxLCBhU3RyMikge1xuICAgIGlmIChhU3RyMSA9PT0gYVN0cjIpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cblxuICAgIGlmIChhU3RyMSA+IGFTdHIyKSB7XG4gICAgICByZXR1cm4gMTtcbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG4gIH1cblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGluZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBzdHJpbmdzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IHN0cmNtcChtYXBwaW5nQS5zb3VyY2UsIG1hcHBpbmdCLnNvdXJjZSk7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHN0cmNtcChtYXBwaW5nQS5uYW1lLCBtYXBwaW5nQi5uYW1lKTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3V0aWwuanNcbiAqKiBtb2R1bGUgaWQgPSAyXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG4gIHZhciBiaW5hcnlTZWFyY2ggPSByZXF1aXJlKCcuL2JpbmFyeS1zZWFyY2gnKTtcbiAgdmFyIEFycmF5U2V0ID0gcmVxdWlyZSgnLi9hcnJheS1zZXQnKS5BcnJheVNldDtcbiAgdmFyIGJhc2U2NFZMUSA9IHJlcXVpcmUoJy4vYmFzZTY0LXZscScpO1xuICB2YXIgcXVpY2tTb3J0ID0gcmVxdWlyZSgnLi9xdWljay1zb3J0JykucXVpY2tTb3J0O1xuXG4gIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyKGFTb3VyY2VNYXApIHtcbiAgICB2YXIgc291cmNlTWFwID0gYVNvdXJjZU1hcDtcbiAgICBpZiAodHlwZW9mIGFTb3VyY2VNYXAgPT09ICdzdHJpbmcnKSB7XG4gICAgICBzb3VyY2VNYXAgPSBKU09OLnBhcnNlKGFTb3VyY2VNYXAucmVwbGFjZSgvXlxcKVxcXVxcfScvLCAnJykpO1xuICAgIH1cblxuICAgIHJldHVybiBzb3VyY2VNYXAuc2VjdGlvbnMgIT0gbnVsbFxuICAgICAgPyBuZXcgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyKHNvdXJjZU1hcClcbiAgICAgIDogbmV3IEJhc2ljU291cmNlTWFwQ29uc3VtZXIoc291cmNlTWFwKTtcbiAgfVxuXG4gIFNvdXJjZU1hcENvbnN1bWVyLmZyb21Tb3VyY2VNYXAgPSBmdW5jdGlvbihhU291cmNlTWFwKSB7XG4gICAgcmV0dXJuIEJhc2ljU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcChhU291cmNlTWFwKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcHBpbmcgc3BlYyB0aGF0IHdlIGFyZSBjb25zdW1pbmcuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8vIGBfX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmQgYF9fb3JpZ2luYWxNYXBwaW5nc2AgYXJlIGFycmF5cyB0aGF0IGhvbGQgdGhlXG4gIC8vIHBhcnNlZCBtYXBwaW5nIGNvb3JkaW5hdGVzIGZyb20gdGhlIHNvdXJjZSBtYXAncyBcIm1hcHBpbmdzXCIgYXR0cmlidXRlLiBUaGV5XG4gIC8vIGFyZSBsYXppbHkgaW5zdGFudGlhdGVkLCBhY2Nlc3NlZCB2aWEgdGhlIGBfZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAvLyBgX29yaWdpbmFsTWFwcGluZ3NgIGdldHRlcnMgcmVzcGVjdGl2ZWx5LCBhbmQgd2Ugb25seSBwYXJzZSB0aGUgbWFwcGluZ3NcbiAgLy8gYW5kIGNyZWF0ZSB0aGVzZSBhcnJheXMgb25jZSBxdWVyaWVkIGZvciBhIHNvdXJjZSBsb2NhdGlvbi4gV2UganVtcCB0aHJvdWdoXG4gIC8vIHRoZXNlIGhvb3BzIGJlY2F1c2UgdGhlcmUgY2FuIGJlIG1hbnkgdGhvdXNhbmRzIG9mIG1hcHBpbmdzLCBhbmQgcGFyc2luZ1xuICAvLyB0aGVtIGlzIGV4cGVuc2l2ZSwgc28gd2Ugb25seSB3YW50IHRvIGRvIGl0IGlmIHdlIG11c3QuXG4gIC8vXG4gIC8vIEVhY2ggb2JqZWN0IGluIHRoZSBhcnJheXMgaXMgb2YgdGhlIGZvcm06XG4gIC8vXG4gIC8vICAgICB7XG4gIC8vICAgICAgIGdlbmVyYXRlZExpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIGdlbmVyYXRlZENvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBzb3VyY2U6IFRoZSBwYXRoIHRvIHRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSB0aGF0IGdlbmVyYXRlZCB0aGlzXG4gIC8vICAgICAgICAgICAgICAgY2h1bmsgb2YgY29kZSxcbiAgLy8gICAgICAgb3JpZ2luYWxMaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSB0aGF0XG4gIC8vICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZHMgdG8gdGhpcyBjaHVuayBvZiBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgb3JpZ2luYWxDb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UgdGhhdFxuICAvLyAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZHMgdG8gdGhpcyBjaHVuayBvZiBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgbmFtZTogVGhlIG5hbWUgb2YgdGhlIG9yaWdpbmFsIHN5bWJvbCB3aGljaCBnZW5lcmF0ZWQgdGhpcyBjaHVuayBvZlxuICAvLyAgICAgICAgICAgICBjb2RlLlxuICAvLyAgICAgfVxuICAvL1xuICAvLyBBbGwgcHJvcGVydGllcyBleGNlcHQgZm9yIGBnZW5lcmF0ZWRMaW5lYCBhbmQgYGdlbmVyYXRlZENvbHVtbmAgY2FuIGJlXG4gIC8vIGBudWxsYC5cbiAgLy9cbiAgLy8gYF9nZW5lcmF0ZWRNYXBwaW5nc2AgaXMgb3JkZXJlZCBieSB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucy5cbiAgLy9cbiAgLy8gYF9vcmlnaW5hbE1hcHBpbmdzYCBpcyBvcmRlcmVkIGJ5IHRoZSBvcmlnaW5hbCBwb3NpdGlvbnMuXG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBudWxsO1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnX2dlbmVyYXRlZE1hcHBpbmdzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgaWYgKCF0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MpIHtcbiAgICAgICAgdGhpcy5fcGFyc2VNYXBwaW5ncyh0aGlzLl9tYXBwaW5ncywgdGhpcy5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncztcbiAgICB9XG4gIH0pO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fX29yaWdpbmFsTWFwcGluZ3MgPSBudWxsO1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnX29yaWdpbmFsTWFwcGluZ3MnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICBpZiAoIXRoaXMuX19vcmlnaW5hbE1hcHBpbmdzKSB7XG4gICAgICAgIHRoaXMuX3BhcnNlTWFwcGluZ3ModGhpcy5fbWFwcGluZ3MsIHRoaXMuc291cmNlUm9vdCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncztcbiAgICB9XG4gIH0pO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fY2hhcklzTWFwcGluZ1NlcGFyYXRvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfY2hhcklzTWFwcGluZ1NlcGFyYXRvcihhU3RyLCBpbmRleCkge1xuICAgICAgdmFyIGMgPSBhU3RyLmNoYXJBdChpbmRleCk7XG4gICAgICByZXR1cm4gYyA9PT0gXCI7XCIgfHwgYyA9PT0gXCIsXCI7XG4gICAgfTtcblxuICAvKipcbiAgICogUGFyc2UgdGhlIG1hcHBpbmdzIGluIGEgc3RyaW5nIGluIHRvIGEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggd2UgY2FuIGVhc2lseVxuICAgKiBxdWVyeSAodGhlIG9yZGVyZWQgYXJyYXlzIGluIHRoZSBgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgICogYHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzYCBwcm9wZXJ0aWVzKS5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiU3ViY2xhc3NlcyBtdXN0IGltcGxlbWVudCBfcGFyc2VNYXBwaW5nc1wiKTtcbiAgICB9O1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUiA9IDE7XG4gIFNvdXJjZU1hcENvbnN1bWVyLk9SSUdJTkFMX09SREVSID0gMjtcblxuICBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCA9IDE7XG4gIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5EID0gMjtcblxuICAvKipcbiAgICogSXRlcmF0ZSBvdmVyIGVhY2ggbWFwcGluZyBiZXR3ZWVuIGFuIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiBhbmQgYVxuICAgKiBnZW5lcmF0ZWQgbGluZS9jb2x1bW4gaW4gdGhpcyBzb3VyY2UgbWFwLlxuICAgKlxuICAgKiBAcGFyYW0gRnVuY3Rpb24gYUNhbGxiYWNrXG4gICAqICAgICAgICBUaGUgZnVuY3Rpb24gdGhhdCBpcyBjYWxsZWQgd2l0aCBlYWNoIG1hcHBpbmcuXG4gICAqIEBwYXJhbSBPYmplY3QgYUNvbnRleHRcbiAgICogICAgICAgIE9wdGlvbmFsLiBJZiBzcGVjaWZpZWQsIHRoaXMgb2JqZWN0IHdpbGwgYmUgdGhlIHZhbHVlIG9mIGB0aGlzYCBldmVyeVxuICAgKiAgICAgICAgdGltZSB0aGF0IGBhQ2FsbGJhY2tgIGlzIGNhbGxlZC5cbiAgICogQHBhcmFtIGFPcmRlclxuICAgKiAgICAgICAgRWl0aGVyIGBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVJgIG9yXG4gICAqICAgICAgICBgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVJgLiBTcGVjaWZpZXMgd2hldGhlciB5b3Ugd2FudCB0b1xuICAgKiAgICAgICAgaXRlcmF0ZSBvdmVyIHRoZSBtYXBwaW5ncyBzb3J0ZWQgYnkgdGhlIGdlbmVyYXRlZCBmaWxlJ3MgbGluZS9jb2x1bW5cbiAgICogICAgICAgIG9yZGVyIG9yIHRoZSBvcmlnaW5hbCdzIHNvdXJjZS9saW5lL2NvbHVtbiBvcmRlciwgcmVzcGVjdGl2ZWx5LiBEZWZhdWx0cyB0b1xuICAgKiAgICAgICAgYFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUmAuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuZWFjaE1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2VhY2hNYXBwaW5nKGFDYWxsYmFjaywgYUNvbnRleHQsIGFPcmRlcikge1xuICAgICAgdmFyIGNvbnRleHQgPSBhQ29udGV4dCB8fCBudWxsO1xuICAgICAgdmFyIG9yZGVyID0gYU9yZGVyIHx8IFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUjtcblxuICAgICAgdmFyIG1hcHBpbmdzO1xuICAgICAgc3dpdGNoIChvcmRlcikge1xuICAgICAgY2FzZSBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVI6XG4gICAgICAgIG1hcHBpbmdzID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3M7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUjpcbiAgICAgICAgbWFwcGluZ3MgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzO1xuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVua25vd24gb3JkZXIgb2YgaXRlcmF0aW9uLlwiKTtcbiAgICAgIH1cblxuICAgICAgdmFyIHNvdXJjZVJvb3QgPSB0aGlzLnNvdXJjZVJvb3Q7XG4gICAgICBtYXBwaW5ncy5tYXAoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgICAgdmFyIHNvdXJjZSA9IG1hcHBpbmcuc291cmNlID09PSBudWxsID8gbnVsbCA6IHRoaXMuX3NvdXJjZXMuYXQobWFwcGluZy5zb3VyY2UpO1xuICAgICAgICBpZiAoc291cmNlICE9IG51bGwgJiYgc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUsXG4gICAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbixcbiAgICAgICAgICBvcmlnaW5hbExpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgIG9yaWdpbmFsQ29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uLFxuICAgICAgICAgIG5hbWU6IG1hcHBpbmcubmFtZSA9PT0gbnVsbCA/IG51bGwgOiB0aGlzLl9uYW1lcy5hdChtYXBwaW5nLm5hbWUpXG4gICAgICAgIH07XG4gICAgICB9LCB0aGlzKS5mb3JFYWNoKGFDYWxsYmFjaywgY29udGV4dCk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyBhbGwgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwcm92aWRlZC4gSWYgbm8gY29sdW1uIGlzIHByb3ZpZGVkLCByZXR1cm5zIGFsbCBtYXBwaW5nc1xuICAgKiBjb3JyZXNwb25kaW5nIHRvIGEgZWl0aGVyIHRoZSBsaW5lIHdlIGFyZSBzZWFyY2hpbmcgZm9yIG9yIHRoZSBuZXh0XG4gICAqIGNsb3Nlc3QgbGluZSB0aGF0IGhhcyBhbnkgbWFwcGluZ3MuIE90aGVyd2lzZSwgcmV0dXJucyBhbGwgbWFwcGluZ3NcbiAgICogY29ycmVzcG9uZGluZyB0byB0aGUgZ2l2ZW4gbGluZSBhbmQgZWl0aGVyIHRoZSBjb2x1bW4gd2UgYXJlIHNlYXJjaGluZyBmb3JcbiAgICogb3IgdGhlIG5leHQgY2xvc2VzdCBjb2x1bW4gdGhhdCBoYXMgYW55IG9mZnNldHMuXG4gICAqXG4gICAqIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogT3B0aW9uYWwuIHRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqXG4gICAqIGFuZCBhbiBhcnJheSBvZiBvYmplY3RzIGlzIHJldHVybmVkLCBlYWNoIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5hbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2FsbEdlbmVyYXRlZFBvc2l0aW9uc0ZvcihhQXJncykge1xuICAgICAgdmFyIGxpbmUgPSB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKTtcblxuICAgICAgLy8gV2hlbiB0aGVyZSBpcyBubyBleGFjdCBtYXRjaCwgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX2ZpbmRNYXBwaW5nXG4gICAgICAvLyByZXR1cm5zIHRoZSBpbmRleCBvZiB0aGUgY2xvc2VzdCBtYXBwaW5nIGxlc3MgdGhhbiB0aGUgbmVlZGxlLiBCeVxuICAgICAgLy8gc2V0dGluZyBuZWVkbGUub3JpZ2luYWxDb2x1bW4gdG8gMCwgd2UgdGh1cyBmaW5kIHRoZSBsYXN0IG1hcHBpbmcgZm9yXG4gICAgICAvLyB0aGUgZ2l2ZW4gbGluZSwgcHJvdmlkZWQgc3VjaCBhIG1hcHBpbmcgZXhpc3RzLlxuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgc291cmNlOiB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpLFxuICAgICAgICBvcmlnaW5hbExpbmU6IGxpbmUsXG4gICAgICAgIG9yaWdpbmFsQ29sdW1uOiB1dGlsLmdldEFyZyhhQXJncywgJ2NvbHVtbicsIDApXG4gICAgICB9O1xuXG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgbmVlZGxlLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBuZWVkbGUuc291cmNlKTtcbiAgICAgIH1cbiAgICAgIGlmICghdGhpcy5fc291cmNlcy5oYXMobmVlZGxlLnNvdXJjZSkpIHtcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgfVxuICAgICAgbmVlZGxlLnNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihuZWVkbGUuc291cmNlKTtcblxuICAgICAgdmFyIG1hcHBpbmdzID0gW107XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKG5lZWRsZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX29yaWdpbmFsTWFwcGluZ3MsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIm9yaWdpbmFsTGluZVwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJvcmlnaW5hbENvbHVtblwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCk7XG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChhQXJncy5jb2x1bW4gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIHZhciBvcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZTtcblxuICAgICAgICAgIC8vIEl0ZXJhdGUgdW50aWwgZWl0aGVyIHdlIHJ1biBvdXQgb2YgbWFwcGluZ3MsIG9yIHdlIHJ1biBpbnRvXG4gICAgICAgICAgLy8gYSBtYXBwaW5nIGZvciBhIGRpZmZlcmVudCBsaW5lIHRoYW4gdGhlIG9uZSB3ZSBmb3VuZC4gU2luY2VcbiAgICAgICAgICAvLyBtYXBwaW5ncyBhcmUgc29ydGVkLCB0aGlzIGlzIGd1YXJhbnRlZWQgdG8gZmluZCBhbGwgbWFwcGluZ3MgZm9yXG4gICAgICAgICAgLy8gdGhlIGxpbmUgd2UgZm91bmQuXG4gICAgICAgICAgd2hpbGUgKG1hcHBpbmcgJiYgbWFwcGluZy5vcmlnaW5hbExpbmUgPT09IG9yaWdpbmFsTGluZSkge1xuICAgICAgICAgICAgbWFwcGluZ3MucHVzaCh7XG4gICAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRMaW5lJywgbnVsbCksXG4gICAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgICBsYXN0Q29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbGFzdEdlbmVyYXRlZENvbHVtbicsIG51bGwpXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbKytpbmRleF07XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhciBvcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICAvLyBJdGVyYXRlIHVudGlsIGVpdGhlciB3ZSBydW4gb3V0IG9mIG1hcHBpbmdzLCBvciB3ZSBydW4gaW50b1xuICAgICAgICAgIC8vIGEgbWFwcGluZyBmb3IgYSBkaWZmZXJlbnQgbGluZSB0aGFuIHRoZSBvbmUgd2Ugd2VyZSBzZWFyY2hpbmcgZm9yLlxuICAgICAgICAgIC8vIFNpbmNlIG1hcHBpbmdzIGFyZSBzb3J0ZWQsIHRoaXMgaXMgZ3VhcmFudGVlZCB0byBmaW5kIGFsbCBtYXBwaW5ncyBmb3JcbiAgICAgICAgICAvLyB0aGUgbGluZSB3ZSBhcmUgc2VhcmNoaW5nIGZvci5cbiAgICAgICAgICB3aGlsZSAobWFwcGluZyAmJlxuICAgICAgICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gbGluZSAmJlxuICAgICAgICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID09IG9yaWdpbmFsQ29sdW1uKSB7XG4gICAgICAgICAgICBtYXBwaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1srK2luZGV4XTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG1hcHBpbmdzO1xuICAgIH07XG5cbiAgZXhwb3J0cy5Tb3VyY2VNYXBDb25zdW1lciA9IFNvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBBIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgaW5zdGFuY2UgcmVwcmVzZW50cyBhIHBhcnNlZCBzb3VyY2UgbWFwIHdoaWNoIHdlIGNhblxuICAgKiBxdWVyeSBmb3IgaW5mb3JtYXRpb24gYWJvdXQgdGhlIG9yaWdpbmFsIGZpbGUgcG9zaXRpb25zIGJ5IGdpdmluZyBpdCBhIGZpbGVcbiAgICogcG9zaXRpb24gaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqXG4gICAqIFRoZSBvbmx5IHBhcmFtZXRlciBpcyB0aGUgcmF3IHNvdXJjZSBtYXAgKGVpdGhlciBhcyBhIEpTT04gc3RyaW5nLCBvclxuICAgKiBhbHJlYWR5IHBhcnNlZCB0byBhbiBvYmplY3QpLiBBY2NvcmRpbmcgdG8gdGhlIHNwZWMsIHNvdXJjZSBtYXBzIGhhdmUgdGhlXG4gICAqIGZvbGxvd2luZyBhdHRyaWJ1dGVzOlxuICAgKlxuICAgKiAgIC0gdmVyc2lvbjogV2hpY2ggdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcCBzcGVjIHRoaXMgbWFwIGlzIGZvbGxvd2luZy5cbiAgICogICAtIHNvdXJjZXM6IEFuIGFycmF5IG9mIFVSTHMgdG8gdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlcy5cbiAgICogICAtIG5hbWVzOiBBbiBhcnJheSBvZiBpZGVudGlmaWVycyB3aGljaCBjYW4gYmUgcmVmZXJyZW5jZWQgYnkgaW5kaXZpZHVhbCBtYXBwaW5ncy5cbiAgICogICAtIHNvdXJjZVJvb3Q6IE9wdGlvbmFsLiBUaGUgVVJMIHJvb3QgZnJvbSB3aGljaCBhbGwgc291cmNlcyBhcmUgcmVsYXRpdmUuXG4gICAqICAgLSBzb3VyY2VzQ29udGVudDogT3B0aW9uYWwuIEFuIGFycmF5IG9mIGNvbnRlbnRzIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZXMuXG4gICAqICAgLSBtYXBwaW5nczogQSBzdHJpbmcgb2YgYmFzZTY0IFZMUXMgd2hpY2ggY29udGFpbiB0aGUgYWN0dWFsIG1hcHBpbmdzLlxuICAgKiAgIC0gZmlsZTogT3B0aW9uYWwuIFRoZSBnZW5lcmF0ZWQgZmlsZSB0aGlzIHNvdXJjZSBtYXAgaXMgYXNzb2NpYXRlZCB3aXRoLlxuICAgKlxuICAgKiBIZXJlIGlzIGFuIGV4YW1wbGUgc291cmNlIG1hcCwgdGFrZW4gZnJvbSB0aGUgc291cmNlIG1hcCBzcGVjWzBdOlxuICAgKlxuICAgKiAgICAge1xuICAgKiAgICAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgICAgZmlsZTogXCJvdXQuanNcIixcbiAgICogICAgICAgc291cmNlUm9vdCA6IFwiXCIsXG4gICAqICAgICAgIHNvdXJjZXM6IFtcImZvby5qc1wiLCBcImJhci5qc1wiXSxcbiAgICogICAgICAgbmFtZXM6IFtcInNyY1wiLCBcIm1hcHNcIiwgXCJhcmVcIiwgXCJmdW5cIl0sXG4gICAqICAgICAgIG1hcHBpbmdzOiBcIkFBLEFCOztBQkNERTtcIlxuICAgKiAgICAgfVxuICAgKlxuICAgKiBbMF06IGh0dHBzOi8vZG9jcy5nb29nbGUuY29tL2RvY3VtZW50L2QvMVUxUkdBZWhRd1J5cFVUb3ZGMUtSbHBpT0Z6ZTBiLV8yZ2M2ZkFIMEtZMGsvZWRpdD9wbGk9MSNcbiAgICovXG4gIGZ1bmN0aW9uIEJhc2ljU291cmNlTWFwQ29uc3VtZXIoYVNvdXJjZU1hcCkge1xuICAgIHZhciBzb3VyY2VNYXAgPSBhU291cmNlTWFwO1xuICAgIGlmICh0eXBlb2YgYVNvdXJjZU1hcCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHNvdXJjZU1hcCA9IEpTT04ucGFyc2UoYVNvdXJjZU1hcC5yZXBsYWNlKC9eXFwpXFxdXFx9Jy8sICcnKSk7XG4gICAgfVxuXG4gICAgdmFyIHZlcnNpb24gPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICd2ZXJzaW9uJyk7XG4gICAgdmFyIHNvdXJjZXMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VzJyk7XG4gICAgLy8gU2FzcyAzLjMgbGVhdmVzIG91dCB0aGUgJ25hbWVzJyBhcnJheSwgc28gd2UgZGV2aWF0ZSBmcm9tIHRoZSBzcGVjICh3aGljaFxuICAgIC8vIHJlcXVpcmVzIHRoZSBhcnJheSkgdG8gcGxheSBuaWNlIGhlcmUuXG4gICAgdmFyIG5hbWVzID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnbmFtZXMnLCBbXSk7XG4gICAgdmFyIHNvdXJjZVJvb3QgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VSb290JywgbnVsbCk7XG4gICAgdmFyIHNvdXJjZXNDb250ZW50ID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnc291cmNlc0NvbnRlbnQnLCBudWxsKTtcbiAgICB2YXIgbWFwcGluZ3MgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdtYXBwaW5ncycpO1xuICAgIHZhciBmaWxlID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnZmlsZScsIG51bGwpO1xuXG4gICAgLy8gT25jZSBhZ2FpbiwgU2FzcyBkZXZpYXRlcyBmcm9tIHRoZSBzcGVjIGFuZCBzdXBwbGllcyB0aGUgdmVyc2lvbiBhcyBhXG4gICAgLy8gc3RyaW5nIHJhdGhlciB0aGFuIGEgbnVtYmVyLCBzbyB3ZSB1c2UgbG9vc2UgZXF1YWxpdHkgY2hlY2tpbmcgaGVyZS5cbiAgICBpZiAodmVyc2lvbiAhPSB0aGlzLl92ZXJzaW9uKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIHZlcnNpb246ICcgKyB2ZXJzaW9uKTtcbiAgICB9XG5cbiAgICBzb3VyY2VzID0gc291cmNlc1xuICAgICAgLy8gU29tZSBzb3VyY2UgbWFwcyBwcm9kdWNlIHJlbGF0aXZlIHNvdXJjZSBwYXRocyBsaWtlIFwiLi9mb28uanNcIiBpbnN0ZWFkIG9mXG4gICAgICAvLyBcImZvby5qc1wiLiAgTm9ybWFsaXplIHRoZXNlIGZpcnN0IHNvIHRoYXQgZnV0dXJlIGNvbXBhcmlzb25zIHdpbGwgc3VjY2VlZC5cbiAgICAgIC8vIFNlZSBidWd6aWwubGEvMTA5MDc2OC5cbiAgICAgIC5tYXAodXRpbC5ub3JtYWxpemUpXG4gICAgICAvLyBBbHdheXMgZW5zdXJlIHRoYXQgYWJzb2x1dGUgc291cmNlcyBhcmUgaW50ZXJuYWxseSBzdG9yZWQgcmVsYXRpdmUgdG9cbiAgICAgIC8vIHRoZSBzb3VyY2Ugcm9vdCwgaWYgdGhlIHNvdXJjZSByb290IGlzIGFic29sdXRlLiBOb3QgZG9pbmcgdGhpcyB3b3VsZFxuICAgICAgLy8gYmUgcGFydGljdWxhcmx5IHByb2JsZW1hdGljIHdoZW4gdGhlIHNvdXJjZSByb290IGlzIGEgcHJlZml4IG9mIHRoZVxuICAgICAgLy8gc291cmNlICh2YWxpZCwgYnV0IHdoeT8/KS4gU2VlIGdpdGh1YiBpc3N1ZSAjMTk5IGFuZCBidWd6aWwubGEvMTE4ODk4Mi5cbiAgICAgIC5tYXAoZnVuY3Rpb24gKHNvdXJjZSkge1xuICAgICAgICByZXR1cm4gc291cmNlUm9vdCAmJiB1dGlsLmlzQWJzb2x1dGUoc291cmNlUm9vdCkgJiYgdXRpbC5pc0Fic29sdXRlKHNvdXJjZSlcbiAgICAgICAgICA/IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlKVxuICAgICAgICAgIDogc291cmNlO1xuICAgICAgfSk7XG5cbiAgICAvLyBQYXNzIGB0cnVlYCBiZWxvdyB0byBhbGxvdyBkdXBsaWNhdGUgbmFtZXMgYW5kIHNvdXJjZXMuIFdoaWxlIHNvdXJjZSBtYXBzXG4gICAgLy8gYXJlIGludGVuZGVkIHRvIGJlIGNvbXByZXNzZWQgYW5kIGRlZHVwbGljYXRlZCwgdGhlIFR5cGVTY3JpcHQgY29tcGlsZXJcbiAgICAvLyBzb21ldGltZXMgZ2VuZXJhdGVzIHNvdXJjZSBtYXBzIHdpdGggZHVwbGljYXRlcyBpbiB0aGVtLiBTZWUgR2l0aHViIGlzc3VlXG4gICAgLy8gIzcyIGFuZCBidWd6aWwubGEvODg5NDkyLlxuICAgIHRoaXMuX25hbWVzID0gQXJyYXlTZXQuZnJvbUFycmF5KG5hbWVzLCB0cnVlKTtcbiAgICB0aGlzLl9zb3VyY2VzID0gQXJyYXlTZXQuZnJvbUFycmF5KHNvdXJjZXMsIHRydWUpO1xuXG4gICAgdGhpcy5zb3VyY2VSb290ID0gc291cmNlUm9vdDtcbiAgICB0aGlzLnNvdXJjZXNDb250ZW50ID0gc291cmNlc0NvbnRlbnQ7XG4gICAgdGhpcy5fbWFwcGluZ3MgPSBtYXBwaW5ncztcbiAgICB0aGlzLmZpbGUgPSBmaWxlO1xuICB9XG5cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmNvbnN1bWVyID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgZnJvbSBhIFNvdXJjZU1hcEdlbmVyYXRvci5cbiAgICpcbiAgICogQHBhcmFtIFNvdXJjZU1hcEdlbmVyYXRvciBhU291cmNlTWFwXG4gICAqICAgICAgICBUaGUgc291cmNlIG1hcCB0aGF0IHdpbGwgYmUgY29uc3VtZWQuXG4gICAqIEByZXR1cm5zIEJhc2ljU291cmNlTWFwQ29uc3VtZXJcbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZnJvbVNvdXJjZU1hcChhU291cmNlTWFwKSB7XG4gICAgICB2YXIgc21jID0gT2JqZWN0LmNyZWF0ZShCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG5cbiAgICAgIHZhciBuYW1lcyA9IHNtYy5fbmFtZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoYVNvdXJjZU1hcC5fbmFtZXMudG9BcnJheSgpLCB0cnVlKTtcbiAgICAgIHZhciBzb3VyY2VzID0gc21jLl9zb3VyY2VzID0gQXJyYXlTZXQuZnJvbUFycmF5KGFTb3VyY2VNYXAuX3NvdXJjZXMudG9BcnJheSgpLCB0cnVlKTtcbiAgICAgIHNtYy5zb3VyY2VSb290ID0gYVNvdXJjZU1hcC5fc291cmNlUm9vdDtcbiAgICAgIHNtYy5zb3VyY2VzQ29udGVudCA9IGFTb3VyY2VNYXAuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQoc21jLl9zb3VyY2VzLnRvQXJyYXkoKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc21jLnNvdXJjZVJvb3QpO1xuICAgICAgc21jLmZpbGUgPSBhU291cmNlTWFwLl9maWxlO1xuXG4gICAgICAvLyBCZWNhdXNlIHdlIGFyZSBtb2RpZnlpbmcgdGhlIGVudHJpZXMgKGJ5IGNvbnZlcnRpbmcgc3RyaW5nIHNvdXJjZXMgYW5kXG4gICAgICAvLyBuYW1lcyB0byBpbmRpY2VzIGludG8gdGhlIHNvdXJjZXMgYW5kIG5hbWVzIEFycmF5U2V0cyksIHdlIGhhdmUgdG8gbWFrZVxuICAgICAgLy8gYSBjb3B5IG9mIHRoZSBlbnRyeSBvciBlbHNlIGJhZCB0aGluZ3MgaGFwcGVuLiBTaGFyZWQgbXV0YWJsZSBzdGF0ZVxuICAgICAgLy8gc3RyaWtlcyBhZ2FpbiEgU2VlIGdpdGh1YiBpc3N1ZSAjMTkxLlxuXG4gICAgICB2YXIgZ2VuZXJhdGVkTWFwcGluZ3MgPSBhU291cmNlTWFwLl9tYXBwaW5ncy50b0FycmF5KCkuc2xpY2UoKTtcbiAgICAgIHZhciBkZXN0R2VuZXJhdGVkTWFwcGluZ3MgPSBzbWMuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIGRlc3RPcmlnaW5hbE1hcHBpbmdzID0gc21jLl9fb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuXG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gZ2VuZXJhdGVkTWFwcGluZ3MubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNyY01hcHBpbmcgPSBnZW5lcmF0ZWRNYXBwaW5nc1tpXTtcbiAgICAgICAgdmFyIGRlc3RNYXBwaW5nID0gbmV3IE1hcHBpbmc7XG4gICAgICAgIGRlc3RNYXBwaW5nLmdlbmVyYXRlZExpbmUgPSBzcmNNYXBwaW5nLmdlbmVyYXRlZExpbmU7XG4gICAgICAgIGRlc3RNYXBwaW5nLmdlbmVyYXRlZENvbHVtbiA9IHNyY01hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgIGlmIChzcmNNYXBwaW5nLnNvdXJjZSkge1xuICAgICAgICAgIGRlc3RNYXBwaW5nLnNvdXJjZSA9IHNvdXJjZXMuaW5kZXhPZihzcmNNYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgZGVzdE1hcHBpbmcub3JpZ2luYWxMaW5lID0gc3JjTWFwcGluZy5vcmlnaW5hbExpbmU7XG4gICAgICAgICAgZGVzdE1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPSBzcmNNYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuXG4gICAgICAgICAgaWYgKHNyY01hcHBpbmcubmFtZSkge1xuICAgICAgICAgICAgZGVzdE1hcHBpbmcubmFtZSA9IG5hbWVzLmluZGV4T2Yoc3JjTWFwcGluZy5uYW1lKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBkZXN0T3JpZ2luYWxNYXBwaW5ncy5wdXNoKGRlc3RNYXBwaW5nKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGRlc3RHZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKGRlc3RNYXBwaW5nKTtcbiAgICAgIH1cblxuICAgICAgcXVpY2tTb3J0KHNtYy5fX29yaWdpbmFsTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMpO1xuXG4gICAgICByZXR1cm4gc21jO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvKipcbiAgICogVGhlIGxpc3Qgb2Ygb3JpZ2luYWwgc291cmNlcy5cbiAgICovXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ3NvdXJjZXMnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc291cmNlcy50b0FycmF5KCkubWFwKGZ1bmN0aW9uIChzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCA/IHV0aWwuam9pbih0aGlzLnNvdXJjZVJvb3QsIHMpIDogcztcbiAgICAgIH0sIHRoaXMpO1xuICAgIH1cbiAgfSk7XG5cbiAgLyoqXG4gICAqIFByb3ZpZGUgdGhlIEpJVCB3aXRoIGEgbmljZSBzaGFwZSAvIGhpZGRlbiBjbGFzcy5cbiAgICovXG4gIGZ1bmN0aW9uIE1hcHBpbmcoKSB7XG4gICAgdGhpcy5nZW5lcmF0ZWRMaW5lID0gMDtcbiAgICB0aGlzLmdlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgdGhpcy5zb3VyY2UgPSBudWxsO1xuICAgIHRoaXMub3JpZ2luYWxMaW5lID0gbnVsbDtcbiAgICB0aGlzLm9yaWdpbmFsQ29sdW1uID0gbnVsbDtcbiAgICB0aGlzLm5hbWUgPSBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlIHRoZSBtYXBwaW5ncyBpbiBhIHN0cmluZyBpbiB0byBhIGRhdGEgc3RydWN0dXJlIHdoaWNoIHdlIGNhbiBlYXNpbHlcbiAgICogcXVlcnkgKHRoZSBvcmRlcmVkIGFycmF5cyBpbiB0aGUgYHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gICAqIGB0aGlzLl9fb3JpZ2luYWxNYXBwaW5nc2AgcHJvcGVydGllcykuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdmFyIGdlbmVyYXRlZExpbmUgPSAxO1xuICAgICAgdmFyIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsTGluZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNTb3VyY2UgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzTmFtZSA9IDA7XG4gICAgICB2YXIgbGVuZ3RoID0gYVN0ci5sZW5ndGg7XG4gICAgICB2YXIgaW5kZXggPSAwO1xuICAgICAgdmFyIGNhY2hlZFNlZ21lbnRzID0ge307XG4gICAgICB2YXIgdGVtcCA9IHt9O1xuICAgICAgdmFyIG9yaWdpbmFsTWFwcGluZ3MgPSBbXTtcbiAgICAgIHZhciBnZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIG1hcHBpbmcsIHN0ciwgc2VnbWVudCwgZW5kLCB2YWx1ZTtcblxuICAgICAgd2hpbGUgKGluZGV4IDwgbGVuZ3RoKSB7XG4gICAgICAgIGlmIChhU3RyLmNoYXJBdChpbmRleCkgPT09ICc7Jykge1xuICAgICAgICAgIGdlbmVyYXRlZExpbmUrKztcbiAgICAgICAgICBpbmRleCsrO1xuICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChhU3RyLmNoYXJBdChpbmRleCkgPT09ICcsJykge1xuICAgICAgICAgIGluZGV4Kys7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgbWFwcGluZyA9IG5ldyBNYXBwaW5nKCk7XG4gICAgICAgICAgbWFwcGluZy5nZW5lcmF0ZWRMaW5lID0gZ2VuZXJhdGVkTGluZTtcblxuICAgICAgICAgIC8vIEJlY2F1c2UgZWFjaCBvZmZzZXQgaXMgZW5jb2RlZCByZWxhdGl2ZSB0byB0aGUgcHJldmlvdXMgb25lLFxuICAgICAgICAgIC8vIG1hbnkgc2VnbWVudHMgb2Z0ZW4gaGF2ZSB0aGUgc2FtZSBlbmNvZGluZy4gV2UgY2FuIGV4cGxvaXQgdGhpc1xuICAgICAgICAgIC8vIGZhY3QgYnkgY2FjaGluZyB0aGUgcGFyc2VkIHZhcmlhYmxlIGxlbmd0aCBmaWVsZHMgb2YgZWFjaCBzZWdtZW50LFxuICAgICAgICAgIC8vIGFsbG93aW5nIHVzIHRvIGF2b2lkIGEgc2Vjb25kIHBhcnNlIGlmIHdlIGVuY291bnRlciB0aGUgc2FtZVxuICAgICAgICAgIC8vIHNlZ21lbnQgYWdhaW4uXG4gICAgICAgICAgZm9yIChlbmQgPSBpbmRleDsgZW5kIDwgbGVuZ3RoOyBlbmQrKykge1xuICAgICAgICAgICAgaWYgKHRoaXMuX2NoYXJJc01hcHBpbmdTZXBhcmF0b3IoYVN0ciwgZW5kKSkge1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgc3RyID0gYVN0ci5zbGljZShpbmRleCwgZW5kKTtcblxuICAgICAgICAgIHNlZ21lbnQgPSBjYWNoZWRTZWdtZW50c1tzdHJdO1xuICAgICAgICAgIGlmIChzZWdtZW50KSB7XG4gICAgICAgICAgICBpbmRleCArPSBzdHIubGVuZ3RoO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZWdtZW50ID0gW107XG4gICAgICAgICAgICB3aGlsZSAoaW5kZXggPCBlbmQpIHtcbiAgICAgICAgICAgICAgYmFzZTY0VkxRLmRlY29kZShhU3RyLCBpbmRleCwgdGVtcCk7XG4gICAgICAgICAgICAgIHZhbHVlID0gdGVtcC52YWx1ZTtcbiAgICAgICAgICAgICAgaW5kZXggPSB0ZW1wLnJlc3Q7XG4gICAgICAgICAgICAgIHNlZ21lbnQucHVzaCh2YWx1ZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA9PT0gMikge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ZvdW5kIGEgc291cmNlLCBidXQgbm8gbGluZSBhbmQgY29sdW1uJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA9PT0gMykge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ZvdW5kIGEgc291cmNlIGFuZCBsaW5lLCBidXQgbm8gY29sdW1uJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNhY2hlZFNlZ21lbnRzW3N0cl0gPSBzZWdtZW50O1xuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEdlbmVyYXRlZCBjb2x1bW4uXG4gICAgICAgICAgbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gPSBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiArIHNlZ21lbnRbMF07XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbjtcblxuICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgIC8vIE9yaWdpbmFsIHNvdXJjZS5cbiAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gcHJldmlvdXNTb3VyY2UgKyBzZWdtZW50WzFdO1xuICAgICAgICAgICAgcHJldmlvdXNTb3VyY2UgKz0gc2VnbWVudFsxXTtcblxuICAgICAgICAgICAgLy8gT3JpZ2luYWwgbGluZS5cbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lID0gcHJldmlvdXNPcmlnaW5hbExpbmUgKyBzZWdtZW50WzJdO1xuICAgICAgICAgICAgcHJldmlvdXNPcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZTtcbiAgICAgICAgICAgIC8vIExpbmVzIGFyZSBzdG9yZWQgMC1iYXNlZFxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgKz0gMTtcblxuICAgICAgICAgICAgLy8gT3JpZ2luYWwgY29sdW1uLlxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gKyBzZWdtZW50WzNdO1xuICAgICAgICAgICAgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA+IDQpIHtcbiAgICAgICAgICAgICAgLy8gT3JpZ2luYWwgbmFtZS5cbiAgICAgICAgICAgICAgbWFwcGluZy5uYW1lID0gcHJldmlvdXNOYW1lICsgc2VnbWVudFs0XTtcbiAgICAgICAgICAgICAgcHJldmlvdXNOYW1lICs9IHNlZ21lbnRbNF07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgZ2VuZXJhdGVkTWFwcGluZ3MucHVzaChtYXBwaW5nKTtcbiAgICAgICAgICBpZiAodHlwZW9mIG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSAnbnVtYmVyJykge1xuICAgICAgICAgICAgb3JpZ2luYWxNYXBwaW5ncy5wdXNoKG1hcHBpbmcpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQoZ2VuZXJhdGVkTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQpO1xuICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gZ2VuZXJhdGVkTWFwcGluZ3M7XG5cbiAgICAgIHF1aWNrU29ydChvcmlnaW5hbE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKTtcbiAgICAgIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzID0gb3JpZ2luYWxNYXBwaW5ncztcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBGaW5kIHRoZSBtYXBwaW5nIHRoYXQgYmVzdCBtYXRjaGVzIHRoZSBoeXBvdGhldGljYWwgXCJuZWVkbGVcIiBtYXBwaW5nIHRoYXRcbiAgICogd2UgYXJlIHNlYXJjaGluZyBmb3IgaW4gdGhlIGdpdmVuIFwiaGF5c3RhY2tcIiBvZiBtYXBwaW5ncy5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9maW5kTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZmluZE1hcHBpbmcoYU5lZWRsZSwgYU1hcHBpbmdzLCBhTGluZU5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYUNvbHVtbk5hbWUsIGFDb21wYXJhdG9yLCBhQmlhcykge1xuICAgICAgLy8gVG8gcmV0dXJuIHRoZSBwb3NpdGlvbiB3ZSBhcmUgc2VhcmNoaW5nIGZvciwgd2UgbXVzdCBmaXJzdCBmaW5kIHRoZVxuICAgICAgLy8gbWFwcGluZyBmb3IgdGhlIGdpdmVuIHBvc2l0aW9uIGFuZCB0aGVuIHJldHVybiB0aGUgb3Bwb3NpdGUgcG9zaXRpb24gaXRcbiAgICAgIC8vIHBvaW50cyB0by4gQmVjYXVzZSB0aGUgbWFwcGluZ3MgYXJlIHNvcnRlZCwgd2UgY2FuIHVzZSBiaW5hcnkgc2VhcmNoIHRvXG4gICAgICAvLyBmaW5kIHRoZSBiZXN0IG1hcHBpbmcuXG5cbiAgICAgIGlmIChhTmVlZGxlW2FMaW5lTmFtZV0gPD0gMCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdMaW5lIG11c3QgYmUgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDEsIGdvdCAnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKyBhTmVlZGxlW2FMaW5lTmFtZV0pO1xuICAgICAgfVxuICAgICAgaWYgKGFOZWVkbGVbYUNvbHVtbk5hbWVdIDwgMCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdDb2x1bW4gbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMCwgZ290ICdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICArIGFOZWVkbGVbYUNvbHVtbk5hbWVdKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGJpbmFyeVNlYXJjaC5zZWFyY2goYU5lZWRsZSwgYU1hcHBpbmdzLCBhQ29tcGFyYXRvciwgYUJpYXMpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIENvbXB1dGUgdGhlIGxhc3QgY29sdW1uIGZvciBlYWNoIGdlbmVyYXRlZCBtYXBwaW5nLiBUaGUgbGFzdCBjb2x1bW4gaXNcbiAgICogaW5jbHVzaXZlLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29tcHV0ZUNvbHVtblNwYW5zID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9jb21wdXRlQ29sdW1uU3BhbnMoKSB7XG4gICAgICBmb3IgKHZhciBpbmRleCA9IDA7IGluZGV4IDwgdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3MubGVuZ3RoOyArK2luZGV4KSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIC8vIE1hcHBpbmdzIGRvIG5vdCBjb250YWluIGEgZmllbGQgZm9yIHRoZSBsYXN0IGdlbmVyYXRlZCBjb2x1bW50LiBXZVxuICAgICAgICAvLyBjYW4gY29tZSB1cCB3aXRoIGFuIG9wdGltaXN0aWMgZXN0aW1hdGUsIGhvd2V2ZXIsIGJ5IGFzc3VtaW5nIHRoYXRcbiAgICAgICAgLy8gbWFwcGluZ3MgYXJlIGNvbnRpZ3VvdXMgKGkuZS4gZ2l2ZW4gdHdvIGNvbnNlY3V0aXZlIG1hcHBpbmdzLCB0aGVcbiAgICAgICAgLy8gZmlyc3QgbWFwcGluZyBlbmRzIHdoZXJlIHRoZSBzZWNvbmQgb25lIHN0YXJ0cykuXG4gICAgICAgIGlmIChpbmRleCArIDEgPCB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGgpIHtcbiAgICAgICAgICB2YXIgbmV4dE1hcHBpbmcgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5nc1tpbmRleCArIDFdO1xuXG4gICAgICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9PT0gbmV4dE1hcHBpbmcuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgbWFwcGluZy5sYXN0R2VuZXJhdGVkQ29sdW1uID0gbmV4dE1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uIC0gMTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRoZSBsYXN0IG1hcHBpbmcgZm9yIGVhY2ggbGluZSBzcGFucyB0aGUgZW50aXJlIGxpbmUuXG4gICAgICAgIG1hcHBpbmcubGFzdEdlbmVyYXRlZENvbHVtbiA9IEluZmluaXR5O1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSwgbGluZSwgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3RcbiAgICogd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gYmlhczogRWl0aGVyICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJy5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlLCBvciBudWxsLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBuYW1lOiBUaGUgb3JpZ2luYWwgaWRlbnRpZmllciwgb3IgbnVsbC5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLm9yaWdpbmFsUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX29yaWdpbmFsUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIGdlbmVyYXRlZExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKFxuICAgICAgICBuZWVkbGUsXG4gICAgICAgIHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzLFxuICAgICAgICBcImdlbmVyYXRlZExpbmVcIixcbiAgICAgICAgXCJnZW5lcmF0ZWRDb2x1bW5cIixcbiAgICAgICAgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCxcbiAgICAgICAgdXRpbC5nZXRBcmcoYUFyZ3MsICdiaWFzJywgU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQpXG4gICAgICApO1xuXG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzW2luZGV4XTtcblxuICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lID09PSBuZWVkbGUuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgIHZhciBzb3VyY2UgPSB1dGlsLmdldEFyZyhtYXBwaW5nLCAnc291cmNlJywgbnVsbCk7XG4gICAgICAgICAgaWYgKHNvdXJjZSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5hdChzb3VyY2UpO1xuICAgICAgICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIHNvdXJjZSA9IHV0aWwuam9pbih0aGlzLnNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHZhciBuYW1lID0gdXRpbC5nZXRBcmcobWFwcGluZywgJ25hbWUnLCBudWxsKTtcbiAgICAgICAgICBpZiAobmFtZSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgbmFtZSA9IHRoaXMuX25hbWVzLmF0KG5hbWUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnb3JpZ2luYWxMaW5lJywgbnVsbCksXG4gICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdvcmlnaW5hbENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgbmFtZTogbmFtZVxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgc291cmNlOiBudWxsLFxuICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgIG5hbWU6IG51bGxcbiAgICAgIH07XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIHRydWUgaWYgd2UgaGF2ZSB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGV2ZXJ5IHNvdXJjZSBpbiB0aGUgc291cmNlXG4gICAqIG1hcCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMgPVxuICAgIGZ1bmN0aW9uIEJhc2ljU291cmNlTWFwQ29uc3VtZXJfaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKSB7XG4gICAgICBpZiAoIXRoaXMuc291cmNlc0NvbnRlbnQpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnQubGVuZ3RoID49IHRoaXMuX3NvdXJjZXMuc2l6ZSgpICYmXG4gICAgICAgICF0aGlzLnNvdXJjZXNDb250ZW50LnNvbWUoZnVuY3Rpb24gKHNjKSB7IHJldHVybiBzYyA9PSBudWxsOyB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UgY29udGVudC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgdGhlIHVybCBvZiB0aGVcbiAgICogb3JpZ2luYWwgc291cmNlIGZpbGUuIFJldHVybnMgbnVsbCBpZiBubyBvcmlnaW5hbCBzb3VyY2UgY29udGVudCBpc1xuICAgKiBhdmFpbGlibGUuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5zb3VyY2VDb250ZW50Rm9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIG51bGxPbk1pc3NpbmcpIHtcbiAgICAgIGlmICghdGhpcy5zb3VyY2VzQ29udGVudCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cblxuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIGFTb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuc291cmNlUm9vdCwgYVNvdXJjZSk7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLl9zb3VyY2VzLmhhcyhhU291cmNlKSkge1xuICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudFt0aGlzLl9zb3VyY2VzLmluZGV4T2YoYVNvdXJjZSldO1xuICAgICAgfVxuXG4gICAgICB2YXIgdXJsO1xuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsXG4gICAgICAgICAgJiYgKHVybCA9IHV0aWwudXJsUGFyc2UodGhpcy5zb3VyY2VSb290KSkpIHtcbiAgICAgICAgLy8gWFhYOiBmaWxlOi8vIFVSSXMgYW5kIGFic29sdXRlIHBhdGhzIGxlYWQgdG8gdW5leHBlY3RlZCBiZWhhdmlvciBmb3JcbiAgICAgICAgLy8gbWFueSB1c2Vycy4gV2UgY2FuIGhlbHAgdGhlbSBvdXQgd2hlbiB0aGV5IGV4cGVjdCBmaWxlOi8vIFVSSXMgdG9cbiAgICAgICAgLy8gYmVoYXZlIGxpa2UgaXQgd291bGQgaWYgdGhleSB3ZXJlIHJ1bm5pbmcgYSBsb2NhbCBIVFRQIHNlcnZlci4gU2VlXG4gICAgICAgIC8vIGh0dHBzOi8vYnVnemlsbGEubW96aWxsYS5vcmcvc2hvd19idWcuY2dpP2lkPTg4NTU5Ny5cbiAgICAgICAgdmFyIGZpbGVVcmlBYnNQYXRoID0gYVNvdXJjZS5yZXBsYWNlKC9eZmlsZTpcXC9cXC8vLCBcIlwiKTtcbiAgICAgICAgaWYgKHVybC5zY2hlbWUgPT0gXCJmaWxlXCJcbiAgICAgICAgICAgICYmIHRoaXMuX3NvdXJjZXMuaGFzKGZpbGVVcmlBYnNQYXRoKSkge1xuICAgICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihmaWxlVXJpQWJzUGF0aCldXG4gICAgICAgIH1cblxuICAgICAgICBpZiAoKCF1cmwucGF0aCB8fCB1cmwucGF0aCA9PSBcIi9cIilcbiAgICAgICAgICAgICYmIHRoaXMuX3NvdXJjZXMuaGFzKFwiL1wiICsgYVNvdXJjZSkpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudFt0aGlzLl9zb3VyY2VzLmluZGV4T2YoXCIvXCIgKyBhU291cmNlKV07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gVGhpcyBmdW5jdGlvbiBpcyB1c2VkIHJlY3Vyc2l2ZWx5IGZyb21cbiAgICAgIC8vIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuc291cmNlQ29udGVudEZvci4gSW4gdGhhdCBjYXNlLCB3ZVxuICAgICAgLy8gZG9uJ3Qgd2FudCB0byB0aHJvdyBpZiB3ZSBjYW4ndCBmaW5kIHRoZSBzb3VyY2UgLSB3ZSBqdXN0IHdhbnQgdG9cbiAgICAgIC8vIHJldHVybiBudWxsLCBzbyB3ZSBwcm92aWRlIGEgZmxhZyB0byBleGl0IGdyYWNlZnVsbHkuXG4gICAgICBpZiAobnVsbE9uTWlzc2luZykge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTb3VyY2UgKyAnXCIgaXMgbm90IGluIHRoZSBTb3VyY2VNYXAuJyk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoXG4gICAqIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGJpYXM6IEVpdGhlciAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnIG9yXG4gICAqICAgICAnU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICogICAgIERlZmF1bHRzIHRvICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuZ2VuZXJhdGVkUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2dlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgc291cmNlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnKTtcbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgIH1cbiAgICAgIGlmICghdGhpcy5fc291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICAgIGxhc3RDb2x1bW46IG51bGxcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihzb3VyY2UpO1xuXG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgb3JpZ2luYWxMaW5lOiB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKFxuICAgICAgICBuZWVkbGUsXG4gICAgICAgIHRoaXMuX29yaWdpbmFsTWFwcGluZ3MsXG4gICAgICAgIFwib3JpZ2luYWxMaW5lXCIsXG4gICAgICAgIFwib3JpZ2luYWxDb2x1bW5cIixcbiAgICAgICAgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyxcbiAgICAgICAgdXRpbC5nZXRBcmcoYUFyZ3MsICdiaWFzJywgU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQpXG4gICAgICApO1xuXG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSA9PT0gbmVlZGxlLnNvdXJjZSkge1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkTGluZScsIG51bGwpLFxuICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICBsYXN0Q29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbGFzdEdlbmVyYXRlZENvbHVtbicsIG51bGwpXG4gICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgIGxhc3RDb2x1bW46IG51bGxcbiAgICAgIH07XG4gICAgfTtcblxuICBleHBvcnRzLkJhc2ljU291cmNlTWFwQ29uc3VtZXIgPSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBBbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIgaW5zdGFuY2UgcmVwcmVzZW50cyBhIHBhcnNlZCBzb3VyY2UgbWFwIHdoaWNoXG4gICAqIHdlIGNhbiBxdWVyeSBmb3IgaW5mb3JtYXRpb24uIEl0IGRpZmZlcnMgZnJvbSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyIGluXG4gICAqIHRoYXQgaXQgdGFrZXMgXCJpbmRleGVkXCIgc291cmNlIG1hcHMgKGkuZS4gb25lcyB3aXRoIGEgXCJzZWN0aW9uc1wiIGZpZWxkKSBhc1xuICAgKiBpbnB1dC5cbiAgICpcbiAgICogVGhlIG9ubHkgcGFyYW1ldGVyIGlzIGEgcmF3IHNvdXJjZSBtYXAgKGVpdGhlciBhcyBhIEpTT04gc3RyaW5nLCBvciBhbHJlYWR5XG4gICAqIHBhcnNlZCB0byBhbiBvYmplY3QpLiBBY2NvcmRpbmcgdG8gdGhlIHNwZWMgZm9yIGluZGV4ZWQgc291cmNlIG1hcHMsIHRoZXlcbiAgICogaGF2ZSB0aGUgZm9sbG93aW5nIGF0dHJpYnV0ZXM6XG4gICAqXG4gICAqICAgLSB2ZXJzaW9uOiBXaGljaCB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwIHNwZWMgdGhpcyBtYXAgaXMgZm9sbG93aW5nLlxuICAgKiAgIC0gZmlsZTogT3B0aW9uYWwuIFRoZSBnZW5lcmF0ZWQgZmlsZSB0aGlzIHNvdXJjZSBtYXAgaXMgYXNzb2NpYXRlZCB3aXRoLlxuICAgKiAgIC0gc2VjdGlvbnM6IEEgbGlzdCBvZiBzZWN0aW9uIGRlZmluaXRpb25zLlxuICAgKlxuICAgKiBFYWNoIHZhbHVlIHVuZGVyIHRoZSBcInNlY3Rpb25zXCIgZmllbGQgaGFzIHR3byBmaWVsZHM6XG4gICAqICAgLSBvZmZzZXQ6IFRoZSBvZmZzZXQgaW50byB0aGUgb3JpZ2luYWwgc3BlY2lmaWVkIGF0IHdoaWNoIHRoaXMgc2VjdGlvblxuICAgKiAgICAgICBiZWdpbnMgdG8gYXBwbHksIGRlZmluZWQgYXMgYW4gb2JqZWN0IHdpdGggYSBcImxpbmVcIiBhbmQgXCJjb2x1bW5cIlxuICAgKiAgICAgICBmaWVsZC5cbiAgICogICAtIG1hcDogQSBzb3VyY2UgbWFwIGRlZmluaXRpb24uIFRoaXMgc291cmNlIG1hcCBjb3VsZCBhbHNvIGJlIGluZGV4ZWQsXG4gICAqICAgICAgIGJ1dCBkb2Vzbid0IGhhdmUgdG8gYmUuXG4gICAqXG4gICAqIEluc3RlYWQgb2YgdGhlIFwibWFwXCIgZmllbGQsIGl0J3MgYWxzbyBwb3NzaWJsZSB0byBoYXZlIGEgXCJ1cmxcIiBmaWVsZFxuICAgKiBzcGVjaWZ5aW5nIGEgVVJMIHRvIHJldHJpZXZlIGEgc291cmNlIG1hcCBmcm9tLCBidXQgdGhhdCdzIGN1cnJlbnRseVxuICAgKiB1bnN1cHBvcnRlZC5cbiAgICpcbiAgICogSGVyZSdzIGFuIGV4YW1wbGUgc291cmNlIG1hcCwgdGFrZW4gZnJvbSB0aGUgc291cmNlIG1hcCBzcGVjWzBdLCBidXRcbiAgICogbW9kaWZpZWQgdG8gb21pdCBhIHNlY3Rpb24gd2hpY2ggdXNlcyB0aGUgXCJ1cmxcIiBmaWVsZC5cbiAgICpcbiAgICogIHtcbiAgICogICAgdmVyc2lvbiA6IDMsXG4gICAqICAgIGZpbGU6IFwiYXBwLmpzXCIsXG4gICAqICAgIHNlY3Rpb25zOiBbe1xuICAgKiAgICAgIG9mZnNldDoge2xpbmU6MTAwLCBjb2x1bW46MTB9LFxuICAgKiAgICAgIG1hcDoge1xuICAgKiAgICAgICAgdmVyc2lvbiA6IDMsXG4gICAqICAgICAgICBmaWxlOiBcInNlY3Rpb24uanNcIixcbiAgICogICAgICAgIHNvdXJjZXM6IFtcImZvby5qc1wiLCBcImJhci5qc1wiXSxcbiAgICogICAgICAgIG5hbWVzOiBbXCJzcmNcIiwgXCJtYXBzXCIsIFwiYXJlXCIsIFwiZnVuXCJdLFxuICAgKiAgICAgICAgbWFwcGluZ3M6IFwiQUFBQSxFOztBQkNERTtcIlxuICAgKiAgICAgIH1cbiAgICogICAgfV0sXG4gICAqICB9XG4gICAqXG4gICAqIFswXTogaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vZG9jdW1lbnQvZC8xVTFSR0FlaFF3UnlwVVRvdkYxS1JscGlPRnplMGItXzJnYzZmQUgwS1kway9lZGl0I2hlYWRpbmc9aC41MzVlczN4ZXByZ3RcbiAgICovXG4gIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcihhU291cmNlTWFwKSB7XG4gICAgdmFyIHNvdXJjZU1hcCA9IGFTb3VyY2VNYXA7XG4gICAgaWYgKHR5cGVvZiBhU291cmNlTWFwID09PSAnc3RyaW5nJykge1xuICAgICAgc291cmNlTWFwID0gSlNPTi5wYXJzZShhU291cmNlTWFwLnJlcGxhY2UoL15cXClcXF1cXH0nLywgJycpKTtcbiAgICB9XG5cbiAgICB2YXIgdmVyc2lvbiA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3ZlcnNpb24nKTtcbiAgICB2YXIgc2VjdGlvbnMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzZWN0aW9ucycpO1xuXG4gICAgaWYgKHZlcnNpb24gIT0gdGhpcy5fdmVyc2lvbikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCB2ZXJzaW9uOiAnICsgdmVyc2lvbik7XG4gICAgfVxuXG4gICAgdGhpcy5fc291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgIHRoaXMuX25hbWVzID0gbmV3IEFycmF5U2V0KCk7XG5cbiAgICB2YXIgbGFzdE9mZnNldCA9IHtcbiAgICAgIGxpbmU6IC0xLFxuICAgICAgY29sdW1uOiAwXG4gICAgfTtcbiAgICB0aGlzLl9zZWN0aW9ucyA9IHNlY3Rpb25zLm1hcChmdW5jdGlvbiAocykge1xuICAgICAgaWYgKHMudXJsKSB7XG4gICAgICAgIC8vIFRoZSB1cmwgZmllbGQgd2lsbCByZXF1aXJlIHN1cHBvcnQgZm9yIGFzeW5jaHJvbmljaXR5LlxuICAgICAgICAvLyBTZWUgaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc291cmNlLW1hcC9pc3N1ZXMvMTZcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTdXBwb3J0IGZvciB1cmwgZmllbGQgaW4gc2VjdGlvbnMgbm90IGltcGxlbWVudGVkLicpO1xuICAgICAgfVxuICAgICAgdmFyIG9mZnNldCA9IHV0aWwuZ2V0QXJnKHMsICdvZmZzZXQnKTtcbiAgICAgIHZhciBvZmZzZXRMaW5lID0gdXRpbC5nZXRBcmcob2Zmc2V0LCAnbGluZScpO1xuICAgICAgdmFyIG9mZnNldENvbHVtbiA9IHV0aWwuZ2V0QXJnKG9mZnNldCwgJ2NvbHVtbicpO1xuXG4gICAgICBpZiAob2Zmc2V0TGluZSA8IGxhc3RPZmZzZXQubGluZSB8fFxuICAgICAgICAgIChvZmZzZXRMaW5lID09PSBsYXN0T2Zmc2V0LmxpbmUgJiYgb2Zmc2V0Q29sdW1uIDwgbGFzdE9mZnNldC5jb2x1bW4pKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignU2VjdGlvbiBvZmZzZXRzIG11c3QgYmUgb3JkZXJlZCBhbmQgbm9uLW92ZXJsYXBwaW5nLicpO1xuICAgICAgfVxuICAgICAgbGFzdE9mZnNldCA9IG9mZnNldDtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgZ2VuZXJhdGVkT2Zmc2V0OiB7XG4gICAgICAgICAgLy8gVGhlIG9mZnNldCBmaWVsZHMgYXJlIDAtYmFzZWQsIGJ1dCB3ZSB1c2UgMS1iYXNlZCBpbmRpY2VzIHdoZW5cbiAgICAgICAgICAvLyBlbmNvZGluZy9kZWNvZGluZyBmcm9tIFZMUS5cbiAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBvZmZzZXRMaW5lICsgMSxcbiAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG9mZnNldENvbHVtbiArIDFcbiAgICAgICAgfSxcbiAgICAgICAgY29uc3VtZXI6IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmdldEFyZyhzLCAnbWFwJykpXG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUpO1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmNvbnN0cnVjdG9yID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8qKlxuICAgKiBUaGUgbGlzdCBvZiBvcmlnaW5hbCBzb3VyY2VzLlxuICAgKi9cbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdzb3VyY2VzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgdmFyIHNvdXJjZXMgPSBbXTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCB0aGlzLl9zZWN0aW9uc1tpXS5jb25zdW1lci5zb3VyY2VzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgc291cmNlcy5wdXNoKHRoaXMuX3NlY3Rpb25zW2ldLmNvbnN1bWVyLnNvdXJjZXNbal0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4gc291cmNlcztcbiAgICB9XG4gIH0pO1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UsIGxpbmUsIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBnZW5lcmF0ZWRcbiAgICogc291cmNlJ3MgbGluZSBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0XG4gICAqIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlLCBvciBudWxsLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBuYW1lOiBUaGUgb3JpZ2luYWwgaWRlbnRpZmllciwgb3IgbnVsbC5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUub3JpZ2luYWxQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX29yaWdpbmFsUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIGdlbmVyYXRlZExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIC8vIEZpbmQgdGhlIHNlY3Rpb24gY29udGFpbmluZyB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9uIHdlJ3JlIHRyeWluZyB0byBtYXBcbiAgICAgIC8vIHRvIGFuIG9yaWdpbmFsIHBvc2l0aW9uLlxuICAgICAgdmFyIHNlY3Rpb25JbmRleCA9IGJpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCB0aGlzLl9zZWN0aW9ucyxcbiAgICAgICAgZnVuY3Rpb24obmVlZGxlLCBzZWN0aW9uKSB7XG4gICAgICAgICAgdmFyIGNtcCA9IG5lZWRsZS5nZW5lcmF0ZWRMaW5lIC0gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZTtcbiAgICAgICAgICBpZiAoY21wKSB7XG4gICAgICAgICAgICByZXR1cm4gY21wO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHJldHVybiAobmVlZGxlLmdlbmVyYXRlZENvbHVtbiAtXG4gICAgICAgICAgICAgICAgICBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICB9KTtcbiAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbc2VjdGlvbkluZGV4XTtcblxuICAgICAgaWYgKCFzZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc291cmNlOiBudWxsLFxuICAgICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICAgIG5hbWU6IG51bGxcbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHNlY3Rpb24uY29uc3VtZXIub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICAgIGxpbmU6IG5lZWRsZS5nZW5lcmF0ZWRMaW5lIC1cbiAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICBjb2x1bW46IG5lZWRsZS5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBuZWVkbGUuZ2VuZXJhdGVkTGluZVxuICAgICAgICAgICA/IHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbiAtIDFcbiAgICAgICAgICAgOiAwKSxcbiAgICAgICAgYmlhczogYUFyZ3MuYmlhc1xuICAgICAgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIHRydWUgaWYgd2UgaGF2ZSB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGV2ZXJ5IHNvdXJjZSBpbiB0aGUgc291cmNlXG4gICAqIG1hcCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5oYXNDb250ZW50c09mQWxsU291cmNlcyA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX2hhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzKCkge1xuICAgICAgcmV0dXJuIHRoaXMuX3NlY3Rpb25zLmV2ZXJ5KGZ1bmN0aW9uIChzKSB7XG4gICAgICAgIHJldHVybiBzLmNvbnN1bWVyLmhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzKCk7XG4gICAgICB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UgY29udGVudC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgdGhlIHVybCBvZiB0aGVcbiAgICogb3JpZ2luYWwgc291cmNlIGZpbGUuIFJldHVybnMgbnVsbCBpZiBubyBvcmlnaW5hbCBzb3VyY2UgY29udGVudCBpc1xuICAgKiBhdmFpbGFibGUuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLnNvdXJjZUNvbnRlbnRGb3IgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIG51bGxPbk1pc3NpbmcpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcblxuICAgICAgICB2YXIgY29udGVudCA9IHNlY3Rpb24uY29uc3VtZXIuc291cmNlQ29udGVudEZvcihhU291cmNlLCB0cnVlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQpIHtcbiAgICAgICAgICByZXR1cm4gY29udGVudDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKG51bGxPbk1pc3NpbmcpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhU291cmNlICsgJ1wiIGlzIG5vdCBpbiB0aGUgU291cmNlTWFwLicpO1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBvcmlnaW5hbCBzb3VyY2UsXG4gICAqIGxpbmUsIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3Qgd2l0aFxuICAgKiB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBmaWxlbmFtZSBvZiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5nZW5lcmF0ZWRQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX2dlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMuX3NlY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbaV07XG5cbiAgICAgICAgLy8gT25seSBjb25zaWRlciB0aGlzIHNlY3Rpb24gaWYgdGhlIHJlcXVlc3RlZCBzb3VyY2UgaXMgaW4gdGhlIGxpc3Qgb2ZcbiAgICAgICAgLy8gc291cmNlcyBvZiB0aGUgY29uc3VtZXIuXG4gICAgICAgIGlmIChzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZXMuaW5kZXhPZih1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpKSA9PT0gLTEpIHtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgZ2VuZXJhdGVkUG9zaXRpb24gPSBzZWN0aW9uLmNvbnN1bWVyLmdlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKTtcbiAgICAgICAgaWYgKGdlbmVyYXRlZFBvc2l0aW9uKSB7XG4gICAgICAgICAgdmFyIHJldCA9IHtcbiAgICAgICAgICAgIGxpbmU6IGdlbmVyYXRlZFBvc2l0aW9uLmxpbmUgK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWRQb3NpdGlvbi5jb2x1bW4gK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSA9PT0gZ2VuZXJhdGVkUG9zaXRpb24ubGluZVxuICAgICAgICAgICAgICAgPyBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4gLSAxXG4gICAgICAgICAgICAgICA6IDApXG4gICAgICAgICAgfTtcbiAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBQYXJzZSB0aGUgbWFwcGluZ3MgaW4gYSBzdHJpbmcgaW4gdG8gYSBkYXRhIHN0cnVjdHVyZSB3aGljaCB3ZSBjYW4gZWFzaWx5XG4gICAqIHF1ZXJ5ICh0aGUgb3JkZXJlZCBhcnJheXMgaW4gdGhlIGB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAgKiBgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3NgIHByb3BlcnRpZXMpLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX3BhcnNlTWFwcGluZ3MoYVN0ciwgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MgPSBbXTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcbiAgICAgICAgdmFyIHNlY3Rpb25NYXBwaW5ncyA9IHNlY3Rpb24uY29uc3VtZXIuX2dlbmVyYXRlZE1hcHBpbmdzO1xuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHNlY3Rpb25NYXBwaW5ncy5sZW5ndGg7IGorKykge1xuICAgICAgICAgIHZhciBtYXBwaW5nID0gc2VjdGlvbk1hcHBpbmdzW2ldO1xuXG4gICAgICAgICAgdmFyIHNvdXJjZSA9IHNlY3Rpb24uY29uc3VtZXIuX3NvdXJjZXMuYXQobWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIGlmIChzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZVJvb3QgIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZSA9IHV0aWwuam9pbihzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMuX3NvdXJjZXMuYWRkKHNvdXJjZSk7XG4gICAgICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKHNvdXJjZSk7XG5cbiAgICAgICAgICB2YXIgbmFtZSA9IHNlY3Rpb24uY29uc3VtZXIuX25hbWVzLmF0KG1hcHBpbmcubmFtZSk7XG4gICAgICAgICAgdGhpcy5fbmFtZXMuYWRkKG5hbWUpO1xuICAgICAgICAgIG5hbWUgPSB0aGlzLl9uYW1lcy5pbmRleE9mKG5hbWUpO1xuXG4gICAgICAgICAgLy8gVGhlIG1hcHBpbmdzIGNvbWluZyBmcm9tIHRoZSBjb25zdW1lciBmb3IgdGhlIHNlY3Rpb24gaGF2ZVxuICAgICAgICAgIC8vIGdlbmVyYXRlZCBwb3NpdGlvbnMgcmVsYXRpdmUgdG8gdGhlIHN0YXJ0IG9mIHRoZSBzZWN0aW9uLCBzbyB3ZVxuICAgICAgICAgIC8vIG5lZWQgdG8gb2Zmc2V0IHRoZW0gdG8gYmUgcmVsYXRpdmUgdG8gdGhlIHN0YXJ0IG9mIHRoZSBjb25jYXRlbmF0ZWRcbiAgICAgICAgICAvLyBnZW5lcmF0ZWQgZmlsZS5cbiAgICAgICAgICB2YXIgYWRqdXN0ZWRNYXBwaW5nID0ge1xuICAgICAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUgK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBtYXBwaW5nLmNvbHVtbiArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBtYXBwaW5nLmdlbmVyYXRlZExpbmUpXG4gICAgICAgICAgICAgID8gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uIC0gMVxuICAgICAgICAgICAgICA6IDAsXG4gICAgICAgICAgICBvcmlnaW5hbExpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgb3JpZ2luYWxDb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgICBuYW1lOiBuYW1lXG4gICAgICAgICAgfTtcblxuICAgICAgICAgIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKGFkanVzdGVkTWFwcGluZyk7XG4gICAgICAgICAgaWYgKHR5cGVvZiBhZGp1c3RlZE1hcHBpbmcub3JpZ2luYWxMaW5lID09PSAnbnVtYmVyJykge1xuICAgICAgICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MucHVzaChhZGp1c3RlZE1hcHBpbmcpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQodGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkKTtcbiAgICAgIHF1aWNrU29ydCh0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyk7XG4gICAgfTtcblxuICBleHBvcnRzLkluZGV4ZWRTb3VyY2VNYXBDb25zdW1lciA9IEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcjtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvc291cmNlLW1hcC1jb25zdW1lci5qc1xuICoqIG1vZHVsZSBpZCA9IDNcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgZXhwb3J0cy5HUkVBVEVTVF9MT1dFUl9CT1VORCA9IDE7XG4gIGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQgPSAyO1xuXG4gIC8qKlxuICAgKiBSZWN1cnNpdmUgaW1wbGVtZW50YXRpb24gb2YgYmluYXJ5IHNlYXJjaC5cbiAgICpcbiAgICogQHBhcmFtIGFMb3cgSW5kaWNlcyBoZXJlIGFuZCBsb3dlciBkbyBub3QgY29udGFpbiB0aGUgbmVlZGxlLlxuICAgKiBAcGFyYW0gYUhpZ2ggSW5kaWNlcyBoZXJlIGFuZCBoaWdoZXIgZG8gbm90IGNvbnRhaW4gdGhlIG5lZWRsZS5cbiAgICogQHBhcmFtIGFOZWVkbGUgVGhlIGVsZW1lbnQgYmVpbmcgc2VhcmNoZWQgZm9yLlxuICAgKiBAcGFyYW0gYUhheXN0YWNrIFRoZSBub24tZW1wdHkgYXJyYXkgYmVpbmcgc2VhcmNoZWQuXG4gICAqIEBwYXJhbSBhQ29tcGFyZSBGdW5jdGlvbiB3aGljaCB0YWtlcyB0d28gZWxlbWVudHMgYW5kIHJldHVybnMgLTEsIDAsIG9yIDEuXG4gICAqIEBwYXJhbSBhQmlhcyBFaXRoZXIgJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlY3Vyc2l2ZVNlYXJjaChhTG93LCBhSGlnaCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpIHtcbiAgICAvLyBUaGlzIGZ1bmN0aW9uIHRlcm1pbmF0ZXMgd2hlbiBvbmUgb2YgdGhlIGZvbGxvd2luZyBpcyB0cnVlOlxuICAgIC8vXG4gICAgLy8gICAxLiBXZSBmaW5kIHRoZSBleGFjdCBlbGVtZW50IHdlIGFyZSBsb29raW5nIGZvci5cbiAgICAvL1xuICAgIC8vICAgMi4gV2UgZGlkIG5vdCBmaW5kIHRoZSBleGFjdCBlbGVtZW50LCBidXQgd2UgY2FuIHJldHVybiB0aGUgaW5kZXggb2ZcbiAgICAvLyAgICAgIHRoZSBuZXh0LWNsb3Nlc3QgZWxlbWVudC5cbiAgICAvL1xuICAgIC8vICAgMy4gV2UgZGlkIG5vdCBmaW5kIHRoZSBleGFjdCBlbGVtZW50LCBhbmQgdGhlcmUgaXMgbm8gbmV4dC1jbG9zZXN0XG4gICAgLy8gICAgICBlbGVtZW50IHRoYW4gdGhlIG9uZSB3ZSBhcmUgc2VhcmNoaW5nIGZvciwgc28gd2UgcmV0dXJuIC0xLlxuICAgIHZhciBtaWQgPSBNYXRoLmZsb29yKChhSGlnaCAtIGFMb3cpIC8gMikgKyBhTG93O1xuICAgIHZhciBjbXAgPSBhQ29tcGFyZShhTmVlZGxlLCBhSGF5c3RhY2tbbWlkXSwgdHJ1ZSk7XG4gICAgaWYgKGNtcCA9PT0gMCkge1xuICAgICAgLy8gRm91bmQgdGhlIGVsZW1lbnQgd2UgYXJlIGxvb2tpbmcgZm9yLlxuICAgICAgcmV0dXJuIG1pZDtcbiAgICB9XG4gICAgZWxzZSBpZiAoY21wID4gMCkge1xuICAgICAgLy8gT3VyIG5lZWRsZSBpcyBncmVhdGVyIHRoYW4gYUhheXN0YWNrW21pZF0uXG4gICAgICBpZiAoYUhpZ2ggLSBtaWQgPiAxKSB7XG4gICAgICAgIC8vIFRoZSBlbGVtZW50IGlzIGluIHRoZSB1cHBlciBoYWxmLlxuICAgICAgICByZXR1cm4gcmVjdXJzaXZlU2VhcmNoKG1pZCwgYUhpZ2gsIGFOZWVkbGUsIGFIYXlzdGFjaywgYUNvbXBhcmUsIGFCaWFzKTtcbiAgICAgIH1cblxuICAgICAgLy8gVGhlIGV4YWN0IG5lZWRsZSBlbGVtZW50IHdhcyBub3QgZm91bmQgaW4gdGhpcyBoYXlzdGFjay4gRGV0ZXJtaW5lIGlmXG4gICAgICAvLyB3ZSBhcmUgaW4gdGVybWluYXRpb24gY2FzZSAoMykgb3IgKDIpIGFuZCByZXR1cm4gdGhlIGFwcHJvcHJpYXRlIHRoaW5nLlxuICAgICAgaWYgKGFCaWFzID09IGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQpIHtcbiAgICAgICAgcmV0dXJuIGFIaWdoIDwgYUhheXN0YWNrLmxlbmd0aCA/IGFIaWdoIDogLTE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gbWlkO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIC8vIE91ciBuZWVkbGUgaXMgbGVzcyB0aGFuIGFIYXlzdGFja1ttaWRdLlxuICAgICAgaWYgKG1pZCAtIGFMb3cgPiAxKSB7XG4gICAgICAgIC8vIFRoZSBlbGVtZW50IGlzIGluIHRoZSBsb3dlciBoYWxmLlxuICAgICAgICByZXR1cm4gcmVjdXJzaXZlU2VhcmNoKGFMb3csIG1pZCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpO1xuICAgICAgfVxuXG4gICAgICAvLyB3ZSBhcmUgaW4gdGVybWluYXRpb24gY2FzZSAoMykgb3IgKDIpIGFuZCByZXR1cm4gdGhlIGFwcHJvcHJpYXRlIHRoaW5nLlxuICAgICAgaWYgKGFCaWFzID09IGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQpIHtcbiAgICAgICAgcmV0dXJuIG1pZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBhTG93IDwgMCA/IC0xIDogYUxvdztcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVGhpcyBpcyBhbiBpbXBsZW1lbnRhdGlvbiBvZiBiaW5hcnkgc2VhcmNoIHdoaWNoIHdpbGwgYWx3YXlzIHRyeSBhbmQgcmV0dXJuXG4gICAqIHRoZSBpbmRleCBvZiB0aGUgY2xvc2VzdCBlbGVtZW50IGlmIHRoZXJlIGlzIG5vIGV4YWN0IGhpdC4gVGhpcyBpcyBiZWNhdXNlXG4gICAqIG1hcHBpbmdzIGJldHdlZW4gb3JpZ2luYWwgYW5kIGdlbmVyYXRlZCBsaW5lL2NvbCBwYWlycyBhcmUgc2luZ2xlIHBvaW50cyxcbiAgICogYW5kIHRoZXJlIGlzIGFuIGltcGxpY2l0IHJlZ2lvbiBiZXR3ZWVuIGVhY2ggb2YgdGhlbSwgc28gYSBtaXNzIGp1c3QgbWVhbnNcbiAgICogdGhhdCB5b3UgYXJlbid0IG9uIHRoZSB2ZXJ5IHN0YXJ0IG9mIGEgcmVnaW9uLlxuICAgKlxuICAgKiBAcGFyYW0gYU5lZWRsZSBUaGUgZWxlbWVudCB5b3UgYXJlIGxvb2tpbmcgZm9yLlxuICAgKiBAcGFyYW0gYUhheXN0YWNrIFRoZSBhcnJheSB0aGF0IGlzIGJlaW5nIHNlYXJjaGVkLlxuICAgKiBAcGFyYW0gYUNvbXBhcmUgQSBmdW5jdGlvbiB3aGljaCB0YWtlcyB0aGUgbmVlZGxlIGFuZCBhbiBlbGVtZW50IGluIHRoZVxuICAgKiAgICAgYXJyYXkgYW5kIHJldHVybnMgLTEsIDAsIG9yIDEgZGVwZW5kaW5nIG9uIHdoZXRoZXIgdGhlIG5lZWRsZSBpcyBsZXNzXG4gICAqICAgICB0aGFuLCBlcXVhbCB0bywgb3IgZ3JlYXRlciB0aGFuIHRoZSBlbGVtZW50LCByZXNwZWN0aXZlbHkuXG4gICAqIEBwYXJhbSBhQmlhcyBFaXRoZXIgJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICogICAgIERlZmF1bHRzIHRvICdiaW5hcnlTZWFyY2guR1JFQVRFU1RfTE9XRVJfQk9VTkQnLlxuICAgKi9cbiAgZXhwb3J0cy5zZWFyY2ggPSBmdW5jdGlvbiBzZWFyY2goYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpIHtcbiAgICBpZiAoYUhheXN0YWNrLmxlbmd0aCA9PT0gMCkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IHJlY3Vyc2l2ZVNlYXJjaCgtMSwgYUhheXN0YWNrLmxlbmd0aCwgYU5lZWRsZSwgYUhheXN0YWNrLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhQ29tcGFyZSwgYUJpYXMgfHwgZXhwb3J0cy5HUkVBVEVTVF9MT1dFUl9CT1VORCk7XG4gICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cblxuICAgIC8vIFdlIGhhdmUgZm91bmQgZWl0aGVyIHRoZSBleGFjdCBlbGVtZW50LCBvciB0aGUgbmV4dC1jbG9zZXN0IGVsZW1lbnQgdGhhblxuICAgIC8vIHRoZSBvbmUgd2UgYXJlIHNlYXJjaGluZyBmb3IuIEhvd2V2ZXIsIHRoZXJlIG1heSBiZSBtb3JlIHRoYW4gb25lIHN1Y2hcbiAgICAvLyBlbGVtZW50LiBNYWtlIHN1cmUgd2UgYWx3YXlzIHJldHVybiB0aGUgc21hbGxlc3Qgb2YgdGhlc2UuXG4gICAgd2hpbGUgKGluZGV4IC0gMSA+PSAwKSB7XG4gICAgICBpZiAoYUNvbXBhcmUoYUhheXN0YWNrW2luZGV4XSwgYUhheXN0YWNrW2luZGV4IC0gMV0sIHRydWUpICE9PSAwKSB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgICAgLS1pbmRleDtcbiAgICB9XG5cbiAgICByZXR1cm4gaW5kZXg7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2JpbmFyeS1zZWFyY2guanNcbiAqKiBtb2R1bGUgaWQgPSA0XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLyoqXG4gICAqIEEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggaXMgYSBjb21iaW5hdGlvbiBvZiBhbiBhcnJheSBhbmQgYSBzZXQuIEFkZGluZyBhIG5ld1xuICAgKiBtZW1iZXIgaXMgTygxKSwgdGVzdGluZyBmb3IgbWVtYmVyc2hpcCBpcyBPKDEpLCBhbmQgZmluZGluZyB0aGUgaW5kZXggb2YgYW5cbiAgICogZWxlbWVudCBpcyBPKDEpLiBSZW1vdmluZyBlbGVtZW50cyBmcm9tIHRoZSBzZXQgaXMgbm90IHN1cHBvcnRlZC4gT25seVxuICAgKiBzdHJpbmdzIGFyZSBzdXBwb3J0ZWQgZm9yIG1lbWJlcnNoaXAuXG4gICAqL1xuICBmdW5jdGlvbiBBcnJheVNldCgpIHtcbiAgICB0aGlzLl9hcnJheSA9IFtdO1xuICAgIHRoaXMuX3NldCA9IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXRpYyBtZXRob2QgZm9yIGNyZWF0aW5nIEFycmF5U2V0IGluc3RhbmNlcyBmcm9tIGFuIGV4aXN0aW5nIGFycmF5LlxuICAgKi9cbiAgQXJyYXlTZXQuZnJvbUFycmF5ID0gZnVuY3Rpb24gQXJyYXlTZXRfZnJvbUFycmF5KGFBcnJheSwgYUFsbG93RHVwbGljYXRlcykge1xuICAgIHZhciBzZXQgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gYUFycmF5Lmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICBzZXQuYWRkKGFBcnJheVtpXSwgYUFsbG93RHVwbGljYXRlcyk7XG4gICAgfVxuICAgIHJldHVybiBzZXQ7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybiBob3cgbWFueSB1bmlxdWUgaXRlbXMgYXJlIGluIHRoaXMgQXJyYXlTZXQuIElmIGR1cGxpY2F0ZXMgaGF2ZSBiZWVuXG4gICAqIGFkZGVkLCB0aGFuIHRob3NlIGRvIG5vdCBjb3VudCB0b3dhcmRzIHRoZSBzaXplLlxuICAgKlxuICAgKiBAcmV0dXJucyBOdW1iZXJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5zaXplID0gZnVuY3Rpb24gQXJyYXlTZXRfc2l6ZSgpIHtcbiAgICByZXR1cm4gT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXModGhpcy5fc2V0KS5sZW5ndGg7XG4gIH07XG5cbiAgLyoqXG4gICAqIEFkZCB0aGUgZ2l2ZW4gc3RyaW5nIHRvIHRoaXMgc2V0LlxuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBBcnJheVNldF9hZGQoYVN0ciwgYUFsbG93RHVwbGljYXRlcykge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICB2YXIgaXNEdXBsaWNhdGUgPSB0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cik7XG4gICAgdmFyIGlkeCA9IHRoaXMuX2FycmF5Lmxlbmd0aDtcbiAgICBpZiAoIWlzRHVwbGljYXRlIHx8IGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYVN0cik7XG4gICAgfVxuICAgIGlmICghaXNEdXBsaWNhdGUpIHtcbiAgICAgIHRoaXMuX3NldFtzU3RyXSA9IGlkeDtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIElzIHRoZSBnaXZlbiBzdHJpbmcgYSBtZW1iZXIgb2YgdGhpcyBzZXQ/XG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmhhcyA9IGZ1bmN0aW9uIEFycmF5U2V0X2hhcyhhU3RyKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIHJldHVybiB0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cik7XG4gIH07XG5cbiAgLyoqXG4gICAqIFdoYXQgaXMgdGhlIGluZGV4IG9mIHRoZSBnaXZlbiBzdHJpbmcgaW4gdGhlIGFycmF5P1xuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5pbmRleE9mID0gZnVuY3Rpb24gQXJyYXlTZXRfaW5kZXhPZihhU3RyKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIGlmICh0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cikpIHtcbiAgICAgIHJldHVybiB0aGlzLl9zZXRbc1N0cl07XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYVN0ciArICdcIiBpcyBub3QgaW4gdGhlIHNldC4nKTtcbiAgfTtcblxuICAvKipcbiAgICogV2hhdCBpcyB0aGUgZWxlbWVudCBhdCB0aGUgZ2l2ZW4gaW5kZXg/XG4gICAqXG4gICAqIEBwYXJhbSBOdW1iZXIgYUlkeFxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmF0ID0gZnVuY3Rpb24gQXJyYXlTZXRfYXQoYUlkeCkge1xuICAgIGlmIChhSWR4ID49IDAgJiYgYUlkeCA8IHRoaXMuX2FycmF5Lmxlbmd0aCkge1xuICAgICAgcmV0dXJuIHRoaXMuX2FycmF5W2FJZHhdO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ05vIGVsZW1lbnQgaW5kZXhlZCBieSAnICsgYUlkeCk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGFycmF5IHJlcHJlc2VudGF0aW9uIG9mIHRoaXMgc2V0ICh3aGljaCBoYXMgdGhlIHByb3BlciBpbmRpY2VzXG4gICAqIGluZGljYXRlZCBieSBpbmRleE9mKS4gTm90ZSB0aGF0IHRoaXMgaXMgYSBjb3B5IG9mIHRoZSBpbnRlcm5hbCBhcnJheSB1c2VkXG4gICAqIGZvciBzdG9yaW5nIHRoZSBtZW1iZXJzIHNvIHRoYXQgbm8gb25lIGNhbiBtZXNzIHdpdGggaW50ZXJuYWwgc3RhdGUuXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUudG9BcnJheSA9IGZ1bmN0aW9uIEFycmF5U2V0X3RvQXJyYXkoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2FycmF5LnNsaWNlKCk7XG4gIH07XG5cbiAgZXhwb3J0cy5BcnJheVNldCA9IEFycmF5U2V0O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9hcnJheS1zZXQuanNcbiAqKiBtb2R1bGUgaWQgPSA1XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICpcbiAqIEJhc2VkIG9uIHRoZSBCYXNlIDY0IFZMUSBpbXBsZW1lbnRhdGlvbiBpbiBDbG9zdXJlIENvbXBpbGVyOlxuICogaHR0cHM6Ly9jb2RlLmdvb2dsZS5jb20vcC9jbG9zdXJlLWNvbXBpbGVyL3NvdXJjZS9icm93c2UvdHJ1bmsvc3JjL2NvbS9nb29nbGUvZGVidWdnaW5nL3NvdXJjZW1hcC9CYXNlNjRWTFEuamF2YVxuICpcbiAqIENvcHlyaWdodCAyMDExIFRoZSBDbG9zdXJlIENvbXBpbGVyIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXRcbiAqIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmVcbiAqIG1ldDpcbiAqXG4gKiAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodFxuICogICAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLlxuICogICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZVxuICogICAgY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmdcbiAqICAgIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZFxuICogICAgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuICogICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0c1xuICogICAgY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkXG4gKiAgICBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uXG4gKlxuICogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SU1xuICogXCJBUyBJU1wiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVFxuICogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SXG4gKiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVFxuICogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsXG4gKiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UXG4gKiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSxcbiAqIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWVxuICogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVFxuICogKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFXG4gKiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLlxuICovXG57XG4gIHZhciBiYXNlNjQgPSByZXF1aXJlKCcuL2Jhc2U2NCcpO1xuXG4gIC8vIEEgc2luZ2xlIGJhc2UgNjQgZGlnaXQgY2FuIGNvbnRhaW4gNiBiaXRzIG9mIGRhdGEuIEZvciB0aGUgYmFzZSA2NCB2YXJpYWJsZVxuICAvLyBsZW5ndGggcXVhbnRpdGllcyB3ZSB1c2UgaW4gdGhlIHNvdXJjZSBtYXAgc3BlYywgdGhlIGZpcnN0IGJpdCBpcyB0aGUgc2lnbixcbiAgLy8gdGhlIG5leHQgZm91ciBiaXRzIGFyZSB0aGUgYWN0dWFsIHZhbHVlLCBhbmQgdGhlIDZ0aCBiaXQgaXMgdGhlXG4gIC8vIGNvbnRpbnVhdGlvbiBiaXQuIFRoZSBjb250aW51YXRpb24gYml0IHRlbGxzIHVzIHdoZXRoZXIgdGhlcmUgYXJlIG1vcmVcbiAgLy8gZGlnaXRzIGluIHRoaXMgdmFsdWUgZm9sbG93aW5nIHRoaXMgZGlnaXQuXG4gIC8vXG4gIC8vICAgQ29udGludWF0aW9uXG4gIC8vICAgfCAgICBTaWduXG4gIC8vICAgfCAgICB8XG4gIC8vICAgViAgICBWXG4gIC8vICAgMTAxMDExXG5cbiAgdmFyIFZMUV9CQVNFX1NISUZUID0gNTtcblxuICAvLyBiaW5hcnk6IDEwMDAwMFxuICB2YXIgVkxRX0JBU0UgPSAxIDw8IFZMUV9CQVNFX1NISUZUO1xuXG4gIC8vIGJpbmFyeTogMDExMTExXG4gIHZhciBWTFFfQkFTRV9NQVNLID0gVkxRX0JBU0UgLSAxO1xuXG4gIC8vIGJpbmFyeTogMTAwMDAwXG4gIHZhciBWTFFfQ09OVElOVUFUSU9OX0JJVCA9IFZMUV9CQVNFO1xuXG4gIC8qKlxuICAgKiBDb252ZXJ0cyBmcm9tIGEgdHdvLWNvbXBsZW1lbnQgdmFsdWUgdG8gYSB2YWx1ZSB3aGVyZSB0aGUgc2lnbiBiaXQgaXNcbiAgICogcGxhY2VkIGluIHRoZSBsZWFzdCBzaWduaWZpY2FudCBiaXQuICBGb3IgZXhhbXBsZSwgYXMgZGVjaW1hbHM6XG4gICAqICAgMSBiZWNvbWVzIDIgKDEwIGJpbmFyeSksIC0xIGJlY29tZXMgMyAoMTEgYmluYXJ5KVxuICAgKiAgIDIgYmVjb21lcyA0ICgxMDAgYmluYXJ5KSwgLTIgYmVjb21lcyA1ICgxMDEgYmluYXJ5KVxuICAgKi9cbiAgZnVuY3Rpb24gdG9WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgcmV0dXJuIGFWYWx1ZSA8IDBcbiAgICAgID8gKCgtYVZhbHVlKSA8PCAxKSArIDFcbiAgICAgIDogKGFWYWx1ZSA8PCAxKSArIDA7XG4gIH1cblxuICAvKipcbiAgICogQ29udmVydHMgdG8gYSB0d28tY29tcGxlbWVudCB2YWx1ZSBmcm9tIGEgdmFsdWUgd2hlcmUgdGhlIHNpZ24gYml0IGlzXG4gICAqIHBsYWNlZCBpbiB0aGUgbGVhc3Qgc2lnbmlmaWNhbnQgYml0LiAgRm9yIGV4YW1wbGUsIGFzIGRlY2ltYWxzOlxuICAgKiAgIDIgKDEwIGJpbmFyeSkgYmVjb21lcyAxLCAzICgxMSBiaW5hcnkpIGJlY29tZXMgLTFcbiAgICogICA0ICgxMDAgYmluYXJ5KSBiZWNvbWVzIDIsIDUgKDEwMSBiaW5hcnkpIGJlY29tZXMgLTJcbiAgICovXG4gIGZ1bmN0aW9uIGZyb21WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgdmFyIGlzTmVnYXRpdmUgPSAoYVZhbHVlICYgMSkgPT09IDE7XG4gICAgdmFyIHNoaWZ0ZWQgPSBhVmFsdWUgPj4gMTtcbiAgICByZXR1cm4gaXNOZWdhdGl2ZVxuICAgICAgPyAtc2hpZnRlZFxuICAgICAgOiBzaGlmdGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGJhc2UgNjQgVkxRIGVuY29kZWQgdmFsdWUuXG4gICAqL1xuICBleHBvcnRzLmVuY29kZSA9IGZ1bmN0aW9uIGJhc2U2NFZMUV9lbmNvZGUoYVZhbHVlKSB7XG4gICAgdmFyIGVuY29kZWQgPSBcIlwiO1xuICAgIHZhciBkaWdpdDtcblxuICAgIHZhciB2bHEgPSB0b1ZMUVNpZ25lZChhVmFsdWUpO1xuXG4gICAgZG8ge1xuICAgICAgZGlnaXQgPSB2bHEgJiBWTFFfQkFTRV9NQVNLO1xuICAgICAgdmxxID4+Pj0gVkxRX0JBU0VfU0hJRlQ7XG4gICAgICBpZiAodmxxID4gMCkge1xuICAgICAgICAvLyBUaGVyZSBhcmUgc3RpbGwgbW9yZSBkaWdpdHMgaW4gdGhpcyB2YWx1ZSwgc28gd2UgbXVzdCBtYWtlIHN1cmUgdGhlXG4gICAgICAgIC8vIGNvbnRpbnVhdGlvbiBiaXQgaXMgbWFya2VkLlxuICAgICAgICBkaWdpdCB8PSBWTFFfQ09OVElOVUFUSU9OX0JJVDtcbiAgICAgIH1cbiAgICAgIGVuY29kZWQgKz0gYmFzZTY0LmVuY29kZShkaWdpdCk7XG4gICAgfSB3aGlsZSAodmxxID4gMCk7XG5cbiAgICByZXR1cm4gZW5jb2RlZDtcbiAgfTtcblxuICAvKipcbiAgICogRGVjb2RlcyB0aGUgbmV4dCBiYXNlIDY0IFZMUSB2YWx1ZSBmcm9tIHRoZSBnaXZlbiBzdHJpbmcgYW5kIHJldHVybnMgdGhlXG4gICAqIHZhbHVlIGFuZCB0aGUgcmVzdCBvZiB0aGUgc3RyaW5nIHZpYSB0aGUgb3V0IHBhcmFtZXRlci5cbiAgICovXG4gIGV4cG9ydHMuZGVjb2RlID0gZnVuY3Rpb24gYmFzZTY0VkxRX2RlY29kZShhU3RyLCBhSW5kZXgsIGFPdXRQYXJhbSkge1xuICAgIHZhciBzdHJMZW4gPSBhU3RyLmxlbmd0aDtcbiAgICB2YXIgcmVzdWx0ID0gMDtcbiAgICB2YXIgc2hpZnQgPSAwO1xuICAgIHZhciBjb250aW51YXRpb24sIGRpZ2l0O1xuXG4gICAgZG8ge1xuICAgICAgaWYgKGFJbmRleCA+PSBzdHJMZW4pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRXhwZWN0ZWQgbW9yZSBkaWdpdHMgaW4gYmFzZSA2NCBWTFEgdmFsdWUuXCIpO1xuICAgICAgfVxuXG4gICAgICBkaWdpdCA9IGJhc2U2NC5kZWNvZGUoYVN0ci5jaGFyQ29kZUF0KGFJbmRleCsrKSk7XG4gICAgICBpZiAoZGlnaXQgPT09IC0xKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkludmFsaWQgYmFzZTY0IGRpZ2l0OiBcIiArIGFTdHIuY2hhckF0KGFJbmRleCAtIDEpKTtcbiAgICAgIH1cblxuICAgICAgY29udGludWF0aW9uID0gISEoZGlnaXQgJiBWTFFfQ09OVElOVUFUSU9OX0JJVCk7XG4gICAgICBkaWdpdCAmPSBWTFFfQkFTRV9NQVNLO1xuICAgICAgcmVzdWx0ID0gcmVzdWx0ICsgKGRpZ2l0IDw8IHNoaWZ0KTtcbiAgICAgIHNoaWZ0ICs9IFZMUV9CQVNFX1NISUZUO1xuICAgIH0gd2hpbGUgKGNvbnRpbnVhdGlvbik7XG5cbiAgICBhT3V0UGFyYW0udmFsdWUgPSBmcm9tVkxRU2lnbmVkKHJlc3VsdCk7XG4gICAgYU91dFBhcmFtLnJlc3QgPSBhSW5kZXg7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2Jhc2U2NC12bHEuanNcbiAqKiBtb2R1bGUgaWQgPSA2XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBpbnRUb0NoYXJNYXAgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLycuc3BsaXQoJycpO1xuXG4gIC8qKlxuICAgKiBFbmNvZGUgYW4gaW50ZWdlciBpbiB0aGUgcmFuZ2Ugb2YgMCB0byA2MyB0byBhIHNpbmdsZSBiYXNlIDY0IGRpZ2l0LlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiAobnVtYmVyKSB7XG4gICAgaWYgKDAgPD0gbnVtYmVyICYmIG51bWJlciA8IGludFRvQ2hhck1hcC5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBpbnRUb0NoYXJNYXBbbnVtYmVyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIk11c3QgYmUgYmV0d2VlbiAwIGFuZCA2MzogXCIgKyBudW1iZXIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBEZWNvZGUgYSBzaW5nbGUgYmFzZSA2NCBjaGFyYWN0ZXIgY29kZSBkaWdpdCB0byBhbiBpbnRlZ2VyLiBSZXR1cm5zIC0xIG9uXG4gICAqIGZhaWx1cmUuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIChjaGFyQ29kZSkge1xuICAgIHZhciBiaWdBID0gNjU7ICAgICAvLyAnQSdcbiAgICB2YXIgYmlnWiA9IDkwOyAgICAgLy8gJ1onXG5cbiAgICB2YXIgbGl0dGxlQSA9IDk3OyAgLy8gJ2EnXG4gICAgdmFyIGxpdHRsZVogPSAxMjI7IC8vICd6J1xuXG4gICAgdmFyIHplcm8gPSA0ODsgICAgIC8vICcwJ1xuICAgIHZhciBuaW5lID0gNTc7ICAgICAvLyAnOSdcblxuICAgIHZhciBwbHVzID0gNDM7ICAgICAvLyAnKydcbiAgICB2YXIgc2xhc2ggPSA0NzsgICAgLy8gJy8nXG5cbiAgICB2YXIgbGl0dGxlT2Zmc2V0ID0gMjY7XG4gICAgdmFyIG51bWJlck9mZnNldCA9IDUyO1xuXG4gICAgLy8gMCAtIDI1OiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWlxuICAgIGlmIChiaWdBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGJpZ1opIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBiaWdBKTtcbiAgICB9XG5cbiAgICAvLyAyNiAtIDUxOiBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5elxuICAgIGlmIChsaXR0bGVBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGxpdHRsZVopIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBsaXR0bGVBICsgbGl0dGxlT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA1MiAtIDYxOiAwMTIzNDU2Nzg5XG4gICAgaWYgKHplcm8gPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gbmluZSkge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIHplcm8gKyBudW1iZXJPZmZzZXQpO1xuICAgIH1cblxuICAgIC8vIDYyOiArXG4gICAgaWYgKGNoYXJDb2RlID09IHBsdXMpIHtcbiAgICAgIHJldHVybiA2MjtcbiAgICB9XG5cbiAgICAvLyA2MzogL1xuICAgIGlmIChjaGFyQ29kZSA9PSBzbGFzaCkge1xuICAgICAgcmV0dXJuIDYzO1xuICAgIH1cblxuICAgIC8vIEludmFsaWQgYmFzZTY0IGRpZ2l0LlxuICAgIHJldHVybiAtMTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmFzZTY0LmpzXG4gKiogbW9kdWxlIGlkID0gN1xuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICAvLyBJdCB0dXJucyBvdXQgdGhhdCBzb21lIChtb3N0PykgSmF2YVNjcmlwdCBlbmdpbmVzIGRvbid0IHNlbGYtaG9zdFxuICAvLyBgQXJyYXkucHJvdG90eXBlLnNvcnRgLiBUaGlzIG1ha2VzIHNlbnNlIGJlY2F1c2UgQysrIHdpbGwgbGlrZWx5IHJlbWFpblxuICAvLyBmYXN0ZXIgdGhhbiBKUyB3aGVuIGRvaW5nIHJhdyBDUFUtaW50ZW5zaXZlIHNvcnRpbmcuIEhvd2V2ZXIsIHdoZW4gdXNpbmcgYVxuICAvLyBjdXN0b20gY29tcGFyYXRvciBmdW5jdGlvbiwgY2FsbGluZyBiYWNrIGFuZCBmb3J0aCBiZXR3ZWVuIHRoZSBWTSdzIEMrKyBhbmRcbiAgLy8gSklUJ2QgSlMgaXMgcmF0aGVyIHNsb3cgKmFuZCogbG9zZXMgSklUIHR5cGUgaW5mb3JtYXRpb24sIHJlc3VsdGluZyBpblxuICAvLyB3b3JzZSBnZW5lcmF0ZWQgY29kZSBmb3IgdGhlIGNvbXBhcmF0b3IgZnVuY3Rpb24gdGhhbiB3b3VsZCBiZSBvcHRpbWFsLiBJblxuICAvLyBmYWN0LCB3aGVuIHNvcnRpbmcgd2l0aCBhIGNvbXBhcmF0b3IsIHRoZXNlIGNvc3RzIG91dHdlaWdoIHRoZSBiZW5lZml0cyBvZlxuICAvLyBzb3J0aW5nIGluIEMrKy4gQnkgdXNpbmcgb3VyIG93biBKUy1pbXBsZW1lbnRlZCBRdWljayBTb3J0IChiZWxvdyksIHdlIGdldFxuICAvLyBhIH4zNTAwbXMgbWVhbiBzcGVlZC11cCBpbiBgYmVuY2gvYmVuY2guaHRtbGAuXG5cbiAgLyoqXG4gICAqIFN3YXAgdGhlIGVsZW1lbnRzIGluZGV4ZWQgYnkgYHhgIGFuZCBgeWAgaW4gdGhlIGFycmF5IGBhcnlgLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIFRoZSBhcnJheS5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHhcbiAgICogICAgICAgIFRoZSBpbmRleCBvZiB0aGUgZmlyc3QgaXRlbS5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHlcbiAgICogICAgICAgIFRoZSBpbmRleCBvZiB0aGUgc2Vjb25kIGl0ZW0uXG4gICAqL1xuICBmdW5jdGlvbiBzd2FwKGFyeSwgeCwgeSkge1xuICAgIHZhciB0ZW1wID0gYXJ5W3hdO1xuICAgIGFyeVt4XSA9IGFyeVt5XTtcbiAgICBhcnlbeV0gPSB0ZW1wO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgYSByYW5kb20gaW50ZWdlciB3aXRoaW4gdGhlIHJhbmdlIGBsb3cgLi4gaGlnaGAgaW5jbHVzaXZlLlxuICAgKlxuICAgKiBAcGFyYW0ge051bWJlcn0gbG93XG4gICAqICAgICAgICBUaGUgbG93ZXIgYm91bmQgb24gdGhlIHJhbmdlLlxuICAgKiBAcGFyYW0ge051bWJlcn0gaGlnaFxuICAgKiAgICAgICAgVGhlIHVwcGVyIGJvdW5kIG9uIHRoZSByYW5nZS5cbiAgICovXG4gIGZ1bmN0aW9uIHJhbmRvbUludEluUmFuZ2UobG93LCBoaWdoKSB7XG4gICAgcmV0dXJuIE1hdGgucm91bmQobG93ICsgKE1hdGgucmFuZG9tKCkgKiAoaGlnaCAtIGxvdykpKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgUXVpY2sgU29ydCBhbGdvcml0aG0uXG4gICAqXG4gICAqIEBwYXJhbSB7QXJyYXl9IGFyeVxuICAgKiAgICAgICAgQW4gYXJyYXkgdG8gc29ydC5cbiAgICogQHBhcmFtIHtmdW5jdGlvbn0gY29tcGFyYXRvclxuICAgKiAgICAgICAgRnVuY3Rpb24gdG8gdXNlIHRvIGNvbXBhcmUgdHdvIGl0ZW1zLlxuICAgKiBAcGFyYW0ge051bWJlcn0gcFxuICAgKiAgICAgICAgU3RhcnQgaW5kZXggb2YgdGhlIGFycmF5XG4gICAqIEBwYXJhbSB7TnVtYmVyfSByXG4gICAqICAgICAgICBFbmQgaW5kZXggb2YgdGhlIGFycmF5XG4gICAqL1xuICBmdW5jdGlvbiBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHAsIHIpIHtcbiAgICAvLyBJZiBvdXIgbG93ZXIgYm91bmQgaXMgbGVzcyB0aGFuIG91ciB1cHBlciBib3VuZCwgd2UgKDEpIHBhcnRpdGlvbiB0aGVcbiAgICAvLyBhcnJheSBpbnRvIHR3byBwaWVjZXMgYW5kICgyKSByZWN1cnNlIG9uIGVhY2ggaGFsZi4gSWYgaXQgaXMgbm90LCB0aGlzIGlzXG4gICAgLy8gdGhlIGVtcHR5IGFycmF5IGFuZCBvdXIgYmFzZSBjYXNlLlxuXG4gICAgaWYgKHAgPCByKSB7XG4gICAgICAvLyAoMSkgUGFydGl0aW9uaW5nLlxuICAgICAgLy9cbiAgICAgIC8vIFRoZSBwYXJ0aXRpb25pbmcgY2hvb3NlcyBhIHBpdm90IGJldHdlZW4gYHBgIGFuZCBgcmAgYW5kIG1vdmVzIGFsbFxuICAgICAgLy8gZWxlbWVudHMgdGhhdCBhcmUgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHRoZSBwaXZvdCB0byB0aGUgYmVmb3JlIGl0LCBhbmRcbiAgICAgIC8vIGFsbCB0aGUgZWxlbWVudHMgdGhhdCBhcmUgZ3JlYXRlciB0aGFuIGl0IGFmdGVyIGl0LiBUaGUgZWZmZWN0IGlzIHRoYXRcbiAgICAgIC8vIG9uY2UgcGFydGl0aW9uIGlzIGRvbmUsIHRoZSBwaXZvdCBpcyBpbiB0aGUgZXhhY3QgcGxhY2UgaXQgd2lsbCBiZSB3aGVuXG4gICAgICAvLyB0aGUgYXJyYXkgaXMgcHV0IGluIHNvcnRlZCBvcmRlciwgYW5kIGl0IHdpbGwgbm90IG5lZWQgdG8gYmUgbW92ZWRcbiAgICAgIC8vIGFnYWluLiBUaGlzIHJ1bnMgaW4gTyhuKSB0aW1lLlxuXG4gICAgICAvLyBBbHdheXMgY2hvb3NlIGEgcmFuZG9tIHBpdm90IHNvIHRoYXQgYW4gaW5wdXQgYXJyYXkgd2hpY2ggaXMgcmV2ZXJzZVxuICAgICAgLy8gc29ydGVkIGRvZXMgbm90IGNhdXNlIE8obl4yKSBydW5uaW5nIHRpbWUuXG4gICAgICB2YXIgcGl2b3RJbmRleCA9IHJhbmRvbUludEluUmFuZ2UocCwgcik7XG4gICAgICB2YXIgaSA9IHAgLSAxO1xuXG4gICAgICBzd2FwKGFyeSwgcGl2b3RJbmRleCwgcik7XG4gICAgICB2YXIgcGl2b3QgPSBhcnlbcl07XG5cbiAgICAgIC8vIEltbWVkaWF0ZWx5IGFmdGVyIGBqYCBpcyBpbmNyZW1lbnRlZCBpbiB0aGlzIGxvb3AsIHRoZSBmb2xsb3dpbmcgaG9sZFxuICAgICAgLy8gdHJ1ZTpcbiAgICAgIC8vXG4gICAgICAvLyAgICogRXZlcnkgZWxlbWVudCBpbiBgYXJ5W3AgLi4gaV1gIGlzIGxlc3MgdGhhbiBvciBlcXVhbCB0byB0aGUgcGl2b3QuXG4gICAgICAvL1xuICAgICAgLy8gICAqIEV2ZXJ5IGVsZW1lbnQgaW4gYGFyeVtpKzEgLi4gai0xXWAgaXMgZ3JlYXRlciB0aGFuIHRoZSBwaXZvdC5cbiAgICAgIGZvciAodmFyIGogPSBwOyBqIDwgcjsgaisrKSB7XG4gICAgICAgIGlmIChjb21wYXJhdG9yKGFyeVtqXSwgcGl2b3QpIDw9IDApIHtcbiAgICAgICAgICBpICs9IDE7XG4gICAgICAgICAgc3dhcChhcnksIGksIGopO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHN3YXAoYXJ5LCBpICsgMSwgaik7XG4gICAgICB2YXIgcSA9IGkgKyAxO1xuXG4gICAgICAvLyAoMikgUmVjdXJzZSBvbiBlYWNoIGhhbGYuXG5cbiAgICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcCwgcSAtIDEpO1xuICAgICAgZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBxICsgMSwgcik7XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFNvcnQgdGhlIGdpdmVuIGFycmF5IGluLXBsYWNlIHdpdGggdGhlIGdpdmVuIGNvbXBhcmF0b3IgZnVuY3Rpb24uXG4gICAqXG4gICAqIEBwYXJhbSB7QXJyYXl9IGFyeVxuICAgKiAgICAgICAgQW4gYXJyYXkgdG8gc29ydC5cbiAgICogQHBhcmFtIHtmdW5jdGlvbn0gY29tcGFyYXRvclxuICAgKiAgICAgICAgRnVuY3Rpb24gdG8gdXNlIHRvIGNvbXBhcmUgdHdvIGl0ZW1zLlxuICAgKi9cbiAgZXhwb3J0cy5xdWlja1NvcnQgPSBmdW5jdGlvbiAoYXJ5LCBjb21wYXJhdG9yKSB7XG4gICAgZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCAwLCBhcnkubGVuZ3RoIC0gMSk7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3F1aWNrLXNvcnQuanNcbiAqKiBtb2R1bGUgaWQgPSA4XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBiYXNlNjRWTFEgPSByZXF1aXJlKCcuL2Jhc2U2NC12bHEnKTtcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcbiAgdmFyIEFycmF5U2V0ID0gcmVxdWlyZSgnLi9hcnJheS1zZXQnKS5BcnJheVNldDtcbiAgdmFyIE1hcHBpbmdMaXN0ID0gcmVxdWlyZSgnLi9tYXBwaW5nLWxpc3QnKS5NYXBwaW5nTGlzdDtcblxuICAvKipcbiAgICogQW4gaW5zdGFuY2Ugb2YgdGhlIFNvdXJjZU1hcEdlbmVyYXRvciByZXByZXNlbnRzIGEgc291cmNlIG1hcCB3aGljaCBpc1xuICAgKiBiZWluZyBidWlsdCBpbmNyZW1lbnRhbGx5LiBZb3UgbWF5IHBhc3MgYW4gb2JqZWN0IHdpdGggdGhlIGZvbGxvd2luZ1xuICAgKiBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gZmlsZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gc291cmNlUm9vdDogQSByb290IGZvciBhbGwgcmVsYXRpdmUgVVJMcyBpbiB0aGlzIHNvdXJjZSBtYXAuXG4gICAqL1xuICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3IoYUFyZ3MpIHtcbiAgICBpZiAoIWFBcmdzKSB7XG4gICAgICBhQXJncyA9IHt9O1xuICAgIH1cbiAgICB0aGlzLl9maWxlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdmaWxlJywgbnVsbCk7XG4gICAgdGhpcy5fc291cmNlUm9vdCA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlUm9vdCcsIG51bGwpO1xuICAgIHRoaXMuX3NraXBWYWxpZGF0aW9uID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdza2lwVmFsaWRhdGlvbicsIGZhbHNlKTtcbiAgICB0aGlzLl9zb3VyY2VzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbmFtZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICB0aGlzLl9tYXBwaW5ncyA9IG5ldyBNYXBwaW5nTGlzdCgpO1xuICAgIHRoaXMuX3NvdXJjZXNDb250ZW50cyA9IG51bGw7XG4gIH1cblxuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBTb3VyY2VNYXBHZW5lcmF0b3IgYmFzZWQgb24gYSBTb3VyY2VNYXBDb25zdW1lclxuICAgKlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBTb3VyY2VNYXAuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IuZnJvbVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2Zyb21Tb3VyY2VNYXAoYVNvdXJjZU1hcENvbnN1bWVyKSB7XG4gICAgICB2YXIgc291cmNlUm9vdCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VSb290O1xuICAgICAgdmFyIGdlbmVyYXRvciA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgICBmaWxlOiBhU291cmNlTWFwQ29uc3VtZXIuZmlsZSxcbiAgICAgICAgc291cmNlUm9vdDogc291cmNlUm9vdFxuICAgICAgfSk7XG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuZWFjaE1hcHBpbmcoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgICAgdmFyIG5ld01hcHBpbmcgPSB7XG4gICAgICAgICAgZ2VuZXJhdGVkOiB7XG4gICAgICAgICAgICBsaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgbmV3TWFwcGluZy5zb3VyY2UgPSBtYXBwaW5nLnNvdXJjZTtcbiAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICBuZXdNYXBwaW5nLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgbmV3TWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIG5ld01hcHBpbmcub3JpZ2luYWwgPSB7XG4gICAgICAgICAgICBsaW5lOiBtYXBwaW5nLm9yaWdpbmFsTGluZSxcbiAgICAgICAgICAgIGNvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtblxuICAgICAgICAgIH07XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgIG5ld01hcHBpbmcubmFtZSA9IG1hcHBpbmcubmFtZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBnZW5lcmF0b3IuYWRkTWFwcGluZyhuZXdNYXBwaW5nKTtcbiAgICAgIH0pO1xuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZXMuZm9yRWFjaChmdW5jdGlvbiAoc291cmNlRmlsZSkge1xuICAgICAgICB2YXIgY29udGVudCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZUZpbGUpO1xuICAgICAgICBpZiAoY29udGVudCAhPSBudWxsKSB7XG4gICAgICAgICAgZ2VuZXJhdG9yLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGdlbmVyYXRvcjtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBZGQgYSBzaW5nbGUgbWFwcGluZyBmcm9tIG9yaWdpbmFsIHNvdXJjZSBsaW5lIGFuZCBjb2x1bW4gdG8gdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gZm9yIHRoaXMgc291cmNlIG1hcCBiZWluZyBjcmVhdGVkLiBUaGUgbWFwcGluZ1xuICAgKiBvYmplY3Qgc2hvdWxkIGhhdmUgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gZ2VuZXJhdGVkOiBBbiBvYmplY3Qgd2l0aCB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMuXG4gICAqICAgLSBvcmlnaW5hbDogQW4gb2JqZWN0IHdpdGggdGhlIG9yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMuXG4gICAqICAgLSBzb3VyY2U6IFRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSAocmVsYXRpdmUgdG8gdGhlIHNvdXJjZVJvb3QpLlxuICAgKiAgIC0gbmFtZTogQW4gb3B0aW9uYWwgb3JpZ2luYWwgdG9rZW4gbmFtZSBmb3IgdGhpcyBtYXBwaW5nLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hZGRNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfYWRkTWFwcGluZyhhQXJncykge1xuICAgICAgdmFyIGdlbmVyYXRlZCA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnZ2VuZXJhdGVkJyk7XG4gICAgICB2YXIgb3JpZ2luYWwgPSB1dGlsLmdldEFyZyhhQXJncywgJ29yaWdpbmFsJywgbnVsbCk7XG4gICAgICB2YXIgc291cmNlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnLCBudWxsKTtcbiAgICAgIHZhciBuYW1lID0gdXRpbC5nZXRBcmcoYUFyZ3MsICduYW1lJywgbnVsbCk7XG5cbiAgICAgIGlmICghdGhpcy5fc2tpcFZhbGlkYXRpb24pIHtcbiAgICAgICAgdGhpcy5fdmFsaWRhdGVNYXBwaW5nKGdlbmVyYXRlZCwgb3JpZ2luYWwsIHNvdXJjZSwgbmFtZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiAhdGhpcy5fc291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICB0aGlzLl9zb3VyY2VzLmFkZChzb3VyY2UpO1xuICAgICAgfVxuXG4gICAgICBpZiAobmFtZSAhPSBudWxsICYmICF0aGlzLl9uYW1lcy5oYXMobmFtZSkpIHtcbiAgICAgICAgdGhpcy5fbmFtZXMuYWRkKG5hbWUpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLl9tYXBwaW5ncy5hZGQoe1xuICAgICAgICBnZW5lcmF0ZWRMaW5lOiBnZW5lcmF0ZWQubGluZSxcbiAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uLFxuICAgICAgICBvcmlnaW5hbExpbmU6IG9yaWdpbmFsICE9IG51bGwgJiYgb3JpZ2luYWwubGluZSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IG9yaWdpbmFsICE9IG51bGwgJiYgb3JpZ2luYWwuY29sdW1uLFxuICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgbmFtZTogbmFtZVxuICAgICAgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogU2V0IHRoZSBzb3VyY2UgY29udGVudCBmb3IgYSBzb3VyY2UgZmlsZS5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuc2V0U291cmNlQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3NldFNvdXJjZUNvbnRlbnQoYVNvdXJjZUZpbGUsIGFTb3VyY2VDb250ZW50KSB7XG4gICAgICB2YXIgc291cmNlID0gYVNvdXJjZUZpbGU7XG4gICAgICBpZiAodGhpcy5fc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIHNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5fc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKGFTb3VyY2VDb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgLy8gQWRkIHRoZSBzb3VyY2UgY29udGVudCB0byB0aGUgX3NvdXJjZXNDb250ZW50cyBtYXAuXG4gICAgICAgIC8vIENyZWF0ZSBhIG5ldyBfc291cmNlc0NvbnRlbnRzIG1hcCBpZiB0aGUgcHJvcGVydHkgaXMgbnVsbC5cbiAgICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgICB0aGlzLl9zb3VyY2VzQ29udGVudHMgPSB7fTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9zb3VyY2VzQ29udGVudHNbdXRpbC50b1NldFN0cmluZyhzb3VyY2UpXSA9IGFTb3VyY2VDb250ZW50O1xuICAgICAgfSBlbHNlIGlmICh0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgLy8gUmVtb3ZlIHRoZSBzb3VyY2UgZmlsZSBmcm9tIHRoZSBfc291cmNlc0NvbnRlbnRzIG1hcC5cbiAgICAgICAgLy8gSWYgdGhlIF9zb3VyY2VzQ29udGVudHMgbWFwIGlzIGVtcHR5LCBzZXQgdGhlIHByb3BlcnR5IHRvIG51bGwuXG4gICAgICAgIGRlbGV0ZSB0aGlzLl9zb3VyY2VzQ29udGVudHNbdXRpbC50b1NldFN0cmluZyhzb3VyY2UpXTtcbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKHRoaXMuX3NvdXJjZXNDb250ZW50cykubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIEFwcGxpZXMgdGhlIG1hcHBpbmdzIG9mIGEgc3ViLXNvdXJjZS1tYXAgZm9yIGEgc3BlY2lmaWMgc291cmNlIGZpbGUgdG8gdGhlXG4gICAqIHNvdXJjZSBtYXAgYmVpbmcgZ2VuZXJhdGVkLiBFYWNoIG1hcHBpbmcgdG8gdGhlIHN1cHBsaWVkIHNvdXJjZSBmaWxlIGlzXG4gICAqIHJld3JpdHRlbiB1c2luZyB0aGUgc3VwcGxpZWQgc291cmNlIG1hcC4gTm90ZTogVGhlIHJlc29sdXRpb24gZm9yIHRoZVxuICAgKiByZXN1bHRpbmcgbWFwcGluZ3MgaXMgdGhlIG1pbmltaXVtIG9mIHRoaXMgbWFwIGFuZCB0aGUgc3VwcGxpZWQgbWFwLlxuICAgKlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBzb3VyY2UgbWFwIHRvIGJlIGFwcGxpZWQuXG4gICAqIEBwYXJhbSBhU291cmNlRmlsZSBPcHRpb25hbC4gVGhlIGZpbGVuYW1lIG9mIHRoZSBzb3VyY2UgZmlsZS5cbiAgICogICAgICAgIElmIG9taXR0ZWQsIFNvdXJjZU1hcENvbnN1bWVyJ3MgZmlsZSBwcm9wZXJ0eSB3aWxsIGJlIHVzZWQuXG4gICAqIEBwYXJhbSBhU291cmNlTWFwUGF0aCBPcHRpb25hbC4gVGhlIGRpcm5hbWUgb2YgdGhlIHBhdGggdG8gdGhlIHNvdXJjZSBtYXBcbiAgICogICAgICAgIHRvIGJlIGFwcGxpZWQuIElmIHJlbGF0aXZlLCBpdCBpcyByZWxhdGl2ZSB0byB0aGUgU291cmNlTWFwQ29uc3VtZXIuXG4gICAqICAgICAgICBUaGlzIHBhcmFtZXRlciBpcyBuZWVkZWQgd2hlbiB0aGUgdHdvIHNvdXJjZSBtYXBzIGFyZW4ndCBpbiB0aGUgc2FtZVxuICAgKiAgICAgICAgZGlyZWN0b3J5LCBhbmQgdGhlIHNvdXJjZSBtYXAgdG8gYmUgYXBwbGllZCBjb250YWlucyByZWxhdGl2ZSBzb3VyY2VcbiAgICogICAgICAgIHBhdGhzLiBJZiBzbywgdGhvc2UgcmVsYXRpdmUgc291cmNlIHBhdGhzIG5lZWQgdG8gYmUgcmV3cml0dGVuXG4gICAqICAgICAgICByZWxhdGl2ZSB0byB0aGUgU291cmNlTWFwR2VuZXJhdG9yLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hcHBseVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2FwcGx5U291cmNlTWFwKGFTb3VyY2VNYXBDb25zdW1lciwgYVNvdXJjZUZpbGUsIGFTb3VyY2VNYXBQYXRoKSB7XG4gICAgICB2YXIgc291cmNlRmlsZSA9IGFTb3VyY2VGaWxlO1xuICAgICAgLy8gSWYgYVNvdXJjZUZpbGUgaXMgb21pdHRlZCwgd2Ugd2lsbCB1c2UgdGhlIGZpbGUgcHJvcGVydHkgb2YgdGhlIFNvdXJjZU1hcFxuICAgICAgaWYgKGFTb3VyY2VGaWxlID09IG51bGwpIHtcbiAgICAgICAgaWYgKGFTb3VyY2VNYXBDb25zdW1lci5maWxlID09IG51bGwpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAnU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hcHBseVNvdXJjZU1hcCByZXF1aXJlcyBlaXRoZXIgYW4gZXhwbGljaXQgc291cmNlIGZpbGUsICcgK1xuICAgICAgICAgICAgJ29yIHRoZSBzb3VyY2UgbWFwXFwncyBcImZpbGVcIiBwcm9wZXJ0eS4gQm90aCB3ZXJlIG9taXR0ZWQuJ1xuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgc291cmNlRmlsZSA9IGFTb3VyY2VNYXBDb25zdW1lci5maWxlO1xuICAgICAgfVxuICAgICAgdmFyIHNvdXJjZVJvb3QgPSB0aGlzLl9zb3VyY2VSb290O1xuICAgICAgLy8gTWFrZSBcInNvdXJjZUZpbGVcIiByZWxhdGl2ZSBpZiBhbiBhYnNvbHV0ZSBVcmwgaXMgcGFzc2VkLlxuICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2VGaWxlKTtcbiAgICAgIH1cbiAgICAgIC8vIEFwcGx5aW5nIHRoZSBTb3VyY2VNYXAgY2FuIGFkZCBhbmQgcmVtb3ZlIGl0ZW1zIGZyb20gdGhlIHNvdXJjZXMgYW5kXG4gICAgICAvLyB0aGUgbmFtZXMgYXJyYXkuXG4gICAgICB2YXIgbmV3U291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgICAgdmFyIG5ld05hbWVzID0gbmV3IEFycmF5U2V0KCk7XG5cbiAgICAgIC8vIEZpbmQgbWFwcGluZ3MgZm9yIHRoZSBcInNvdXJjZUZpbGVcIlxuICAgICAgdGhpcy5fbWFwcGluZ3MudW5zb3J0ZWRGb3JFYWNoKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSA9PT0gc291cmNlRmlsZSAmJiBtYXBwaW5nLm9yaWdpbmFsTGluZSAhPSBudWxsKSB7XG4gICAgICAgICAgLy8gQ2hlY2sgaWYgaXQgY2FuIGJlIG1hcHBlZCBieSB0aGUgc291cmNlIG1hcCwgdGhlbiB1cGRhdGUgdGhlIG1hcHBpbmcuXG4gICAgICAgICAgdmFyIG9yaWdpbmFsID0gYVNvdXJjZU1hcENvbnN1bWVyLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW5cbiAgICAgICAgICB9KTtcbiAgICAgICAgICBpZiAob3JpZ2luYWwuc291cmNlICE9IG51bGwpIHtcbiAgICAgICAgICAgIC8vIENvcHkgbWFwcGluZ1xuICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSBvcmlnaW5hbC5zb3VyY2U7XG4gICAgICAgICAgICBpZiAoYVNvdXJjZU1hcFBhdGggIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IHV0aWwuam9pbihhU291cmNlTWFwUGF0aCwgbWFwcGluZy5zb3VyY2UpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSA9IG9yaWdpbmFsLmxpbmU7XG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID0gb3JpZ2luYWwuY29sdW1uO1xuICAgICAgICAgICAgaWYgKG9yaWdpbmFsLm5hbWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUgPSBvcmlnaW5hbC5uYW1lO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBzb3VyY2UgPSBtYXBwaW5nLnNvdXJjZTtcbiAgICAgICAgaWYgKHNvdXJjZSAhPSBudWxsICYmICFuZXdTb3VyY2VzLmhhcyhzb3VyY2UpKSB7XG4gICAgICAgICAgbmV3U291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBuYW1lID0gbWFwcGluZy5uYW1lO1xuICAgICAgICBpZiAobmFtZSAhPSBudWxsICYmICFuZXdOYW1lcy5oYXMobmFtZSkpIHtcbiAgICAgICAgICBuZXdOYW1lcy5hZGQobmFtZSk7XG4gICAgICAgIH1cblxuICAgICAgfSwgdGhpcyk7XG4gICAgICB0aGlzLl9zb3VyY2VzID0gbmV3U291cmNlcztcbiAgICAgIHRoaXMuX25hbWVzID0gbmV3TmFtZXM7XG5cbiAgICAgIC8vIENvcHkgc291cmNlc0NvbnRlbnRzIG9mIGFwcGxpZWQgbWFwLlxuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZXMuZm9yRWFjaChmdW5jdGlvbiAoc291cmNlRmlsZSkge1xuICAgICAgICB2YXIgY29udGVudCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZUZpbGUpO1xuICAgICAgICBpZiAoY29udGVudCAhPSBudWxsKSB7XG4gICAgICAgICAgaWYgKGFTb3VyY2VNYXBQYXRoICE9IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZUZpbGUgPSB1dGlsLmpvaW4oYVNvdXJjZU1hcFBhdGgsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2VGaWxlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5zZXRTb3VyY2VDb250ZW50KHNvdXJjZUZpbGUsIGNvbnRlbnQpO1xuICAgICAgICB9XG4gICAgICB9LCB0aGlzKTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBIG1hcHBpbmcgY2FuIGhhdmUgb25lIG9mIHRoZSB0aHJlZSBsZXZlbHMgb2YgZGF0YTpcbiAgICpcbiAgICogICAxLiBKdXN0IHRoZSBnZW5lcmF0ZWQgcG9zaXRpb24uXG4gICAqICAgMi4gVGhlIEdlbmVyYXRlZCBwb3NpdGlvbiwgb3JpZ2luYWwgcG9zaXRpb24sIGFuZCBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgMy4gR2VuZXJhdGVkIGFuZCBvcmlnaW5hbCBwb3NpdGlvbiwgb3JpZ2luYWwgc291cmNlLCBhcyB3ZWxsIGFzIGEgbmFtZVxuICAgKiAgICAgIHRva2VuLlxuICAgKlxuICAgKiBUbyBtYWludGFpbiBjb25zaXN0ZW5jeSwgd2UgdmFsaWRhdGUgdGhhdCBhbnkgbmV3IG1hcHBpbmcgYmVpbmcgYWRkZWQgZmFsbHNcbiAgICogaW4gdG8gb25lIG9mIHRoZXNlIGNhdGVnb3JpZXMuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl92YWxpZGF0ZU1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl92YWxpZGF0ZU1hcHBpbmcoYUdlbmVyYXRlZCwgYU9yaWdpbmFsLCBhU291cmNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYU5hbWUpIHtcbiAgICAgIGlmIChhR2VuZXJhdGVkICYmICdsaW5lJyBpbiBhR2VuZXJhdGVkICYmICdjb2x1bW4nIGluIGFHZW5lcmF0ZWRcbiAgICAgICAgICAmJiBhR2VuZXJhdGVkLmxpbmUgPiAwICYmIGFHZW5lcmF0ZWQuY29sdW1uID49IDBcbiAgICAgICAgICAmJiAhYU9yaWdpbmFsICYmICFhU291cmNlICYmICFhTmFtZSkge1xuICAgICAgICAvLyBDYXNlIDEuXG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGFHZW5lcmF0ZWQgJiYgJ2xpbmUnIGluIGFHZW5lcmF0ZWQgJiYgJ2NvbHVtbicgaW4gYUdlbmVyYXRlZFxuICAgICAgICAgICAgICAgJiYgYU9yaWdpbmFsICYmICdsaW5lJyBpbiBhT3JpZ2luYWwgJiYgJ2NvbHVtbicgaW4gYU9yaWdpbmFsXG4gICAgICAgICAgICAgICAmJiBhR2VuZXJhdGVkLmxpbmUgPiAwICYmIGFHZW5lcmF0ZWQuY29sdW1uID49IDBcbiAgICAgICAgICAgICAgICYmIGFPcmlnaW5hbC5saW5lID4gMCAmJiBhT3JpZ2luYWwuY29sdW1uID49IDBcbiAgICAgICAgICAgICAgICYmIGFTb3VyY2UpIHtcbiAgICAgICAgLy8gQ2FzZXMgMiBhbmQgMy5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignSW52YWxpZCBtYXBwaW5nOiAnICsgSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICAgIGdlbmVyYXRlZDogYUdlbmVyYXRlZCxcbiAgICAgICAgICBzb3VyY2U6IGFTb3VyY2UsXG4gICAgICAgICAgb3JpZ2luYWw6IGFPcmlnaW5hbCxcbiAgICAgICAgICBuYW1lOiBhTmFtZVxuICAgICAgICB9KSk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogU2VyaWFsaXplIHRoZSBhY2N1bXVsYXRlZCBtYXBwaW5ncyBpbiB0byB0aGUgc3RyZWFtIG9mIGJhc2UgNjQgVkxRc1xuICAgKiBzcGVjaWZpZWQgYnkgdGhlIHNvdXJjZSBtYXAgZm9ybWF0LlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fc2VyaWFsaXplTWFwcGluZ3MgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9zZXJpYWxpemVNYXBwaW5ncygpIHtcbiAgICAgIHZhciBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRMaW5lID0gMTtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsTGluZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNOYW1lID0gMDtcbiAgICAgIHZhciBwcmV2aW91c1NvdXJjZSA9IDA7XG4gICAgICB2YXIgcmVzdWx0ID0gJyc7XG4gICAgICB2YXIgbWFwcGluZztcbiAgICAgIHZhciBuYW1lSWR4O1xuICAgICAgdmFyIHNvdXJjZUlkeDtcblxuICAgICAgdmFyIG1hcHBpbmdzID0gdGhpcy5fbWFwcGluZ3MudG9BcnJheSgpO1xuICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IG1hcHBpbmdzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIG1hcHBpbmcgPSBtYXBwaW5nc1tpXTtcblxuICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lICE9PSBwcmV2aW91c0dlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICAgICAgd2hpbGUgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSAhPT0gcHJldmlvdXNHZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgICByZXN1bHQgKz0gJzsnO1xuICAgICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIGlmIChpID4gMCkge1xuICAgICAgICAgICAgaWYgKCF1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmcsIG1hcHBpbmdzW2kgLSAxXSkpIHtcbiAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXN1bHQgKz0gJywnO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlSWR4ID0gdGhpcy5fc291cmNlcy5pbmRleE9mKG1hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShzb3VyY2VJZHggLSBwcmV2aW91c1NvdXJjZSk7XG4gICAgICAgICAgcHJldmlvdXNTb3VyY2UgPSBzb3VyY2VJZHg7XG5cbiAgICAgICAgICAvLyBsaW5lcyBhcmUgc3RvcmVkIDAtYmFzZWQgaW4gU291cmNlTWFwIHNwZWMgdmVyc2lvbiAzXG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobWFwcGluZy5vcmlnaW5hbExpbmUgLSAxXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBwcmV2aW91c09yaWdpbmFsTGluZSk7XG4gICAgICAgICAgcHJldmlvdXNPcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZSAtIDE7XG5cbiAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShtYXBwaW5nLm9yaWdpbmFsQ29sdW1uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBwcmV2aW91c09yaWdpbmFsQ29sdW1uKTtcbiAgICAgICAgICBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgIGlmIChtYXBwaW5nLm5hbWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgbmFtZUlkeCA9IHRoaXMuX25hbWVzLmluZGV4T2YobWFwcGluZy5uYW1lKTtcbiAgICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG5hbWVJZHggLSBwcmV2aW91c05hbWUpO1xuICAgICAgICAgICAgcHJldmlvdXNOYW1lID0gbmFtZUlkeDtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xuXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50KGFTb3VyY2VzLCBhU291cmNlUm9vdCkge1xuICAgICAgcmV0dXJuIGFTb3VyY2VzLm1hcChmdW5jdGlvbiAoc291cmNlKSB7XG4gICAgICAgIGlmICghdGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGFTb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKGFTb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgICB9XG4gICAgICAgIHZhciBrZXkgPSB1dGlsLnRvU2V0U3RyaW5nKHNvdXJjZSk7XG4gICAgICAgIHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodGhpcy5fc291cmNlc0NvbnRlbnRzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSlcbiAgICAgICAgICA/IHRoaXMuX3NvdXJjZXNDb250ZW50c1trZXldXG4gICAgICAgICAgOiBudWxsO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogRXh0ZXJuYWxpemUgdGhlIHNvdXJjZSBtYXAuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLnRvSlNPTiA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3RvSlNPTigpIHtcbiAgICAgIHZhciBtYXAgPSB7XG4gICAgICAgIHZlcnNpb246IHRoaXMuX3ZlcnNpb24sXG4gICAgICAgIHNvdXJjZXM6IHRoaXMuX3NvdXJjZXMudG9BcnJheSgpLFxuICAgICAgICBuYW1lczogdGhpcy5fbmFtZXMudG9BcnJheSgpLFxuICAgICAgICBtYXBwaW5nczogdGhpcy5fc2VyaWFsaXplTWFwcGluZ3MoKVxuICAgICAgfTtcbiAgICAgIGlmICh0aGlzLl9maWxlICE9IG51bGwpIHtcbiAgICAgICAgbWFwLmZpbGUgPSB0aGlzLl9maWxlO1xuICAgICAgfVxuICAgICAgaWYgKHRoaXMuX3NvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBtYXAuc291cmNlUm9vdCA9IHRoaXMuX3NvdXJjZVJvb3Q7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgIG1hcC5zb3VyY2VzQ29udGVudCA9IHRoaXMuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQobWFwLnNvdXJjZXMsIG1hcC5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG1hcDtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZW5kZXIgdGhlIHNvdXJjZSBtYXAgYmVpbmcgZ2VuZXJhdGVkIHRvIGEgc3RyaW5nLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS50b1N0cmluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3RvU3RyaW5nKCkge1xuICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHRoaXMudG9KU09OKCkpO1xuICAgIH07XG5cbiAgZXhwb3J0cy5Tb3VyY2VNYXBHZW5lcmF0b3IgPSBTb3VyY2VNYXBHZW5lcmF0b3I7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3NvdXJjZS1tYXAtZ2VuZXJhdG9yLmpzXG4gKiogbW9kdWxlIGlkID0gOVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDE0IE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgdXRpbCA9IHJlcXVpcmUoJy4vdXRpbCcpO1xuXG4gIC8qKlxuICAgKiBEZXRlcm1pbmUgd2hldGhlciBtYXBwaW5nQiBpcyBhZnRlciBtYXBwaW5nQSB3aXRoIHJlc3BlY3QgdG8gZ2VuZXJhdGVkXG4gICAqIHBvc2l0aW9uLlxuICAgKi9cbiAgZnVuY3Rpb24gZ2VuZXJhdGVkUG9zaXRpb25BZnRlcihtYXBwaW5nQSwgbWFwcGluZ0IpIHtcbiAgICAvLyBPcHRpbWl6ZWQgZm9yIG1vc3QgY29tbW9uIGNhc2VcbiAgICB2YXIgbGluZUEgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lO1xuICAgIHZhciBsaW5lQiA9IG1hcHBpbmdCLmdlbmVyYXRlZExpbmU7XG4gICAgdmFyIGNvbHVtbkEgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW47XG4gICAgdmFyIGNvbHVtbkIgPSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgcmV0dXJuIGxpbmVCID4gbGluZUEgfHwgbGluZUIgPT0gbGluZUEgJiYgY29sdW1uQiA+PSBjb2x1bW5BIHx8XG4gICAgICAgICAgIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQobWFwcGluZ0EsIG1hcHBpbmdCKSA8PSAwO1xuICB9XG5cbiAgLyoqXG4gICAqIEEgZGF0YSBzdHJ1Y3R1cmUgdG8gcHJvdmlkZSBhIHNvcnRlZCB2aWV3IG9mIGFjY3VtdWxhdGVkIG1hcHBpbmdzIGluIGFcbiAgICogcGVyZm9ybWFuY2UgY29uc2Npb3VzIG1hbm5lci4gSXQgdHJhZGVzIGEgbmVnbGliYWJsZSBvdmVyaGVhZCBpbiBnZW5lcmFsXG4gICAqIGNhc2UgZm9yIGEgbGFyZ2Ugc3BlZWR1cCBpbiBjYXNlIG9mIG1hcHBpbmdzIGJlaW5nIGFkZGVkIGluIG9yZGVyLlxuICAgKi9cbiAgZnVuY3Rpb24gTWFwcGluZ0xpc3QoKSB7XG4gICAgdGhpcy5fYXJyYXkgPSBbXTtcbiAgICB0aGlzLl9zb3J0ZWQgPSB0cnVlO1xuICAgIC8vIFNlcnZlcyBhcyBpbmZpbXVtXG4gICAgdGhpcy5fbGFzdCA9IHtnZW5lcmF0ZWRMaW5lOiAtMSwgZ2VuZXJhdGVkQ29sdW1uOiAwfTtcbiAgfVxuXG4gIC8qKlxuICAgKiBJdGVyYXRlIHRocm91Z2ggaW50ZXJuYWwgaXRlbXMuIFRoaXMgbWV0aG9kIHRha2VzIHRoZSBzYW1lIGFyZ3VtZW50cyB0aGF0XG4gICAqIGBBcnJheS5wcm90b3R5cGUuZm9yRWFjaGAgdGFrZXMuXG4gICAqXG4gICAqIE5PVEU6IFRoZSBvcmRlciBvZiB0aGUgbWFwcGluZ3MgaXMgTk9UIGd1YXJhbnRlZWQuXG4gICAqL1xuICBNYXBwaW5nTGlzdC5wcm90b3R5cGUudW5zb3J0ZWRGb3JFYWNoID1cbiAgICBmdW5jdGlvbiBNYXBwaW5nTGlzdF9mb3JFYWNoKGFDYWxsYmFjaywgYVRoaXNBcmcpIHtcbiAgICAgIHRoaXMuX2FycmF5LmZvckVhY2goYUNhbGxiYWNrLCBhVGhpc0FyZyk7XG4gICAgfTtcblxuICAvKipcbiAgICogQWRkIHRoZSBnaXZlbiBzb3VyY2UgbWFwcGluZy5cbiAgICpcbiAgICogQHBhcmFtIE9iamVjdCBhTWFwcGluZ1xuICAgKi9cbiAgTWFwcGluZ0xpc3QucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIE1hcHBpbmdMaXN0X2FkZChhTWFwcGluZykge1xuICAgIGlmIChnZW5lcmF0ZWRQb3NpdGlvbkFmdGVyKHRoaXMuX2xhc3QsIGFNYXBwaW5nKSkge1xuICAgICAgdGhpcy5fbGFzdCA9IGFNYXBwaW5nO1xuICAgICAgdGhpcy5fYXJyYXkucHVzaChhTWFwcGluZyk7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRoaXMuX3NvcnRlZCA9IGZhbHNlO1xuICAgICAgdGhpcy5fYXJyYXkucHVzaChhTWFwcGluZyk7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBmbGF0LCBzb3J0ZWQgYXJyYXkgb2YgbWFwcGluZ3MuIFRoZSBtYXBwaW5ncyBhcmUgc29ydGVkIGJ5XG4gICAqIGdlbmVyYXRlZCBwb3NpdGlvbi5cbiAgICpcbiAgICogV0FSTklORzogVGhpcyBtZXRob2QgcmV0dXJucyBpbnRlcm5hbCBkYXRhIHdpdGhvdXQgY29weWluZywgZm9yXG4gICAqIHBlcmZvcm1hbmNlLiBUaGUgcmV0dXJuIHZhbHVlIG11c3QgTk9UIGJlIG11dGF0ZWQsIGFuZCBzaG91bGQgYmUgdHJlYXRlZCBhc1xuICAgKiBhbiBpbW11dGFibGUgYm9ycm93LiBJZiB5b3Ugd2FudCB0byB0YWtlIG93bmVyc2hpcCwgeW91IG11c3QgbWFrZSB5b3VyIG93blxuICAgKiBjb3B5LlxuICAgKi9cbiAgTWFwcGluZ0xpc3QucHJvdG90eXBlLnRvQXJyYXkgPSBmdW5jdGlvbiBNYXBwaW5nTGlzdF90b0FycmF5KCkge1xuICAgIGlmICghdGhpcy5fc29ydGVkKSB7XG4gICAgICB0aGlzLl9hcnJheS5zb3J0KHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQpO1xuICAgICAgdGhpcy5fc29ydGVkID0gdHJ1ZTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXMuX2FycmF5O1xuICB9O1xuXG4gIGV4cG9ydHMuTWFwcGluZ0xpc3QgPSBNYXBwaW5nTGlzdDtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvbWFwcGluZy1saXN0LmpzXG4gKiogbW9kdWxlIGlkID0gMTBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_quick_sort.js b/devtools/shared/sourcemap/tests/unit/test_quick_sort.js
new file mode 100644
index 000000000..bd2100725
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_quick_sort.js
@@ -0,0 +1,228 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var quickSort = __webpack_require__(1).quickSort;
+
+ function numberCompare(a, b) {
+ return a - b;
+ }
+
+ exports['test sorting sorted array'] = function (assert) {
+ var ary = [0,1,2,3,4,5,6,7,8,9];
+
+ var quickSorted = ary.slice();
+ quickSort(quickSorted, numberCompare);
+
+ assert.equal(JSON.stringify(ary),
+ JSON.stringify(quickSorted));
+ };
+
+ exports['test sorting reverse-sorted array'] = function (assert) {
+ var ary = [9,8,7,6,5,4,3,2,1,0];
+
+ var quickSorted = ary.slice();
+ quickSort(quickSorted, numberCompare);
+
+ assert.equal(JSON.stringify(ary.sort(numberCompare)),
+ JSON.stringify(quickSorted));
+ };
+
+ exports['test sorting unsorted array'] = function (assert) {
+ var ary = [];
+ for (var i = 0; i < 10; i++) {
+ ary.push(Math.random());
+ }
+
+ var quickSorted = ary.slice();
+ quickSort(quickSorted, numberCompare);
+
+ assert.equal(JSON.stringify(ary.sort(numberCompare)),
+ JSON.stringify(quickSorted));
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgYzUzYTk0ZGE1ZGE3OTZjMGEyYjAiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LXF1aWNrLXNvcnQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3F1aWNrLXNvcnQuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1QkFBZTtBQUNmO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdENBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0Esb0JBQW1CLFFBQVE7QUFDM0I7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDN0NBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQixPQUFPO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBIiwiZmlsZSI6InRlc3RfcXVpY2tfc29ydC5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgYzUzYTk0ZGE1ZGE3OTZjMGEyYjBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBxdWlja1NvcnQgPSByZXF1aXJlKCcuLi9saWIvcXVpY2stc29ydCcpLnF1aWNrU29ydDtcblxuICBmdW5jdGlvbiBudW1iZXJDb21wYXJlKGEsIGIpIHtcbiAgICByZXR1cm4gYSAtIGI7XG4gIH1cblxuICBleHBvcnRzWyd0ZXN0IHNvcnRpbmcgc29ydGVkIGFycmF5J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIGFyeSA9IFswLDEsMiwzLDQsNSw2LDcsOCw5XTtcblxuICAgIHZhciBxdWlja1NvcnRlZCA9IGFyeS5zbGljZSgpO1xuICAgIHF1aWNrU29ydChxdWlja1NvcnRlZCwgbnVtYmVyQ29tcGFyZSk7XG5cbiAgICBhc3NlcnQuZXF1YWwoSlNPTi5zdHJpbmdpZnkoYXJ5KSxcbiAgICAgICAgICAgICAgICAgSlNPTi5zdHJpbmdpZnkocXVpY2tTb3J0ZWQpKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IHNvcnRpbmcgcmV2ZXJzZS1zb3J0ZWQgYXJyYXknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgYXJ5ID0gWzksOCw3LDYsNSw0LDMsMiwxLDBdO1xuXG4gICAgdmFyIHF1aWNrU29ydGVkID0gYXJ5LnNsaWNlKCk7XG4gICAgcXVpY2tTb3J0KHF1aWNrU29ydGVkLCBudW1iZXJDb21wYXJlKTtcblxuICAgIGFzc2VydC5lcXVhbChKU09OLnN0cmluZ2lmeShhcnkuc29ydChudW1iZXJDb21wYXJlKSksXG4gICAgICAgICAgICAgICAgIEpTT04uc3RyaW5naWZ5KHF1aWNrU29ydGVkKSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBzb3J0aW5nIHVuc29ydGVkIGFycmF5J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIGFyeSA9IFtdO1xuICAgIGZvciAodmFyIGkgPSAwOyBpIDwgMTA7IGkrKykge1xuICAgICAgYXJ5LnB1c2goTWF0aC5yYW5kb20oKSk7XG4gICAgfVxuXG4gICAgdmFyIHF1aWNrU29ydGVkID0gYXJ5LnNsaWNlKCk7XG4gICAgcXVpY2tTb3J0KHF1aWNrU29ydGVkLCBudW1iZXJDb21wYXJlKTtcblxuICAgIGFzc2VydC5lcXVhbChKU09OLnN0cmluZ2lmeShhcnkuc29ydChudW1iZXJDb21wYXJlKSksXG4gICAgICAgICAgICAgICAgIEpTT04uc3RyaW5naWZ5KHF1aWNrU29ydGVkKSk7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC90ZXN0LXF1aWNrLXNvcnQuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIC8vIEl0IHR1cm5zIG91dCB0aGF0IHNvbWUgKG1vc3Q/KSBKYXZhU2NyaXB0IGVuZ2luZXMgZG9uJ3Qgc2VsZi1ob3N0XG4gIC8vIGBBcnJheS5wcm90b3R5cGUuc29ydGAuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSBDKysgd2lsbCBsaWtlbHkgcmVtYWluXG4gIC8vIGZhc3RlciB0aGFuIEpTIHdoZW4gZG9pbmcgcmF3IENQVS1pbnRlbnNpdmUgc29ydGluZy4gSG93ZXZlciwgd2hlbiB1c2luZyBhXG4gIC8vIGN1c3RvbSBjb21wYXJhdG9yIGZ1bmN0aW9uLCBjYWxsaW5nIGJhY2sgYW5kIGZvcnRoIGJldHdlZW4gdGhlIFZNJ3MgQysrIGFuZFxuICAvLyBKSVQnZCBKUyBpcyByYXRoZXIgc2xvdyAqYW5kKiBsb3NlcyBKSVQgdHlwZSBpbmZvcm1hdGlvbiwgcmVzdWx0aW5nIGluXG4gIC8vIHdvcnNlIGdlbmVyYXRlZCBjb2RlIGZvciB0aGUgY29tcGFyYXRvciBmdW5jdGlvbiB0aGFuIHdvdWxkIGJlIG9wdGltYWwuIEluXG4gIC8vIGZhY3QsIHdoZW4gc29ydGluZyB3aXRoIGEgY29tcGFyYXRvciwgdGhlc2UgY29zdHMgb3V0d2VpZ2ggdGhlIGJlbmVmaXRzIG9mXG4gIC8vIHNvcnRpbmcgaW4gQysrLiBCeSB1c2luZyBvdXIgb3duIEpTLWltcGxlbWVudGVkIFF1aWNrIFNvcnQgKGJlbG93KSwgd2UgZ2V0XG4gIC8vIGEgfjM1MDBtcyBtZWFuIHNwZWVkLXVwIGluIGBiZW5jaC9iZW5jaC5odG1sYC5cblxuICAvKipcbiAgICogU3dhcCB0aGUgZWxlbWVudHMgaW5kZXhlZCBieSBgeGAgYW5kIGB5YCBpbiB0aGUgYXJyYXkgYGFyeWAuXG4gICAqXG4gICAqIEBwYXJhbSB7QXJyYXl9IGFyeVxuICAgKiAgICAgICAgVGhlIGFycmF5LlxuICAgKiBAcGFyYW0ge051bWJlcn0geFxuICAgKiAgICAgICAgVGhlIGluZGV4IG9mIHRoZSBmaXJzdCBpdGVtLlxuICAgKiBAcGFyYW0ge051bWJlcn0geVxuICAgKiAgICAgICAgVGhlIGluZGV4IG9mIHRoZSBzZWNvbmQgaXRlbS5cbiAgICovXG4gIGZ1bmN0aW9uIHN3YXAoYXJ5LCB4LCB5KSB7XG4gICAgdmFyIHRlbXAgPSBhcnlbeF07XG4gICAgYXJ5W3hdID0gYXJ5W3ldO1xuICAgIGFyeVt5XSA9IHRlbXA7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBhIHJhbmRvbSBpbnRlZ2VyIHdpdGhpbiB0aGUgcmFuZ2UgYGxvdyAuLiBoaWdoYCBpbmNsdXNpdmUuXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBsb3dcbiAgICogICAgICAgIFRoZSBsb3dlciBib3VuZCBvbiB0aGUgcmFuZ2UuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBoaWdoXG4gICAqICAgICAgICBUaGUgdXBwZXIgYm91bmQgb24gdGhlIHJhbmdlLlxuICAgKi9cbiAgZnVuY3Rpb24gcmFuZG9tSW50SW5SYW5nZShsb3csIGhpZ2gpIHtcbiAgICByZXR1cm4gTWF0aC5yb3VuZChsb3cgKyAoTWF0aC5yYW5kb20oKSAqIChoaWdoIC0gbG93KSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBRdWljayBTb3J0IGFsZ29yaXRobS5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBBbiBhcnJheSB0byBzb3J0LlxuICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjb21wYXJhdG9yXG4gICAqICAgICAgICBGdW5jdGlvbiB0byB1c2UgdG8gY29tcGFyZSB0d28gaXRlbXMuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBwXG4gICAqICAgICAgICBTdGFydCBpbmRleCBvZiB0aGUgYXJyYXlcbiAgICogQHBhcmFtIHtOdW1iZXJ9IHJcbiAgICogICAgICAgIEVuZCBpbmRleCBvZiB0aGUgYXJyYXlcbiAgICovXG4gIGZ1bmN0aW9uIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcCwgcikge1xuICAgIC8vIElmIG91ciBsb3dlciBib3VuZCBpcyBsZXNzIHRoYW4gb3VyIHVwcGVyIGJvdW5kLCB3ZSAoMSkgcGFydGl0aW9uIHRoZVxuICAgIC8vIGFycmF5IGludG8gdHdvIHBpZWNlcyBhbmQgKDIpIHJlY3Vyc2Ugb24gZWFjaCBoYWxmLiBJZiBpdCBpcyBub3QsIHRoaXMgaXNcbiAgICAvLyB0aGUgZW1wdHkgYXJyYXkgYW5kIG91ciBiYXNlIGNhc2UuXG5cbiAgICBpZiAocCA8IHIpIHtcbiAgICAgIC8vICgxKSBQYXJ0aXRpb25pbmcuXG4gICAgICAvL1xuICAgICAgLy8gVGhlIHBhcnRpdGlvbmluZyBjaG9vc2VzIGEgcGl2b3QgYmV0d2VlbiBgcGAgYW5kIGByYCBhbmQgbW92ZXMgYWxsXG4gICAgICAvLyBlbGVtZW50cyB0aGF0IGFyZSBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHBpdm90IHRvIHRoZSBiZWZvcmUgaXQsIGFuZFxuICAgICAgLy8gYWxsIHRoZSBlbGVtZW50cyB0aGF0IGFyZSBncmVhdGVyIHRoYW4gaXQgYWZ0ZXIgaXQuIFRoZSBlZmZlY3QgaXMgdGhhdFxuICAgICAgLy8gb25jZSBwYXJ0aXRpb24gaXMgZG9uZSwgdGhlIHBpdm90IGlzIGluIHRoZSBleGFjdCBwbGFjZSBpdCB3aWxsIGJlIHdoZW5cbiAgICAgIC8vIHRoZSBhcnJheSBpcyBwdXQgaW4gc29ydGVkIG9yZGVyLCBhbmQgaXQgd2lsbCBub3QgbmVlZCB0byBiZSBtb3ZlZFxuICAgICAgLy8gYWdhaW4uIFRoaXMgcnVucyBpbiBPKG4pIHRpbWUuXG5cbiAgICAgIC8vIEFsd2F5cyBjaG9vc2UgYSByYW5kb20gcGl2b3Qgc28gdGhhdCBhbiBpbnB1dCBhcnJheSB3aGljaCBpcyByZXZlcnNlXG4gICAgICAvLyBzb3J0ZWQgZG9lcyBub3QgY2F1c2UgTyhuXjIpIHJ1bm5pbmcgdGltZS5cbiAgICAgIHZhciBwaXZvdEluZGV4ID0gcmFuZG9tSW50SW5SYW5nZShwLCByKTtcbiAgICAgIHZhciBpID0gcCAtIDE7XG5cbiAgICAgIHN3YXAoYXJ5LCBwaXZvdEluZGV4LCByKTtcbiAgICAgIHZhciBwaXZvdCA9IGFyeVtyXTtcblxuICAgICAgLy8gSW1tZWRpYXRlbHkgYWZ0ZXIgYGpgIGlzIGluY3JlbWVudGVkIGluIHRoaXMgbG9vcCwgdGhlIGZvbGxvd2luZyBob2xkXG4gICAgICAvLyB0cnVlOlxuICAgICAgLy9cbiAgICAgIC8vICAgKiBFdmVyeSBlbGVtZW50IGluIGBhcnlbcCAuLiBpXWAgaXMgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHRoZSBwaXZvdC5cbiAgICAgIC8vXG4gICAgICAvLyAgICogRXZlcnkgZWxlbWVudCBpbiBgYXJ5W2krMSAuLiBqLTFdYCBpcyBncmVhdGVyIHRoYW4gdGhlIHBpdm90LlxuICAgICAgZm9yICh2YXIgaiA9IHA7IGogPCByOyBqKyspIHtcbiAgICAgICAgaWYgKGNvbXBhcmF0b3IoYXJ5W2pdLCBwaXZvdCkgPD0gMCkge1xuICAgICAgICAgIGkgKz0gMTtcbiAgICAgICAgICBzd2FwKGFyeSwgaSwgaik7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgc3dhcChhcnksIGkgKyAxLCBqKTtcbiAgICAgIHZhciBxID0gaSArIDE7XG5cbiAgICAgIC8vICgyKSBSZWN1cnNlIG9uIGVhY2ggaGFsZi5cblxuICAgICAgZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBwLCBxIC0gMSk7XG4gICAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHEgKyAxLCByKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU29ydCB0aGUgZ2l2ZW4gYXJyYXkgaW4tcGxhY2Ugd2l0aCB0aGUgZ2l2ZW4gY29tcGFyYXRvciBmdW5jdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBBbiBhcnJheSB0byBzb3J0LlxuICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjb21wYXJhdG9yXG4gICAqICAgICAgICBGdW5jdGlvbiB0byB1c2UgdG8gY29tcGFyZSB0d28gaXRlbXMuXG4gICAqL1xuICBleHBvcnRzLnF1aWNrU29ydCA9IGZ1bmN0aW9uIChhcnksIGNvbXBhcmF0b3IpIHtcbiAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIDAsIGFyeS5sZW5ndGggLSAxKTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvcXVpY2stc29ydC5qc1xuICoqIG1vZHVsZSBpZCA9IDFcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_source_map_consumer.js b/devtools/shared/sourcemap/tests/unit/test_source_map_consumer.js
new file mode 100644
index 000000000..77bbf06ec
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_source_map_consumer.js
@@ -0,0 +1,4005 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(1);
+ var SourceMapConsumer = __webpack_require__(3).SourceMapConsumer;
+ var IndexedSourceMapConsumer = __webpack_require__(3).IndexedSourceMapConsumer;
+ var BasicSourceMapConsumer = __webpack_require__(3).BasicSourceMapConsumer;
+ var SourceMapGenerator = __webpack_require__(9).SourceMapGenerator;
+
+ exports['test that we can instantiate with a string or an object'] = function (assert) {
+ assert.doesNotThrow(function () {
+ var map = new SourceMapConsumer(util.testMap);
+ });
+ assert.doesNotThrow(function () {
+ var map = new SourceMapConsumer(JSON.stringify(util.testMap));
+ });
+ };
+
+ exports['test that the object returned from new SourceMapConsumer inherits from SourceMapConsumer'] = function (assert) {
+ assert.ok(new SourceMapConsumer(util.testMap) instanceof SourceMapConsumer);
+ }
+
+ exports['test that a BasicSourceMapConsumer is returned for sourcemaps without sections'] = function(assert) {
+ assert.ok(new SourceMapConsumer(util.testMap) instanceof BasicSourceMapConsumer);
+ };
+
+ exports['test that an IndexedSourceMapConsumer is returned for sourcemaps with sections'] = function(assert) {
+ assert.ok(new SourceMapConsumer(util.indexedTestMap) instanceof IndexedSourceMapConsumer);
+ };
+
+ exports['test that the `sources` field has the original sources'] = function (assert) {
+ var map;
+ var sources;
+
+ map = new SourceMapConsumer(util.testMap);
+ sources = map.sources;
+ assert.equal(sources[0], '/the/root/one.js');
+ assert.equal(sources[1], '/the/root/two.js');
+ assert.equal(sources.length, 2);
+
+ map = new SourceMapConsumer(util.indexedTestMap);
+ sources = map.sources;
+ assert.equal(sources[0], '/the/root/one.js');
+ assert.equal(sources[1], '/the/root/two.js');
+ assert.equal(sources.length, 2);
+
+ map = new SourceMapConsumer(util.indexedTestMapDifferentSourceRoots);
+ sources = map.sources;
+ assert.equal(sources[0], '/the/root/one.js');
+ assert.equal(sources[1], '/different/root/two.js');
+ assert.equal(sources.length, 2);
+
+ map = new SourceMapConsumer(util.testMapNoSourceRoot);
+ sources = map.sources;
+ assert.equal(sources[0], 'one.js');
+ assert.equal(sources[1], 'two.js');
+ assert.equal(sources.length, 2);
+
+ map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+ sources = map.sources;
+ assert.equal(sources[0], 'one.js');
+ assert.equal(sources[1], 'two.js');
+ assert.equal(sources.length, 2);
+ };
+
+ exports['test that the source root is reflected in a mapping\'s source field'] = function (assert) {
+ var map;
+ var mapping;
+
+ map = new SourceMapConsumer(util.testMap);
+
+ mapping = map.originalPositionFor({
+ line: 2,
+ column: 1
+ });
+ assert.equal(mapping.source, '/the/root/two.js');
+
+ mapping = map.originalPositionFor({
+ line: 1,
+ column: 1
+ });
+ assert.equal(mapping.source, '/the/root/one.js');
+
+
+ map = new SourceMapConsumer(util.testMapNoSourceRoot);
+
+ mapping = map.originalPositionFor({
+ line: 2,
+ column: 1
+ });
+ assert.equal(mapping.source, 'two.js');
+
+ mapping = map.originalPositionFor({
+ line: 1,
+ column: 1
+ });
+ assert.equal(mapping.source, 'one.js');
+
+
+ map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+
+ mapping = map.originalPositionFor({
+ line: 2,
+ column: 1
+ });
+ assert.equal(mapping.source, 'two.js');
+
+ mapping = map.originalPositionFor({
+ line: 1,
+ column: 1
+ });
+ assert.equal(mapping.source, 'one.js');
+ };
+
+ exports['test mapping tokens back exactly'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMap);
+
+ util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
+ util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
+ util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
+ util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
+ util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
+
+ util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
+ util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
+ util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
+ util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
+ util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
+ };
+
+ exports['test mapping tokens back exactly in indexed source map'] = function (assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+
+ util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
+ util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
+ util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
+ util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
+ util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
+
+ util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
+ util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
+ util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
+ util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
+ util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
+ };
+
+
+ exports['test mapping tokens back exactly'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMap);
+
+ util.assertMapping(1, 1, '/the/root/one.js', 1, 1, null, null, map, assert);
+ util.assertMapping(1, 5, '/the/root/one.js', 1, 5, null, null, map, assert);
+ util.assertMapping(1, 9, '/the/root/one.js', 1, 11, null, null, map, assert);
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 21, 'bar', null, map, assert);
+ util.assertMapping(1, 21, '/the/root/one.js', 2, 3, null, null, map, assert);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 10, 'baz', null, map, assert);
+ util.assertMapping(1, 32, '/the/root/one.js', 2, 14, 'bar', null, map, assert);
+
+ util.assertMapping(2, 1, '/the/root/two.js', 1, 1, null, null, map, assert);
+ util.assertMapping(2, 5, '/the/root/two.js', 1, 5, null, null, map, assert);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 11, null, null, map, assert);
+ util.assertMapping(2, 18, '/the/root/two.js', 1, 21, 'n', null, map, assert);
+ util.assertMapping(2, 21, '/the/root/two.js', 2, 3, null, null, map, assert);
+ util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', null, map, assert);
+ };
+
+ exports['test mapping tokens fuzzy'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMap);
+
+ // Finding original positions with default (glb) bias.
+ util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true);
+ util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true);
+ util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true);
+
+ // Finding original positions with lub bias.
+ util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+ util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+ util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+
+ // Finding generated positions with default (glb) bias.
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true);
+
+ // Finding generated positions with lub bias.
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ };
+
+ exports['test mapping tokens fuzzy in indexed source map'] = function (assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+
+ // Finding original positions with default (glb) bias.
+ util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', null, map, assert, true);
+ util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', null, map, assert, true);
+ util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, null, map, assert, true);
+
+ // Finding original positions with lub bias.
+ util.assertMapping(1, 16, '/the/root/one.js', 1, 21, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+ util.assertMapping(1, 26, '/the/root/one.js', 2, 10, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+ util.assertMapping(2, 6, '/the/root/two.js', 1, 11, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, true);
+
+ // Finding generated positions with default (glb) bias.
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', null, map, assert, null, true);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', null, map, assert, null, true);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, null, map, assert, null, true);
+
+ // Finding generated positions with lub bias.
+ util.assertMapping(1, 18, '/the/root/one.js', 1, 20, 'bar', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ util.assertMapping(1, 28, '/the/root/one.js', 2, 7, 'baz', SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ util.assertMapping(2, 9, '/the/root/two.js', 1, 6, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ };
+
+ exports['test mappings and end of lines'] = function (assert) {
+ var smg = new SourceMapGenerator({
+ file: 'foo.js'
+ });
+ smg.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 1 },
+ source: 'bar.js'
+ });
+ smg.addMapping({
+ original: { line: 2, column: 2 },
+ generated: { line: 2, column: 2 },
+ source: 'bar.js'
+ });
+ smg.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 1 },
+ source: 'baz.js'
+ });
+
+ var map = SourceMapConsumer.fromSourceMap(smg);
+
+ // When finding original positions, mappings end at the end of the line.
+ util.assertMapping(2, 1, null, null, null, null, null, map, assert, true)
+
+ // When finding generated positions, mappings do not end at the end of the line.
+ util.assertMapping(1, 1, 'bar.js', 2, 1, null, null, map, assert, null, true);
+
+ // When finding generated positions with, mappings end at the end of the source.
+ util.assertMapping(null, null, 'bar.js', 3, 1, null, SourceMapConsumer.LEAST_UPPER_BOUND, map, assert, null, true);
+ };
+
+ exports['test creating source map consumers with )]}\' prefix'] = function (assert) {
+ assert.doesNotThrow(function () {
+ var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap));
+ });
+ };
+
+ exports['test eachMapping'] = function (assert) {
+ var map;
+
+ map = new SourceMapConsumer(util.testMap);
+ var previousLine = -Infinity;
+ var previousColumn = -Infinity;
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.generatedLine >= previousLine);
+
+ assert.ok(mapping.source === '/the/root/one.js' || mapping.source === '/the/root/two.js');
+
+ if (mapping.generatedLine === previousLine) {
+ assert.ok(mapping.generatedColumn >= previousColumn);
+ previousColumn = mapping.generatedColumn;
+ }
+ else {
+ previousLine = mapping.generatedLine;
+ previousColumn = -Infinity;
+ }
+ });
+
+ map = new SourceMapConsumer(util.testMapNoSourceRoot);
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.source === 'one.js' || mapping.source === 'two.js');
+ });
+
+ map = new SourceMapConsumer(util.testMapEmptySourceRoot);
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.source === 'one.js' || mapping.source === 'two.js');
+ });
+ };
+
+ exports['test eachMapping for indexed source maps'] = function(assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+ var previousLine = -Infinity;
+ var previousColumn = -Infinity;
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.generatedLine >= previousLine);
+
+ if (mapping.source) {
+ assert.equal(mapping.source.indexOf(util.testMap.sourceRoot), 0);
+ }
+
+ if (mapping.generatedLine === previousLine) {
+ assert.ok(mapping.generatedColumn >= previousColumn);
+ previousColumn = mapping.generatedColumn;
+ }
+ else {
+ previousLine = mapping.generatedLine;
+ previousColumn = -Infinity;
+ }
+ });
+ };
+
+
+ exports['test iterating over mappings in a different order'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMap);
+ var previousLine = -Infinity;
+ var previousColumn = -Infinity;
+ var previousSource = "";
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.source >= previousSource);
+
+ if (mapping.source === previousSource) {
+ assert.ok(mapping.originalLine >= previousLine);
+
+ if (mapping.originalLine === previousLine) {
+ assert.ok(mapping.originalColumn >= previousColumn);
+ previousColumn = mapping.originalColumn;
+ }
+ else {
+ previousLine = mapping.originalLine;
+ previousColumn = -Infinity;
+ }
+ }
+ else {
+ previousSource = mapping.source;
+ previousLine = -Infinity;
+ previousColumn = -Infinity;
+ }
+ }, null, SourceMapConsumer.ORIGINAL_ORDER);
+ };
+
+ exports['test iterating over mappings in a different order in indexed source maps'] = function (assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+ var previousLine = -Infinity;
+ var previousColumn = -Infinity;
+ var previousSource = "";
+ map.eachMapping(function (mapping) {
+ assert.ok(mapping.source >= previousSource);
+
+ if (mapping.source === previousSource) {
+ assert.ok(mapping.originalLine >= previousLine);
+
+ if (mapping.originalLine === previousLine) {
+ assert.ok(mapping.originalColumn >= previousColumn);
+ previousColumn = mapping.originalColumn;
+ }
+ else {
+ previousLine = mapping.originalLine;
+ previousColumn = -Infinity;
+ }
+ }
+ else {
+ previousSource = mapping.source;
+ previousLine = -Infinity;
+ previousColumn = -Infinity;
+ }
+ }, null, SourceMapConsumer.ORIGINAL_ORDER);
+ };
+
+ exports['test that we can set the context for `this` in eachMapping'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMap);
+ var context = {};
+ map.eachMapping(function () {
+ assert.equal(this, context);
+ }, context);
+ };
+
+ exports['test that we can set the context for `this` in eachMapping in indexed source maps'] = function (assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+ var context = {};
+ map.eachMapping(function () {
+ assert.equal(this, context);
+ }, context);
+ };
+
+ exports['test that the `sourcesContent` field has the original sources'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMapWithSourcesContent);
+ var sourcesContent = map.sourcesContent;
+
+ assert.equal(sourcesContent[0], ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(sourcesContent[1], ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.equal(sourcesContent.length, 2);
+ };
+
+ exports['test that we can get the original sources for the sources'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMapWithSourcesContent);
+ var sources = map.sources;
+
+ assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.throws(function () {
+ map.sourceContentFor("");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("/the/root/three.js");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("three.js");
+ }, Error);
+ };
+
+ exports['test that we can get the original source content with relative source paths'] = function (assert) {
+ var map = new SourceMapConsumer(util.testMapRelativeSources);
+ var sources = map.sources;
+
+ assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.throws(function () {
+ map.sourceContentFor("");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("/the/root/three.js");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("three.js");
+ }, Error);
+ };
+
+ exports['test that we can get the original source content for the sources on an indexed source map'] = function (assert) {
+ var map = new SourceMapConsumer(util.indexedTestMap);
+ var sources = map.sources;
+
+ assert.equal(map.sourceContentFor(sources[0]), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor(sources[1]), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.equal(map.sourceContentFor("one.js"), ' ONE.foo = function (bar) {\n return baz(bar);\n };');
+ assert.equal(map.sourceContentFor("two.js"), ' TWO.inc = function (n) {\n return n + 1;\n };');
+ assert.throws(function () {
+ map.sourceContentFor("");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("/the/root/three.js");
+ }, Error);
+ assert.throws(function () {
+ map.sourceContentFor("three.js");
+ }, Error);
+ };
+
+ exports['test hasContentsOfAllSources, single source with contents'] = function (assert) {
+ // Has one source: foo.js (with contents).
+ var mapWithContents = new SourceMapGenerator();
+ mapWithContents.addMapping({ source: 'foo.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ mapWithContents.setSourceContent('foo.js', 'content of foo.js');
+ var consumer = new SourceMapConsumer(mapWithContents.toJSON());
+ assert.ok(consumer.hasContentsOfAllSources());
+ };
+
+ exports['test hasContentsOfAllSources, single source without contents'] = function (assert) {
+ // Has one source: foo.js (without contents).
+ var mapWithoutContents = new SourceMapGenerator();
+ mapWithoutContents.addMapping({ source: 'foo.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ var consumer = new SourceMapConsumer(mapWithoutContents.toJSON());
+ assert.ok(!consumer.hasContentsOfAllSources());
+ };
+
+ exports['test hasContentsOfAllSources, two sources with contents'] = function (assert) {
+ // Has two sources: foo.js (with contents) and bar.js (with contents).
+ var mapWithBothContents = new SourceMapGenerator();
+ mapWithBothContents.addMapping({ source: 'foo.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ mapWithBothContents.addMapping({ source: 'bar.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ mapWithBothContents.setSourceContent('foo.js', 'content of foo.js');
+ mapWithBothContents.setSourceContent('bar.js', 'content of bar.js');
+ var consumer = new SourceMapConsumer(mapWithBothContents.toJSON());
+ assert.ok(consumer.hasContentsOfAllSources());
+ };
+
+ exports['test hasContentsOfAllSources, two sources one with and one without contents'] = function (assert) {
+ // Has two sources: foo.js (with contents) and bar.js (without contents).
+ var mapWithoutSomeContents = new SourceMapGenerator();
+ mapWithoutSomeContents.addMapping({ source: 'foo.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ mapWithoutSomeContents.addMapping({ source: 'bar.js',
+ original: { line: 1, column: 10 },
+ generated: { line: 1, column: 10 } });
+ mapWithoutSomeContents.setSourceContent('foo.js', 'content of foo.js');
+ var consumer = new SourceMapConsumer(mapWithoutSomeContents.toJSON());
+ assert.ok(!consumer.hasContentsOfAllSources());
+ };
+
+ exports['test sourceRoot + generatedPositionFor'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'foo/bar',
+ file: 'baz.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'bang.coffee'
+ });
+ map.addMapping({
+ original: { line: 5, column: 5 },
+ generated: { line: 6, column: 6 },
+ source: 'bang.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ // Should handle without sourceRoot.
+ var pos = map.generatedPositionFor({
+ line: 1,
+ column: 1,
+ source: 'bang.coffee'
+ });
+
+ assert.equal(pos.line, 2);
+ assert.equal(pos.column, 2);
+
+ // Should handle with sourceRoot.
+ var pos = map.generatedPositionFor({
+ line: 1,
+ column: 1,
+ source: 'foo/bar/bang.coffee'
+ });
+
+ assert.equal(pos.line, 2);
+ assert.equal(pos.column, 2);
+ };
+
+ exports['test sourceRoot + generatedPositionFor for path above the root'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'foo/bar',
+ file: 'baz.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: '../bang.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ // Should handle with sourceRoot.
+ var pos = map.generatedPositionFor({
+ line: 1,
+ column: 1,
+ source: 'foo/bang.coffee'
+ });
+
+ assert.equal(pos.line, 2);
+ assert.equal(pos.column, 2);
+ };
+
+ exports['test allGeneratedPositionsFor for line'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'bar.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 1 },
+ generated: { line: 3, column: 2 },
+ source: 'bar.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 2 },
+ generated: { line: 3, column: 3 },
+ source: 'bar.coffee'
+ });
+ map.addMapping({
+ original: { line: 3, column: 1 },
+ generated: { line: 4, column: 2 },
+ source: 'bar.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 2,
+ source: 'bar.coffee'
+ });
+
+ assert.equal(mappings.length, 2);
+ assert.equal(mappings[0].line, 3);
+ assert.equal(mappings[0].column, 2);
+ assert.equal(mappings[1].line, 3);
+ assert.equal(mappings[1].column, 3);
+ };
+
+ exports['test allGeneratedPositionsFor for line fuzzy'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'bar.coffee'
+ });
+ map.addMapping({
+ original: { line: 3, column: 1 },
+ generated: { line: 4, column: 2 },
+ source: 'bar.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 2,
+ source: 'bar.coffee'
+ });
+
+ assert.equal(mappings.length, 1);
+ assert.equal(mappings[0].line, 4);
+ assert.equal(mappings[0].column, 2);
+ };
+
+ exports['test allGeneratedPositionsFor for empty source map'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 2,
+ source: 'bar.coffee'
+ });
+
+ assert.equal(mappings.length, 0);
+ };
+
+ exports['test allGeneratedPositionsFor for column'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 2 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 3 },
+ source: 'foo.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 1,
+ column: 1,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 2);
+ assert.equal(mappings[0].line, 1);
+ assert.equal(mappings[0].column, 2);
+ assert.equal(mappings[1].line, 1);
+ assert.equal(mappings[1].column, 3);
+ };
+
+ exports['test allGeneratedPositionsFor for column fuzzy'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 2 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 3 },
+ source: 'foo.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 1,
+ column: 0,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 2);
+ assert.equal(mappings[0].line, 1);
+ assert.equal(mappings[0].column, 2);
+ assert.equal(mappings[1].line, 1);
+ assert.equal(mappings[1].column, 3);
+ };
+
+ exports['test allGeneratedPositionsFor for column on different line fuzzy'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 2, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 1 },
+ generated: { line: 2, column: 3 },
+ source: 'foo.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 1,
+ column: 0,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 0);
+ };
+
+ exports['test computeColumnSpans'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 1, column: 1 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 1 },
+ generated: { line: 2, column: 1 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 2 },
+ generated: { line: 2, column: 10 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 2, column: 3 },
+ generated: { line: 2, column: 20 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 3, column: 1 },
+ generated: { line: 3, column: 1 },
+ source: 'foo.coffee'
+ });
+ map.addMapping({
+ original: { line: 3, column: 2 },
+ generated: { line: 3, column: 2 },
+ source: 'foo.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ map.computeColumnSpans();
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 1,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 1);
+ assert.equal(mappings[0].lastColumn, Infinity);
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 2,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 3);
+ assert.equal(mappings[0].lastColumn, 9);
+ assert.equal(mappings[1].lastColumn, 19);
+ assert.equal(mappings[2].lastColumn, Infinity);
+
+ var mappings = map.allGeneratedPositionsFor({
+ line: 3,
+ source: 'foo.coffee'
+ });
+
+ assert.equal(mappings.length, 2);
+ assert.equal(mappings[0].lastColumn, 1);
+ assert.equal(mappings[1].lastColumn, Infinity);
+ };
+
+ exports['test sourceRoot + originalPositionFor'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'foo/bar',
+ file: 'baz.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'bang.coffee'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var pos = map.originalPositionFor({
+ line: 2,
+ column: 2,
+ });
+
+ // Should always have the prepended source root
+ assert.equal(pos.source, 'foo/bar/bang.coffee');
+ assert.equal(pos.line, 1);
+ assert.equal(pos.column, 1);
+ };
+
+ exports['test github issue #56'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'http://',
+ file: 'www.example.com/foo.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'www.example.com/original.js'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var sources = map.sources;
+ assert.equal(sources.length, 1);
+ assert.equal(sources[0], 'http://www.example.com/original.js');
+ };
+
+ exports['test github issue #43'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'http://example.com',
+ file: 'foo.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'http://cdn.example.com/original.js'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var sources = map.sources;
+ assert.equal(sources.length, 1,
+ 'Should only be one source.');
+ assert.equal(sources[0], 'http://cdn.example.com/original.js',
+ 'Should not be joined with the sourceRoot.');
+ };
+
+ exports['test absolute path, but same host sources'] = function (assert) {
+ var map = new SourceMapGenerator({
+ sourceRoot: 'http://example.com/foo/bar',
+ file: 'foo.js'
+ });
+ map.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: '/original.js'
+ });
+ map = new SourceMapConsumer(map.toString());
+
+ var sources = map.sources;
+ assert.equal(sources.length, 1,
+ 'Should only be one source.');
+ assert.equal(sources[0], 'http://example.com/original.js',
+ 'Source should be relative the host of the source root.');
+ };
+
+ exports['test indexed source map errors when sections are out of order by line'] = function(assert) {
+ // Make a deep copy of the indexedTestMap
+ var misorderedIndexedTestMap = JSON.parse(JSON.stringify(util.indexedTestMap));
+
+ misorderedIndexedTestMap.sections[0].offset = {
+ line: 2,
+ column: 0
+ };
+
+ assert.throws(function() {
+ new SourceMapConsumer(misorderedIndexedTestMap);
+ }, Error);
+ };
+
+ exports['test github issue #64'] = function (assert) {
+ var map = new SourceMapConsumer({
+ "version": 3,
+ "file": "foo.js",
+ "sourceRoot": "http://example.com/",
+ "sources": ["/a"],
+ "names": [],
+ "mappings": "AACA",
+ "sourcesContent": ["foo"]
+ });
+
+ assert.equal(map.sourceContentFor("a"), "foo");
+ assert.equal(map.sourceContentFor("/a"), "foo");
+ };
+
+ exports['test bug 885597'] = function (assert) {
+ var map = new SourceMapConsumer({
+ "version": 3,
+ "file": "foo.js",
+ "sourceRoot": "file:///Users/AlGore/Invented/The/Internet/",
+ "sources": ["/a"],
+ "names": [],
+ "mappings": "AACA",
+ "sourcesContent": ["foo"]
+ });
+
+ var s = map.sources[0];
+ assert.equal(map.sourceContentFor(s), "foo");
+ };
+
+ exports['test github issue #72, duplicate sources'] = function (assert) {
+ var map = new SourceMapConsumer({
+ "version": 3,
+ "file": "foo.js",
+ "sources": ["source1.js", "source1.js", "source3.js"],
+ "names": [],
+ "mappings": ";EAAC;;IAEE;;MEEE",
+ "sourceRoot": "http://example.com"
+ });
+
+ var pos = map.originalPositionFor({
+ line: 2,
+ column: 2
+ });
+ assert.equal(pos.source, 'http://example.com/source1.js');
+ assert.equal(pos.line, 1);
+ assert.equal(pos.column, 1);
+
+ var pos = map.originalPositionFor({
+ line: 4,
+ column: 4
+ });
+ assert.equal(pos.source, 'http://example.com/source1.js');
+ assert.equal(pos.line, 3);
+ assert.equal(pos.column, 3);
+
+ var pos = map.originalPositionFor({
+ line: 6,
+ column: 6
+ });
+ assert.equal(pos.source, 'http://example.com/source3.js');
+ assert.equal(pos.line, 5);
+ assert.equal(pos.column, 5);
+ };
+
+ exports['test github issue #72, duplicate names'] = function (assert) {
+ var map = new SourceMapConsumer({
+ "version": 3,
+ "file": "foo.js",
+ "sources": ["source.js"],
+ "names": ["name1", "name1", "name3"],
+ "mappings": ";EAACA;;IAEEA;;MAEEE",
+ "sourceRoot": "http://example.com"
+ });
+
+ var pos = map.originalPositionFor({
+ line: 2,
+ column: 2
+ });
+ assert.equal(pos.name, 'name1');
+ assert.equal(pos.line, 1);
+ assert.equal(pos.column, 1);
+
+ var pos = map.originalPositionFor({
+ line: 4,
+ column: 4
+ });
+ assert.equal(pos.name, 'name1');
+ assert.equal(pos.line, 3);
+ assert.equal(pos.column, 3);
+
+ var pos = map.originalPositionFor({
+ line: 6,
+ column: 6
+ });
+ assert.equal(pos.name, 'name3');
+ assert.equal(pos.line, 5);
+ assert.equal(pos.column, 5);
+ };
+
+ exports['test SourceMapConsumer.fromSourceMap'] = function (assert) {
+ var smg = new SourceMapGenerator({
+ sourceRoot: 'http://example.com/',
+ file: 'foo.js'
+ });
+ smg.addMapping({
+ original: { line: 1, column: 1 },
+ generated: { line: 2, column: 2 },
+ source: 'bar.js'
+ });
+ smg.addMapping({
+ original: { line: 2, column: 2 },
+ generated: { line: 4, column: 4 },
+ source: 'baz.js',
+ name: 'dirtMcGirt'
+ });
+ smg.setSourceContent('baz.js', 'baz.js content');
+
+ var smc = SourceMapConsumer.fromSourceMap(smg);
+ assert.equal(smc.file, 'foo.js');
+ assert.equal(smc.sourceRoot, 'http://example.com/');
+ assert.equal(smc.sources.length, 2);
+ assert.equal(smc.sources[0], 'http://example.com/bar.js');
+ assert.equal(smc.sources[1], 'http://example.com/baz.js');
+ assert.equal(smc.sourceContentFor('baz.js'), 'baz.js content');
+
+ var pos = smc.originalPositionFor({
+ line: 2,
+ column: 2
+ });
+ assert.equal(pos.line, 1);
+ assert.equal(pos.column, 1);
+ assert.equal(pos.source, 'http://example.com/bar.js');
+ assert.equal(pos.name, null);
+
+ pos = smc.generatedPositionFor({
+ line: 1,
+ column: 1,
+ source: 'http://example.com/bar.js'
+ });
+ assert.equal(pos.line, 2);
+ assert.equal(pos.column, 2);
+
+ pos = smc.originalPositionFor({
+ line: 4,
+ column: 4
+ });
+ assert.equal(pos.line, 2);
+ assert.equal(pos.column, 2);
+ assert.equal(pos.source, 'http://example.com/baz.js');
+ assert.equal(pos.name, 'dirtMcGirt');
+
+ pos = smc.generatedPositionFor({
+ line: 2,
+ column: 2,
+ source: 'http://example.com/baz.js'
+ });
+ assert.equal(pos.line, 4);
+ assert.equal(pos.column, 4);
+ };
+
+ exports['test issue #191'] = function (assert) {
+ var generator = new SourceMapGenerator({ file: 'a.css' });
+ generator.addMapping({
+ source: 'b.css',
+ original: {
+ line: 1,
+ column: 0
+ },
+ generated: {
+ line: 1,
+ column: 0
+ }
+ });
+
+ // Create a SourceMapConsumer from the SourceMapGenerator, ...
+ var consumer = SourceMapConsumer.fromSourceMap(generator);
+ // ... and then try and use the SourceMapGenerator again. This should not
+ // throw.
+ generator.toJSON();
+
+ assert.ok(true, "Using a SourceMapGenerator again after creating a " +
+ "SourceMapConsumer from it should not throw");
+ };
+
+ exports['test sources where their prefix is the source root: issue #199'] = function (assert) {
+ var testSourceMap = {
+ "version": 3,
+ "sources": ["/source/app/app/app.js"],
+ "names": ["System"],
+ "mappings": "AAAAA",
+ "file": "app/app.js",
+ "sourcesContent": ["'use strict';"],
+ "sourceRoot":"/source/"
+ };
+
+ var consumer = new SourceMapConsumer(testSourceMap);
+
+ function consumerHasSource(s) {
+ assert.ok(consumer.sourceContentFor(s));
+ }
+
+ consumer.sources.forEach(consumerHasSource);
+ testSourceMap.sources.forEach(consumerHasSource);
+ };
+
+ exports['test sources where their prefix is the source root and the source root is a url: issue #199'] = function (assert) {
+ var testSourceMap = {
+ "version": 3,
+ "sources": ["http://example.com/source/app/app/app.js"],
+ "names": ["System"],
+ "mappings": "AAAAA",
+ "sourcesContent": ["'use strict';"],
+ "sourceRoot":"http://example.com/source/"
+ };
+
+ var consumer = new SourceMapConsumer(testSourceMap);
+
+ function consumerHasSource(s) {
+ assert.ok(consumer.sourceContentFor(s));
+ }
+
+ consumer.sources.forEach(consumerHasSource);
+ testSourceMap.sources.forEach(consumerHasSource);
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ // This is a test mapping which maps functions from two different files
+ // (one.js and two.js) to a minified generated source.
+ //
+ // Here is one.js:
+ //
+ // ONE.foo = function (bar) {
+ // return baz(bar);
+ // };
+ //
+ // Here is two.js:
+ //
+ // TWO.inc = function (n) {
+ // return n + 1;
+ // };
+ //
+ // And here is the generated code (min.js):
+ //
+ // ONE.foo=function(a){return baz(a);};
+ // TWO.inc=function(a){return a+1;};
+ exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
+ " TWO.inc=function(a){return a+1;};";
+ exports.testMap = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapNoSourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapEmptySourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ // This mapping is identical to above, but uses the indexed format instead.
+ exports.indexedTestMap = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ }
+ ]
+ };
+ exports.indexedTestMapDifferentSourceRoots = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/different/root"
+ }
+ }
+ ]
+ };
+ exports.testMapWithSourcesContent = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapRelativeSources = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['./one.js', './two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.emptyMap = {
+ version: 3,
+ file: 'min.js',
+ names: [],
+ sources: [],
+ mappings: ''
+ };
+
+
+ function assertMapping(generatedLine, generatedColumn, originalSource,
+ originalLine, originalColumn, name, bias, map, assert,
+ dontTestGenerated, dontTestOriginal) {
+ if (!dontTestOriginal) {
+ var origMapping = map.originalPositionFor({
+ line: generatedLine,
+ column: generatedColumn,
+ bias: bias
+ });
+ assert.equal(origMapping.name, name,
+ 'Incorrect name, expected ' + JSON.stringify(name)
+ + ', got ' + JSON.stringify(origMapping.name));
+ assert.equal(origMapping.line, originalLine,
+ 'Incorrect line, expected ' + JSON.stringify(originalLine)
+ + ', got ' + JSON.stringify(origMapping.line));
+ assert.equal(origMapping.column, originalColumn,
+ 'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ + ', got ' + JSON.stringify(origMapping.column));
+
+ var expectedSource;
+
+ if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
+ expectedSource = originalSource;
+ } else if (originalSource) {
+ expectedSource = map.sourceRoot
+ ? util.join(map.sourceRoot, originalSource)
+ : originalSource;
+ } else {
+ expectedSource = null;
+ }
+
+ assert.equal(origMapping.source, expectedSource,
+ 'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ + ', got ' + JSON.stringify(origMapping.source));
+ }
+
+ if (!dontTestGenerated) {
+ var genMapping = map.generatedPositionFor({
+ source: originalSource,
+ line: originalLine,
+ column: originalColumn,
+ bias: bias
+ });
+ assert.equal(genMapping.line, generatedLine,
+ 'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ + ', got ' + JSON.stringify(genMapping.line));
+ assert.equal(genMapping.column, generatedColumn,
+ 'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ + ', got ' + JSON.stringify(genMapping.column));
+ }
+ }
+ exports.assertMapping = assertMapping;
+
+ function assertEqualMaps(assert, actualMap, expectedMap) {
+ assert.equal(actualMap.version, expectedMap.version, "version mismatch");
+ assert.equal(actualMap.file, expectedMap.file, "file mismatch");
+ assert.equal(actualMap.names.length,
+ expectedMap.names.length,
+ "names length mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ for (var i = 0; i < actualMap.names.length; i++) {
+ assert.equal(actualMap.names[i],
+ expectedMap.names[i],
+ "names[" + i + "] mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ }
+ assert.equal(actualMap.sources.length,
+ expectedMap.sources.length,
+ "sources length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ for (var i = 0; i < actualMap.sources.length; i++) {
+ assert.equal(actualMap.sources[i],
+ expectedMap.sources[i],
+ "sources[" + i + "] length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ }
+ assert.equal(actualMap.sourceRoot,
+ expectedMap.sourceRoot,
+ "sourceRoot mismatch: " +
+ actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
+ assert.equal(actualMap.mappings, expectedMap.mappings,
+ "mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
+ if (actualMap.sourcesContent) {
+ assert.equal(actualMap.sourcesContent.length,
+ expectedMap.sourcesContent.length,
+ "sourcesContent length mismatch");
+ for (var i = 0; i < actualMap.sourcesContent.length; i++) {
+ assert.equal(actualMap.sourcesContent[i],
+ expectedMap.sourcesContent[i],
+ "sourcesContent[" + i + "] mismatch");
+ }
+ }
+ }
+ exports.assertEqualMaps = assertEqualMaps;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+ var binarySearch = __webpack_require__(4);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var base64VLQ = __webpack_require__(6);
+ var quickSort = __webpack_require__(8).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(7);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(6);
+ var util = __webpack_require__(2);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var MappingList = __webpack_require__(10).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgZGExYmM2OWM5ZmUyOGIwN2RiNzIiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LXNvdXJjZS1tYXAtY29uc3VtZXIuanMiLCJ3ZWJwYWNrOi8vLy4vdGVzdC91dGlsLmpzIiwid2VicGFjazovLy8uL2xpYi91dGlsLmpzIiwid2VicGFjazovLy8uL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyLmpzIiwid2VicGFjazovLy8uL2xpYi9iaW5hcnktc2VhcmNoLmpzIiwid2VicGFjazovLy8uL2xpYi9hcnJheS1zZXQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC12bHEuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvcXVpY2stc29ydC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW1hcC1nZW5lcmF0b3IuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL21hcHBpbmctbGlzdC5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7Ozs7Ozs7OztBQUFBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLHVCQUFlO0FBQ2Y7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN0Q0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDs7O0FBR0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDs7O0FBR0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7O0FBRUw7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSx1REFBc0Q7QUFDdEQ7QUFDQSwyQ0FBMEM7QUFDMUMsTUFBSztBQUNMOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7OztBQUdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSxpRUFBZ0UscUJBQXFCLEtBQUs7QUFDMUYsK0RBQThELGtCQUFrQixLQUFLO0FBQ3JGO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBLGdGQUErRSxxQkFBcUIsS0FBSztBQUN6Ryw4RUFBNkUsa0JBQWtCLEtBQUs7QUFDcEcsOEVBQTZFLHFCQUFxQixLQUFLO0FBQ3ZHLDRFQUEyRSxrQkFBa0IsS0FBSztBQUNsRztBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7O0FBRUEsZ0ZBQStFLHFCQUFxQixLQUFLO0FBQ3pHLDhFQUE2RSxrQkFBa0IsS0FBSztBQUNwRyw4RUFBNkUscUJBQXFCLEtBQUs7QUFDdkcsNEVBQTJFLGtCQUFrQixLQUFLO0FBQ2xHO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSxnRkFBK0UscUJBQXFCLEtBQUs7QUFDekcsOEVBQTZFLGtCQUFrQixLQUFLO0FBQ3BHLDhFQUE2RSxxQkFBcUIsS0FBSztBQUN2Ryw0RUFBMkUsa0JBQWtCLEtBQUs7QUFDbEc7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsaUNBQWdDO0FBQ2hDLDZDQUE0QyxzQkFBc0I7QUFDbEUsOENBQTZDLHNCQUFzQixFQUFFO0FBQ3JFO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLG9DQUFtQztBQUNuQyxnREFBK0Msc0JBQXNCO0FBQ3JFLGlEQUFnRCxzQkFBc0IsRUFBRTtBQUN4RTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EscUNBQW9DO0FBQ3BDLGlEQUFnRCxzQkFBc0I7QUFDdEUsa0RBQWlELHNCQUFzQixFQUFFO0FBQ3pFLHFDQUFvQztBQUNwQyxpREFBZ0Qsc0JBQXNCO0FBQ3RFLGtEQUFpRCxzQkFBc0IsRUFBRTtBQUN6RTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHdDQUF1QztBQUN2QyxvREFBbUQsc0JBQXNCO0FBQ3pFLHFEQUFvRCxzQkFBc0IsRUFBRTtBQUM1RSx3Q0FBdUM7QUFDdkMsb0RBQW1ELHNCQUFzQjtBQUN6RSxxREFBb0Qsc0JBQXNCLEVBQUU7QUFDNUU7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixzQkFBc0I7QUFDeEM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixzQkFBc0I7QUFDeEM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEMsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0Esa0JBQWlCLHFCQUFxQjtBQUN0QyxtQkFBa0IscUJBQXFCO0FBQ3ZDO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxxQkFBb0IsTUFBTSxNQUFNO0FBQ2hDO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHFCQUFvQixPQUFPLE9BQU87QUFDbEM7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxrQkFBaUIscUJBQXFCO0FBQ3RDLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBOztBQUVBO0FBQ0EsNkNBQTRDLGdCQUFnQjtBQUM1RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHdDQUF1QztBQUN2QztBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esd0NBQXVDO0FBQ3ZDO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDaG1DQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBMkI7QUFDM0IsNEJBQTJCO0FBQzNCLHFEQUFvRCxnQkFBZ0I7QUFDcEUscURBQW9ELGFBQWE7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBd0M7QUFDeEMsaUNBQWdDO0FBQ2hDLGlCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXNDO0FBQ3RDLDhCQUE2QjtBQUM3QixpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDO0FBQ3hDLGlDQUFnQztBQUNoQyxpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVDQUFzQztBQUN0Qyw4QkFBNkI7QUFDN0IsaUJBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBa0M7QUFDbEMsMkJBQTBCO0FBQzFCLFdBQVU7QUFDVixpQ0FBZ0M7QUFDaEMsd0JBQXVCO0FBQ3ZCLFdBQVU7QUFDVjtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUNBQWtDO0FBQ2xDLDJCQUEwQjtBQUMxQixXQUFVO0FBQ1YsaUNBQWdDO0FBQ2hDLHdCQUF1QjtBQUN2QixXQUFVO0FBQ1Y7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBbUIsNEJBQTRCO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQiw4QkFBOEI7QUFDakQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLHFDQUFxQztBQUMxRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDdlNBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxpREFBZ0QsUUFBUTtBQUN4RDtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNoWEEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQjtBQUNyQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYTs7QUFFYjtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFhOztBQUViO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBNkIsTUFBTTtBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBLHlEQUF3RCxZQUFZO0FBQ3BFO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0Esc0NBQXFDO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBMkIsY0FBYztBQUN6QztBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMEJBQXlCLHdDQUF3QztBQUNqRTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWlELG1CQUFtQixFQUFFO0FBQ3RFOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQixvQkFBb0I7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUErQixNQUFNO0FBQ3JDO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseURBQXdEO0FBQ3hEOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQsd0JBQXVCLCtDQUErQztBQUN0RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLDJCQUEyQjtBQUNoRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hEOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQ7QUFDQTtBQUNBLHdCQUF1Qiw0QkFBNEI7QUFDbkQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN6akNBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7Ozs7O0FDL0dBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF3QyxTQUFTO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdkdBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDREQUEyRDtBQUMzRCxxQkFBb0I7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUM1SUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFrQjtBQUNsQixtQkFBa0I7O0FBRWxCLHNCQUFxQjtBQUNyQix1QkFBc0I7O0FBRXRCLG1CQUFrQjtBQUNsQixtQkFBa0I7O0FBRWxCLG1CQUFrQjtBQUNsQixvQkFBbUI7O0FBRW5CO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNuRUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsY0FBYSxNQUFNO0FBQ25CO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsU0FBUztBQUN0QjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLE9BQU87QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsU0FBUztBQUN0QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNsSEEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxRQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDZDQUE0QyxTQUFTO0FBQ3JEOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHlCQUF3QjtBQUN4QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDM1lBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCO0FBQ2xCOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBIiwiZmlsZSI6InRlc3Rfc291cmNlX21hcF9jb25zdW1lci5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgZGExYmM2OWM5ZmUyOGIwN2RiNzJcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZShcIi4vdXRpbFwiKTtcbiAgdmFyIFNvdXJjZU1hcENvbnN1bWVyID0gcmVxdWlyZSgnLi4vbGliL3NvdXJjZS1tYXAtY29uc3VtZXInKS5Tb3VyY2VNYXBDb25zdW1lcjtcbiAgdmFyIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lciA9IHJlcXVpcmUoJy4uL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyJykuSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyO1xuICB2YXIgQmFzaWNTb3VyY2VNYXBDb25zdW1lciA9IHJlcXVpcmUoJy4uL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyJykuQmFzaWNTb3VyY2VNYXBDb25zdW1lcjtcbiAgdmFyIFNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4uL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvcicpLlNvdXJjZU1hcEdlbmVyYXRvcjtcblxuICBleHBvcnRzWyd0ZXN0IHRoYXQgd2UgY2FuIGluc3RhbnRpYXRlIHdpdGggYSBzdHJpbmcgb3IgYW4gb2JqZWN0J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbiAoKSB7XG4gICAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcCk7XG4gICAgfSk7XG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbiAoKSB7XG4gICAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKEpTT04uc3RyaW5naWZ5KHV0aWwudGVzdE1hcCkpO1xuICAgIH0pO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhhdCB0aGUgb2JqZWN0IHJldHVybmVkIGZyb20gbmV3IFNvdXJjZU1hcENvbnN1bWVyIGluaGVyaXRzIGZyb20gU291cmNlTWFwQ29uc3VtZXInXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICBhc3NlcnQub2sobmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcCkgaW5zdGFuY2VvZiBTb3VyY2VNYXBDb25zdW1lcik7XG4gIH1cblxuICBleHBvcnRzWyd0ZXN0IHRoYXQgYSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyIGlzIHJldHVybmVkIGZvciBzb3VyY2VtYXBzIHdpdGhvdXQgc2VjdGlvbnMnXSA9IGZ1bmN0aW9uKGFzc2VydCkge1xuICAgIGFzc2VydC5vayhuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwKSBpbnN0YW5jZW9mIEJhc2ljU291cmNlTWFwQ29uc3VtZXIpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhhdCBhbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIgaXMgcmV0dXJuZWQgZm9yIHNvdXJjZW1hcHMgd2l0aCBzZWN0aW9ucyddID0gZnVuY3Rpb24oYXNzZXJ0KSB7XG4gICAgYXNzZXJ0Lm9rKG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmluZGV4ZWRUZXN0TWFwKSBpbnN0YW5jZW9mIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHRoZSBgc291cmNlc2AgZmllbGQgaGFzIHRoZSBvcmlnaW5hbCBzb3VyY2VzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcDtcbiAgICB2YXIgc291cmNlcztcblxuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApO1xuICAgIHNvdXJjZXMgPSBtYXAuc291cmNlcztcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1swXSwgJy90aGUvcm9vdC9vbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1sxXSwgJy90aGUvcm9vdC90d28uanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlcy5sZW5ndGgsIDIpO1xuXG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuaW5kZXhlZFRlc3RNYXApO1xuICAgIHNvdXJjZXMgPSBtYXAuc291cmNlcztcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1swXSwgJy90aGUvcm9vdC9vbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1sxXSwgJy90aGUvcm9vdC90d28uanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlcy5sZW5ndGgsIDIpO1xuXG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuaW5kZXhlZFRlc3RNYXBEaWZmZXJlbnRTb3VyY2VSb290cyk7XG4gICAgc291cmNlcyA9IG1hcC5zb3VyY2VzO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzWzBdLCAnL3RoZS9yb290L29uZS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzWzFdLCAnL2RpZmZlcmVudC9yb290L3R3by5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzLmxlbmd0aCwgMik7XG5cbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwTm9Tb3VyY2VSb290KTtcbiAgICBzb3VyY2VzID0gbWFwLnNvdXJjZXM7XG4gICAgYXNzZXJ0LmVxdWFsKHNvdXJjZXNbMF0sICdvbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1sxXSwgJ3R3by5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzLmxlbmd0aCwgMik7XG5cbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwRW1wdHlTb3VyY2VSb290KTtcbiAgICBzb3VyY2VzID0gbWFwLnNvdXJjZXM7XG4gICAgYXNzZXJ0LmVxdWFsKHNvdXJjZXNbMF0sICdvbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1sxXSwgJ3R3by5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzLmxlbmd0aCwgMik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHRoZSBzb3VyY2Ugcm9vdCBpcyByZWZsZWN0ZWQgaW4gYSBtYXBwaW5nXFwncyBzb3VyY2UgZmllbGQnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwO1xuICAgIHZhciBtYXBwaW5nO1xuXG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcCk7XG5cbiAgICBtYXBwaW5nID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIGNvbHVtbjogMVxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nLnNvdXJjZSwgJy90aGUvcm9vdC90d28uanMnKTtcblxuICAgIG1hcHBpbmcgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAxLFxuICAgICAgY29sdW1uOiAxXG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmcuc291cmNlLCAnL3RoZS9yb290L29uZS5qcycpO1xuXG5cbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwTm9Tb3VyY2VSb290KTtcblxuICAgIG1hcHBpbmcgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAyLFxuICAgICAgY29sdW1uOiAxXG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmcuc291cmNlLCAndHdvLmpzJyk7XG5cbiAgICBtYXBwaW5nID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMSxcbiAgICAgIGNvbHVtbjogMVxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nLnNvdXJjZSwgJ29uZS5qcycpO1xuXG5cbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwRW1wdHlTb3VyY2VSb290KTtcblxuICAgIG1hcHBpbmcgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAyLFxuICAgICAgY29sdW1uOiAxXG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmcuc291cmNlLCAndHdvLmpzJyk7XG5cbiAgICBtYXBwaW5nID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMSxcbiAgICAgIGNvbHVtbjogMVxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nLnNvdXJjZSwgJ29uZS5qcycpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgbWFwcGluZyB0b2tlbnMgYmFjayBleGFjdGx5J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApO1xuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDEsICcvdGhlL3Jvb3Qvb25lLmpzJywgMSwgMSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCA1LCAnL3RoZS9yb290L29uZS5qcycsIDEsIDUsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgOSwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAxMSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxOCwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAyMSwgJ2JhcicsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjEsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMywgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAyOCwgJy90aGUvcm9vdC9vbmUuanMnLCAyLCAxMCwgJ2JheicsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMzIsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTQsICdiYXInLCBudWxsLCBtYXAsIGFzc2VydCk7XG5cbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMSwgJy90aGUvcm9vdC90d28uanMnLCAxLCAxLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDUsICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgNSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA5LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDExLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDE4LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDIxLCAnbicsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMjEsICcvdGhlL3Jvb3QvdHdvLmpzJywgMiwgMywgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAyOCwgJy90aGUvcm9vdC90d28uanMnLCAyLCAxMCwgJ24nLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBtYXBwaW5nIHRva2VucyBiYWNrIGV4YWN0bHkgaW4gaW5kZXhlZCBzb3VyY2UgbWFwJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmluZGV4ZWRUZXN0TWFwKTtcblxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxLCAnL3RoZS9yb290L29uZS5qcycsIDEsIDEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgNSwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCA1LCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDksICcvdGhlL3Jvb3Qvb25lLmpzJywgMSwgMTEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMTgsICcvdGhlL3Jvb3Qvb25lLmpzJywgMSwgMjEsICdiYXInLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDIxLCAnL3RoZS9yb290L29uZS5qcycsIDIsIDMsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjgsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTAsICdiYXonLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDMyLCAnL3RoZS9yb290L29uZS5qcycsIDIsIDE0LCAnYmFyJywgbnVsbCwgbWFwLCBhc3NlcnQpO1xuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDEsICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA1LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDUsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgOSwgJy90aGUvcm9vdC90d28uanMnLCAxLCAxMSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAxOCwgJy90aGUvcm9vdC90d28uanMnLCAxLCAyMSwgJ24nLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDIxLCAnL3RoZS9yb290L3R3by5qcycsIDIsIDMsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMjgsICcvdGhlL3Jvb3QvdHdvLmpzJywgMiwgMTAsICduJywgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICB9O1xuXG5cbiAgZXhwb3J0c1sndGVzdCBtYXBwaW5nIHRva2VucyBiYWNrIGV4YWN0bHknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcCk7XG5cbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMSwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAxLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDUsICcvdGhlL3Jvb3Qvb25lLmpzJywgMSwgNSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCA5LCAnL3RoZS9yb290L29uZS5qcycsIDEsIDExLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDE4LCAnL3RoZS9yb290L29uZS5qcycsIDEsIDIxLCAnYmFyJywgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAyMSwgJy90aGUvcm9vdC9vbmUuanMnLCAyLCAzLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDI4LCAnL3RoZS9yb290L29uZS5qcycsIDIsIDEwLCAnYmF6JywgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAzMiwgJy90aGUvcm9vdC9vbmUuanMnLCAyLCAxNCwgJ2JhcicsIG51bGwsIG1hcCwgYXNzZXJ0KTtcblxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAxLCAnL3RoZS9yb290L3R3by5qcycsIDEsIDEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgNSwgJy90aGUvcm9vdC90d28uanMnLCAxLCA1LCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDksICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMTEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMTgsICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMjEsICduJywgbnVsbCwgbWFwLCBhc3NlcnQpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCAyMSwgJy90aGUvcm9vdC90d28uanMnLCAyLCAzLCBudWxsLCBudWxsLCBtYXAsIGFzc2VydCk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDI4LCAnL3RoZS9yb290L3R3by5qcycsIDIsIDEwLCAnbicsIG51bGwsIG1hcCwgYXNzZXJ0KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IG1hcHBpbmcgdG9rZW5zIGZ1enp5J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApO1xuXG4gICAgLy8gRmluZGluZyBvcmlnaW5hbCBwb3NpdGlvbnMgd2l0aCBkZWZhdWx0IChnbGIpIGJpYXMuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDIwLCAnL3RoZS9yb290L29uZS5qcycsIDEsIDIxLCAnYmFyJywgbnVsbCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAzMCwgJy90aGUvcm9vdC9vbmUuanMnLCAyLCAxMCwgJ2JheicsIG51bGwsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMTIsICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMTEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcblxuICAgIC8vIEZpbmRpbmcgb3JpZ2luYWwgcG9zaXRpb25zIHdpdGggbHViIGJpYXMuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDE2LCAnL3RoZS9yb290L29uZS5qcycsIDEsIDIxLCAnYmFyJywgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjYsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTAsICdiYXonLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA2LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDExLCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuXG4gICAgLy8gRmluZGluZyBnZW5lcmF0ZWQgcG9zaXRpb25zIHdpdGggZGVmYXVsdCAoZ2xiKSBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxOCwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAyMiwgJ2JhcicsIG51bGwsIG1hcCwgYXNzZXJ0LCBudWxsLCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjgsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTMsICdiYXonLCBudWxsLCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDksICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMTYsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0LCBudWxsLCB0cnVlKTtcblxuICAgIC8vIEZpbmRpbmcgZ2VuZXJhdGVkIHBvc2l0aW9ucyB3aXRoIGx1YiBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxOCwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAyMCwgJ2JhcicsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDI4LCAnL3RoZS9yb290L29uZS5qcycsIDIsIDcsICdiYXonLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA5LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDYsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBtYXBwaW5nIHRva2VucyBmdXp6eSBpbiBpbmRleGVkIHNvdXJjZSBtYXAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuaW5kZXhlZFRlc3RNYXApO1xuXG4gICAgLy8gRmluZGluZyBvcmlnaW5hbCBwb3NpdGlvbnMgd2l0aCBkZWZhdWx0IChnbGIpIGJpYXMuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDIwLCAnL3RoZS9yb290L29uZS5qcycsIDEsIDIxLCAnYmFyJywgbnVsbCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAzMCwgJy90aGUvcm9vdC9vbmUuanMnLCAyLCAxMCwgJ2JheicsIG51bGwsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMTIsICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMTEsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcblxuICAgIC8vIEZpbmRpbmcgb3JpZ2luYWwgcG9zaXRpb25zIHdpdGggbHViIGJpYXMuXG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDE2LCAnL3RoZS9yb290L29uZS5qcycsIDEsIDIxLCAnYmFyJywgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQsIG1hcCwgYXNzZXJ0LCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjYsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTAsICdiYXonLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA2LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDExLCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIHRydWUpO1xuXG4gICAgLy8gRmluZGluZyBnZW5lcmF0ZWQgcG9zaXRpb25zIHdpdGggZGVmYXVsdCAoZ2xiKSBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxOCwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAyMiwgJ2JhcicsIG51bGwsIG1hcCwgYXNzZXJ0LCBudWxsLCB0cnVlKTtcbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMSwgMjgsICcvdGhlL3Jvb3Qvb25lLmpzJywgMiwgMTMsICdiYXonLCBudWxsLCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDIsIDksICcvdGhlL3Jvb3QvdHdvLmpzJywgMSwgMTYsIG51bGwsIG51bGwsIG1hcCwgYXNzZXJ0LCBudWxsLCB0cnVlKTtcblxuICAgIC8vIEZpbmRpbmcgZ2VuZXJhdGVkIHBvc2l0aW9ucyB3aXRoIGx1YiBiaWFzLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxOCwgJy90aGUvcm9vdC9vbmUuanMnLCAxLCAyMCwgJ2JhcicsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gICAgdXRpbC5hc3NlcnRNYXBwaW5nKDEsIDI4LCAnL3RoZS9yb290L29uZS5qcycsIDIsIDcsICdiYXonLCBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCwgbWFwLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygyLCA5LCAnL3RoZS9yb290L3R3by5qcycsIDEsIDYsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBtYXBwaW5ncyBhbmQgZW5kIG9mIGxpbmVzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHNtZyA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2Zvby5qcydcbiAgICB9KTtcbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIHNvdXJjZTogJ2Jhci5qcydcbiAgICB9KTtcbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2Jhci5qcydcbiAgICB9KTtcbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIHNvdXJjZTogJ2Jhei5qcydcbiAgICB9KTtcblxuICAgIHZhciBtYXAgPSBTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwKHNtZyk7XG5cbiAgICAvLyBXaGVuIGZpbmRpbmcgb3JpZ2luYWwgcG9zaXRpb25zLCBtYXBwaW5ncyBlbmQgYXQgdGhlIGVuZCBvZiB0aGUgbGluZS5cbiAgICB1dGlsLmFzc2VydE1hcHBpbmcoMiwgMSwgbnVsbCwgbnVsbCwgbnVsbCwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQsIHRydWUpXG5cbiAgICAvLyBXaGVuIGZpbmRpbmcgZ2VuZXJhdGVkIHBvc2l0aW9ucywgbWFwcGluZ3MgZG8gbm90IGVuZCBhdCB0aGUgZW5kIG9mIHRoZSBsaW5lLlxuICAgIHV0aWwuYXNzZXJ0TWFwcGluZygxLCAxLCAnYmFyLmpzJywgMiwgMSwgbnVsbCwgbnVsbCwgbWFwLCBhc3NlcnQsIG51bGwsIHRydWUpO1xuXG4gICAgLy8gV2hlbiBmaW5kaW5nIGdlbmVyYXRlZCBwb3NpdGlvbnMgd2l0aCwgbWFwcGluZ3MgZW5kIGF0IHRoZSBlbmQgb2YgdGhlIHNvdXJjZS5cbiAgICB1dGlsLmFzc2VydE1hcHBpbmcobnVsbCwgbnVsbCwgJ2Jhci5qcycsIDMsIDEsIG51bGwsIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5ELCBtYXAsIGFzc2VydCwgbnVsbCwgdHJ1ZSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBjcmVhdGluZyBzb3VyY2UgbWFwIGNvbnN1bWVycyB3aXRoICldfVxcJyBwcmVmaXgnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICBhc3NlcnQuZG9lc05vdFRocm93KGZ1bmN0aW9uICgpIHtcbiAgICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIoXCIpXX0nXCIgKyBKU09OLnN0cmluZ2lmeSh1dGlsLnRlc3RNYXApKTtcbiAgICB9KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGVhY2hNYXBwaW5nJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcDtcblxuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApO1xuICAgIHZhciBwcmV2aW91c0xpbmUgPSAtSW5maW5pdHk7XG4gICAgdmFyIHByZXZpb3VzQ29sdW1uID0gLUluZmluaXR5O1xuICAgIG1hcC5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgYXNzZXJ0Lm9rKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA+PSBwcmV2aW91c0xpbmUpO1xuXG4gICAgICBhc3NlcnQub2sobWFwcGluZy5zb3VyY2UgPT09ICcvdGhlL3Jvb3Qvb25lLmpzJyB8fCBtYXBwaW5nLnNvdXJjZSA9PT0gJy90aGUvcm9vdC90d28uanMnKTtcblxuICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9PT0gcHJldmlvdXNMaW5lKSB7XG4gICAgICAgIGFzc2VydC5vayhtYXBwaW5nLmdlbmVyYXRlZENvbHVtbiA+PSBwcmV2aW91c0NvbHVtbik7XG4gICAgICAgIHByZXZpb3VzQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgcHJldmlvdXNMaW5lID0gbWFwcGluZy5nZW5lcmF0ZWRMaW5lO1xuICAgICAgICBwcmV2aW91c0NvbHVtbiA9IC1JbmZpbml0eTtcbiAgICAgIH1cbiAgICB9KTtcblxuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXBOb1NvdXJjZVJvb3QpO1xuICAgIG1hcC5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgYXNzZXJ0Lm9rKG1hcHBpbmcuc291cmNlID09PSAnb25lLmpzJyB8fCBtYXBwaW5nLnNvdXJjZSA9PT0gJ3R3by5qcycpO1xuICAgIH0pO1xuXG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcEVtcHR5U291cmNlUm9vdCk7XG4gICAgbWFwLmVhY2hNYXBwaW5nKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICBhc3NlcnQub2sobWFwcGluZy5zb3VyY2UgPT09ICdvbmUuanMnIHx8IG1hcHBpbmcuc291cmNlID09PSAndHdvLmpzJyk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBlYWNoTWFwcGluZyBmb3IgaW5kZXhlZCBzb3VyY2UgbWFwcyddID0gZnVuY3Rpb24oYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmluZGV4ZWRUZXN0TWFwKTtcbiAgICB2YXIgcHJldmlvdXNMaW5lID0gLUluZmluaXR5O1xuICAgIHZhciBwcmV2aW91c0NvbHVtbiA9IC1JbmZpbml0eTtcbiAgICBtYXAuZWFjaE1hcHBpbmcoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgIGFzc2VydC5vayhtYXBwaW5nLmdlbmVyYXRlZExpbmUgPj0gcHJldmlvdXNMaW5lKTtcblxuICAgICAgaWYgKG1hcHBpbmcuc291cmNlKSB7XG4gICAgICAgIGFzc2VydC5lcXVhbChtYXBwaW5nLnNvdXJjZS5pbmRleE9mKHV0aWwudGVzdE1hcC5zb3VyY2VSb290KSwgMCk7XG4gICAgICB9XG5cbiAgICAgIGlmIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgPT09IHByZXZpb3VzTGluZSkge1xuICAgICAgICBhc3NlcnQub2sobWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gPj0gcHJldmlvdXNDb2x1bW4pO1xuICAgICAgICBwcmV2aW91c0NvbHVtbiA9IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHByZXZpb3VzTGluZSA9IG1hcHBpbmcuZ2VuZXJhdGVkTGluZTtcbiAgICAgICAgcHJldmlvdXNDb2x1bW4gPSAtSW5maW5pdHk7XG4gICAgICB9XG4gICAgfSk7XG4gIH07XG5cblxuICBleHBvcnRzWyd0ZXN0IGl0ZXJhdGluZyBvdmVyIG1hcHBpbmdzIGluIGEgZGlmZmVyZW50IG9yZGVyJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApO1xuICAgIHZhciBwcmV2aW91c0xpbmUgPSAtSW5maW5pdHk7XG4gICAgdmFyIHByZXZpb3VzQ29sdW1uID0gLUluZmluaXR5O1xuICAgIHZhciBwcmV2aW91c1NvdXJjZSA9IFwiXCI7XG4gICAgbWFwLmVhY2hNYXBwaW5nKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICBhc3NlcnQub2sobWFwcGluZy5zb3VyY2UgPj0gcHJldmlvdXNTb3VyY2UpO1xuXG4gICAgICBpZiAobWFwcGluZy5zb3VyY2UgPT09IHByZXZpb3VzU291cmNlKSB7XG4gICAgICAgIGFzc2VydC5vayhtYXBwaW5nLm9yaWdpbmFsTGluZSA+PSBwcmV2aW91c0xpbmUpO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gcHJldmlvdXNMaW5lKSB7XG4gICAgICAgICAgYXNzZXJ0Lm9rKG1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPj0gcHJldmlvdXNDb2x1bW4pO1xuICAgICAgICAgIHByZXZpb3VzQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBwcmV2aW91c0xpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZTtcbiAgICAgICAgICBwcmV2aW91c0NvbHVtbiA9IC1JbmZpbml0eTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHByZXZpb3VzU291cmNlID0gbWFwcGluZy5zb3VyY2U7XG4gICAgICAgIHByZXZpb3VzTGluZSA9IC1JbmZpbml0eTtcbiAgICAgICAgcHJldmlvdXNDb2x1bW4gPSAtSW5maW5pdHk7XG4gICAgICB9XG4gICAgfSwgbnVsbCwgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVIpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgaXRlcmF0aW5nIG92ZXIgbWFwcGluZ3MgaW4gYSBkaWZmZXJlbnQgb3JkZXIgaW4gaW5kZXhlZCBzb3VyY2UgbWFwcyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC5pbmRleGVkVGVzdE1hcCk7XG4gICAgdmFyIHByZXZpb3VzTGluZSA9IC1JbmZpbml0eTtcbiAgICB2YXIgcHJldmlvdXNDb2x1bW4gPSAtSW5maW5pdHk7XG4gICAgdmFyIHByZXZpb3VzU291cmNlID0gXCJcIjtcbiAgICBtYXAuZWFjaE1hcHBpbmcoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgIGFzc2VydC5vayhtYXBwaW5nLnNvdXJjZSA+PSBwcmV2aW91c1NvdXJjZSk7XG5cbiAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSA9PT0gcHJldmlvdXNTb3VyY2UpIHtcbiAgICAgICAgYXNzZXJ0Lm9rKG1hcHBpbmcub3JpZ2luYWxMaW5lID49IHByZXZpb3VzTGluZSk7XG5cbiAgICAgICAgaWYgKG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSBwcmV2aW91c0xpbmUpIHtcbiAgICAgICAgICBhc3NlcnQub2sobWFwcGluZy5vcmlnaW5hbENvbHVtbiA+PSBwcmV2aW91c0NvbHVtbik7XG4gICAgICAgICAgcHJldmlvdXNDb2x1bW4gPSBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIHByZXZpb3VzTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lO1xuICAgICAgICAgIHByZXZpb3VzQ29sdW1uID0gLUluZmluaXR5O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgcHJldmlvdXNTb3VyY2UgPSBtYXBwaW5nLnNvdXJjZTtcbiAgICAgICAgcHJldmlvdXNMaW5lID0gLUluZmluaXR5O1xuICAgICAgICBwcmV2aW91c0NvbHVtbiA9IC1JbmZpbml0eTtcbiAgICAgIH1cbiAgICB9LCBudWxsLCBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHdlIGNhbiBzZXQgdGhlIGNvbnRleHQgZm9yIGB0aGlzYCBpbiBlYWNoTWFwcGluZyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwKTtcbiAgICB2YXIgY29udGV4dCA9IHt9O1xuICAgIG1hcC5lYWNoTWFwcGluZyhmdW5jdGlvbiAoKSB7XG4gICAgICBhc3NlcnQuZXF1YWwodGhpcywgY29udGV4dCk7XG4gICAgfSwgY29udGV4dCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHdlIGNhbiBzZXQgdGhlIGNvbnRleHQgZm9yIGB0aGlzYCBpbiBlYWNoTWFwcGluZyBpbiBpbmRleGVkIHNvdXJjZSBtYXBzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmluZGV4ZWRUZXN0TWFwKTtcbiAgICB2YXIgY29udGV4dCA9IHt9O1xuICAgIG1hcC5lYWNoTWFwcGluZyhmdW5jdGlvbiAoKSB7XG4gICAgICBhc3NlcnQuZXF1YWwodGhpcywgY29udGV4dCk7XG4gICAgfSwgY29udGV4dCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHRoZSBgc291cmNlc0NvbnRlbnRgIGZpZWxkIGhhcyB0aGUgb3JpZ2luYWwgc291cmNlcyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC50ZXN0TWFwV2l0aFNvdXJjZXNDb250ZW50KTtcbiAgICB2YXIgc291cmNlc0NvbnRlbnQgPSBtYXAuc291cmNlc0NvbnRlbnQ7XG5cbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc0NvbnRlbnRbMF0sICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4gICByZXR1cm4gYmF6KGJhcik7XFxuIH07Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKHNvdXJjZXNDb250ZW50WzFdLCAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbiAgIHJldHVybiBuICsgMTtcXG4gfTsnKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc0NvbnRlbnQubGVuZ3RoLCAyKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IHRoYXQgd2UgY2FuIGdldCB0aGUgb3JpZ2luYWwgc291cmNlcyBmb3IgdGhlIHNvdXJjZXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcFdpdGhTb3VyY2VzQ29udGVudCk7XG4gICAgdmFyIHNvdXJjZXMgPSBtYXAuc291cmNlcztcblxuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlQ29udGVudEZvcihzb3VyY2VzWzBdKSwgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbiAgIHJldHVybiBiYXooYmFyKTtcXG4gfTsnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlc1sxXSksICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuICAgcmV0dXJuIG4gKyAxO1xcbiB9OycpO1xuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlQ29udGVudEZvcihcIm9uZS5qc1wiKSwgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbiAgIHJldHVybiBiYXooYmFyKTtcXG4gfTsnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZUNvbnRlbnRGb3IoXCJ0d28uanNcIiksICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuICAgcmV0dXJuIG4gKyAxO1xcbiB9OycpO1xuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLnNvdXJjZUNvbnRlbnRGb3IoXCJcIik7XG4gICAgfSwgRXJyb3IpO1xuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLnNvdXJjZUNvbnRlbnRGb3IoXCIvdGhlL3Jvb3QvdGhyZWUuanNcIik7XG4gICAgfSwgRXJyb3IpO1xuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLnNvdXJjZUNvbnRlbnRGb3IoXCJ0aHJlZS5qc1wiKTtcbiAgICB9LCBFcnJvcik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHdlIGNhbiBnZXQgdGhlIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50IHdpdGggcmVsYXRpdmUgc291cmNlIHBhdGhzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXBSZWxhdGl2ZVNvdXJjZXMpO1xuICAgIHZhciBzb3VyY2VzID0gbWFwLnNvdXJjZXM7XG5cbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlc1swXSksICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4gICByZXR1cm4gYmF6KGJhcik7XFxuIH07Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZXNbMV0pLCAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbiAgIHJldHVybiBuICsgMTtcXG4gfTsnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZUNvbnRlbnRGb3IoXCJvbmUuanNcIiksICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4gICByZXR1cm4gYmF6KGJhcik7XFxuIH07Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKFwidHdvLmpzXCIpLCAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbiAgIHJldHVybiBuICsgMTtcXG4gfTsnKTtcbiAgICBhc3NlcnQudGhyb3dzKGZ1bmN0aW9uICgpIHtcbiAgICAgIG1hcC5zb3VyY2VDb250ZW50Rm9yKFwiXCIpO1xuICAgIH0sIEVycm9yKTtcbiAgICBhc3NlcnQudGhyb3dzKGZ1bmN0aW9uICgpIHtcbiAgICAgIG1hcC5zb3VyY2VDb250ZW50Rm9yKFwiL3RoZS9yb290L3RocmVlLmpzXCIpO1xuICAgIH0sIEVycm9yKTtcbiAgICBhc3NlcnQudGhyb3dzKGZ1bmN0aW9uICgpIHtcbiAgICAgIG1hcC5zb3VyY2VDb250ZW50Rm9yKFwidGhyZWUuanNcIik7XG4gICAgfSwgRXJyb3IpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhhdCB3ZSBjYW4gZ2V0IHRoZSBvcmlnaW5hbCBzb3VyY2UgY29udGVudCBmb3IgdGhlIHNvdXJjZXMgb24gYW4gaW5kZXhlZCBzb3VyY2UgbWFwJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmluZGV4ZWRUZXN0TWFwKTtcbiAgICB2YXIgc291cmNlcyA9IG1hcC5zb3VyY2VzO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZXNbMF0pLCAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuICAgcmV0dXJuIGJheihiYXIpO1xcbiB9OycpO1xuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlQ29udGVudEZvcihzb3VyY2VzWzFdKSwgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4gICByZXR1cm4gbiArIDE7XFxuIH07Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKFwib25lLmpzXCIpLCAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuICAgcmV0dXJuIGJheihiYXIpO1xcbiB9OycpO1xuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlQ29udGVudEZvcihcInR3by5qc1wiKSwgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4gICByZXR1cm4gbiArIDE7XFxuIH07Jyk7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBtYXAuc291cmNlQ29udGVudEZvcihcIlwiKTtcbiAgICB9LCBFcnJvcik7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBtYXAuc291cmNlQ29udGVudEZvcihcIi90aGUvcm9vdC90aHJlZS5qc1wiKTtcbiAgICB9LCBFcnJvcik7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBtYXAuc291cmNlQ29udGVudEZvcihcInRocmVlLmpzXCIpO1xuICAgIH0sIEVycm9yKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzLCBzaW5nbGUgc291cmNlIHdpdGggY29udGVudHMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICAvLyBIYXMgb25lIHNvdXJjZTogZm9vLmpzICh3aXRoIGNvbnRlbnRzKS5cbiAgICB2YXIgbWFwV2l0aENvbnRlbnRzID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcigpO1xuICAgIG1hcFdpdGhDb250ZW50cy5hZGRNYXBwaW5nKHsgc291cmNlOiAnZm9vLmpzJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0gfSk7XG4gICAgbWFwV2l0aENvbnRlbnRzLnNldFNvdXJjZUNvbnRlbnQoJ2Zvby5qcycsICdjb250ZW50IG9mIGZvby5qcycpO1xuICAgIHZhciBjb25zdW1lciA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXBXaXRoQ29udGVudHMudG9KU09OKCkpO1xuICAgIGFzc2VydC5vayhjb25zdW1lci5oYXNDb250ZW50c09mQWxsU291cmNlcygpKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzLCBzaW5nbGUgc291cmNlIHdpdGhvdXQgY29udGVudHMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICAvLyBIYXMgb25lIHNvdXJjZTogZm9vLmpzICh3aXRob3V0IGNvbnRlbnRzKS5cbiAgICB2YXIgbWFwV2l0aG91dENvbnRlbnRzID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcigpO1xuICAgIG1hcFdpdGhvdXRDb250ZW50cy5hZGRNYXBwaW5nKHsgc291cmNlOiAnZm9vLmpzJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0gfSk7XG4gICAgdmFyIGNvbnN1bWVyID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcFdpdGhvdXRDb250ZW50cy50b0pTT04oKSk7XG4gICAgYXNzZXJ0Lm9rKCFjb25zdW1lci5oYXNDb250ZW50c09mQWxsU291cmNlcygpKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzLCB0d28gc291cmNlcyB3aXRoIGNvbnRlbnRzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgLy8gSGFzIHR3byBzb3VyY2VzOiBmb28uanMgKHdpdGggY29udGVudHMpIGFuZCBiYXIuanMgKHdpdGggY29udGVudHMpLlxuICAgIHZhciBtYXBXaXRoQm90aENvbnRlbnRzID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcigpO1xuICAgIG1hcFdpdGhCb3RoQ29udGVudHMuYWRkTWFwcGluZyh7IHNvdXJjZTogJ2Zvby5qcycsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxMCB9LFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0gfSk7XG4gICAgbWFwV2l0aEJvdGhDb250ZW50cy5hZGRNYXBwaW5nKHsgc291cmNlOiAnYmFyLmpzJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSB9KTtcbiAgICBtYXBXaXRoQm90aENvbnRlbnRzLnNldFNvdXJjZUNvbnRlbnQoJ2Zvby5qcycsICdjb250ZW50IG9mIGZvby5qcycpO1xuICAgIG1hcFdpdGhCb3RoQ29udGVudHMuc2V0U291cmNlQ29udGVudCgnYmFyLmpzJywgJ2NvbnRlbnQgb2YgYmFyLmpzJyk7XG4gICAgdmFyIGNvbnN1bWVyID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcFdpdGhCb3RoQ29udGVudHMudG9KU09OKCkpO1xuICAgIGFzc2VydC5vayhjb25zdW1lci5oYXNDb250ZW50c09mQWxsU291cmNlcygpKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzLCB0d28gc291cmNlcyBvbmUgd2l0aCBhbmQgb25lIHdpdGhvdXQgY29udGVudHMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICAvLyBIYXMgdHdvIHNvdXJjZXM6IGZvby5qcyAod2l0aCBjb250ZW50cykgYW5kIGJhci5qcyAod2l0aG91dCBjb250ZW50cykuXG4gICAgdmFyIG1hcFdpdGhvdXRTb21lQ29udGVudHMgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKCk7XG4gICAgbWFwV2l0aG91dFNvbWVDb250ZW50cy5hZGRNYXBwaW5nKHsgc291cmNlOiAnZm9vLmpzJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0sXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSB9KTtcbiAgICBtYXBXaXRob3V0U29tZUNvbnRlbnRzLmFkZE1hcHBpbmcoeyBzb3VyY2U6ICdiYXIuanMnLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAxMCB9IH0pO1xuICAgIG1hcFdpdGhvdXRTb21lQ29udGVudHMuc2V0U291cmNlQ29udGVudCgnZm9vLmpzJywgJ2NvbnRlbnQgb2YgZm9vLmpzJyk7XG4gICAgdmFyIGNvbnN1bWVyID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcFdpdGhvdXRTb21lQ29udGVudHMudG9KU09OKCkpO1xuICAgIGFzc2VydC5vayghY29uc3VtZXIuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKSk7XG59O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgc291cmNlUm9vdCArIGdlbmVyYXRlZFBvc2l0aW9uRm9yJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgc291cmNlUm9vdDogJ2Zvby9iYXInLFxuICAgICAgZmlsZTogJ2Jhei5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2JhbmcuY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDUsIGNvbHVtbjogNSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDYsIGNvbHVtbjogNiB9LFxuICAgICAgc291cmNlOiAnYmFuZy5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIC8vIFNob3VsZCBoYW5kbGUgd2l0aG91dCBzb3VyY2VSb290LlxuICAgIHZhciBwb3MgPSBtYXAuZ2VuZXJhdGVkUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMSxcbiAgICAgIGNvbHVtbjogMSxcbiAgICAgIHNvdXJjZTogJ2JhbmcuY29mZmVlJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCAyKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgMik7XG5cbiAgICAvLyBTaG91bGQgaGFuZGxlIHdpdGggc291cmNlUm9vdC5cbiAgICB2YXIgcG9zID0gbWFwLmdlbmVyYXRlZFBvc2l0aW9uRm9yKHtcbiAgICAgIGxpbmU6IDEsXG4gICAgICBjb2x1bW46IDEsXG4gICAgICBzb3VyY2U6ICdmb28vYmFyL2JhbmcuY29mZmVlJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCAyKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgMik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBzb3VyY2VSb290ICsgZ2VuZXJhdGVkUG9zaXRpb25Gb3IgZm9yIHBhdGggYWJvdmUgdGhlIHJvb3QnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBzb3VyY2VSb290OiAnZm9vL2JhcicsXG4gICAgICBmaWxlOiAnYmF6LmpzJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9LFxuICAgICAgc291cmNlOiAnLi4vYmFuZy5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIC8vIFNob3VsZCBoYW5kbGUgd2l0aCBzb3VyY2VSb290LlxuICAgIHZhciBwb3MgPSBtYXAuZ2VuZXJhdGVkUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMSxcbiAgICAgIGNvbHVtbjogMSxcbiAgICAgIHNvdXJjZTogJ2Zvby9iYW5nLmNvZmZlZSdcbiAgICB9KTtcblxuICAgIGFzc2VydC5lcXVhbChwb3MubGluZSwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5jb2x1bW4sIDIpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yIGZvciBsaW5lJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2dlbmVyYXRlZC5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2Zvby5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdiYXIuY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDMsIGNvbHVtbjogMiB9LFxuICAgICAgc291cmNlOiAnYmFyLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAzLCBjb2x1bW46IDMgfSxcbiAgICAgIHNvdXJjZTogJ2Jhci5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMywgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogNCwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdiYXIuY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXAudG9TdHJpbmcoKSk7XG5cbiAgICB2YXIgbWFwcGluZ3MgPSBtYXAuYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yKHtcbiAgICAgIGxpbmU6IDIsXG4gICAgICBzb3VyY2U6ICdiYXIuY29mZmVlJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzLmxlbmd0aCwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzBdLmxpbmUsIDMpO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nc1swXS5jb2x1bW4sIDIpO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nc1sxXS5saW5lLCAzKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMV0uY29sdW1uLCAzKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGFsbEdlbmVyYXRlZFBvc2l0aW9uc0ZvciBmb3IgbGluZSBmdXp6eSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdnZW5lcmF0ZWQuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9LFxuICAgICAgc291cmNlOiAnYmFyLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAzLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiA0LCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2Jhci5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIHZhciBtYXBwaW5ncyA9IG1hcC5hbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIHNvdXJjZTogJ2Jhci5jb2ZmZWUnXG4gICAgfSk7XG5cbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3MubGVuZ3RoLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMF0ubGluZSwgNCk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzBdLmNvbHVtbiwgMik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgZm9yIGVtcHR5IHNvdXJjZSBtYXAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnZ2VuZXJhdGVkLmpzJ1xuICAgIH0pO1xuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXAudG9TdHJpbmcoKSk7XG5cbiAgICB2YXIgbWFwcGluZ3MgPSBtYXAuYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yKHtcbiAgICAgIGxpbmU6IDIsXG4gICAgICBzb3VyY2U6ICdiYXIuY29mZmVlJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzLmxlbmd0aCwgMCk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgZm9yIGNvbHVtbiddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdnZW5lcmF0ZWQuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMyB9LFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwLnRvU3RyaW5nKCkpO1xuXG4gICAgdmFyIG1hcHBpbmdzID0gbWFwLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0Zvcih7XG4gICAgICBsaW5lOiAxLFxuICAgICAgY29sdW1uOiAxLFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcblxuICAgIGFzc2VydC5lcXVhbChtYXBwaW5ncy5sZW5ndGgsIDIpO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nc1swXS5saW5lLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMF0uY29sdW1uLCAyKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMV0ubGluZSwgMSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzFdLmNvbHVtbiwgMyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgZm9yIGNvbHVtbiBmdXp6eSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdnZW5lcmF0ZWQuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMyB9LFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwLnRvU3RyaW5nKCkpO1xuXG4gICAgdmFyIG1hcHBpbmdzID0gbWFwLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0Zvcih7XG4gICAgICBsaW5lOiAxLFxuICAgICAgY29sdW1uOiAwLFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcblxuICAgIGFzc2VydC5lcXVhbChtYXBwaW5ncy5sZW5ndGgsIDIpO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nc1swXS5saW5lLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMF0uY29sdW1uLCAyKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMV0ubGluZSwgMSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzFdLmNvbHVtbiwgMyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgZm9yIGNvbHVtbiBvbiBkaWZmZXJlbnQgbGluZSBmdXp6eSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdnZW5lcmF0ZWQuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMyB9LFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwLnRvU3RyaW5nKCkpO1xuXG4gICAgdmFyIG1hcHBpbmdzID0gbWFwLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0Zvcih7XG4gICAgICBsaW5lOiAxLFxuICAgICAgY29sdW1uOiAwLFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcblxuICAgIGFzc2VydC5lcXVhbChtYXBwaW5ncy5sZW5ndGgsIDApO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgY29tcHV0ZUNvbHVtblNwYW5zJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2dlbmVyYXRlZC5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIHNvdXJjZTogJ2Zvby5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAxIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAxIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMTAgfSxcbiAgICAgIHNvdXJjZTogJ2Zvby5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAzIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyMCB9LFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAzLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAzLCBjb2x1bW46IDEgfSxcbiAgICAgIHNvdXJjZTogJ2Zvby5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMywgY29sdW1uOiAyIH0sXG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMywgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXAudG9TdHJpbmcoKSk7XG5cbiAgICBtYXAuY29tcHV0ZUNvbHVtblNwYW5zKCk7XG5cbiAgICB2YXIgbWFwcGluZ3MgPSBtYXAuYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yKHtcbiAgICAgIGxpbmU6IDEsXG4gICAgICBzb3VyY2U6ICdmb28uY29mZmVlJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzLmxlbmd0aCwgMSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzBdLmxhc3RDb2x1bW4sIEluZmluaXR5KTtcblxuICAgIHZhciBtYXBwaW5ncyA9IG1hcC5hbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIHNvdXJjZTogJ2Zvby5jb2ZmZWUnXG4gICAgfSk7XG5cbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3MubGVuZ3RoLCAzKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMF0ubGFzdENvbHVtbiwgOSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcHBpbmdzWzFdLmxhc3RDb2x1bW4sIDE5KTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMl0ubGFzdENvbHVtbiwgSW5maW5pdHkpO1xuXG4gICAgdmFyIG1hcHBpbmdzID0gbWFwLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0Zvcih7XG4gICAgICBsaW5lOiAzLFxuICAgICAgc291cmNlOiAnZm9vLmNvZmZlZSdcbiAgICB9KTtcblxuICAgIGFzc2VydC5lcXVhbChtYXBwaW5ncy5sZW5ndGgsIDIpO1xuICAgIGFzc2VydC5lcXVhbChtYXBwaW5nc1swXS5sYXN0Q29sdW1uLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwcGluZ3NbMV0ubGFzdENvbHVtbiwgSW5maW5pdHkpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgc291cmNlUm9vdCArIG9yaWdpbmFsUG9zaXRpb25Gb3InXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBzb3VyY2VSb290OiAnZm9vL2JhcicsXG4gICAgICBmaWxlOiAnYmF6LmpzJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9LFxuICAgICAgc291cmNlOiAnYmFuZy5jb2ZmZWUnXG4gICAgfSk7XG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIHZhciBwb3MgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAyLFxuICAgICAgY29sdW1uOiAyLFxuICAgIH0pO1xuXG4gICAgLy8gU2hvdWxkIGFsd2F5cyBoYXZlIHRoZSBwcmVwZW5kZWQgc291cmNlIHJvb3RcbiAgICBhc3NlcnQuZXF1YWwocG9zLnNvdXJjZSwgJ2Zvby9iYXIvYmFuZy5jb2ZmZWUnKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmxpbmUsIDEpO1xuICAgIGFzc2VydC5lcXVhbChwb3MuY29sdW1uLCAxKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGdpdGh1YiBpc3N1ZSAjNTYnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBzb3VyY2VSb290OiAnaHR0cDovLycsXG4gICAgICBmaWxlOiAnd3d3LmV4YW1wbGUuY29tL2Zvby5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ3d3dy5leGFtcGxlLmNvbS9vcmlnaW5hbC5qcydcbiAgICB9KTtcbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwLnRvU3RyaW5nKCkpO1xuXG4gICAgdmFyIHNvdXJjZXMgPSBtYXAuc291cmNlcztcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlcy5sZW5ndGgsIDEpO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzWzBdLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9vcmlnaW5hbC5qcycpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgZ2l0aHViIGlzc3VlICM0MyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIHNvdXJjZVJvb3Q6ICdodHRwOi8vZXhhbXBsZS5jb20nLFxuICAgICAgZmlsZTogJ2Zvby5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2h0dHA6Ly9jZG4uZXhhbXBsZS5jb20vb3JpZ2luYWwuanMnXG4gICAgfSk7XG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIHZhciBzb3VyY2VzID0gbWFwLnNvdXJjZXM7XG4gICAgYXNzZXJ0LmVxdWFsKHNvdXJjZXMubGVuZ3RoLCAxLFxuICAgICAgICAgICAgICAgICAnU2hvdWxkIG9ubHkgYmUgb25lIHNvdXJjZS4nKTtcbiAgICBhc3NlcnQuZXF1YWwoc291cmNlc1swXSwgJ2h0dHA6Ly9jZG4uZXhhbXBsZS5jb20vb3JpZ2luYWwuanMnLFxuICAgICAgICAgICAgICAgICAnU2hvdWxkIG5vdCBiZSBqb2luZWQgd2l0aCB0aGUgc291cmNlUm9vdC4nKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGFic29sdXRlIHBhdGgsIGJ1dCBzYW1lIGhvc3Qgc291cmNlcyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIHNvdXJjZVJvb3Q6ICdodHRwOi8vZXhhbXBsZS5jb20vZm9vL2JhcicsXG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9LFxuICAgICAgc291cmNlOiAnL29yaWdpbmFsLmpzJ1xuICAgIH0pO1xuICAgIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXAudG9TdHJpbmcoKSk7XG5cbiAgICB2YXIgc291cmNlcyA9IG1hcC5zb3VyY2VzO1xuICAgIGFzc2VydC5lcXVhbChzb3VyY2VzLmxlbmd0aCwgMSxcbiAgICAgICAgICAgICAgICAgJ1Nob3VsZCBvbmx5IGJlIG9uZSBzb3VyY2UuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHNvdXJjZXNbMF0sICdodHRwOi8vZXhhbXBsZS5jb20vb3JpZ2luYWwuanMnLFxuICAgICAgICAgICAgICAgICAnU291cmNlIHNob3VsZCBiZSByZWxhdGl2ZSB0aGUgaG9zdCBvZiB0aGUgc291cmNlIHJvb3QuJyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBpbmRleGVkIHNvdXJjZSBtYXAgZXJyb3JzIHdoZW4gc2VjdGlvbnMgYXJlIG91dCBvZiBvcmRlciBieSBsaW5lJ10gPSBmdW5jdGlvbihhc3NlcnQpIHtcbiAgICAvLyBNYWtlIGEgZGVlcCBjb3B5IG9mIHRoZSBpbmRleGVkVGVzdE1hcFxuICAgIHZhciBtaXNvcmRlcmVkSW5kZXhlZFRlc3RNYXAgPSBKU09OLnBhcnNlKEpTT04uc3RyaW5naWZ5KHV0aWwuaW5kZXhlZFRlc3RNYXApKTtcblxuICAgIG1pc29yZGVyZWRJbmRleGVkVGVzdE1hcC5zZWN0aW9uc1swXS5vZmZzZXQgPSB7XG4gICAgICBsaW5lOiAyLFxuICAgICAgY29sdW1uOiAwXG4gICAgfTtcblxuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24oKSB7XG4gICAgICBuZXcgU291cmNlTWFwQ29uc3VtZXIobWlzb3JkZXJlZEluZGV4ZWRUZXN0TWFwKTtcbiAgICB9LCBFcnJvcik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBnaXRodWIgaXNzdWUgIzY0J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih7XG4gICAgICBcInZlcnNpb25cIjogMyxcbiAgICAgIFwiZmlsZVwiOiBcImZvby5qc1wiLFxuICAgICAgXCJzb3VyY2VSb290XCI6IFwiaHR0cDovL2V4YW1wbGUuY29tL1wiLFxuICAgICAgXCJzb3VyY2VzXCI6IFtcIi9hXCJdLFxuICAgICAgXCJuYW1lc1wiOiBbXSxcbiAgICAgIFwibWFwcGluZ3NcIjogXCJBQUNBXCIsXG4gICAgICBcInNvdXJjZXNDb250ZW50XCI6IFtcImZvb1wiXVxuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKFwiYVwiKSwgXCJmb29cIik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKFwiL2FcIiksIFwiZm9vXCIpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYnVnIDg4NTU5NyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIoe1xuICAgICAgXCJ2ZXJzaW9uXCI6IDMsXG4gICAgICBcImZpbGVcIjogXCJmb28uanNcIixcbiAgICAgIFwic291cmNlUm9vdFwiOiBcImZpbGU6Ly8vVXNlcnMvQWxHb3JlL0ludmVudGVkL1RoZS9JbnRlcm5ldC9cIixcbiAgICAgIFwic291cmNlc1wiOiBbXCIvYVwiXSxcbiAgICAgIFwibmFtZXNcIjogW10sXG4gICAgICBcIm1hcHBpbmdzXCI6IFwiQUFDQVwiLFxuICAgICAgXCJzb3VyY2VzQ29udGVudFwiOiBbXCJmb29cIl1cbiAgICB9KTtcblxuICAgIHZhciBzID0gbWFwLnNvdXJjZXNbMF07XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VDb250ZW50Rm9yKHMpLCBcImZvb1wiKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGdpdGh1YiBpc3N1ZSAjNzIsIGR1cGxpY2F0ZSBzb3VyY2VzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih7XG4gICAgICBcInZlcnNpb25cIjogMyxcbiAgICAgIFwiZmlsZVwiOiBcImZvby5qc1wiLFxuICAgICAgXCJzb3VyY2VzXCI6IFtcInNvdXJjZTEuanNcIiwgXCJzb3VyY2UxLmpzXCIsIFwic291cmNlMy5qc1wiXSxcbiAgICAgIFwibmFtZXNcIjogW10sXG4gICAgICBcIm1hcHBpbmdzXCI6IFwiO0VBQUM7O0lBRUU7O01FRUVcIixcbiAgICAgIFwic291cmNlUm9vdFwiOiBcImh0dHA6Ly9leGFtcGxlLmNvbVwiXG4gICAgfSk7XG5cbiAgICB2YXIgcG9zID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIGNvbHVtbjogMlxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3Muc291cmNlLCAnaHR0cDovL2V4YW1wbGUuY29tL3NvdXJjZTEuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmxpbmUsIDEpO1xuICAgIGFzc2VydC5lcXVhbChwb3MuY29sdW1uLCAxKTtcblxuICAgIHZhciBwb3MgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiA0LFxuICAgICAgY29sdW1uOiA0XG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5zb3VyY2UsICdodHRwOi8vZXhhbXBsZS5jb20vc291cmNlMS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChwb3MubGluZSwgMyk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5jb2x1bW4sIDMpO1xuXG4gICAgdmFyIHBvcyA9IG1hcC5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgIGxpbmU6IDYsXG4gICAgICBjb2x1bW46IDZcbiAgICB9KTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLnNvdXJjZSwgJ2h0dHA6Ly9leGFtcGxlLmNvbS9zb3VyY2UzLmpzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCA1KTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgNSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBnaXRodWIgaXNzdWUgIzcyLCBkdXBsaWNhdGUgbmFtZXMnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKHtcbiAgICAgIFwidmVyc2lvblwiOiAzLFxuICAgICAgXCJmaWxlXCI6IFwiZm9vLmpzXCIsXG4gICAgICBcInNvdXJjZXNcIjogW1wic291cmNlLmpzXCJdLFxuICAgICAgXCJuYW1lc1wiOiBbXCJuYW1lMVwiLCBcIm5hbWUxXCIsIFwibmFtZTNcIl0sXG4gICAgICBcIm1hcHBpbmdzXCI6IFwiO0VBQUNBOztJQUVFQTs7TUFFRUVcIixcbiAgICAgIFwic291cmNlUm9vdFwiOiBcImh0dHA6Ly9leGFtcGxlLmNvbVwiXG4gICAgfSk7XG5cbiAgICB2YXIgcG9zID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIGNvbHVtbjogMlxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3MubmFtZSwgJ25hbWUxJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgMSk7XG5cbiAgICB2YXIgcG9zID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogNCxcbiAgICAgIGNvbHVtbjogNFxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3MubmFtZSwgJ25hbWUxJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCAzKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgMyk7XG5cbiAgICB2YXIgcG9zID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogNixcbiAgICAgIGNvbHVtbjogNlxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3MubmFtZSwgJ25hbWUzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5saW5lLCA1KTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmNvbHVtbiwgNSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHNtZyA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgc291cmNlUm9vdDogJ2h0dHA6Ly9leGFtcGxlLmNvbS8nLFxuICAgICAgZmlsZTogJ2Zvby5qcydcbiAgICB9KTtcbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJ2Jhci5qcydcbiAgICB9KTtcbiAgICBzbWcuYWRkTWFwcGluZyh7XG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiA0LCBjb2x1bW46IDQgfSxcbiAgICAgIHNvdXJjZTogJ2Jhei5qcycsXG4gICAgICBuYW1lOiAnZGlydE1jR2lydCdcbiAgICB9KTtcbiAgICBzbWcuc2V0U291cmNlQ29udGVudCgnYmF6LmpzJywgJ2Jhei5qcyBjb250ZW50Jyk7XG5cbiAgICB2YXIgc21jID0gU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcChzbWcpO1xuICAgIGFzc2VydC5lcXVhbChzbWMuZmlsZSwgJ2Zvby5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzbWMuc291cmNlUm9vdCwgJ2h0dHA6Ly9leGFtcGxlLmNvbS8nKTtcbiAgICBhc3NlcnQuZXF1YWwoc21jLnNvdXJjZXMubGVuZ3RoLCAyKTtcbiAgICBhc3NlcnQuZXF1YWwoc21jLnNvdXJjZXNbMF0sICdodHRwOi8vZXhhbXBsZS5jb20vYmFyLmpzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKHNtYy5zb3VyY2VzWzFdLCAnaHR0cDovL2V4YW1wbGUuY29tL2Jhei5qcycpO1xuICAgIGFzc2VydC5lcXVhbChzbWMuc291cmNlQ29udGVudEZvcignYmF6LmpzJyksICdiYXouanMgY29udGVudCcpO1xuXG4gICAgdmFyIHBvcyA9IHNtYy5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgIGxpbmU6IDIsXG4gICAgICBjb2x1bW46IDJcbiAgICB9KTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmxpbmUsIDEpO1xuICAgIGFzc2VydC5lcXVhbChwb3MuY29sdW1uLCAxKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLnNvdXJjZSwgJ2h0dHA6Ly9leGFtcGxlLmNvbS9iYXIuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLm5hbWUsIG51bGwpO1xuXG4gICAgcG9zID0gc21jLmdlbmVyYXRlZFBvc2l0aW9uRm9yKHtcbiAgICAgIGxpbmU6IDEsXG4gICAgICBjb2x1bW46IDEsXG4gICAgICBzb3VyY2U6ICdodHRwOi8vZXhhbXBsZS5jb20vYmFyLmpzJ1xuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3MubGluZSwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5jb2x1bW4sIDIpO1xuXG4gICAgcG9zID0gc21jLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogNCxcbiAgICAgIGNvbHVtbjogNFxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChwb3MubGluZSwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKHBvcy5jb2x1bW4sIDIpO1xuICAgIGFzc2VydC5lcXVhbChwb3Muc291cmNlLCAnaHR0cDovL2V4YW1wbGUuY29tL2Jhei5qcycpO1xuICAgIGFzc2VydC5lcXVhbChwb3MubmFtZSwgJ2RpcnRNY0dpcnQnKTtcblxuICAgIHBvcyA9IHNtYy5nZW5lcmF0ZWRQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAyLFxuICAgICAgY29sdW1uOiAyLFxuICAgICAgc291cmNlOiAnaHR0cDovL2V4YW1wbGUuY29tL2Jhei5qcydcbiAgICB9KTtcbiAgICBhc3NlcnQuZXF1YWwocG9zLmxpbmUsIDQpO1xuICAgIGFzc2VydC5lcXVhbChwb3MuY29sdW1uLCA0KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGlzc3VlICMxOTEnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgZ2VuZXJhdG9yID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7IGZpbGU6ICdhLmNzcycgfSk7XG4gICAgZ2VuZXJhdG9yLmFkZE1hcHBpbmcoe1xuICAgICAgc291cmNlOiAgICdiLmNzcycsXG4gICAgICBvcmlnaW5hbDoge1xuICAgICAgICBsaW5lOiAgIDEsXG4gICAgICAgIGNvbHVtbjogMFxuICAgICAgfSxcbiAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICBsaW5lOiAgIDEsXG4gICAgICAgIGNvbHVtbjogMFxuICAgICAgfVxuICAgIH0pO1xuXG4gICAgLy8gQ3JlYXRlIGEgU291cmNlTWFwQ29uc3VtZXIgZnJvbSB0aGUgU291cmNlTWFwR2VuZXJhdG9yLCAuLi5cbiAgICB2YXIgY29uc3VtZXIgID0gU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcChnZW5lcmF0b3IpO1xuICAgIC8vIC4uLiBhbmQgdGhlbiB0cnkgYW5kIHVzZSB0aGUgU291cmNlTWFwR2VuZXJhdG9yIGFnYWluLiBUaGlzIHNob3VsZCBub3RcbiAgICAvLyB0aHJvdy5cbiAgICBnZW5lcmF0b3IudG9KU09OKCk7XG5cbiAgICBhc3NlcnQub2sodHJ1ZSwgXCJVc2luZyBhIFNvdXJjZU1hcEdlbmVyYXRvciBhZ2FpbiBhZnRlciBjcmVhdGluZyBhIFwiICtcbiAgICAgICAgICAgICAgICAgICAgXCJTb3VyY2VNYXBDb25zdW1lciBmcm9tIGl0IHNob3VsZCBub3QgdGhyb3dcIik7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBzb3VyY2VzIHdoZXJlIHRoZWlyIHByZWZpeCBpcyB0aGUgc291cmNlIHJvb3Q6IGlzc3VlICMxOTknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgdGVzdFNvdXJjZU1hcCA9IHtcbiAgICAgIFwidmVyc2lvblwiOiAzLFxuICAgICAgXCJzb3VyY2VzXCI6IFtcIi9zb3VyY2UvYXBwL2FwcC9hcHAuanNcIl0sXG4gICAgICBcIm5hbWVzXCI6IFtcIlN5c3RlbVwiXSxcbiAgICAgIFwibWFwcGluZ3NcIjogXCJBQUFBQVwiLFxuICAgICAgXCJmaWxlXCI6IFwiYXBwL2FwcC5qc1wiLFxuICAgICAgXCJzb3VyY2VzQ29udGVudFwiOiBbXCIndXNlIHN0cmljdCc7XCJdLFxuICAgICAgXCJzb3VyY2VSb290XCI6XCIvc291cmNlL1wiXG4gICAgfTtcblxuICAgIHZhciBjb25zdW1lciA9IG5ldyBTb3VyY2VNYXBDb25zdW1lcih0ZXN0U291cmNlTWFwKTtcblxuICAgIGZ1bmN0aW9uIGNvbnN1bWVySGFzU291cmNlKHMpIHtcbiAgICAgIGFzc2VydC5vayhjb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKHMpKTtcbiAgICB9XG5cbiAgICBjb25zdW1lci5zb3VyY2VzLmZvckVhY2goY29uc3VtZXJIYXNTb3VyY2UpO1xuICAgIHRlc3RTb3VyY2VNYXAuc291cmNlcy5mb3JFYWNoKGNvbnN1bWVySGFzU291cmNlKTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IHNvdXJjZXMgd2hlcmUgdGhlaXIgcHJlZml4IGlzIHRoZSBzb3VyY2Ugcm9vdCBhbmQgdGhlIHNvdXJjZSByb290IGlzIGEgdXJsOiBpc3N1ZSAjMTk5J10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIHRlc3RTb3VyY2VNYXAgPSB7XG4gICAgICBcInZlcnNpb25cIjogMyxcbiAgICAgIFwic291cmNlc1wiOiBbXCJodHRwOi8vZXhhbXBsZS5jb20vc291cmNlL2FwcC9hcHAvYXBwLmpzXCJdLFxuICAgICAgXCJuYW1lc1wiOiBbXCJTeXN0ZW1cIl0sXG4gICAgICBcIm1hcHBpbmdzXCI6IFwiQUFBQUFcIixcbiAgICAgIFwic291cmNlc0NvbnRlbnRcIjogW1wiJ3VzZSBzdHJpY3QnO1wiXSxcbiAgICAgIFwic291cmNlUm9vdFwiOlwiaHR0cDovL2V4YW1wbGUuY29tL3NvdXJjZS9cIlxuICAgIH07XG5cbiAgICB2YXIgY29uc3VtZXIgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIodGVzdFNvdXJjZU1hcCk7XG5cbiAgICBmdW5jdGlvbiBjb25zdW1lckhhc1NvdXJjZShzKSB7XG4gICAgICBhc3NlcnQub2soY29uc3VtZXIuc291cmNlQ29udGVudEZvcihzKSk7XG4gICAgfVxuXG4gICAgY29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGNvbnN1bWVySGFzU291cmNlKTtcbiAgICB0ZXN0U291cmNlTWFwLnNvdXJjZXMuZm9yRWFjaChjb25zdW1lckhhc1NvdXJjZSk7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC90ZXN0LXNvdXJjZS1tYXAtY29uc3VtZXIuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi4vbGliL3V0aWwnKTtcblxuICAvLyBUaGlzIGlzIGEgdGVzdCBtYXBwaW5nIHdoaWNoIG1hcHMgZnVuY3Rpb25zIGZyb20gdHdvIGRpZmZlcmVudCBmaWxlc1xuICAvLyAob25lLmpzIGFuZCB0d28uanMpIHRvIGEgbWluaWZpZWQgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgLy9cbiAgLy8gSGVyZSBpcyBvbmUuanM6XG4gIC8vXG4gIC8vICAgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcbiAgLy8gICAgIHJldHVybiBiYXooYmFyKTtcbiAgLy8gICB9O1xuICAvL1xuICAvLyBIZXJlIGlzIHR3by5qczpcbiAgLy9cbiAgLy8gICBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcbiAgLy8gICAgIHJldHVybiBuICsgMTtcbiAgLy8gICB9O1xuICAvL1xuICAvLyBBbmQgaGVyZSBpcyB0aGUgZ2VuZXJhdGVkIGNvZGUgKG1pbi5qcyk6XG4gIC8vXG4gIC8vICAgT05FLmZvbz1mdW5jdGlvbihhKXtyZXR1cm4gYmF6KGEpO307XG4gIC8vICAgVFdPLmluYz1mdW5jdGlvbihhKXtyZXR1cm4gYSsxO307XG4gIGV4cG9ydHMudGVzdEdlbmVyYXRlZENvZGUgPSBcIiBPTkUuZm9vPWZ1bmN0aW9uKGEpe3JldHVybiBiYXooYSk7fTtcXG5cIitcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiIFRXTy5pbmM9ZnVuY3Rpb24oYSl7cmV0dXJuIGErMTt9O1wiO1xuICBleHBvcnRzLnRlc3RNYXAgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VSb290OiAnL3RoZS9yb290JyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLnRlc3RNYXBOb1NvdXJjZVJvb3QgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLnRlc3RNYXBFbXB0eVNvdXJjZVJvb3QgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VSb290OiAnJyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICAvLyBUaGlzIG1hcHBpbmcgaXMgaWRlbnRpY2FsIHRvIGFib3ZlLCBidXQgdXNlcyB0aGUgaW5kZXhlZCBmb3JtYXQgaW5zdGVhZC5cbiAgZXhwb3J0cy5pbmRleGVkVGVzdE1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIHNlY3Rpb25zOiBbXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDAsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJvbmUuanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAgICAgICAnIH07JyxcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcImJhclwiLFxuICAgICAgICAgICAgXCJiYXpcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSURcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL3RoZS9yb290XCJcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMSxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcInR3by5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAgICAgICAnIH07J1xuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiblwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQVwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvdGhlL3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgXVxuICB9O1xuICBleHBvcnRzLmluZGV4ZWRUZXN0TWFwRGlmZmVyZW50U291cmNlUm9vdHMgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBzZWN0aW9uczogW1xuICAgICAge1xuICAgICAgICBvZmZzZXQ6IHtcbiAgICAgICAgICBsaW5lOiAwLFxuICAgICAgICAgIGNvbHVtbjogMFxuICAgICAgICB9LFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICB2ZXJzaW9uOiAzLFxuICAgICAgICAgIHNvdXJjZXM6IFtcbiAgICAgICAgICAgIFwib25lLmpzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAgICAgICAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuJyArXG4gICAgICAgICAgICAnICAgcmV0dXJuIGJheihiYXIpO1xcbicgK1xuICAgICAgICAgICAgJyB9OycsXG4gICAgICAgICAgXSxcbiAgICAgICAgICBuYW1lczogW1xuICAgICAgICAgICAgXCJiYXJcIixcbiAgICAgICAgICAgIFwiYmF6XCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIG1hcHBpbmdzOiBcIkNBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEXCIsXG4gICAgICAgICAgZmlsZTogXCJtaW4uanNcIixcbiAgICAgICAgICBzb3VyY2VSb290OiBcIi90aGUvcm9vdFwiXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDEsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJ0d28uanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuJyArXG4gICAgICAgICAgICAnICAgcmV0dXJuIG4gKyAxO1xcbicgK1xuICAgICAgICAgICAgJyB9OydcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcIm5cIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0FcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL2RpZmZlcmVudC9yb290XCJcbiAgICAgICAgfVxuICAgICAgfVxuICAgIF1cbiAgfTtcbiAgZXhwb3J0cy50ZXN0TWFwV2l0aFNvdXJjZXNDb250ZW50ID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFsnYmFyJywgJ2JheicsICduJ10sXG4gICAgc291cmNlczogWydvbmUuanMnLCAndHdvLmpzJ10sXG4gICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAnIH07JyxcbiAgICAgICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuJyArXG4gICAgICAnICAgcmV0dXJuIG4gKyAxO1xcbicgK1xuICAgICAgJyB9OydcbiAgICBdLFxuICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcFJlbGF0aXZlU291cmNlcyA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnLi9vbmUuanMnLCAnLi90d28uanMnXSxcbiAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICcgfTsnLFxuICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAnIH07J1xuICAgIF0sXG4gICAgc291cmNlUm9vdDogJy90aGUvcm9vdCcsXG4gICAgbWFwcGluZ3M6ICdDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRDtDQ0RiLElBQUksSUFBTSxTQUFVRSxHQUNsQixPQUFPQSdcbiAgfTtcbiAgZXhwb3J0cy5lbXB0eU1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbXSxcbiAgICBzb3VyY2VzOiBbXSxcbiAgICBtYXBwaW5nczogJydcbiAgfTtcblxuXG4gIGZ1bmN0aW9uIGFzc2VydE1hcHBpbmcoZ2VuZXJhdGVkTGluZSwgZ2VuZXJhdGVkQ29sdW1uLCBvcmlnaW5hbFNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICBvcmlnaW5hbExpbmUsIG9yaWdpbmFsQ29sdW1uLCBuYW1lLCBiaWFzLCBtYXAsIGFzc2VydCxcbiAgICAgICAgICAgICAgICAgICAgICAgICBkb250VGVzdEdlbmVyYXRlZCwgZG9udFRlc3RPcmlnaW5hbCkge1xuICAgIGlmICghZG9udFRlc3RPcmlnaW5hbCkge1xuICAgICAgdmFyIG9yaWdNYXBwaW5nID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgICBsaW5lOiBnZW5lcmF0ZWRMaW5lLFxuICAgICAgICBjb2x1bW46IGdlbmVyYXRlZENvbHVtbixcbiAgICAgICAgYmlhczogYmlhc1xuICAgICAgfSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcubmFtZSwgbmFtZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IG5hbWUsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShuYW1lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5uYW1lKSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcubGluZSwgb3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbGluZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdpbmFsTGluZSlcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcubGluZSkpO1xuICAgICAgYXNzZXJ0LmVxdWFsKG9yaWdNYXBwaW5nLmNvbHVtbiwgb3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBjb2x1bW4sIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShvcmlnaW5hbENvbHVtbilcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcuY29sdW1uKSk7XG5cbiAgICAgIHZhciBleHBlY3RlZFNvdXJjZTtcblxuICAgICAgaWYgKG9yaWdpbmFsU291cmNlICYmIG1hcC5zb3VyY2VSb290ICYmIG9yaWdpbmFsU291cmNlLmluZGV4T2YobWFwLnNvdXJjZVJvb3QpID09PSAwKSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gb3JpZ2luYWxTb3VyY2U7XG4gICAgICB9IGVsc2UgaWYgKG9yaWdpbmFsU291cmNlKSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gbWFwLnNvdXJjZVJvb3RcbiAgICAgICAgICA/IHV0aWwuam9pbihtYXAuc291cmNlUm9vdCwgb3JpZ2luYWxTb3VyY2UpXG4gICAgICAgICAgOiBvcmlnaW5hbFNvdXJjZTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgYXNzZXJ0LmVxdWFsKG9yaWdNYXBwaW5nLnNvdXJjZSwgZXhwZWN0ZWRTb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBzb3VyY2UsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShleHBlY3RlZFNvdXJjZSlcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcuc291cmNlKSk7XG4gICAgfVxuXG4gICAgaWYgKCFkb250VGVzdEdlbmVyYXRlZCkge1xuICAgICAgdmFyIGdlbk1hcHBpbmcgPSBtYXAuZ2VuZXJhdGVkUG9zaXRpb25Gb3Ioe1xuICAgICAgICBzb3VyY2U6IG9yaWdpbmFsU291cmNlLFxuICAgICAgICBsaW5lOiBvcmlnaW5hbExpbmUsXG4gICAgICAgIGNvbHVtbjogb3JpZ2luYWxDb2x1bW4sXG4gICAgICAgIGJpYXM6IGJpYXNcbiAgICAgIH0pO1xuICAgICAgYXNzZXJ0LmVxdWFsKGdlbk1hcHBpbmcubGluZSwgZ2VuZXJhdGVkTGluZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IGxpbmUsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShnZW5lcmF0ZWRMaW5lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShnZW5NYXBwaW5nLmxpbmUpKTtcbiAgICAgIGFzc2VydC5lcXVhbChnZW5NYXBwaW5nLmNvbHVtbiwgZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgY29sdW1uLCBleHBlY3RlZCAnICsgSlNPTi5zdHJpbmdpZnkoZ2VuZXJhdGVkQ29sdW1uKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShnZW5NYXBwaW5nLmNvbHVtbikpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmFzc2VydE1hcHBpbmcgPSBhc3NlcnRNYXBwaW5nO1xuXG4gIGZ1bmN0aW9uIGFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCwgZXhwZWN0ZWRNYXApIHtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnZlcnNpb24sIGV4cGVjdGVkTWFwLnZlcnNpb24sIFwidmVyc2lvbiBtaXNtYXRjaFwiKTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLmZpbGUsIGV4cGVjdGVkTWFwLmZpbGUsIFwiZmlsZSBtaXNtYXRjaFwiKTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLm5hbWVzLmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAubmFtZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBcIm5hbWVzIGxlbmd0aCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5uYW1lcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAubmFtZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAubmFtZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAubmFtZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgXCJuYW1lc1tcIiArIGkgKyBcIl0gbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5uYW1lcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSk7XG4gICAgfVxuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAuc291cmNlcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBcInNvdXJjZXMgbGVuZ3RoIG1pc21hdGNoOiBcIiArXG4gICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpICsgXCIgIT0gXCIgKyBleHBlY3RlZE1hcC5zb3VyY2VzLmpvaW4oXCIsIFwiKSk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAuc291cmNlcy5sZW5ndGg7IGkrKykge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzW2ldLFxuICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzW1wiICsgaSArIFwiXSBsZW5ndGggbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpKTtcbiAgICB9XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VSb290LFxuICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5zb3VyY2VSb290LFxuICAgICAgICAgICAgICAgICBcInNvdXJjZVJvb3QgbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlUm9vdCArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAuc291cmNlUm9vdCk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5tYXBwaW5ncywgZXhwZWN0ZWRNYXAubWFwcGluZ3MsXG4gICAgICAgICAgICAgICAgIFwibWFwcGluZ3MgbWlzbWF0Y2g6XFxuQWN0dWFsOiAgIFwiICsgYWN0dWFsTWFwLm1hcHBpbmdzICsgXCJcXG5FeHBlY3RlZDogXCIgKyBleHBlY3RlZE1hcC5tYXBwaW5ncyk7XG4gICAgaWYgKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudCkge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc0NvbnRlbnQubGVuZ3RoLFxuICAgICAgICAgICAgICAgICAgIFwic291cmNlc0NvbnRlbnQgbGVuZ3RoIG1pc21hdGNoXCIpO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAuc291cmNlc0NvbnRlbnQubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudFtpXSxcbiAgICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXNDb250ZW50W2ldLFxuICAgICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzQ29udGVudFtcIiArIGkgKyBcIl0gbWlzbWF0Y2hcIik7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuYXNzZXJ0RXF1YWxNYXBzID0gYXNzZXJ0RXF1YWxNYXBzO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdXRpbC5qc1xuICoqIG1vZHVsZSBpZCA9IDFcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgLyoqXG4gICAqIFRoaXMgaXMgYSBoZWxwZXIgZnVuY3Rpb24gZm9yIGdldHRpbmcgdmFsdWVzIGZyb20gcGFyYW1ldGVyL29wdGlvbnNcbiAgICogb2JqZWN0cy5cbiAgICpcbiAgICogQHBhcmFtIGFyZ3MgVGhlIG9iamVjdCB3ZSBhcmUgZXh0cmFjdGluZyB2YWx1ZXMgZnJvbVxuICAgKiBAcGFyYW0gbmFtZSBUaGUgbmFtZSBvZiB0aGUgcHJvcGVydHkgd2UgYXJlIGdldHRpbmcuXG4gICAqIEBwYXJhbSBkZWZhdWx0VmFsdWUgQW4gb3B0aW9uYWwgdmFsdWUgdG8gcmV0dXJuIGlmIHRoZSBwcm9wZXJ0eSBpcyBtaXNzaW5nXG4gICAqIGZyb20gdGhlIG9iamVjdC4gSWYgdGhpcyBpcyBub3Qgc3BlY2lmaWVkIGFuZCB0aGUgcHJvcGVydHkgaXMgbWlzc2luZywgYW5cbiAgICogZXJyb3Igd2lsbCBiZSB0aHJvd24uXG4gICAqL1xuICBmdW5jdGlvbiBnZXRBcmcoYUFyZ3MsIGFOYW1lLCBhRGVmYXVsdFZhbHVlKSB7XG4gICAgaWYgKGFOYW1lIGluIGFBcmdzKSB7XG4gICAgICByZXR1cm4gYUFyZ3NbYU5hbWVdO1xuICAgIH0gZWxzZSBpZiAoYXJndW1lbnRzLmxlbmd0aCA9PT0gMykge1xuICAgICAgcmV0dXJuIGFEZWZhdWx0VmFsdWU7XG4gICAgfSBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYU5hbWUgKyAnXCIgaXMgYSByZXF1aXJlZCBhcmd1bWVudC4nKTtcbiAgICB9XG4gIH1cbiAgZXhwb3J0cy5nZXRBcmcgPSBnZXRBcmc7XG5cbiAgdmFyIHVybFJlZ2V4cCA9IC9eKD86KFtcXHcrXFwtLl0rKTopP1xcL1xcLyg/OihcXHcrOlxcdyspQCk/KFtcXHcuXSopKD86OihcXGQrKSk/KFxcUyopJC87XG4gIHZhciBkYXRhVXJsUmVnZXhwID0gL15kYXRhOi4rXFwsLiskLztcblxuICBmdW5jdGlvbiB1cmxQYXJzZShhVXJsKSB7XG4gICAgdmFyIG1hdGNoID0gYVVybC5tYXRjaCh1cmxSZWdleHApO1xuICAgIGlmICghbWF0Y2gpIHtcbiAgICAgIHJldHVybiBudWxsO1xuICAgIH1cbiAgICByZXR1cm4ge1xuICAgICAgc2NoZW1lOiBtYXRjaFsxXSxcbiAgICAgIGF1dGg6IG1hdGNoWzJdLFxuICAgICAgaG9zdDogbWF0Y2hbM10sXG4gICAgICBwb3J0OiBtYXRjaFs0XSxcbiAgICAgIHBhdGg6IG1hdGNoWzVdXG4gICAgfTtcbiAgfVxuICBleHBvcnRzLnVybFBhcnNlID0gdXJsUGFyc2U7XG5cbiAgZnVuY3Rpb24gdXJsR2VuZXJhdGUoYVBhcnNlZFVybCkge1xuICAgIHZhciB1cmwgPSAnJztcbiAgICBpZiAoYVBhcnNlZFVybC5zY2hlbWUpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLnNjaGVtZSArICc6JztcbiAgICB9XG4gICAgdXJsICs9ICcvLyc7XG4gICAgaWYgKGFQYXJzZWRVcmwuYXV0aCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuYXV0aCArICdAJztcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwuaG9zdCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuaG9zdDtcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwucG9ydCkge1xuICAgICAgdXJsICs9IFwiOlwiICsgYVBhcnNlZFVybC5wb3J0XG4gICAgfVxuICAgIGlmIChhUGFyc2VkVXJsLnBhdGgpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLnBhdGg7XG4gICAgfVxuICAgIHJldHVybiB1cmw7XG4gIH1cbiAgZXhwb3J0cy51cmxHZW5lcmF0ZSA9IHVybEdlbmVyYXRlO1xuXG4gIC8qKlxuICAgKiBOb3JtYWxpemVzIGEgcGF0aCwgb3IgdGhlIHBhdGggcG9ydGlvbiBvZiBhIFVSTDpcbiAgICpcbiAgICogLSBSZXBsYWNlcyBjb25zZXF1dGl2ZSBzbGFzaGVzIHdpdGggb25lIHNsYXNoLlxuICAgKiAtIFJlbW92ZXMgdW5uZWNlc3NhcnkgJy4nIHBhcnRzLlxuICAgKiAtIFJlbW92ZXMgdW5uZWNlc3NhcnkgJzxkaXI+Ly4uJyBwYXJ0cy5cbiAgICpcbiAgICogQmFzZWQgb24gY29kZSBpbiB0aGUgTm9kZS5qcyAncGF0aCcgY29yZSBtb2R1bGUuXG4gICAqXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciB1cmwgdG8gbm9ybWFsaXplLlxuICAgKi9cbiAgZnVuY3Rpb24gbm9ybWFsaXplKGFQYXRoKSB7XG4gICAgdmFyIHBhdGggPSBhUGF0aDtcbiAgICB2YXIgdXJsID0gdXJsUGFyc2UoYVBhdGgpO1xuICAgIGlmICh1cmwpIHtcbiAgICAgIGlmICghdXJsLnBhdGgpIHtcbiAgICAgICAgcmV0dXJuIGFQYXRoO1xuICAgICAgfVxuICAgICAgcGF0aCA9IHVybC5wYXRoO1xuICAgIH1cbiAgICB2YXIgaXNBYnNvbHV0ZSA9IGV4cG9ydHMuaXNBYnNvbHV0ZShwYXRoKTtcblxuICAgIHZhciBwYXJ0cyA9IHBhdGguc3BsaXQoL1xcLysvKTtcbiAgICBmb3IgKHZhciBwYXJ0LCB1cCA9IDAsIGkgPSBwYXJ0cy5sZW5ndGggLSAxOyBpID49IDA7IGktLSkge1xuICAgICAgcGFydCA9IHBhcnRzW2ldO1xuICAgICAgaWYgKHBhcnQgPT09ICcuJykge1xuICAgICAgICBwYXJ0cy5zcGxpY2UoaSwgMSk7XG4gICAgICB9IGVsc2UgaWYgKHBhcnQgPT09ICcuLicpIHtcbiAgICAgICAgdXArKztcbiAgICAgIH0gZWxzZSBpZiAodXAgPiAwKSB7XG4gICAgICAgIGlmIChwYXJ0ID09PSAnJykge1xuICAgICAgICAgIC8vIFRoZSBmaXJzdCBwYXJ0IGlzIGJsYW5rIGlmIHRoZSBwYXRoIGlzIGFic29sdXRlLiBUcnlpbmcgdG8gZ29cbiAgICAgICAgICAvLyBhYm92ZSB0aGUgcm9vdCBpcyBhIG5vLW9wLiBUaGVyZWZvcmUgd2UgY2FuIHJlbW92ZSBhbGwgJy4uJyBwYXJ0c1xuICAgICAgICAgIC8vIGRpcmVjdGx5IGFmdGVyIHRoZSByb290LlxuICAgICAgICAgIHBhcnRzLnNwbGljZShpICsgMSwgdXApO1xuICAgICAgICAgIHVwID0gMDtcbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBwYXJ0cy5zcGxpY2UoaSwgMik7XG4gICAgICAgICAgdXAtLTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH1cbiAgICBwYXRoID0gcGFydHMuam9pbignLycpO1xuXG4gICAgaWYgKHBhdGggPT09ICcnKSB7XG4gICAgICBwYXRoID0gaXNBYnNvbHV0ZSA/ICcvJyA6ICcuJztcbiAgICB9XG5cbiAgICBpZiAodXJsKSB7XG4gICAgICB1cmwucGF0aCA9IHBhdGg7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUodXJsKTtcbiAgICB9XG4gICAgcmV0dXJuIHBhdGg7XG4gIH1cbiAgZXhwb3J0cy5ub3JtYWxpemUgPSBub3JtYWxpemU7XG5cbiAgLyoqXG4gICAqIEpvaW5zIHR3byBwYXRocy9VUkxzLlxuICAgKlxuICAgKiBAcGFyYW0gYVJvb3QgVGhlIHJvb3QgcGF0aCBvciBVUkwuXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciBVUkwgdG8gYmUgam9pbmVkIHdpdGggdGhlIHJvb3QuXG4gICAqXG4gICAqIC0gSWYgYVBhdGggaXMgYSBVUkwgb3IgYSBkYXRhIFVSSSwgYVBhdGggaXMgcmV0dXJuZWQsIHVubGVzcyBhUGF0aCBpcyBhXG4gICAqICAgc2NoZW1lLXJlbGF0aXZlIFVSTDogVGhlbiB0aGUgc2NoZW1lIG9mIGFSb290LCBpZiBhbnksIGlzIHByZXBlbmRlZFxuICAgKiAgIGZpcnN0LlxuICAgKiAtIE90aGVyd2lzZSBhUGF0aCBpcyBhIHBhdGguIElmIGFSb290IGlzIGEgVVJMLCB0aGVuIGl0cyBwYXRoIHBvcnRpb25cbiAgICogICBpcyB1cGRhdGVkIHdpdGggdGhlIHJlc3VsdCBhbmQgYVJvb3QgaXMgcmV0dXJuZWQuIE90aGVyd2lzZSB0aGUgcmVzdWx0XG4gICAqICAgaXMgcmV0dXJuZWQuXG4gICAqICAgLSBJZiBhUGF0aCBpcyBhYnNvbHV0ZSwgdGhlIHJlc3VsdCBpcyBhUGF0aC5cbiAgICogICAtIE90aGVyd2lzZSB0aGUgdHdvIHBhdGhzIGFyZSBqb2luZWQgd2l0aCBhIHNsYXNoLlxuICAgKiAtIEpvaW5pbmcgZm9yIGV4YW1wbGUgJ2h0dHA6Ly8nIGFuZCAnd3d3LmV4YW1wbGUuY29tJyBpcyBhbHNvIHN1cHBvcnRlZC5cbiAgICovXG4gIGZ1bmN0aW9uIGpvaW4oYVJvb3QsIGFQYXRoKSB7XG4gICAgaWYgKGFSb290ID09PSBcIlwiKSB7XG4gICAgICBhUm9vdCA9IFwiLlwiO1xuICAgIH1cbiAgICBpZiAoYVBhdGggPT09IFwiXCIpIHtcbiAgICAgIGFQYXRoID0gXCIuXCI7XG4gICAgfVxuICAgIHZhciBhUGF0aFVybCA9IHVybFBhcnNlKGFQYXRoKTtcbiAgICB2YXIgYVJvb3RVcmwgPSB1cmxQYXJzZShhUm9vdCk7XG4gICAgaWYgKGFSb290VXJsKSB7XG4gICAgICBhUm9vdCA9IGFSb290VXJsLnBhdGggfHwgJy8nO1xuICAgIH1cblxuICAgIC8vIGBqb2luKGZvbywgJy8vd3d3LmV4YW1wbGUub3JnJylgXG4gICAgaWYgKGFQYXRoVXJsICYmICFhUGF0aFVybC5zY2hlbWUpIHtcbiAgICAgIGlmIChhUm9vdFVybCkge1xuICAgICAgICBhUGF0aFVybC5zY2hlbWUgPSBhUm9vdFVybC5zY2hlbWU7XG4gICAgICB9XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVBhdGhVcmwpO1xuICAgIH1cblxuICAgIGlmIChhUGF0aFVybCB8fCBhUGF0aC5tYXRjaChkYXRhVXJsUmVnZXhwKSkge1xuICAgICAgcmV0dXJuIGFQYXRoO1xuICAgIH1cblxuICAgIC8vIGBqb2luKCdodHRwOi8vJywgJ3d3dy5leGFtcGxlLmNvbScpYFxuICAgIGlmIChhUm9vdFVybCAmJiAhYVJvb3RVcmwuaG9zdCAmJiAhYVJvb3RVcmwucGF0aCkge1xuICAgICAgYVJvb3RVcmwuaG9zdCA9IGFQYXRoO1xuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKGFSb290VXJsKTtcbiAgICB9XG5cbiAgICB2YXIgam9pbmVkID0gYVBhdGguY2hhckF0KDApID09PSAnLydcbiAgICAgID8gYVBhdGhcbiAgICAgIDogbm9ybWFsaXplKGFSb290LnJlcGxhY2UoL1xcLyskLywgJycpICsgJy8nICsgYVBhdGgpO1xuXG4gICAgaWYgKGFSb290VXJsKSB7XG4gICAgICBhUm9vdFVybC5wYXRoID0gam9pbmVkO1xuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKGFSb290VXJsKTtcbiAgICB9XG4gICAgcmV0dXJuIGpvaW5lZDtcbiAgfVxuICBleHBvcnRzLmpvaW4gPSBqb2luO1xuXG4gIGV4cG9ydHMuaXNBYnNvbHV0ZSA9IGZ1bmN0aW9uIChhUGF0aCkge1xuICAgIHJldHVybiBhUGF0aC5jaGFyQXQoMCkgPT09ICcvJyB8fCAhIWFQYXRoLm1hdGNoKHVybFJlZ2V4cCk7XG4gIH07XG5cbiAgLyoqXG4gICAqIE1ha2UgYSBwYXRoIHJlbGF0aXZlIHRvIGEgVVJMIG9yIGFub3RoZXIgcGF0aC5cbiAgICpcbiAgICogQHBhcmFtIGFSb290IFRoZSByb290IHBhdGggb3IgVVJMLlxuICAgKiBAcGFyYW0gYVBhdGggVGhlIHBhdGggb3IgVVJMIHRvIGJlIG1hZGUgcmVsYXRpdmUgdG8gYVJvb3QuXG4gICAqL1xuICBmdW5jdGlvbiByZWxhdGl2ZShhUm9vdCwgYVBhdGgpIHtcbiAgICBpZiAoYVJvb3QgPT09IFwiXCIpIHtcbiAgICAgIGFSb290ID0gXCIuXCI7XG4gICAgfVxuXG4gICAgYVJvb3QgPSBhUm9vdC5yZXBsYWNlKC9cXC8kLywgJycpO1xuXG4gICAgLy8gSXQgaXMgcG9zc2libGUgZm9yIHRoZSBwYXRoIHRvIGJlIGFib3ZlIHRoZSByb290LiBJbiB0aGlzIGNhc2UsIHNpbXBseVxuICAgIC8vIGNoZWNraW5nIHdoZXRoZXIgdGhlIHJvb3QgaXMgYSBwcmVmaXggb2YgdGhlIHBhdGggd29uJ3Qgd29yay4gSW5zdGVhZCwgd2VcbiAgICAvLyBuZWVkIHRvIHJlbW92ZSBjb21wb25lbnRzIGZyb20gdGhlIHJvb3Qgb25lIGJ5IG9uZSwgdW50aWwgZWl0aGVyIHdlIGZpbmRcbiAgICAvLyBhIHByZWZpeCB0aGF0IGZpdHMsIG9yIHdlIHJ1biBvdXQgb2YgY29tcG9uZW50cyB0byByZW1vdmUuXG4gICAgdmFyIGxldmVsID0gMDtcbiAgICB3aGlsZSAoYVBhdGguaW5kZXhPZihhUm9vdCArICcvJykgIT09IDApIHtcbiAgICAgIHZhciBpbmRleCA9IGFSb290Lmxhc3RJbmRleE9mKFwiL1wiKTtcbiAgICAgIGlmIChpbmRleCA8IDApIHtcbiAgICAgICAgcmV0dXJuIGFQYXRoO1xuICAgICAgfVxuXG4gICAgICAvLyBJZiB0aGUgb25seSBwYXJ0IG9mIHRoZSByb290IHRoYXQgaXMgbGVmdCBpcyB0aGUgc2NoZW1lIChpLmUuIGh0dHA6Ly8sXG4gICAgICAvLyBmaWxlOi8vLywgZXRjLiksIG9uZSBvciBtb3JlIHNsYXNoZXMgKC8pLCBvciBzaW1wbHkgbm90aGluZyBhdCBhbGwsIHdlXG4gICAgICAvLyBoYXZlIGV4aGF1c3RlZCBhbGwgY29tcG9uZW50cywgc28gdGhlIHBhdGggaXMgbm90IHJlbGF0aXZlIHRvIHRoZSByb290LlxuICAgICAgYVJvb3QgPSBhUm9vdC5zbGljZSgwLCBpbmRleCk7XG4gICAgICBpZiAoYVJvb3QubWF0Y2goL14oW15cXC9dKzpcXC8pP1xcLyokLykpIHtcbiAgICAgICAgcmV0dXJuIGFQYXRoO1xuICAgICAgfVxuXG4gICAgICArK2xldmVsO1xuICAgIH1cblxuICAgIC8vIE1ha2Ugc3VyZSB3ZSBhZGQgYSBcIi4uL1wiIGZvciBlYWNoIGNvbXBvbmVudCB3ZSByZW1vdmVkIGZyb20gdGhlIHJvb3QuXG4gICAgcmV0dXJuIEFycmF5KGxldmVsICsgMSkuam9pbihcIi4uL1wiKSArIGFQYXRoLnN1YnN0cihhUm9vdC5sZW5ndGggKyAxKTtcbiAgfVxuICBleHBvcnRzLnJlbGF0aXZlID0gcmVsYXRpdmU7XG5cbiAgLyoqXG4gICAqIEJlY2F1c2UgYmVoYXZpb3IgZ29lcyB3YWNreSB3aGVuIHlvdSBzZXQgYF9fcHJvdG9fX2Agb24gb2JqZWN0cywgd2VcbiAgICogaGF2ZSB0byBwcmVmaXggYWxsIHRoZSBzdHJpbmdzIGluIG91ciBzZXQgd2l0aCBhbiBhcmJpdHJhcnkgY2hhcmFjdGVyLlxuICAgKlxuICAgKiBTZWUgaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc291cmNlLW1hcC9wdWxsLzMxIGFuZFxuICAgKiBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL2lzc3Vlcy8zMFxuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIGZ1bmN0aW9uIHRvU2V0U3RyaW5nKGFTdHIpIHtcbiAgICByZXR1cm4gJyQnICsgYVN0cjtcbiAgfVxuICBleHBvcnRzLnRvU2V0U3RyaW5nID0gdG9TZXRTdHJpbmc7XG5cbiAgZnVuY3Rpb24gZnJvbVNldFN0cmluZyhhU3RyKSB7XG4gICAgcmV0dXJuIGFTdHIuc3Vic3RyKDEpO1xuICB9XG4gIGV4cG9ydHMuZnJvbVNldFN0cmluZyA9IGZyb21TZXRTdHJpbmc7XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2hlcmUgdGhlIG9yaWdpbmFsIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqXG4gICAqIE9wdGlvbmFsbHkgcGFzcyBpbiBgdHJ1ZWAgYXMgYG9ubHlDb21wYXJlR2VuZXJhdGVkYCB0byBjb25zaWRlciB0d29cbiAgICogbWFwcGluZ3Mgd2l0aCB0aGUgc2FtZSBvcmlnaW5hbCBzb3VyY2UvbGluZS9jb2x1bW4sIGJ1dCBkaWZmZXJlbnQgZ2VuZXJhdGVkXG4gICAqIGxpbmUgYW5kIGNvbHVtbiB0aGUgc2FtZS4gVXNlZnVsIHdoZW4gc2VhcmNoaW5nIGZvciBhIG1hcHBpbmcgd2l0aCBhXG4gICAqIHN0dWJiZWQgb3V0IG1hcHBpbmcuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyhtYXBwaW5nQSwgbWFwcGluZ0IsIG9ubHlDb21wYXJlT3JpZ2luYWwpIHtcbiAgICB2YXIgY21wID0gbWFwcGluZ0Euc291cmNlIC0gbWFwcGluZ0Iuc291cmNlO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwIHx8IG9ubHlDb21wYXJlT3JpZ2luYWwpIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZSAtIG1hcHBpbmdCLmdlbmVyYXRlZExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICByZXR1cm4gbWFwcGluZ0EubmFtZSAtIG1hcHBpbmdCLm5hbWU7XG4gIH1cbiAgZXhwb3J0cy5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyA9IGNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zO1xuXG4gIC8qKlxuICAgKiBDb21wYXJhdG9yIGJldHdlZW4gdHdvIG1hcHBpbmdzIHdpdGggZGVmbGF0ZWQgc291cmNlIGFuZCBuYW1lIGluZGljZXMgd2hlcmVcbiAgICogdGhlIGdlbmVyYXRlZCBwb3NpdGlvbnMgYXJlIGNvbXBhcmVkLlxuICAgKlxuICAgKiBPcHRpb25hbGx5IHBhc3MgaW4gYHRydWVgIGFzIGBvbmx5Q29tcGFyZUdlbmVyYXRlZGAgdG8gY29uc2lkZXIgdHdvXG4gICAqIG1hcHBpbmdzIHdpdGggdGhlIHNhbWUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiwgYnV0IGRpZmZlcmVudFxuICAgKiBzb3VyY2UvbmFtZS9vcmlnaW5hbCBsaW5lIGFuZCBjb2x1bW4gdGhlIHNhbWUuIFVzZWZ1bCB3aGVuIHNlYXJjaGluZyBmb3IgYVxuICAgKiBtYXBwaW5nIHdpdGggYSBzdHViYmVkIG91dCBtYXBwaW5nLlxuICAgKi9cbiAgZnVuY3Rpb24gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQobWFwcGluZ0EsIG1hcHBpbmdCLCBvbmx5Q29tcGFyZUdlbmVyYXRlZCkge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwIHx8IG9ubHlDb21wYXJlR2VuZXJhdGVkKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLnNvdXJjZSAtIG1hcHBpbmdCLnNvdXJjZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsTGluZSAtIG1hcHBpbmdCLm9yaWdpbmFsTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsQ29sdW1uIC0gbWFwcGluZ0Iub3JpZ2luYWxDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICByZXR1cm4gbWFwcGluZ0EubmFtZSAtIG1hcHBpbmdCLm5hbWU7XG4gIH1cbiAgZXhwb3J0cy5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCA9IGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkO1xuXG4gIGZ1bmN0aW9uIHN0cmNtcChhU3RyMSwgYVN0cjIpIHtcbiAgICBpZiAoYVN0cjEgPT09IGFTdHIyKSB7XG4gICAgICByZXR1cm4gMDtcbiAgICB9XG5cbiAgICBpZiAoYVN0cjEgPiBhU3RyMikge1xuICAgICAgcmV0dXJuIDE7XG4gICAgfVxuXG4gICAgcmV0dXJuIC0xO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2l0aCBpbmZsYXRlZCBzb3VyY2UgYW5kIG5hbWUgc3RyaW5ncyB3aGVyZVxuICAgKiB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IpIHtcbiAgICB2YXIgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZSAtIG1hcHBpbmdCLmdlbmVyYXRlZExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBzdHJjbXAobWFwcGluZ0Euc291cmNlLCBtYXBwaW5nQi5zb3VyY2UpO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBzdHJjbXAobWFwcGluZ0EubmFtZSwgbWFwcGluZ0IubmFtZSk7XG4gIH1cbiAgZXhwb3J0cy5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZCA9IGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi91dGlsLmpzXG4gKiogbW9kdWxlIGlkID0gMlxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgdXRpbCA9IHJlcXVpcmUoJy4vdXRpbCcpO1xuICB2YXIgYmluYXJ5U2VhcmNoID0gcmVxdWlyZSgnLi9iaW5hcnktc2VhcmNoJyk7XG4gIHZhciBBcnJheVNldCA9IHJlcXVpcmUoJy4vYXJyYXktc2V0JykuQXJyYXlTZXQ7XG4gIHZhciBiYXNlNjRWTFEgPSByZXF1aXJlKCcuL2Jhc2U2NC12bHEnKTtcbiAgdmFyIHF1aWNrU29ydCA9IHJlcXVpcmUoJy4vcXVpY2stc29ydCcpLnF1aWNrU29ydDtcblxuICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcihhU291cmNlTWFwKSB7XG4gICAgdmFyIHNvdXJjZU1hcCA9IGFTb3VyY2VNYXA7XG4gICAgaWYgKHR5cGVvZiBhU291cmNlTWFwID09PSAnc3RyaW5nJykge1xuICAgICAgc291cmNlTWFwID0gSlNPTi5wYXJzZShhU291cmNlTWFwLnJlcGxhY2UoL15cXClcXF1cXH0nLywgJycpKTtcbiAgICB9XG5cbiAgICByZXR1cm4gc291cmNlTWFwLnNlY3Rpb25zICE9IG51bGxcbiAgICAgID8gbmV3IEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcihzb3VyY2VNYXApXG4gICAgICA6IG5ldyBCYXNpY1NvdXJjZU1hcENvbnN1bWVyKHNvdXJjZU1hcCk7XG4gIH1cblxuICBTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwID0gZnVuY3Rpb24oYVNvdXJjZU1hcCkge1xuICAgIHJldHVybiBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLmZyb21Tb3VyY2VNYXAoYVNvdXJjZU1hcCk7XG4gIH1cblxuICAvKipcbiAgICogVGhlIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXBwaW5nIHNwZWMgdGhhdCB3ZSBhcmUgY29uc3VtaW5nLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvLyBgX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kIGBfX29yaWdpbmFsTWFwcGluZ3NgIGFyZSBhcnJheXMgdGhhdCBob2xkIHRoZVxuICAvLyBwYXJzZWQgbWFwcGluZyBjb29yZGluYXRlcyBmcm9tIHRoZSBzb3VyY2UgbWFwJ3MgXCJtYXBwaW5nc1wiIGF0dHJpYnV0ZS4gVGhleVxuICAvLyBhcmUgbGF6aWx5IGluc3RhbnRpYXRlZCwgYWNjZXNzZWQgdmlhIHRoZSBgX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgLy8gYF9vcmlnaW5hbE1hcHBpbmdzYCBnZXR0ZXJzIHJlc3BlY3RpdmVseSwgYW5kIHdlIG9ubHkgcGFyc2UgdGhlIG1hcHBpbmdzXG4gIC8vIGFuZCBjcmVhdGUgdGhlc2UgYXJyYXlzIG9uY2UgcXVlcmllZCBmb3IgYSBzb3VyY2UgbG9jYXRpb24uIFdlIGp1bXAgdGhyb3VnaFxuICAvLyB0aGVzZSBob29wcyBiZWNhdXNlIHRoZXJlIGNhbiBiZSBtYW55IHRob3VzYW5kcyBvZiBtYXBwaW5ncywgYW5kIHBhcnNpbmdcbiAgLy8gdGhlbSBpcyBleHBlbnNpdmUsIHNvIHdlIG9ubHkgd2FudCB0byBkbyBpdCBpZiB3ZSBtdXN0LlxuICAvL1xuICAvLyBFYWNoIG9iamVjdCBpbiB0aGUgYXJyYXlzIGlzIG9mIHRoZSBmb3JtOlxuICAvL1xuICAvLyAgICAge1xuICAvLyAgICAgICBnZW5lcmF0ZWRMaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBnZW5lcmF0ZWRDb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgc291cmNlOiBUaGUgcGF0aCB0byB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGUgdGhhdCBnZW5lcmF0ZWQgdGhpc1xuICAvLyAgICAgICAgICAgICAgIGNodW5rIG9mIGNvZGUsXG4gIC8vICAgICAgIG9yaWdpbmFsTGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UgdGhhdFxuICAvLyAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmRzIHRvIHRoaXMgY2h1bmsgb2YgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIG9yaWdpbmFsQ29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlIHRoYXRcbiAgLy8gICAgICAgICAgICAgICAgICAgICAgIGNvcnJlc3BvbmRzIHRvIHRoaXMgY2h1bmsgb2YgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIG5hbWU6IFRoZSBuYW1lIG9mIHRoZSBvcmlnaW5hbCBzeW1ib2wgd2hpY2ggZ2VuZXJhdGVkIHRoaXMgY2h1bmsgb2ZcbiAgLy8gICAgICAgICAgICAgY29kZS5cbiAgLy8gICAgIH1cbiAgLy9cbiAgLy8gQWxsIHByb3BlcnRpZXMgZXhjZXB0IGZvciBgZ2VuZXJhdGVkTGluZWAgYW5kIGBnZW5lcmF0ZWRDb2x1bW5gIGNhbiBiZVxuICAvLyBgbnVsbGAuXG4gIC8vXG4gIC8vIGBfZ2VuZXJhdGVkTWFwcGluZ3NgIGlzIG9yZGVyZWQgYnkgdGhlIGdlbmVyYXRlZCBwb3NpdGlvbnMuXG4gIC8vXG4gIC8vIGBfb3JpZ2luYWxNYXBwaW5nc2AgaXMgb3JkZXJlZCBieSB0aGUgb3JpZ2luYWwgcG9zaXRpb25zLlxuXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fX2dlbmVyYXRlZE1hcHBpbmdzID0gbnVsbDtcbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ19nZW5lcmF0ZWRNYXBwaW5ncycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIGlmICghdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzKSB7XG4gICAgICAgIHRoaXMuX3BhcnNlTWFwcGluZ3ModGhpcy5fbWFwcGluZ3MsIHRoaXMuc291cmNlUm9vdCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3M7XG4gICAgfVxuICB9KTtcblxuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX19vcmlnaW5hbE1hcHBpbmdzID0gbnVsbDtcbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ19vcmlnaW5hbE1hcHBpbmdzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgaWYgKCF0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncykge1xuICAgICAgICB0aGlzLl9wYXJzZU1hcHBpbmdzKHRoaXMuX21hcHBpbmdzLCB0aGlzLnNvdXJjZVJvb3QpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdGhpcy5fX29yaWdpbmFsTWFwcGluZ3M7XG4gICAgfVxuICB9KTtcblxuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX2NoYXJJc01hcHBpbmdTZXBhcmF0b3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2NoYXJJc01hcHBpbmdTZXBhcmF0b3IoYVN0ciwgaW5kZXgpIHtcbiAgICAgIHZhciBjID0gYVN0ci5jaGFyQXQoaW5kZXgpO1xuICAgICAgcmV0dXJuIGMgPT09IFwiO1wiIHx8IGMgPT09IFwiLFwiO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFBhcnNlIHRoZSBtYXBwaW5ncyBpbiBhIHN0cmluZyBpbiB0byBhIGRhdGEgc3RydWN0dXJlIHdoaWNoIHdlIGNhbiBlYXNpbHlcbiAgICogcXVlcnkgKHRoZSBvcmRlcmVkIGFycmF5cyBpbiB0aGUgYHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gICAqIGB0aGlzLl9fb3JpZ2luYWxNYXBwaW5nc2AgcHJvcGVydGllcykuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3BhcnNlTWFwcGluZ3MgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX3BhcnNlTWFwcGluZ3MoYVN0ciwgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcihcIlN1YmNsYXNzZXMgbXVzdCBpbXBsZW1lbnQgX3BhcnNlTWFwcGluZ3NcIik7XG4gICAgfTtcblxuICBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVIgPSAxO1xuICBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUiA9IDI7XG5cbiAgU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQgPSAxO1xuICBTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCA9IDI7XG5cbiAgLyoqXG4gICAqIEl0ZXJhdGUgb3ZlciBlYWNoIG1hcHBpbmcgYmV0d2VlbiBhbiBvcmlnaW5hbCBzb3VyY2UvbGluZS9jb2x1bW4gYW5kIGFcbiAgICogZ2VuZXJhdGVkIGxpbmUvY29sdW1uIGluIHRoaXMgc291cmNlIG1hcC5cbiAgICpcbiAgICogQHBhcmFtIEZ1bmN0aW9uIGFDYWxsYmFja1xuICAgKiAgICAgICAgVGhlIGZ1bmN0aW9uIHRoYXQgaXMgY2FsbGVkIHdpdGggZWFjaCBtYXBwaW5nLlxuICAgKiBAcGFyYW0gT2JqZWN0IGFDb250ZXh0XG4gICAqICAgICAgICBPcHRpb25hbC4gSWYgc3BlY2lmaWVkLCB0aGlzIG9iamVjdCB3aWxsIGJlIHRoZSB2YWx1ZSBvZiBgdGhpc2AgZXZlcnlcbiAgICogICAgICAgIHRpbWUgdGhhdCBgYUNhbGxiYWNrYCBpcyBjYWxsZWQuXG4gICAqIEBwYXJhbSBhT3JkZXJcbiAgICogICAgICAgIEVpdGhlciBgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSYCBvclxuICAgKiAgICAgICAgYFNvdXJjZU1hcENvbnN1bWVyLk9SSUdJTkFMX09SREVSYC4gU3BlY2lmaWVzIHdoZXRoZXIgeW91IHdhbnQgdG9cbiAgICogICAgICAgIGl0ZXJhdGUgb3ZlciB0aGUgbWFwcGluZ3Mgc29ydGVkIGJ5IHRoZSBnZW5lcmF0ZWQgZmlsZSdzIGxpbmUvY29sdW1uXG4gICAqICAgICAgICBvcmRlciBvciB0aGUgb3JpZ2luYWwncyBzb3VyY2UvbGluZS9jb2x1bW4gb3JkZXIsIHJlc3BlY3RpdmVseS4gRGVmYXVsdHMgdG9cbiAgICogICAgICAgIGBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVJgLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmVhY2hNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9lYWNoTWFwcGluZyhhQ2FsbGJhY2ssIGFDb250ZXh0LCBhT3JkZXIpIHtcbiAgICAgIHZhciBjb250ZXh0ID0gYUNvbnRleHQgfHwgbnVsbDtcbiAgICAgIHZhciBvcmRlciA9IGFPcmRlciB8fCBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVI7XG5cbiAgICAgIHZhciBtYXBwaW5ncztcbiAgICAgIHN3aXRjaCAob3JkZXIpIHtcbiAgICAgIGNhc2UgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSOlxuICAgICAgICBtYXBwaW5ncyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzO1xuICAgICAgICBicmVhaztcbiAgICAgIGNhc2UgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVI6XG4gICAgICAgIG1hcHBpbmdzID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5ncztcbiAgICAgICAgYnJlYWs7XG4gICAgICBkZWZhdWx0OlxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJVbmtub3duIG9yZGVyIG9mIGl0ZXJhdGlvbi5cIik7XG4gICAgICB9XG5cbiAgICAgIHZhciBzb3VyY2VSb290ID0gdGhpcy5zb3VyY2VSb290O1xuICAgICAgbWFwcGluZ3MubWFwKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICAgIHZhciBzb3VyY2UgPSBtYXBwaW5nLnNvdXJjZSA9PT0gbnVsbCA/IG51bGwgOiB0aGlzLl9zb3VyY2VzLmF0KG1hcHBpbmcuc291cmNlKTtcbiAgICAgICAgaWYgKHNvdXJjZSAhPSBudWxsICYmIHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgIHNvdXJjZSA9IHV0aWwuam9pbihzb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgICB9XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgICAgZ2VuZXJhdGVkTGluZTogbWFwcGluZy5nZW5lcmF0ZWRMaW5lLFxuICAgICAgICAgIGdlbmVyYXRlZENvbHVtbjogbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4sXG4gICAgICAgICAgb3JpZ2luYWxMaW5lOiBtYXBwaW5nLm9yaWdpbmFsTGluZSxcbiAgICAgICAgICBvcmlnaW5hbENvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICBuYW1lOiBtYXBwaW5nLm5hbWUgPT09IG51bGwgPyBudWxsIDogdGhpcy5fbmFtZXMuYXQobWFwcGluZy5uYW1lKVxuICAgICAgICB9O1xuICAgICAgfSwgdGhpcykuZm9yRWFjaChhQ2FsbGJhY2ssIGNvbnRleHQpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgYWxsIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBvcmlnaW5hbCBzb3VyY2UsXG4gICAqIGxpbmUsIGFuZCBjb2x1bW4gcHJvdmlkZWQuIElmIG5vIGNvbHVtbiBpcyBwcm92aWRlZCwgcmV0dXJucyBhbGwgbWFwcGluZ3NcbiAgICogY29ycmVzcG9uZGluZyB0byBhIGVpdGhlciB0aGUgbGluZSB3ZSBhcmUgc2VhcmNoaW5nIGZvciBvciB0aGUgbmV4dFxuICAgKiBjbG9zZXN0IGxpbmUgdGhhdCBoYXMgYW55IG1hcHBpbmdzLiBPdGhlcndpc2UsIHJldHVybnMgYWxsIG1hcHBpbmdzXG4gICAqIGNvcnJlc3BvbmRpbmcgdG8gdGhlIGdpdmVuIGxpbmUgYW5kIGVpdGhlciB0aGUgY29sdW1uIHdlIGFyZSBzZWFyY2hpbmcgZm9yXG4gICAqIG9yIHRoZSBuZXh0IGNsb3Nlc3QgY29sdW1uIHRoYXQgaGFzIGFueSBvZmZzZXRzLlxuICAgKlxuICAgKiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3Qgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBmaWxlbmFtZSBvZiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IE9wdGlvbmFsLiB0aGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKlxuICAgKiBhbmQgYW4gYXJyYXkgb2Ygb2JqZWN0cyBpcyByZXR1cm5lZCwgZWFjaCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9hbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBsaW5lID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyk7XG5cbiAgICAgIC8vIFdoZW4gdGhlcmUgaXMgbm8gZXhhY3QgbWF0Y2gsIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9maW5kTWFwcGluZ1xuICAgICAgLy8gcmV0dXJucyB0aGUgaW5kZXggb2YgdGhlIGNsb3Nlc3QgbWFwcGluZyBsZXNzIHRoYW4gdGhlIG5lZWRsZS4gQnlcbiAgICAgIC8vIHNldHRpbmcgbmVlZGxlLm9yaWdpbmFsQ29sdW1uIHRvIDAsIHdlIHRodXMgZmluZCB0aGUgbGFzdCBtYXBwaW5nIGZvclxuICAgICAgLy8gdGhlIGdpdmVuIGxpbmUsIHByb3ZpZGVkIHN1Y2ggYSBtYXBwaW5nIGV4aXN0cy5cbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIHNvdXJjZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnKSxcbiAgICAgICAgb3JpZ2luYWxMaW5lOiBsaW5lLFxuICAgICAgICBvcmlnaW5hbENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nLCAwKVxuICAgICAgfTtcblxuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIG5lZWRsZS5zb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuc291cmNlUm9vdCwgbmVlZGxlLnNvdXJjZSk7XG4gICAgICB9XG4gICAgICBpZiAoIXRoaXMuX3NvdXJjZXMuaGFzKG5lZWRsZS5zb3VyY2UpKSB7XG4gICAgICAgIHJldHVybiBbXTtcbiAgICAgIH1cbiAgICAgIG5lZWRsZS5zb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmluZGV4T2YobmVlZGxlLnNvdXJjZSk7XG5cbiAgICAgIHZhciBtYXBwaW5ncyA9IFtdO1xuXG4gICAgICB2YXIgaW5kZXggPSB0aGlzLl9maW5kTWFwcGluZyhuZWVkbGUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJvcmlnaW5hbExpbmVcIixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwib3JpZ2luYWxDb2x1bW5cIixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQpO1xuICAgICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgICAgdmFyIG1hcHBpbmcgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzW2luZGV4XTtcblxuICAgICAgICBpZiAoYUFyZ3MuY29sdW1uID09PSB1bmRlZmluZWQpIHtcbiAgICAgICAgICB2YXIgb3JpZ2luYWxMaW5lID0gbWFwcGluZy5vcmlnaW5hbExpbmU7XG5cbiAgICAgICAgICAvLyBJdGVyYXRlIHVudGlsIGVpdGhlciB3ZSBydW4gb3V0IG9mIG1hcHBpbmdzLCBvciB3ZSBydW4gaW50b1xuICAgICAgICAgIC8vIGEgbWFwcGluZyBmb3IgYSBkaWZmZXJlbnQgbGluZSB0aGFuIHRoZSBvbmUgd2UgZm91bmQuIFNpbmNlXG4gICAgICAgICAgLy8gbWFwcGluZ3MgYXJlIHNvcnRlZCwgdGhpcyBpcyBndWFyYW50ZWVkIHRvIGZpbmQgYWxsIG1hcHBpbmdzIGZvclxuICAgICAgICAgIC8vIHRoZSBsaW5lIHdlIGZvdW5kLlxuICAgICAgICAgIHdoaWxlIChtYXBwaW5nICYmIG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSBvcmlnaW5hbExpbmUpIHtcbiAgICAgICAgICAgIG1hcHBpbmdzLnB1c2goe1xuICAgICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkTGluZScsIG51bGwpLFxuICAgICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgICAgbGFzdENvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2xhc3RHZW5lcmF0ZWRDb2x1bW4nLCBudWxsKVxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIG1hcHBpbmcgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzWysraW5kZXhdO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICB2YXIgb3JpZ2luYWxDb2x1bW4gPSBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuXG4gICAgICAgICAgLy8gSXRlcmF0ZSB1bnRpbCBlaXRoZXIgd2UgcnVuIG91dCBvZiBtYXBwaW5ncywgb3Igd2UgcnVuIGludG9cbiAgICAgICAgICAvLyBhIG1hcHBpbmcgZm9yIGEgZGlmZmVyZW50IGxpbmUgdGhhbiB0aGUgb25lIHdlIHdlcmUgc2VhcmNoaW5nIGZvci5cbiAgICAgICAgICAvLyBTaW5jZSBtYXBwaW5ncyBhcmUgc29ydGVkLCB0aGlzIGlzIGd1YXJhbnRlZWQgdG8gZmluZCBhbGwgbWFwcGluZ3MgZm9yXG4gICAgICAgICAgLy8gdGhlIGxpbmUgd2UgYXJlIHNlYXJjaGluZyBmb3IuXG4gICAgICAgICAgd2hpbGUgKG1hcHBpbmcgJiZcbiAgICAgICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgPT09IGxpbmUgJiZcbiAgICAgICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbiA9PSBvcmlnaW5hbENvbHVtbikge1xuICAgICAgICAgICAgbWFwcGluZ3MucHVzaCh7XG4gICAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRMaW5lJywgbnVsbCksXG4gICAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgICBsYXN0Q29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbGFzdEdlbmVyYXRlZENvbHVtbicsIG51bGwpXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbKytpbmRleF07XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBtYXBwaW5ncztcbiAgICB9O1xuXG4gIGV4cG9ydHMuU291cmNlTWFwQ29uc3VtZXIgPSBTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogQSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyIGluc3RhbmNlIHJlcHJlc2VudHMgYSBwYXJzZWQgc291cmNlIG1hcCB3aGljaCB3ZSBjYW5cbiAgICogcXVlcnkgZm9yIGluZm9ybWF0aW9uIGFib3V0IHRoZSBvcmlnaW5hbCBmaWxlIHBvc2l0aW9ucyBieSBnaXZpbmcgaXQgYSBmaWxlXG4gICAqIHBvc2l0aW9uIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKlxuICAgKiBUaGUgb25seSBwYXJhbWV0ZXIgaXMgdGhlIHJhdyBzb3VyY2UgbWFwIChlaXRoZXIgYXMgYSBKU09OIHN0cmluZywgb3JcbiAgICogYWxyZWFkeSBwYXJzZWQgdG8gYW4gb2JqZWN0KS4gQWNjb3JkaW5nIHRvIHRoZSBzcGVjLCBzb3VyY2UgbWFwcyBoYXZlIHRoZVxuICAgKiBmb2xsb3dpbmcgYXR0cmlidXRlczpcbiAgICpcbiAgICogICAtIHZlcnNpb246IFdoaWNoIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXAgc3BlYyB0aGlzIG1hcCBpcyBmb2xsb3dpbmcuXG4gICAqICAgLSBzb3VyY2VzOiBBbiBhcnJheSBvZiBVUkxzIHRvIHRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZXMuXG4gICAqICAgLSBuYW1lczogQW4gYXJyYXkgb2YgaWRlbnRpZmllcnMgd2hpY2ggY2FuIGJlIHJlZmVycmVuY2VkIGJ5IGluZGl2aWR1YWwgbWFwcGluZ3MuXG4gICAqICAgLSBzb3VyY2VSb290OiBPcHRpb25hbC4gVGhlIFVSTCByb290IGZyb20gd2hpY2ggYWxsIHNvdXJjZXMgYXJlIHJlbGF0aXZlLlxuICAgKiAgIC0gc291cmNlc0NvbnRlbnQ6IE9wdGlvbmFsLiBBbiBhcnJheSBvZiBjb250ZW50cyBvZiB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGVzLlxuICAgKiAgIC0gbWFwcGluZ3M6IEEgc3RyaW5nIG9mIGJhc2U2NCBWTFFzIHdoaWNoIGNvbnRhaW4gdGhlIGFjdHVhbCBtYXBwaW5ncy5cbiAgICogICAtIGZpbGU6IE9wdGlvbmFsLiBUaGUgZ2VuZXJhdGVkIGZpbGUgdGhpcyBzb3VyY2UgbWFwIGlzIGFzc29jaWF0ZWQgd2l0aC5cbiAgICpcbiAgICogSGVyZSBpcyBhbiBleGFtcGxlIHNvdXJjZSBtYXAsIHRha2VuIGZyb20gdGhlIHNvdXJjZSBtYXAgc3BlY1swXTpcbiAgICpcbiAgICogICAgIHtcbiAgICogICAgICAgdmVyc2lvbiA6IDMsXG4gICAqICAgICAgIGZpbGU6IFwib3V0LmpzXCIsXG4gICAqICAgICAgIHNvdXJjZVJvb3QgOiBcIlwiLFxuICAgKiAgICAgICBzb3VyY2VzOiBbXCJmb28uanNcIiwgXCJiYXIuanNcIl0sXG4gICAqICAgICAgIG5hbWVzOiBbXCJzcmNcIiwgXCJtYXBzXCIsIFwiYXJlXCIsIFwiZnVuXCJdLFxuICAgKiAgICAgICBtYXBwaW5nczogXCJBQSxBQjs7QUJDREU7XCJcbiAgICogICAgIH1cbiAgICpcbiAgICogWzBdOiBodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9kb2N1bWVudC9kLzFVMVJHQWVoUXdSeXBVVG92RjFLUmxwaU9GemUwYi1fMmdjNmZBSDBLWTBrL2VkaXQ/cGxpPTEjXG4gICAqL1xuICBmdW5jdGlvbiBCYXNpY1NvdXJjZU1hcENvbnN1bWVyKGFTb3VyY2VNYXApIHtcbiAgICB2YXIgc291cmNlTWFwID0gYVNvdXJjZU1hcDtcbiAgICBpZiAodHlwZW9mIGFTb3VyY2VNYXAgPT09ICdzdHJpbmcnKSB7XG4gICAgICBzb3VyY2VNYXAgPSBKU09OLnBhcnNlKGFTb3VyY2VNYXAucmVwbGFjZSgvXlxcKVxcXVxcfScvLCAnJykpO1xuICAgIH1cblxuICAgIHZhciB2ZXJzaW9uID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAndmVyc2lvbicpO1xuICAgIHZhciBzb3VyY2VzID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnc291cmNlcycpO1xuICAgIC8vIFNhc3MgMy4zIGxlYXZlcyBvdXQgdGhlICduYW1lcycgYXJyYXksIHNvIHdlIGRldmlhdGUgZnJvbSB0aGUgc3BlYyAod2hpY2hcbiAgICAvLyByZXF1aXJlcyB0aGUgYXJyYXkpIHRvIHBsYXkgbmljZSBoZXJlLlxuICAgIHZhciBuYW1lcyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ25hbWVzJywgW10pO1xuICAgIHZhciBzb3VyY2VSb290ID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnc291cmNlUm9vdCcsIG51bGwpO1xuICAgIHZhciBzb3VyY2VzQ29udGVudCA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NvdXJjZXNDb250ZW50JywgbnVsbCk7XG4gICAgdmFyIG1hcHBpbmdzID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnbWFwcGluZ3MnKTtcbiAgICB2YXIgZmlsZSA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ2ZpbGUnLCBudWxsKTtcblxuICAgIC8vIE9uY2UgYWdhaW4sIFNhc3MgZGV2aWF0ZXMgZnJvbSB0aGUgc3BlYyBhbmQgc3VwcGxpZXMgdGhlIHZlcnNpb24gYXMgYVxuICAgIC8vIHN0cmluZyByYXRoZXIgdGhhbiBhIG51bWJlciwgc28gd2UgdXNlIGxvb3NlIGVxdWFsaXR5IGNoZWNraW5nIGhlcmUuXG4gICAgaWYgKHZlcnNpb24gIT0gdGhpcy5fdmVyc2lvbikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCB2ZXJzaW9uOiAnICsgdmVyc2lvbik7XG4gICAgfVxuXG4gICAgc291cmNlcyA9IHNvdXJjZXNcbiAgICAgIC8vIFNvbWUgc291cmNlIG1hcHMgcHJvZHVjZSByZWxhdGl2ZSBzb3VyY2UgcGF0aHMgbGlrZSBcIi4vZm9vLmpzXCIgaW5zdGVhZCBvZlxuICAgICAgLy8gXCJmb28uanNcIi4gIE5vcm1hbGl6ZSB0aGVzZSBmaXJzdCBzbyB0aGF0IGZ1dHVyZSBjb21wYXJpc29ucyB3aWxsIHN1Y2NlZWQuXG4gICAgICAvLyBTZWUgYnVnemlsLmxhLzEwOTA3NjguXG4gICAgICAubWFwKHV0aWwubm9ybWFsaXplKVxuICAgICAgLy8gQWx3YXlzIGVuc3VyZSB0aGF0IGFic29sdXRlIHNvdXJjZXMgYXJlIGludGVybmFsbHkgc3RvcmVkIHJlbGF0aXZlIHRvXG4gICAgICAvLyB0aGUgc291cmNlIHJvb3QsIGlmIHRoZSBzb3VyY2Ugcm9vdCBpcyBhYnNvbHV0ZS4gTm90IGRvaW5nIHRoaXMgd291bGRcbiAgICAgIC8vIGJlIHBhcnRpY3VsYXJseSBwcm9ibGVtYXRpYyB3aGVuIHRoZSBzb3VyY2Ugcm9vdCBpcyBhIHByZWZpeCBvZiB0aGVcbiAgICAgIC8vIHNvdXJjZSAodmFsaWQsIGJ1dCB3aHk/PykuIFNlZSBnaXRodWIgaXNzdWUgIzE5OSBhbmQgYnVnemlsLmxhLzExODg5ODIuXG4gICAgICAubWFwKGZ1bmN0aW9uIChzb3VyY2UpIHtcbiAgICAgICAgcmV0dXJuIHNvdXJjZVJvb3QgJiYgdXRpbC5pc0Fic29sdXRlKHNvdXJjZVJvb3QpICYmIHV0aWwuaXNBYnNvbHV0ZShzb3VyY2UpXG4gICAgICAgICAgPyB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIHNvdXJjZSlcbiAgICAgICAgICA6IHNvdXJjZTtcbiAgICAgIH0pO1xuXG4gICAgLy8gUGFzcyBgdHJ1ZWAgYmVsb3cgdG8gYWxsb3cgZHVwbGljYXRlIG5hbWVzIGFuZCBzb3VyY2VzLiBXaGlsZSBzb3VyY2UgbWFwc1xuICAgIC8vIGFyZSBpbnRlbmRlZCB0byBiZSBjb21wcmVzc2VkIGFuZCBkZWR1cGxpY2F0ZWQsIHRoZSBUeXBlU2NyaXB0IGNvbXBpbGVyXG4gICAgLy8gc29tZXRpbWVzIGdlbmVyYXRlcyBzb3VyY2UgbWFwcyB3aXRoIGR1cGxpY2F0ZXMgaW4gdGhlbS4gU2VlIEdpdGh1YiBpc3N1ZVxuICAgIC8vICM3MiBhbmQgYnVnemlsLmxhLzg4OTQ5Mi5cbiAgICB0aGlzLl9uYW1lcyA9IEFycmF5U2V0LmZyb21BcnJheShuYW1lcywgdHJ1ZSk7XG4gICAgdGhpcy5fc291cmNlcyA9IEFycmF5U2V0LmZyb21BcnJheShzb3VyY2VzLCB0cnVlKTtcblxuICAgIHRoaXMuc291cmNlUm9vdCA9IHNvdXJjZVJvb3Q7XG4gICAgdGhpcy5zb3VyY2VzQ29udGVudCA9IHNvdXJjZXNDb250ZW50O1xuICAgIHRoaXMuX21hcHBpbmdzID0gbWFwcGluZ3M7XG4gICAgdGhpcy5maWxlID0gZmlsZTtcbiAgfVxuXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUpO1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5jb25zdW1lciA9IFNvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBDcmVhdGUgYSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyIGZyb20gYSBTb3VyY2VNYXBHZW5lcmF0b3IuXG4gICAqXG4gICAqIEBwYXJhbSBTb3VyY2VNYXBHZW5lcmF0b3IgYVNvdXJjZU1hcFxuICAgKiAgICAgICAgVGhlIHNvdXJjZSBtYXAgdGhhdCB3aWxsIGJlIGNvbnN1bWVkLlxuICAgKiBAcmV0dXJucyBCYXNpY1NvdXJjZU1hcENvbnN1bWVyXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLmZyb21Tb3VyY2VNYXAgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2Zyb21Tb3VyY2VNYXAoYVNvdXJjZU1hcCkge1xuICAgICAgdmFyIHNtYyA9IE9iamVjdC5jcmVhdGUoQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUpO1xuXG4gICAgICB2YXIgbmFtZXMgPSBzbWMuX25hbWVzID0gQXJyYXlTZXQuZnJvbUFycmF5KGFTb3VyY2VNYXAuX25hbWVzLnRvQXJyYXkoKSwgdHJ1ZSk7XG4gICAgICB2YXIgc291cmNlcyA9IHNtYy5fc291cmNlcyA9IEFycmF5U2V0LmZyb21BcnJheShhU291cmNlTWFwLl9zb3VyY2VzLnRvQXJyYXkoKSwgdHJ1ZSk7XG4gICAgICBzbWMuc291cmNlUm9vdCA9IGFTb3VyY2VNYXAuX3NvdXJjZVJvb3Q7XG4gICAgICBzbWMuc291cmNlc0NvbnRlbnQgPSBhU291cmNlTWFwLl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50KHNtYy5fc291cmNlcy50b0FycmF5KCksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNtYy5zb3VyY2VSb290KTtcbiAgICAgIHNtYy5maWxlID0gYVNvdXJjZU1hcC5fZmlsZTtcblxuICAgICAgLy8gQmVjYXVzZSB3ZSBhcmUgbW9kaWZ5aW5nIHRoZSBlbnRyaWVzIChieSBjb252ZXJ0aW5nIHN0cmluZyBzb3VyY2VzIGFuZFxuICAgICAgLy8gbmFtZXMgdG8gaW5kaWNlcyBpbnRvIHRoZSBzb3VyY2VzIGFuZCBuYW1lcyBBcnJheVNldHMpLCB3ZSBoYXZlIHRvIG1ha2VcbiAgICAgIC8vIGEgY29weSBvZiB0aGUgZW50cnkgb3IgZWxzZSBiYWQgdGhpbmdzIGhhcHBlbi4gU2hhcmVkIG11dGFibGUgc3RhdGVcbiAgICAgIC8vIHN0cmlrZXMgYWdhaW4hIFNlZSBnaXRodWIgaXNzdWUgIzE5MS5cblxuICAgICAgdmFyIGdlbmVyYXRlZE1hcHBpbmdzID0gYVNvdXJjZU1hcC5fbWFwcGluZ3MudG9BcnJheSgpLnNsaWNlKCk7XG4gICAgICB2YXIgZGVzdEdlbmVyYXRlZE1hcHBpbmdzID0gc21jLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBbXTtcbiAgICAgIHZhciBkZXN0T3JpZ2luYWxNYXBwaW5ncyA9IHNtYy5fX29yaWdpbmFsTWFwcGluZ3MgPSBbXTtcblxuICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbmd0aCA9IGdlbmVyYXRlZE1hcHBpbmdzLmxlbmd0aDsgaSA8IGxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBzcmNNYXBwaW5nID0gZ2VuZXJhdGVkTWFwcGluZ3NbaV07XG4gICAgICAgIHZhciBkZXN0TWFwcGluZyA9IG5ldyBNYXBwaW5nO1xuICAgICAgICBkZXN0TWFwcGluZy5nZW5lcmF0ZWRMaW5lID0gc3JjTWFwcGluZy5nZW5lcmF0ZWRMaW5lO1xuICAgICAgICBkZXN0TWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gPSBzcmNNYXBwaW5nLmdlbmVyYXRlZENvbHVtbjtcblxuICAgICAgICBpZiAoc3JjTWFwcGluZy5zb3VyY2UpIHtcbiAgICAgICAgICBkZXN0TWFwcGluZy5zb3VyY2UgPSBzb3VyY2VzLmluZGV4T2Yoc3JjTWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIGRlc3RNYXBwaW5nLm9yaWdpbmFsTGluZSA9IHNyY01hcHBpbmcub3JpZ2luYWxMaW5lO1xuICAgICAgICAgIGRlc3RNYXBwaW5nLm9yaWdpbmFsQ29sdW1uID0gc3JjTWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgIGlmIChzcmNNYXBwaW5nLm5hbWUpIHtcbiAgICAgICAgICAgIGRlc3RNYXBwaW5nLm5hbWUgPSBuYW1lcy5pbmRleE9mKHNyY01hcHBpbmcubmFtZSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgZGVzdE9yaWdpbmFsTWFwcGluZ3MucHVzaChkZXN0TWFwcGluZyk7XG4gICAgICAgIH1cblxuICAgICAgICBkZXN0R2VuZXJhdGVkTWFwcGluZ3MucHVzaChkZXN0TWFwcGluZyk7XG4gICAgICB9XG5cbiAgICAgIHF1aWNrU29ydChzbWMuX19vcmlnaW5hbE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKTtcblxuICAgICAgcmV0dXJuIHNtYztcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBUaGUgdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcHBpbmcgc3BlYyB0aGF0IHdlIGFyZSBjb25zdW1pbmcuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLyoqXG4gICAqIFRoZSBsaXN0IG9mIG9yaWdpbmFsIHNvdXJjZXMuXG4gICAqL1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdzb3VyY2VzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgcmV0dXJuIHRoaXMuX3NvdXJjZXMudG9BcnJheSgpLm1hcChmdW5jdGlvbiAocykge1xuICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VSb290ICE9IG51bGwgPyB1dGlsLmpvaW4odGhpcy5zb3VyY2VSb290LCBzKSA6IHM7XG4gICAgICB9LCB0aGlzKTtcbiAgICB9XG4gIH0pO1xuXG4gIC8qKlxuICAgKiBQcm92aWRlIHRoZSBKSVQgd2l0aCBhIG5pY2Ugc2hhcGUgLyBoaWRkZW4gY2xhc3MuXG4gICAqL1xuICBmdW5jdGlvbiBNYXBwaW5nKCkge1xuICAgIHRoaXMuZ2VuZXJhdGVkTGluZSA9IDA7XG4gICAgdGhpcy5nZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgIHRoaXMuc291cmNlID0gbnVsbDtcbiAgICB0aGlzLm9yaWdpbmFsTGluZSA9IG51bGw7XG4gICAgdGhpcy5vcmlnaW5hbENvbHVtbiA9IG51bGw7XG4gICAgdGhpcy5uYW1lID0gbnVsbDtcbiAgfVxuXG4gIC8qKlxuICAgKiBQYXJzZSB0aGUgbWFwcGluZ3MgaW4gYSBzdHJpbmcgaW4gdG8gYSBkYXRhIHN0cnVjdHVyZSB3aGljaCB3ZSBjYW4gZWFzaWx5XG4gICAqIHF1ZXJ5ICh0aGUgb3JkZXJlZCBhcnJheXMgaW4gdGhlIGB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAgKiBgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3NgIHByb3BlcnRpZXMpLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3BhcnNlTWFwcGluZ3MgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX3BhcnNlTWFwcGluZ3MoYVN0ciwgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHZhciBnZW5lcmF0ZWRMaW5lID0gMTtcbiAgICAgIHZhciBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbExpbmUgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzU291cmNlID0gMDtcbiAgICAgIHZhciBwcmV2aW91c05hbWUgPSAwO1xuICAgICAgdmFyIGxlbmd0aCA9IGFTdHIubGVuZ3RoO1xuICAgICAgdmFyIGluZGV4ID0gMDtcbiAgICAgIHZhciBjYWNoZWRTZWdtZW50cyA9IHt9O1xuICAgICAgdmFyIHRlbXAgPSB7fTtcbiAgICAgIHZhciBvcmlnaW5hbE1hcHBpbmdzID0gW107XG4gICAgICB2YXIgZ2VuZXJhdGVkTWFwcGluZ3MgPSBbXTtcbiAgICAgIHZhciBtYXBwaW5nLCBzdHIsIHNlZ21lbnQsIGVuZCwgdmFsdWU7XG5cbiAgICAgIHdoaWxlIChpbmRleCA8IGxlbmd0aCkge1xuICAgICAgICBpZiAoYVN0ci5jaGFyQXQoaW5kZXgpID09PSAnOycpIHtcbiAgICAgICAgICBnZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgaW5kZXgrKztcbiAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSBpZiAoYVN0ci5jaGFyQXQoaW5kZXgpID09PSAnLCcpIHtcbiAgICAgICAgICBpbmRleCsrO1xuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIG1hcHBpbmcgPSBuZXcgTWFwcGluZygpO1xuICAgICAgICAgIG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9IGdlbmVyYXRlZExpbmU7XG5cbiAgICAgICAgICAvLyBCZWNhdXNlIGVhY2ggb2Zmc2V0IGlzIGVuY29kZWQgcmVsYXRpdmUgdG8gdGhlIHByZXZpb3VzIG9uZSxcbiAgICAgICAgICAvLyBtYW55IHNlZ21lbnRzIG9mdGVuIGhhdmUgdGhlIHNhbWUgZW5jb2RpbmcuIFdlIGNhbiBleHBsb2l0IHRoaXNcbiAgICAgICAgICAvLyBmYWN0IGJ5IGNhY2hpbmcgdGhlIHBhcnNlZCB2YXJpYWJsZSBsZW5ndGggZmllbGRzIG9mIGVhY2ggc2VnbWVudCxcbiAgICAgICAgICAvLyBhbGxvd2luZyB1cyB0byBhdm9pZCBhIHNlY29uZCBwYXJzZSBpZiB3ZSBlbmNvdW50ZXIgdGhlIHNhbWVcbiAgICAgICAgICAvLyBzZWdtZW50IGFnYWluLlxuICAgICAgICAgIGZvciAoZW5kID0gaW5kZXg7IGVuZCA8IGxlbmd0aDsgZW5kKyspIHtcbiAgICAgICAgICAgIGlmICh0aGlzLl9jaGFySXNNYXBwaW5nU2VwYXJhdG9yKGFTdHIsIGVuZCkpIHtcbiAgICAgICAgICAgICAgYnJlYWs7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHN0ciA9IGFTdHIuc2xpY2UoaW5kZXgsIGVuZCk7XG5cbiAgICAgICAgICBzZWdtZW50ID0gY2FjaGVkU2VnbWVudHNbc3RyXTtcbiAgICAgICAgICBpZiAoc2VnbWVudCkge1xuICAgICAgICAgICAgaW5kZXggKz0gc3RyLmxlbmd0aDtcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgc2VnbWVudCA9IFtdO1xuICAgICAgICAgICAgd2hpbGUgKGluZGV4IDwgZW5kKSB7XG4gICAgICAgICAgICAgIGJhc2U2NFZMUS5kZWNvZGUoYVN0ciwgaW5kZXgsIHRlbXApO1xuICAgICAgICAgICAgICB2YWx1ZSA9IHRlbXAudmFsdWU7XG4gICAgICAgICAgICAgIGluZGV4ID0gdGVtcC5yZXN0O1xuICAgICAgICAgICAgICBzZWdtZW50LnB1c2godmFsdWUpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2VnbWVudC5sZW5ndGggPT09IDIpIHtcbiAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdGb3VuZCBhIHNvdXJjZSwgYnV0IG5vIGxpbmUgYW5kIGNvbHVtbicpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBpZiAoc2VnbWVudC5sZW5ndGggPT09IDMpIHtcbiAgICAgICAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdGb3VuZCBhIHNvdXJjZSBhbmQgbGluZSwgYnV0IG5vIGNvbHVtbicpO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjYWNoZWRTZWdtZW50c1tzdHJdID0gc2VnbWVudDtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICAvLyBHZW5lcmF0ZWQgY29sdW1uLlxuICAgICAgICAgIG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uID0gcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gKyBzZWdtZW50WzBdO1xuICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG5cbiAgICAgICAgICBpZiAoc2VnbWVudC5sZW5ndGggPiAxKSB7XG4gICAgICAgICAgICAvLyBPcmlnaW5hbCBzb3VyY2UuXG4gICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IHByZXZpb3VzU291cmNlICsgc2VnbWVudFsxXTtcbiAgICAgICAgICAgIHByZXZpb3VzU291cmNlICs9IHNlZ21lbnRbMV07XG5cbiAgICAgICAgICAgIC8vIE9yaWdpbmFsIGxpbmUuXG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSA9IHByZXZpb3VzT3JpZ2luYWxMaW5lICsgc2VnbWVudFsyXTtcbiAgICAgICAgICAgIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gbWFwcGluZy5vcmlnaW5hbExpbmU7XG4gICAgICAgICAgICAvLyBMaW5lcyBhcmUgc3RvcmVkIDAtYmFzZWRcbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lICs9IDE7XG5cbiAgICAgICAgICAgIC8vIE9yaWdpbmFsIGNvbHVtbi5cbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPSBwcmV2aW91c09yaWdpbmFsQ29sdW1uICsgc2VnbWVudFszXTtcbiAgICAgICAgICAgIHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gPSBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuXG4gICAgICAgICAgICBpZiAoc2VnbWVudC5sZW5ndGggPiA0KSB7XG4gICAgICAgICAgICAgIC8vIE9yaWdpbmFsIG5hbWUuXG4gICAgICAgICAgICAgIG1hcHBpbmcubmFtZSA9IHByZXZpb3VzTmFtZSArIHNlZ21lbnRbNF07XG4gICAgICAgICAgICAgIHByZXZpb3VzTmFtZSArPSBzZWdtZW50WzRdO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cblxuICAgICAgICAgIGdlbmVyYXRlZE1hcHBpbmdzLnB1c2gobWFwcGluZyk7XG4gICAgICAgICAgaWYgKHR5cGVvZiBtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gJ251bWJlcicpIHtcbiAgICAgICAgICAgIG9yaWdpbmFsTWFwcGluZ3MucHVzaChtYXBwaW5nKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcXVpY2tTb3J0KGdlbmVyYXRlZE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkKTtcbiAgICAgIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IGdlbmVyYXRlZE1hcHBpbmdzO1xuXG4gICAgICBxdWlja1NvcnQob3JpZ2luYWxNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyk7XG4gICAgICB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncyA9IG9yaWdpbmFsTWFwcGluZ3M7XG4gICAgfTtcblxuICAvKipcbiAgICogRmluZCB0aGUgbWFwcGluZyB0aGF0IGJlc3QgbWF0Y2hlcyB0aGUgaHlwb3RoZXRpY2FsIFwibmVlZGxlXCIgbWFwcGluZyB0aGF0XG4gICAqIHdlIGFyZSBzZWFyY2hpbmcgZm9yIGluIHRoZSBnaXZlbiBcImhheXN0YWNrXCIgb2YgbWFwcGluZ3MuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fZmluZE1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2ZpbmRNYXBwaW5nKGFOZWVkbGUsIGFNYXBwaW5ncywgYUxpbmVOYW1lLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFDb2x1bW5OYW1lLCBhQ29tcGFyYXRvciwgYUJpYXMpIHtcbiAgICAgIC8vIFRvIHJldHVybiB0aGUgcG9zaXRpb24gd2UgYXJlIHNlYXJjaGluZyBmb3IsIHdlIG11c3QgZmlyc3QgZmluZCB0aGVcbiAgICAgIC8vIG1hcHBpbmcgZm9yIHRoZSBnaXZlbiBwb3NpdGlvbiBhbmQgdGhlbiByZXR1cm4gdGhlIG9wcG9zaXRlIHBvc2l0aW9uIGl0XG4gICAgICAvLyBwb2ludHMgdG8uIEJlY2F1c2UgdGhlIG1hcHBpbmdzIGFyZSBzb3J0ZWQsIHdlIGNhbiB1c2UgYmluYXJ5IHNlYXJjaCB0b1xuICAgICAgLy8gZmluZCB0aGUgYmVzdCBtYXBwaW5nLlxuXG4gICAgICBpZiAoYU5lZWRsZVthTGluZU5hbWVdIDw9IDApIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignTGluZSBtdXN0IGJlIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAxLCBnb3QgJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgYU5lZWRsZVthTGluZU5hbWVdKTtcbiAgICAgIH1cbiAgICAgIGlmIChhTmVlZGxlW2FDb2x1bW5OYW1lXSA8IDApIHtcbiAgICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcignQ29sdW1uIG11c3QgYmUgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDAsIGdvdCAnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKyBhTmVlZGxlW2FDb2x1bW5OYW1lXSk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBiaW5hcnlTZWFyY2guc2VhcmNoKGFOZWVkbGUsIGFNYXBwaW5ncywgYUNvbXBhcmF0b3IsIGFCaWFzKTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBDb21wdXRlIHRoZSBsYXN0IGNvbHVtbiBmb3IgZWFjaCBnZW5lcmF0ZWQgbWFwcGluZy4gVGhlIGxhc3QgY29sdW1uIGlzXG4gICAqIGluY2x1c2l2ZS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmNvbXB1dGVDb2x1bW5TcGFucyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfY29tcHV0ZUNvbHVtblNwYW5zKCkge1xuICAgICAgZm9yICh2YXIgaW5kZXggPSAwOyBpbmRleCA8IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzLmxlbmd0aDsgKytpbmRleCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzW2luZGV4XTtcblxuICAgICAgICAvLyBNYXBwaW5ncyBkbyBub3QgY29udGFpbiBhIGZpZWxkIGZvciB0aGUgbGFzdCBnZW5lcmF0ZWQgY29sdW1udC4gV2VcbiAgICAgICAgLy8gY2FuIGNvbWUgdXAgd2l0aCBhbiBvcHRpbWlzdGljIGVzdGltYXRlLCBob3dldmVyLCBieSBhc3N1bWluZyB0aGF0XG4gICAgICAgIC8vIG1hcHBpbmdzIGFyZSBjb250aWd1b3VzIChpLmUuIGdpdmVuIHR3byBjb25zZWN1dGl2ZSBtYXBwaW5ncywgdGhlXG4gICAgICAgIC8vIGZpcnN0IG1hcHBpbmcgZW5kcyB3aGVyZSB0aGUgc2Vjb25kIG9uZSBzdGFydHMpLlxuICAgICAgICBpZiAoaW5kZXggKyAxIDwgdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3MubGVuZ3RoKSB7XG4gICAgICAgICAgdmFyIG5leHRNYXBwaW5nID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3NbaW5kZXggKyAxXTtcblxuICAgICAgICAgIGlmIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgPT09IG5leHRNYXBwaW5nLmdlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICAgIG1hcHBpbmcubGFzdEdlbmVyYXRlZENvbHVtbiA9IG5leHRNYXBwaW5nLmdlbmVyYXRlZENvbHVtbiAtIDE7XG4gICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICAvLyBUaGUgbGFzdCBtYXBwaW5nIGZvciBlYWNoIGxpbmUgc3BhbnMgdGhlIGVudGlyZSBsaW5lLlxuICAgICAgICBtYXBwaW5nLmxhc3RHZW5lcmF0ZWRDb2x1bW4gPSBJbmZpbml0eTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UsIGxpbmUsIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBnZW5lcmF0ZWRcbiAgICogc291cmNlJ3MgbGluZSBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0XG4gICAqIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIGJpYXM6IEVpdGhlciAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnIG9yXG4gICAqICAgICAnU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICogICAgIERlZmF1bHRzIHRvICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSwgb3IgbnVsbC5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gbmFtZTogVGhlIG9yaWdpbmFsIGlkZW50aWZpZXIsIG9yIG51bGwuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5vcmlnaW5hbFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9vcmlnaW5hbFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBnZW5lcmF0ZWRMaW5lOiB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKSxcbiAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiB1dGlsLmdldEFyZyhhQXJncywgJ2NvbHVtbicpXG4gICAgICB9O1xuXG4gICAgICB2YXIgaW5kZXggPSB0aGlzLl9maW5kTWFwcGluZyhcbiAgICAgICAgbmVlZGxlLFxuICAgICAgICB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncyxcbiAgICAgICAgXCJnZW5lcmF0ZWRMaW5lXCIsXG4gICAgICAgIFwiZ2VuZXJhdGVkQ29sdW1uXCIsXG4gICAgICAgIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQsXG4gICAgICAgIHV0aWwuZ2V0QXJnKGFBcmdzLCAnYmlhcycsIFNvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EKVxuICAgICAgKTtcblxuICAgICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgICAgdmFyIG1hcHBpbmcgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9PT0gbmVlZGxlLmdlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICB2YXIgc291cmNlID0gdXRpbC5nZXRBcmcobWFwcGluZywgJ3NvdXJjZScsIG51bGwpO1xuICAgICAgICAgIGlmIChzb3VyY2UgIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuYXQoc291cmNlKTtcbiAgICAgICAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgICBzb3VyY2UgPSB1dGlsLmpvaW4odGhpcy5zb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICB2YXIgbmFtZSA9IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICduYW1lJywgbnVsbCk7XG4gICAgICAgICAgaWYgKG5hbWUgIT09IG51bGwpIHtcbiAgICAgICAgICAgIG5hbWUgPSB0aGlzLl9uYW1lcy5hdChuYW1lKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ29yaWdpbmFsTGluZScsIG51bGwpLFxuICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnb3JpZ2luYWxDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgIG5hbWU6IG5hbWVcbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIHNvdXJjZTogbnVsbCxcbiAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICBuYW1lOiBudWxsXG4gICAgICB9O1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybiB0cnVlIGlmIHdlIGhhdmUgdGhlIHNvdXJjZSBjb250ZW50IGZvciBldmVyeSBzb3VyY2UgaW4gdGhlIHNvdXJjZVxuICAgKiBtYXAsIGZhbHNlIG90aGVyd2lzZS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzID1cbiAgICBmdW5jdGlvbiBCYXNpY1NvdXJjZU1hcENvbnN1bWVyX2hhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzKCkge1xuICAgICAgaWYgKCF0aGlzLnNvdXJjZXNDb250ZW50KSB7XG4gICAgICAgIHJldHVybiBmYWxzZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50Lmxlbmd0aCA+PSB0aGlzLl9zb3VyY2VzLnNpemUoKSAmJlxuICAgICAgICAhdGhpcy5zb3VyY2VzQ29udGVudC5zb21lKGZ1bmN0aW9uIChzYykgeyByZXR1cm4gc2MgPT0gbnVsbDsgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgb3JpZ2luYWwgc291cmNlIGNvbnRlbnQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIHRoZSB1cmwgb2YgdGhlXG4gICAqIG9yaWdpbmFsIHNvdXJjZSBmaWxlLiBSZXR1cm5zIG51bGwgaWYgbm8gb3JpZ2luYWwgc291cmNlIGNvbnRlbnQgaXNcbiAgICogYXZhaWxpYmxlLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuc291cmNlQ29udGVudEZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfc291cmNlQ29udGVudEZvcihhU291cmNlLCBudWxsT25NaXNzaW5nKSB7XG4gICAgICBpZiAoIXRoaXMuc291cmNlc0NvbnRlbnQpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBhU291cmNlID0gdXRpbC5yZWxhdGl2ZSh0aGlzLnNvdXJjZVJvb3QsIGFTb3VyY2UpO1xuICAgICAgfVxuXG4gICAgICBpZiAodGhpcy5fc291cmNlcy5oYXMoYVNvdXJjZSkpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnRbdGhpcy5fc291cmNlcy5pbmRleE9mKGFTb3VyY2UpXTtcbiAgICAgIH1cblxuICAgICAgdmFyIHVybDtcbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbFxuICAgICAgICAgICYmICh1cmwgPSB1dGlsLnVybFBhcnNlKHRoaXMuc291cmNlUm9vdCkpKSB7XG4gICAgICAgIC8vIFhYWDogZmlsZTovLyBVUklzIGFuZCBhYnNvbHV0ZSBwYXRocyBsZWFkIHRvIHVuZXhwZWN0ZWQgYmVoYXZpb3IgZm9yXG4gICAgICAgIC8vIG1hbnkgdXNlcnMuIFdlIGNhbiBoZWxwIHRoZW0gb3V0IHdoZW4gdGhleSBleHBlY3QgZmlsZTovLyBVUklzIHRvXG4gICAgICAgIC8vIGJlaGF2ZSBsaWtlIGl0IHdvdWxkIGlmIHRoZXkgd2VyZSBydW5uaW5nIGEgbG9jYWwgSFRUUCBzZXJ2ZXIuIFNlZVxuICAgICAgICAvLyBodHRwczovL2J1Z3ppbGxhLm1vemlsbGEub3JnL3Nob3dfYnVnLmNnaT9pZD04ODU1OTcuXG4gICAgICAgIHZhciBmaWxlVXJpQWJzUGF0aCA9IGFTb3VyY2UucmVwbGFjZSgvXmZpbGU6XFwvXFwvLywgXCJcIik7XG4gICAgICAgIGlmICh1cmwuc2NoZW1lID09IFwiZmlsZVwiXG4gICAgICAgICAgICAmJiB0aGlzLl9zb3VyY2VzLmhhcyhmaWxlVXJpQWJzUGF0aCkpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudFt0aGlzLl9zb3VyY2VzLmluZGV4T2YoZmlsZVVyaUFic1BhdGgpXVxuICAgICAgICB9XG5cbiAgICAgICAgaWYgKCghdXJsLnBhdGggfHwgdXJsLnBhdGggPT0gXCIvXCIpXG4gICAgICAgICAgICAmJiB0aGlzLl9zb3VyY2VzLmhhcyhcIi9cIiArIGFTb3VyY2UpKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnRbdGhpcy5fc291cmNlcy5pbmRleE9mKFwiL1wiICsgYVNvdXJjZSldO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIC8vIFRoaXMgZnVuY3Rpb24gaXMgdXNlZCByZWN1cnNpdmVseSBmcm9tXG4gICAgICAvLyBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLnNvdXJjZUNvbnRlbnRGb3IuIEluIHRoYXQgY2FzZSwgd2VcbiAgICAgIC8vIGRvbid0IHdhbnQgdG8gdGhyb3cgaWYgd2UgY2FuJ3QgZmluZCB0aGUgc291cmNlIC0gd2UganVzdCB3YW50IHRvXG4gICAgICAvLyByZXR1cm4gbnVsbCwgc28gd2UgcHJvdmlkZSBhIGZsYWcgdG8gZXhpdCBncmFjZWZ1bGx5LlxuICAgICAgaWYgKG51bGxPbk1pc3NpbmcpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhU291cmNlICsgJ1wiIGlzIG5vdCBpbiB0aGUgU291cmNlTWFwLicpO1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBvcmlnaW5hbCBzb3VyY2UsXG4gICAqIGxpbmUsIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3Qgd2l0aFxuICAgKiB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBmaWxlbmFtZSBvZiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBiaWFzOiBFaXRoZXIgJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ1NvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5EJy4gU3BlY2lmaWVzIHdoZXRoZXIgdG8gcmV0dXJuIHRoZVxuICAgKiAgICAgY2xvc2VzdCBlbGVtZW50IHRoYXQgaXMgc21hbGxlciB0aGFuIG9yIGdyZWF0ZXIgdGhhbiB0aGUgb25lIHdlIGFyZVxuICAgKiAgICAgc2VhcmNoaW5nIGZvciwgcmVzcGVjdGl2ZWx5LCBpZiB0aGUgZXhhY3QgZWxlbWVudCBjYW5ub3QgYmUgZm91bmQuXG4gICAqICAgICBEZWZhdWx0cyB0byAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmdlbmVyYXRlZFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9nZW5lcmF0ZWRQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgdmFyIHNvdXJjZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJyk7XG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgc291cmNlID0gdXRpbC5yZWxhdGl2ZSh0aGlzLnNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICB9XG4gICAgICBpZiAoIXRoaXMuX3NvdXJjZXMuaGFzKHNvdXJjZSkpIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgICBsYXN0Q29sdW1uOiBudWxsXG4gICAgICAgIH07XG4gICAgICB9XG4gICAgICBzb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmluZGV4T2Yoc291cmNlKTtcblxuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgIG9yaWdpbmFsTGluZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyksXG4gICAgICAgIG9yaWdpbmFsQ29sdW1uOiB1dGlsLmdldEFyZyhhQXJncywgJ2NvbHVtbicpXG4gICAgICB9O1xuXG4gICAgICB2YXIgaW5kZXggPSB0aGlzLl9maW5kTWFwcGluZyhcbiAgICAgICAgbmVlZGxlLFxuICAgICAgICB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzLFxuICAgICAgICBcIm9yaWdpbmFsTGluZVwiLFxuICAgICAgICBcIm9yaWdpbmFsQ29sdW1uXCIsXG4gICAgICAgIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMsXG4gICAgICAgIHV0aWwuZ2V0QXJnKGFBcmdzLCAnYmlhcycsIFNvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EKVxuICAgICAgKTtcblxuICAgICAgaWYgKGluZGV4ID49IDApIHtcbiAgICAgICAgdmFyIG1hcHBpbmcgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzW2luZGV4XTtcblxuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgPT09IG5lZWRsZS5zb3VyY2UpIHtcbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgbGFzdENvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2xhc3RHZW5lcmF0ZWRDb2x1bW4nLCBudWxsKVxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICBsYXN0Q29sdW1uOiBudWxsXG4gICAgICB9O1xuICAgIH07XG5cbiAgZXhwb3J0cy5CYXNpY1NvdXJjZU1hcENvbnN1bWVyID0gQmFzaWNTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogQW4gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyIGluc3RhbmNlIHJlcHJlc2VudHMgYSBwYXJzZWQgc291cmNlIG1hcCB3aGljaFxuICAgKiB3ZSBjYW4gcXVlcnkgZm9yIGluZm9ybWF0aW9uLiBJdCBkaWZmZXJzIGZyb20gQmFzaWNTb3VyY2VNYXBDb25zdW1lciBpblxuICAgKiB0aGF0IGl0IHRha2VzIFwiaW5kZXhlZFwiIHNvdXJjZSBtYXBzIChpLmUuIG9uZXMgd2l0aCBhIFwic2VjdGlvbnNcIiBmaWVsZCkgYXNcbiAgICogaW5wdXQuXG4gICAqXG4gICAqIFRoZSBvbmx5IHBhcmFtZXRlciBpcyBhIHJhdyBzb3VyY2UgbWFwIChlaXRoZXIgYXMgYSBKU09OIHN0cmluZywgb3IgYWxyZWFkeVxuICAgKiBwYXJzZWQgdG8gYW4gb2JqZWN0KS4gQWNjb3JkaW5nIHRvIHRoZSBzcGVjIGZvciBpbmRleGVkIHNvdXJjZSBtYXBzLCB0aGV5XG4gICAqIGhhdmUgdGhlIGZvbGxvd2luZyBhdHRyaWJ1dGVzOlxuICAgKlxuICAgKiAgIC0gdmVyc2lvbjogV2hpY2ggdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcCBzcGVjIHRoaXMgbWFwIGlzIGZvbGxvd2luZy5cbiAgICogICAtIGZpbGU6IE9wdGlvbmFsLiBUaGUgZ2VuZXJhdGVkIGZpbGUgdGhpcyBzb3VyY2UgbWFwIGlzIGFzc29jaWF0ZWQgd2l0aC5cbiAgICogICAtIHNlY3Rpb25zOiBBIGxpc3Qgb2Ygc2VjdGlvbiBkZWZpbml0aW9ucy5cbiAgICpcbiAgICogRWFjaCB2YWx1ZSB1bmRlciB0aGUgXCJzZWN0aW9uc1wiIGZpZWxkIGhhcyB0d28gZmllbGRzOlxuICAgKiAgIC0gb2Zmc2V0OiBUaGUgb2Zmc2V0IGludG8gdGhlIG9yaWdpbmFsIHNwZWNpZmllZCBhdCB3aGljaCB0aGlzIHNlY3Rpb25cbiAgICogICAgICAgYmVnaW5zIHRvIGFwcGx5LCBkZWZpbmVkIGFzIGFuIG9iamVjdCB3aXRoIGEgXCJsaW5lXCIgYW5kIFwiY29sdW1uXCJcbiAgICogICAgICAgZmllbGQuXG4gICAqICAgLSBtYXA6IEEgc291cmNlIG1hcCBkZWZpbml0aW9uLiBUaGlzIHNvdXJjZSBtYXAgY291bGQgYWxzbyBiZSBpbmRleGVkLFxuICAgKiAgICAgICBidXQgZG9lc24ndCBoYXZlIHRvIGJlLlxuICAgKlxuICAgKiBJbnN0ZWFkIG9mIHRoZSBcIm1hcFwiIGZpZWxkLCBpdCdzIGFsc28gcG9zc2libGUgdG8gaGF2ZSBhIFwidXJsXCIgZmllbGRcbiAgICogc3BlY2lmeWluZyBhIFVSTCB0byByZXRyaWV2ZSBhIHNvdXJjZSBtYXAgZnJvbSwgYnV0IHRoYXQncyBjdXJyZW50bHlcbiAgICogdW5zdXBwb3J0ZWQuXG4gICAqXG4gICAqIEhlcmUncyBhbiBleGFtcGxlIHNvdXJjZSBtYXAsIHRha2VuIGZyb20gdGhlIHNvdXJjZSBtYXAgc3BlY1swXSwgYnV0XG4gICAqIG1vZGlmaWVkIHRvIG9taXQgYSBzZWN0aW9uIHdoaWNoIHVzZXMgdGhlIFwidXJsXCIgZmllbGQuXG4gICAqXG4gICAqICB7XG4gICAqICAgIHZlcnNpb24gOiAzLFxuICAgKiAgICBmaWxlOiBcImFwcC5qc1wiLFxuICAgKiAgICBzZWN0aW9uczogW3tcbiAgICogICAgICBvZmZzZXQ6IHtsaW5lOjEwMCwgY29sdW1uOjEwfSxcbiAgICogICAgICBtYXA6IHtcbiAgICogICAgICAgIHZlcnNpb24gOiAzLFxuICAgKiAgICAgICAgZmlsZTogXCJzZWN0aW9uLmpzXCIsXG4gICAqICAgICAgICBzb3VyY2VzOiBbXCJmb28uanNcIiwgXCJiYXIuanNcIl0sXG4gICAqICAgICAgICBuYW1lczogW1wic3JjXCIsIFwibWFwc1wiLCBcImFyZVwiLCBcImZ1blwiXSxcbiAgICogICAgICAgIG1hcHBpbmdzOiBcIkFBQUEsRTs7QUJDREU7XCJcbiAgICogICAgICB9XG4gICAqICAgIH1dLFxuICAgKiAgfVxuICAgKlxuICAgKiBbMF06IGh0dHBzOi8vZG9jcy5nb29nbGUuY29tL2RvY3VtZW50L2QvMVUxUkdBZWhRd1J5cFVUb3ZGMUtSbHBpT0Z6ZTBiLV8yZ2M2ZkFIMEtZMGsvZWRpdCNoZWFkaW5nPWguNTM1ZXMzeGVwcmd0XG4gICAqL1xuICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIoYVNvdXJjZU1hcCkge1xuICAgIHZhciBzb3VyY2VNYXAgPSBhU291cmNlTWFwO1xuICAgIGlmICh0eXBlb2YgYVNvdXJjZU1hcCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHNvdXJjZU1hcCA9IEpTT04ucGFyc2UoYVNvdXJjZU1hcC5yZXBsYWNlKC9eXFwpXFxdXFx9Jy8sICcnKSk7XG4gICAgfVxuXG4gICAgdmFyIHZlcnNpb24gPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICd2ZXJzaW9uJyk7XG4gICAgdmFyIHNlY3Rpb25zID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnc2VjdGlvbnMnKTtcblxuICAgIGlmICh2ZXJzaW9uICE9IHRoaXMuX3ZlcnNpb24pIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVW5zdXBwb3J0ZWQgdmVyc2lvbjogJyArIHZlcnNpb24pO1xuICAgIH1cblxuICAgIHRoaXMuX3NvdXJjZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICB0aGlzLl9uYW1lcyA9IG5ldyBBcnJheVNldCgpO1xuXG4gICAgdmFyIGxhc3RPZmZzZXQgPSB7XG4gICAgICBsaW5lOiAtMSxcbiAgICAgIGNvbHVtbjogMFxuICAgIH07XG4gICAgdGhpcy5fc2VjdGlvbnMgPSBzZWN0aW9ucy5tYXAoZnVuY3Rpb24gKHMpIHtcbiAgICAgIGlmIChzLnVybCkge1xuICAgICAgICAvLyBUaGUgdXJsIGZpZWxkIHdpbGwgcmVxdWlyZSBzdXBwb3J0IGZvciBhc3luY2hyb25pY2l0eS5cbiAgICAgICAgLy8gU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvaXNzdWVzLzE2XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignU3VwcG9ydCBmb3IgdXJsIGZpZWxkIGluIHNlY3Rpb25zIG5vdCBpbXBsZW1lbnRlZC4nKTtcbiAgICAgIH1cbiAgICAgIHZhciBvZmZzZXQgPSB1dGlsLmdldEFyZyhzLCAnb2Zmc2V0Jyk7XG4gICAgICB2YXIgb2Zmc2V0TGluZSA9IHV0aWwuZ2V0QXJnKG9mZnNldCwgJ2xpbmUnKTtcbiAgICAgIHZhciBvZmZzZXRDb2x1bW4gPSB1dGlsLmdldEFyZyhvZmZzZXQsICdjb2x1bW4nKTtcblxuICAgICAgaWYgKG9mZnNldExpbmUgPCBsYXN0T2Zmc2V0LmxpbmUgfHxcbiAgICAgICAgICAob2Zmc2V0TGluZSA9PT0gbGFzdE9mZnNldC5saW5lICYmIG9mZnNldENvbHVtbiA8IGxhc3RPZmZzZXQuY29sdW1uKSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1NlY3Rpb24gb2Zmc2V0cyBtdXN0IGJlIG9yZGVyZWQgYW5kIG5vbi1vdmVybGFwcGluZy4nKTtcbiAgICAgIH1cbiAgICAgIGxhc3RPZmZzZXQgPSBvZmZzZXQ7XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGdlbmVyYXRlZE9mZnNldDoge1xuICAgICAgICAgIC8vIFRoZSBvZmZzZXQgZmllbGRzIGFyZSAwLWJhc2VkLCBidXQgd2UgdXNlIDEtYmFzZWQgaW5kaWNlcyB3aGVuXG4gICAgICAgICAgLy8gZW5jb2RpbmcvZGVjb2RpbmcgZnJvbSBWTFEuXG4gICAgICAgICAgZ2VuZXJhdGVkTGluZTogb2Zmc2V0TGluZSArIDEsXG4gICAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBvZmZzZXRDb2x1bW4gKyAxXG4gICAgICAgIH0sXG4gICAgICAgIGNvbnN1bWVyOiBuZXcgU291cmNlTWFwQ29uc3VtZXIodXRpbC5nZXRBcmcocywgJ21hcCcpKVxuICAgICAgfVxuICAgIH0pO1xuICB9XG5cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlKTtcbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5jb25zdHJ1Y3RvciA9IFNvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBUaGUgdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcHBpbmcgc3BlYyB0aGF0IHdlIGFyZSBjb25zdW1pbmcuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvKipcbiAgICogVGhlIGxpc3Qgb2Ygb3JpZ2luYWwgc291cmNlcy5cbiAgICovXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnc291cmNlcycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIHZhciBzb3VyY2VzID0gW107XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMuX3NlY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgdGhpcy5fc2VjdGlvbnNbaV0uY29uc3VtZXIuc291cmNlcy5sZW5ndGg7IGorKykge1xuICAgICAgICAgIHNvdXJjZXMucHVzaCh0aGlzLl9zZWN0aW9uc1tpXS5jb25zdW1lci5zb3VyY2VzW2pdKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgcmV0dXJuIHNvdXJjZXM7XG4gICAgfVxuICB9KTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgb3JpZ2luYWwgc291cmNlLCBsaW5lLCBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgZ2VuZXJhdGVkXG4gICAqIHNvdXJjZSdzIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdFxuICAgKiB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSwgb3IgbnVsbC5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gbmFtZTogVGhlIG9yaWdpbmFsIGlkZW50aWZpZXIsIG9yIG51bGwuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLm9yaWdpbmFsUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9vcmlnaW5hbFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBnZW5lcmF0ZWRMaW5lOiB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKSxcbiAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiB1dGlsLmdldEFyZyhhQXJncywgJ2NvbHVtbicpXG4gICAgICB9O1xuXG4gICAgICAvLyBGaW5kIHRoZSBzZWN0aW9uIGNvbnRhaW5pbmcgdGhlIGdlbmVyYXRlZCBwb3NpdGlvbiB3ZSdyZSB0cnlpbmcgdG8gbWFwXG4gICAgICAvLyB0byBhbiBvcmlnaW5hbCBwb3NpdGlvbi5cbiAgICAgIHZhciBzZWN0aW9uSW5kZXggPSBiaW5hcnlTZWFyY2guc2VhcmNoKG5lZWRsZSwgdGhpcy5fc2VjdGlvbnMsXG4gICAgICAgIGZ1bmN0aW9uKG5lZWRsZSwgc2VjdGlvbikge1xuICAgICAgICAgIHZhciBjbXAgPSBuZWVkbGUuZ2VuZXJhdGVkTGluZSAtIHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmU7XG4gICAgICAgICAgaWYgKGNtcCkge1xuICAgICAgICAgICAgcmV0dXJuIGNtcDtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICByZXR1cm4gKG5lZWRsZS5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgICAgICAgICAgc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgfSk7XG4gICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW3NlY3Rpb25JbmRleF07XG5cbiAgICAgIGlmICghc2VjdGlvbikge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHNvdXJjZTogbnVsbCxcbiAgICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgICBuYW1lOiBudWxsXG4gICAgICAgIH07XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBzZWN0aW9uLmNvbnN1bWVyLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgICBsaW5lOiBuZWVkbGUuZ2VuZXJhdGVkTGluZSAtXG4gICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgLSAxKSxcbiAgICAgICAgY29sdW1uOiBuZWVkbGUuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSA9PT0gbmVlZGxlLmdlbmVyYXRlZExpbmVcbiAgICAgICAgICAgPyBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4gLSAxXG4gICAgICAgICAgIDogMCksXG4gICAgICAgIGJpYXM6IGFBcmdzLmJpYXNcbiAgICAgIH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybiB0cnVlIGlmIHdlIGhhdmUgdGhlIHNvdXJjZSBjb250ZW50IGZvciBldmVyeSBzb3VyY2UgaW4gdGhlIHNvdXJjZVxuICAgKiBtYXAsIGZhbHNlIG90aGVyd2lzZS5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9oYXNDb250ZW50c09mQWxsU291cmNlcygpIHtcbiAgICAgIHJldHVybiB0aGlzLl9zZWN0aW9ucy5ldmVyeShmdW5jdGlvbiAocykge1xuICAgICAgICByZXR1cm4gcy5jb25zdW1lci5oYXNDb250ZW50c09mQWxsU291cmNlcygpO1xuICAgICAgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgb3JpZ2luYWwgc291cmNlIGNvbnRlbnQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIHRoZSB1cmwgb2YgdGhlXG4gICAqIG9yaWdpbmFsIHNvdXJjZSBmaWxlLiBSZXR1cm5zIG51bGwgaWYgbm8gb3JpZ2luYWwgc291cmNlIGNvbnRlbnQgaXNcbiAgICogYXZhaWxhYmxlLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5zb3VyY2VDb250ZW50Rm9yID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfc291cmNlQ29udGVudEZvcihhU291cmNlLCBudWxsT25NaXNzaW5nKSB7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMuX3NlY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbaV07XG5cbiAgICAgICAgdmFyIGNvbnRlbnQgPSBzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3IoYVNvdXJjZSwgdHJ1ZSk7XG4gICAgICAgIGlmIChjb250ZW50KSB7XG4gICAgICAgICAgcmV0dXJuIGNvbnRlbnQ7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIGlmIChudWxsT25NaXNzaW5nKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYVNvdXJjZSArICdcIiBpcyBub3QgaW4gdGhlIFNvdXJjZU1hcC4nKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgb3JpZ2luYWwgc291cmNlLFxuICAgKiBsaW5lLCBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0IHdpdGhcbiAgICogdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuZ2VuZXJhdGVkUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9nZW5lcmF0ZWRQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW2ldO1xuXG4gICAgICAgIC8vIE9ubHkgY29uc2lkZXIgdGhpcyBzZWN0aW9uIGlmIHRoZSByZXF1ZXN0ZWQgc291cmNlIGlzIGluIHRoZSBsaXN0IG9mXG4gICAgICAgIC8vIHNvdXJjZXMgb2YgdGhlIGNvbnN1bWVyLlxuICAgICAgICBpZiAoc2VjdGlvbi5jb25zdW1lci5zb3VyY2VzLmluZGV4T2YodXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnKSkgPT09IC0xKSB7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdmFyIGdlbmVyYXRlZFBvc2l0aW9uID0gc2VjdGlvbi5jb25zdW1lci5nZW5lcmF0ZWRQb3NpdGlvbkZvcihhQXJncyk7XG4gICAgICAgIGlmIChnZW5lcmF0ZWRQb3NpdGlvbikge1xuICAgICAgICAgIHZhciByZXQgPSB7XG4gICAgICAgICAgICBsaW5lOiBnZW5lcmF0ZWRQb3NpdGlvbi5saW5lICtcbiAgICAgICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgLSAxKSxcbiAgICAgICAgICAgIGNvbHVtbjogZ2VuZXJhdGVkUG9zaXRpb24uY29sdW1uICtcbiAgICAgICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgPT09IGdlbmVyYXRlZFBvc2l0aW9uLmxpbmVcbiAgICAgICAgICAgICAgID8gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uIC0gMVxuICAgICAgICAgICAgICAgOiAwKVxuICAgICAgICAgIH07XG4gICAgICAgICAgcmV0dXJuIHJldDtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICBjb2x1bW46IG51bGxcbiAgICAgIH07XG4gICAgfTtcblxuICAvKipcbiAgICogUGFyc2UgdGhlIG1hcHBpbmdzIGluIGEgc3RyaW5nIGluIHRvIGEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggd2UgY2FuIGVhc2lseVxuICAgKiBxdWVyeSAodGhlIG9yZGVyZWQgYXJyYXlzIGluIHRoZSBgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgICogYHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzYCBwcm9wZXJ0aWVzKS5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3BhcnNlTWFwcGluZ3MgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9wYXJzZU1hcHBpbmdzKGFTdHIsIGFTb3VyY2VSb290KSB7XG4gICAgICB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBbXTtcbiAgICAgIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzID0gW107XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMuX3NlY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbaV07XG4gICAgICAgIHZhciBzZWN0aW9uTWFwcGluZ3MgPSBzZWN0aW9uLmNvbnN1bWVyLl9nZW5lcmF0ZWRNYXBwaW5ncztcbiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCBzZWN0aW9uTWFwcGluZ3MubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICB2YXIgbWFwcGluZyA9IHNlY3Rpb25NYXBwaW5nc1tpXTtcblxuICAgICAgICAgIHZhciBzb3VyY2UgPSBzZWN0aW9uLmNvbnN1bWVyLl9zb3VyY2VzLmF0KG1hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICBpZiAoc2VjdGlvbi5jb25zdW1lci5zb3VyY2VSb290ICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2UgPSB1dGlsLmpvaW4oc2VjdGlvbi5jb25zdW1lci5zb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLl9zb3VyY2VzLmFkZChzb3VyY2UpO1xuICAgICAgICAgIHNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihzb3VyY2UpO1xuXG4gICAgICAgICAgdmFyIG5hbWUgPSBzZWN0aW9uLmNvbnN1bWVyLl9uYW1lcy5hdChtYXBwaW5nLm5hbWUpO1xuICAgICAgICAgIHRoaXMuX25hbWVzLmFkZChuYW1lKTtcbiAgICAgICAgICBuYW1lID0gdGhpcy5fbmFtZXMuaW5kZXhPZihuYW1lKTtcblxuICAgICAgICAgIC8vIFRoZSBtYXBwaW5ncyBjb21pbmcgZnJvbSB0aGUgY29uc3VtZXIgZm9yIHRoZSBzZWN0aW9uIGhhdmVcbiAgICAgICAgICAvLyBnZW5lcmF0ZWQgcG9zaXRpb25zIHJlbGF0aXZlIHRvIHRoZSBzdGFydCBvZiB0aGUgc2VjdGlvbiwgc28gd2VcbiAgICAgICAgICAvLyBuZWVkIHRvIG9mZnNldCB0aGVtIHRvIGJlIHJlbGF0aXZlIHRvIHRoZSBzdGFydCBvZiB0aGUgY29uY2F0ZW5hdGVkXG4gICAgICAgICAgLy8gZ2VuZXJhdGVkIGZpbGUuXG4gICAgICAgICAgdmFyIGFkanVzdGVkTWFwcGluZyA9IHtcbiAgICAgICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICAgICAgZ2VuZXJhdGVkTGluZTogbWFwcGluZy5nZW5lcmF0ZWRMaW5lICtcbiAgICAgICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgLSAxKSxcbiAgICAgICAgICAgIGdlbmVyYXRlZENvbHVtbjogbWFwcGluZy5jb2x1bW4gK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSA9PT0gbWFwcGluZy5nZW5lcmF0ZWRMaW5lKVxuICAgICAgICAgICAgICA/IHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbiAtIDFcbiAgICAgICAgICAgICAgOiAwLFxuICAgICAgICAgICAgb3JpZ2luYWxMaW5lOiBtYXBwaW5nLm9yaWdpbmFsTGluZSxcbiAgICAgICAgICAgIG9yaWdpbmFsQ29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uLFxuICAgICAgICAgICAgbmFtZTogbmFtZVxuICAgICAgICAgIH07XG5cbiAgICAgICAgICB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MucHVzaChhZGp1c3RlZE1hcHBpbmcpO1xuICAgICAgICAgIGlmICh0eXBlb2YgYWRqdXN0ZWRNYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gJ251bWJlcicpIHtcbiAgICAgICAgICAgIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzLnB1c2goYWRqdXN0ZWRNYXBwaW5nKTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcXVpY2tTb3J0KHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCk7XG4gICAgICBxdWlja1NvcnQodGhpcy5fX29yaWdpbmFsTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMpO1xuICAgIH07XG5cbiAgZXhwb3J0cy5JbmRleGVkU291cmNlTWFwQ29uc3VtZXIgPSBJbmRleGVkU291cmNlTWFwQ29uc3VtZXI7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3NvdXJjZS1tYXAtY29uc3VtZXIuanNcbiAqKiBtb2R1bGUgaWQgPSAzXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIGV4cG9ydHMuR1JFQVRFU1RfTE9XRVJfQk9VTkQgPSAxO1xuICBleHBvcnRzLkxFQVNUX1VQUEVSX0JPVU5EID0gMjtcblxuICAvKipcbiAgICogUmVjdXJzaXZlIGltcGxlbWVudGF0aW9uIG9mIGJpbmFyeSBzZWFyY2guXG4gICAqXG4gICAqIEBwYXJhbSBhTG93IEluZGljZXMgaGVyZSBhbmQgbG93ZXIgZG8gbm90IGNvbnRhaW4gdGhlIG5lZWRsZS5cbiAgICogQHBhcmFtIGFIaWdoIEluZGljZXMgaGVyZSBhbmQgaGlnaGVyIGRvIG5vdCBjb250YWluIHRoZSBuZWVkbGUuXG4gICAqIEBwYXJhbSBhTmVlZGxlIFRoZSBlbGVtZW50IGJlaW5nIHNlYXJjaGVkIGZvci5cbiAgICogQHBhcmFtIGFIYXlzdGFjayBUaGUgbm9uLWVtcHR5IGFycmF5IGJlaW5nIHNlYXJjaGVkLlxuICAgKiBAcGFyYW0gYUNvbXBhcmUgRnVuY3Rpb24gd2hpY2ggdGFrZXMgdHdvIGVsZW1lbnRzIGFuZCByZXR1cm5zIC0xLCAwLCBvciAxLlxuICAgKiBAcGFyYW0gYUJpYXMgRWl0aGVyICdiaW5hcnlTZWFyY2guR1JFQVRFU1RfTE9XRVJfQk9VTkQnIG9yXG4gICAqICAgICAnYmluYXJ5U2VhcmNoLkxFQVNUX1VQUEVSX0JPVU5EJy4gU3BlY2lmaWVzIHdoZXRoZXIgdG8gcmV0dXJuIHRoZVxuICAgKiAgICAgY2xvc2VzdCBlbGVtZW50IHRoYXQgaXMgc21hbGxlciB0aGFuIG9yIGdyZWF0ZXIgdGhhbiB0aGUgb25lIHdlIGFyZVxuICAgKiAgICAgc2VhcmNoaW5nIGZvciwgcmVzcGVjdGl2ZWx5LCBpZiB0aGUgZXhhY3QgZWxlbWVudCBjYW5ub3QgYmUgZm91bmQuXG4gICAqL1xuICBmdW5jdGlvbiByZWN1cnNpdmVTZWFyY2goYUxvdywgYUhpZ2gsIGFOZWVkbGUsIGFIYXlzdGFjaywgYUNvbXBhcmUsIGFCaWFzKSB7XG4gICAgLy8gVGhpcyBmdW5jdGlvbiB0ZXJtaW5hdGVzIHdoZW4gb25lIG9mIHRoZSBmb2xsb3dpbmcgaXMgdHJ1ZTpcbiAgICAvL1xuICAgIC8vICAgMS4gV2UgZmluZCB0aGUgZXhhY3QgZWxlbWVudCB3ZSBhcmUgbG9va2luZyBmb3IuXG4gICAgLy9cbiAgICAvLyAgIDIuIFdlIGRpZCBub3QgZmluZCB0aGUgZXhhY3QgZWxlbWVudCwgYnV0IHdlIGNhbiByZXR1cm4gdGhlIGluZGV4IG9mXG4gICAgLy8gICAgICB0aGUgbmV4dC1jbG9zZXN0IGVsZW1lbnQuXG4gICAgLy9cbiAgICAvLyAgIDMuIFdlIGRpZCBub3QgZmluZCB0aGUgZXhhY3QgZWxlbWVudCwgYW5kIHRoZXJlIGlzIG5vIG5leHQtY2xvc2VzdFxuICAgIC8vICAgICAgZWxlbWVudCB0aGFuIHRoZSBvbmUgd2UgYXJlIHNlYXJjaGluZyBmb3IsIHNvIHdlIHJldHVybiAtMS5cbiAgICB2YXIgbWlkID0gTWF0aC5mbG9vcigoYUhpZ2ggLSBhTG93KSAvIDIpICsgYUxvdztcbiAgICB2YXIgY21wID0gYUNvbXBhcmUoYU5lZWRsZSwgYUhheXN0YWNrW21pZF0sIHRydWUpO1xuICAgIGlmIChjbXAgPT09IDApIHtcbiAgICAgIC8vIEZvdW5kIHRoZSBlbGVtZW50IHdlIGFyZSBsb29raW5nIGZvci5cbiAgICAgIHJldHVybiBtaWQ7XG4gICAgfVxuICAgIGVsc2UgaWYgKGNtcCA+IDApIHtcbiAgICAgIC8vIE91ciBuZWVkbGUgaXMgZ3JlYXRlciB0aGFuIGFIYXlzdGFja1ttaWRdLlxuICAgICAgaWYgKGFIaWdoIC0gbWlkID4gMSkge1xuICAgICAgICAvLyBUaGUgZWxlbWVudCBpcyBpbiB0aGUgdXBwZXIgaGFsZi5cbiAgICAgICAgcmV0dXJuIHJlY3Vyc2l2ZVNlYXJjaChtaWQsIGFIaWdoLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcyk7XG4gICAgICB9XG5cbiAgICAgIC8vIFRoZSBleGFjdCBuZWVkbGUgZWxlbWVudCB3YXMgbm90IGZvdW5kIGluIHRoaXMgaGF5c3RhY2suIERldGVybWluZSBpZlxuICAgICAgLy8gd2UgYXJlIGluIHRlcm1pbmF0aW9uIGNhc2UgKDMpIG9yICgyKSBhbmQgcmV0dXJuIHRoZSBhcHByb3ByaWF0ZSB0aGluZy5cbiAgICAgIGlmIChhQmlhcyA9PSBleHBvcnRzLkxFQVNUX1VQUEVSX0JPVU5EKSB7XG4gICAgICAgIHJldHVybiBhSGlnaCA8IGFIYXlzdGFjay5sZW5ndGggPyBhSGlnaCA6IC0xO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIG1pZDtcbiAgICAgIH1cbiAgICB9XG4gICAgZWxzZSB7XG4gICAgICAvLyBPdXIgbmVlZGxlIGlzIGxlc3MgdGhhbiBhSGF5c3RhY2tbbWlkXS5cbiAgICAgIGlmIChtaWQgLSBhTG93ID4gMSkge1xuICAgICAgICAvLyBUaGUgZWxlbWVudCBpcyBpbiB0aGUgbG93ZXIgaGFsZi5cbiAgICAgICAgcmV0dXJuIHJlY3Vyc2l2ZVNlYXJjaChhTG93LCBtaWQsIGFOZWVkbGUsIGFIYXlzdGFjaywgYUNvbXBhcmUsIGFCaWFzKTtcbiAgICAgIH1cblxuICAgICAgLy8gd2UgYXJlIGluIHRlcm1pbmF0aW9uIGNhc2UgKDMpIG9yICgyKSBhbmQgcmV0dXJuIHRoZSBhcHByb3ByaWF0ZSB0aGluZy5cbiAgICAgIGlmIChhQmlhcyA9PSBleHBvcnRzLkxFQVNUX1VQUEVSX0JPVU5EKSB7XG4gICAgICAgIHJldHVybiBtaWQ7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gYUxvdyA8IDAgPyAtMSA6IGFMb3c7XG4gICAgICB9XG4gICAgfVxuICB9XG5cbiAgLyoqXG4gICAqIFRoaXMgaXMgYW4gaW1wbGVtZW50YXRpb24gb2YgYmluYXJ5IHNlYXJjaCB3aGljaCB3aWxsIGFsd2F5cyB0cnkgYW5kIHJldHVyblxuICAgKiB0aGUgaW5kZXggb2YgdGhlIGNsb3Nlc3QgZWxlbWVudCBpZiB0aGVyZSBpcyBubyBleGFjdCBoaXQuIFRoaXMgaXMgYmVjYXVzZVxuICAgKiBtYXBwaW5ncyBiZXR3ZWVuIG9yaWdpbmFsIGFuZCBnZW5lcmF0ZWQgbGluZS9jb2wgcGFpcnMgYXJlIHNpbmdsZSBwb2ludHMsXG4gICAqIGFuZCB0aGVyZSBpcyBhbiBpbXBsaWNpdCByZWdpb24gYmV0d2VlbiBlYWNoIG9mIHRoZW0sIHNvIGEgbWlzcyBqdXN0IG1lYW5zXG4gICAqIHRoYXQgeW91IGFyZW4ndCBvbiB0aGUgdmVyeSBzdGFydCBvZiBhIHJlZ2lvbi5cbiAgICpcbiAgICogQHBhcmFtIGFOZWVkbGUgVGhlIGVsZW1lbnQgeW91IGFyZSBsb29raW5nIGZvci5cbiAgICogQHBhcmFtIGFIYXlzdGFjayBUaGUgYXJyYXkgdGhhdCBpcyBiZWluZyBzZWFyY2hlZC5cbiAgICogQHBhcmFtIGFDb21wYXJlIEEgZnVuY3Rpb24gd2hpY2ggdGFrZXMgdGhlIG5lZWRsZSBhbmQgYW4gZWxlbWVudCBpbiB0aGVcbiAgICogICAgIGFycmF5IGFuZCByZXR1cm5zIC0xLCAwLCBvciAxIGRlcGVuZGluZyBvbiB3aGV0aGVyIHRoZSBuZWVkbGUgaXMgbGVzc1xuICAgKiAgICAgdGhhbiwgZXF1YWwgdG8sIG9yIGdyZWF0ZXIgdGhhbiB0aGUgZWxlbWVudCwgcmVzcGVjdGl2ZWx5LlxuICAgKiBAcGFyYW0gYUJpYXMgRWl0aGVyICdiaW5hcnlTZWFyY2guR1JFQVRFU1RfTE9XRVJfQk9VTkQnIG9yXG4gICAqICAgICAnYmluYXJ5U2VhcmNoLkxFQVNUX1VQUEVSX0JPVU5EJy4gU3BlY2lmaWVzIHdoZXRoZXIgdG8gcmV0dXJuIHRoZVxuICAgKiAgICAgY2xvc2VzdCBlbGVtZW50IHRoYXQgaXMgc21hbGxlciB0aGFuIG9yIGdyZWF0ZXIgdGhhbiB0aGUgb25lIHdlIGFyZVxuICAgKiAgICAgc2VhcmNoaW5nIGZvciwgcmVzcGVjdGl2ZWx5LCBpZiB0aGUgZXhhY3QgZWxlbWVudCBjYW5ub3QgYmUgZm91bmQuXG4gICAqICAgICBEZWZhdWx0cyB0byAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJy5cbiAgICovXG4gIGV4cG9ydHMuc2VhcmNoID0gZnVuY3Rpb24gc2VhcmNoKGFOZWVkbGUsIGFIYXlzdGFjaywgYUNvbXBhcmUsIGFCaWFzKSB7XG4gICAgaWYgKGFIYXlzdGFjay5sZW5ndGggPT09IDApIHtcbiAgICAgIHJldHVybiAtMTtcbiAgICB9XG5cbiAgICB2YXIgaW5kZXggPSByZWN1cnNpdmVTZWFyY2goLTEsIGFIYXlzdGFjay5sZW5ndGgsIGFOZWVkbGUsIGFIYXlzdGFjayxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYUNvbXBhcmUsIGFCaWFzIHx8IGV4cG9ydHMuR1JFQVRFU1RfTE9XRVJfQk9VTkQpO1xuICAgIGlmIChpbmRleCA8IDApIHtcbiAgICAgIHJldHVybiAtMTtcbiAgICB9XG5cbiAgICAvLyBXZSBoYXZlIGZvdW5kIGVpdGhlciB0aGUgZXhhY3QgZWxlbWVudCwgb3IgdGhlIG5leHQtY2xvc2VzdCBlbGVtZW50IHRoYW5cbiAgICAvLyB0aGUgb25lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLiBIb3dldmVyLCB0aGVyZSBtYXkgYmUgbW9yZSB0aGFuIG9uZSBzdWNoXG4gICAgLy8gZWxlbWVudC4gTWFrZSBzdXJlIHdlIGFsd2F5cyByZXR1cm4gdGhlIHNtYWxsZXN0IG9mIHRoZXNlLlxuICAgIHdoaWxlIChpbmRleCAtIDEgPj0gMCkge1xuICAgICAgaWYgKGFDb21wYXJlKGFIYXlzdGFja1tpbmRleF0sIGFIYXlzdGFja1tpbmRleCAtIDFdLCB0cnVlKSAhPT0gMCkge1xuICAgICAgICBicmVhaztcbiAgICAgIH1cbiAgICAgIC0taW5kZXg7XG4gICAgfVxuXG4gICAgcmV0dXJuIGluZGV4O1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9iaW5hcnktc2VhcmNoLmpzXG4gKiogbW9kdWxlIGlkID0gNFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgdXRpbCA9IHJlcXVpcmUoJy4vdXRpbCcpO1xuXG4gIC8qKlxuICAgKiBBIGRhdGEgc3RydWN0dXJlIHdoaWNoIGlzIGEgY29tYmluYXRpb24gb2YgYW4gYXJyYXkgYW5kIGEgc2V0LiBBZGRpbmcgYSBuZXdcbiAgICogbWVtYmVyIGlzIE8oMSksIHRlc3RpbmcgZm9yIG1lbWJlcnNoaXAgaXMgTygxKSwgYW5kIGZpbmRpbmcgdGhlIGluZGV4IG9mIGFuXG4gICAqIGVsZW1lbnQgaXMgTygxKS4gUmVtb3ZpbmcgZWxlbWVudHMgZnJvbSB0aGUgc2V0IGlzIG5vdCBzdXBwb3J0ZWQuIE9ubHlcbiAgICogc3RyaW5ncyBhcmUgc3VwcG9ydGVkIGZvciBtZW1iZXJzaGlwLlxuICAgKi9cbiAgZnVuY3Rpb24gQXJyYXlTZXQoKSB7XG4gICAgdGhpcy5fYXJyYXkgPSBbXTtcbiAgICB0aGlzLl9zZXQgPSB7fTtcbiAgfVxuXG4gIC8qKlxuICAgKiBTdGF0aWMgbWV0aG9kIGZvciBjcmVhdGluZyBBcnJheVNldCBpbnN0YW5jZXMgZnJvbSBhbiBleGlzdGluZyBhcnJheS5cbiAgICovXG4gIEFycmF5U2V0LmZyb21BcnJheSA9IGZ1bmN0aW9uIEFycmF5U2V0X2Zyb21BcnJheShhQXJyYXksIGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICB2YXIgc2V0ID0gbmV3IEFycmF5U2V0KCk7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IGFBcnJheS5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgc2V0LmFkZChhQXJyYXlbaV0sIGFBbGxvd0R1cGxpY2F0ZXMpO1xuICAgIH1cbiAgICByZXR1cm4gc2V0O1xuICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gaG93IG1hbnkgdW5pcXVlIGl0ZW1zIGFyZSBpbiB0aGlzIEFycmF5U2V0LiBJZiBkdXBsaWNhdGVzIGhhdmUgYmVlblxuICAgKiBhZGRlZCwgdGhhbiB0aG9zZSBkbyBub3QgY291bnQgdG93YXJkcyB0aGUgc2l6ZS5cbiAgICpcbiAgICogQHJldHVybnMgTnVtYmVyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuc2l6ZSA9IGZ1bmN0aW9uIEFycmF5U2V0X3NpemUoKSB7XG4gICAgcmV0dXJuIE9iamVjdC5nZXRPd25Qcm9wZXJ0eU5hbWVzKHRoaXMuX3NldCkubGVuZ3RoO1xuICB9O1xuXG4gIC8qKlxuICAgKiBBZGQgdGhlIGdpdmVuIHN0cmluZyB0byB0aGlzIHNldC5cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuYWRkID0gZnVuY3Rpb24gQXJyYXlTZXRfYWRkKGFTdHIsIGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgdmFyIGlzRHVwbGljYXRlID0gdGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpO1xuICAgIHZhciBpZHggPSB0aGlzLl9hcnJheS5sZW5ndGg7XG4gICAgaWYgKCFpc0R1cGxpY2F0ZSB8fCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgICB0aGlzLl9hcnJheS5wdXNoKGFTdHIpO1xuICAgIH1cbiAgICBpZiAoIWlzRHVwbGljYXRlKSB7XG4gICAgICB0aGlzLl9zZXRbc1N0cl0gPSBpZHg7XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBJcyB0aGUgZ2l2ZW4gc3RyaW5nIGEgbWVtYmVyIG9mIHRoaXMgc2V0P1xuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5oYXMgPSBmdW5jdGlvbiBBcnJheVNldF9oYXMoYVN0cikge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICByZXR1cm4gdGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXaGF0IGlzIHRoZSBpbmRleCBvZiB0aGUgZ2l2ZW4gc3RyaW5nIGluIHRoZSBhcnJheT9cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuaW5kZXhPZiA9IGZ1bmN0aW9uIEFycmF5U2V0X2luZGV4T2YoYVN0cikge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICBpZiAodGhpcy5fc2V0Lmhhc093blByb3BlcnR5KHNTdHIpKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc2V0W3NTdHJdO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTdHIgKyAnXCIgaXMgbm90IGluIHRoZSBzZXQuJyk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFdoYXQgaXMgdGhlIGVsZW1lbnQgYXQgdGhlIGdpdmVuIGluZGV4P1xuICAgKlxuICAgKiBAcGFyYW0gTnVtYmVyIGFJZHhcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5hdCA9IGZ1bmN0aW9uIEFycmF5U2V0X2F0KGFJZHgpIHtcbiAgICBpZiAoYUlkeCA+PSAwICYmIGFJZHggPCB0aGlzLl9hcnJheS5sZW5ndGgpIHtcbiAgICAgIHJldHVybiB0aGlzLl9hcnJheVthSWR4XTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdObyBlbGVtZW50IGluZGV4ZWQgYnkgJyArIGFJZHgpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBhcnJheSByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNldCAod2hpY2ggaGFzIHRoZSBwcm9wZXIgaW5kaWNlc1xuICAgKiBpbmRpY2F0ZWQgYnkgaW5kZXhPZikuIE5vdGUgdGhhdCB0aGlzIGlzIGEgY29weSBvZiB0aGUgaW50ZXJuYWwgYXJyYXkgdXNlZFxuICAgKiBmb3Igc3RvcmluZyB0aGUgbWVtYmVycyBzbyB0aGF0IG5vIG9uZSBjYW4gbWVzcyB3aXRoIGludGVybmFsIHN0YXRlLlxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLnRvQXJyYXkgPSBmdW5jdGlvbiBBcnJheVNldF90b0FycmF5KCkge1xuICAgIHJldHVybiB0aGlzLl9hcnJheS5zbGljZSgpO1xuICB9O1xuXG4gIGV4cG9ydHMuQXJyYXlTZXQgPSBBcnJheVNldDtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYXJyYXktc2V0LmpzXG4gKiogbW9kdWxlIGlkID0gNVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqXG4gKiBCYXNlZCBvbiB0aGUgQmFzZSA2NCBWTFEgaW1wbGVtZW50YXRpb24gaW4gQ2xvc3VyZSBDb21waWxlcjpcbiAqIGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvY2xvc3VyZS1jb21waWxlci9zb3VyY2UvYnJvd3NlL3RydW5rL3NyYy9jb20vZ29vZ2xlL2RlYnVnZ2luZy9zb3VyY2VtYXAvQmFzZTY0VkxRLmphdmFcbiAqXG4gKiBDb3B5cmlnaHQgMjAxMSBUaGUgQ2xvc3VyZSBDb21waWxlciBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0XG4gKiBtb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlXG4gKiBtZXQ6XG4gKlxuICogICogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHRcbiAqICAgIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci5cbiAqICAqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmVcbiAqICAgIGNvcHlyaWdodCBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nXG4gKiAgICBkaXNjbGFpbWVyIGluIHRoZSBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWRcbiAqICAgIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi5cbiAqICAqIE5laXRoZXIgdGhlIG5hbWUgb2YgR29vZ2xlIEluYy4gbm9yIHRoZSBuYW1lcyBvZiBpdHNcbiAqICAgIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZFxuICogICAgZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLlxuICpcbiAqIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlNcbiAqIFwiQVMgSVNcIiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1RcbiAqIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUlxuICogQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFRcbiAqIE9XTkVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVOVEFMLFxuICogU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVFxuICogTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsXG4gKiBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTiBBTllcbiAqIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlRcbiAqIChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRVxuICogT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS5cbiAqL1xue1xuICB2YXIgYmFzZTY0ID0gcmVxdWlyZSgnLi9iYXNlNjQnKTtcblxuICAvLyBBIHNpbmdsZSBiYXNlIDY0IGRpZ2l0IGNhbiBjb250YWluIDYgYml0cyBvZiBkYXRhLiBGb3IgdGhlIGJhc2UgNjQgdmFyaWFibGVcbiAgLy8gbGVuZ3RoIHF1YW50aXRpZXMgd2UgdXNlIGluIHRoZSBzb3VyY2UgbWFwIHNwZWMsIHRoZSBmaXJzdCBiaXQgaXMgdGhlIHNpZ24sXG4gIC8vIHRoZSBuZXh0IGZvdXIgYml0cyBhcmUgdGhlIGFjdHVhbCB2YWx1ZSwgYW5kIHRoZSA2dGggYml0IGlzIHRoZVxuICAvLyBjb250aW51YXRpb24gYml0LiBUaGUgY29udGludWF0aW9uIGJpdCB0ZWxscyB1cyB3aGV0aGVyIHRoZXJlIGFyZSBtb3JlXG4gIC8vIGRpZ2l0cyBpbiB0aGlzIHZhbHVlIGZvbGxvd2luZyB0aGlzIGRpZ2l0LlxuICAvL1xuICAvLyAgIENvbnRpbnVhdGlvblxuICAvLyAgIHwgICAgU2lnblxuICAvLyAgIHwgICAgfFxuICAvLyAgIFYgICAgVlxuICAvLyAgIDEwMTAxMVxuXG4gIHZhciBWTFFfQkFTRV9TSElGVCA9IDU7XG5cbiAgLy8gYmluYXJ5OiAxMDAwMDBcbiAgdmFyIFZMUV9CQVNFID0gMSA8PCBWTFFfQkFTRV9TSElGVDtcblxuICAvLyBiaW5hcnk6IDAxMTExMVxuICB2YXIgVkxRX0JBU0VfTUFTSyA9IFZMUV9CQVNFIC0gMTtcblxuICAvLyBiaW5hcnk6IDEwMDAwMFxuICB2YXIgVkxRX0NPTlRJTlVBVElPTl9CSVQgPSBWTFFfQkFTRTtcblxuICAvKipcbiAgICogQ29udmVydHMgZnJvbSBhIHR3by1jb21wbGVtZW50IHZhbHVlIHRvIGEgdmFsdWUgd2hlcmUgdGhlIHNpZ24gYml0IGlzXG4gICAqIHBsYWNlZCBpbiB0aGUgbGVhc3Qgc2lnbmlmaWNhbnQgYml0LiAgRm9yIGV4YW1wbGUsIGFzIGRlY2ltYWxzOlxuICAgKiAgIDEgYmVjb21lcyAyICgxMCBiaW5hcnkpLCAtMSBiZWNvbWVzIDMgKDExIGJpbmFyeSlcbiAgICogICAyIGJlY29tZXMgNCAoMTAwIGJpbmFyeSksIC0yIGJlY29tZXMgNSAoMTAxIGJpbmFyeSlcbiAgICovXG4gIGZ1bmN0aW9uIHRvVkxRU2lnbmVkKGFWYWx1ZSkge1xuICAgIHJldHVybiBhVmFsdWUgPCAwXG4gICAgICA/ICgoLWFWYWx1ZSkgPDwgMSkgKyAxXG4gICAgICA6IChhVmFsdWUgPDwgMSkgKyAwO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbnZlcnRzIHRvIGEgdHdvLWNvbXBsZW1lbnQgdmFsdWUgZnJvbSBhIHZhbHVlIHdoZXJlIHRoZSBzaWduIGJpdCBpc1xuICAgKiBwbGFjZWQgaW4gdGhlIGxlYXN0IHNpZ25pZmljYW50IGJpdC4gIEZvciBleGFtcGxlLCBhcyBkZWNpbWFsczpcbiAgICogICAyICgxMCBiaW5hcnkpIGJlY29tZXMgMSwgMyAoMTEgYmluYXJ5KSBiZWNvbWVzIC0xXG4gICAqICAgNCAoMTAwIGJpbmFyeSkgYmVjb21lcyAyLCA1ICgxMDEgYmluYXJ5KSBiZWNvbWVzIC0yXG4gICAqL1xuICBmdW5jdGlvbiBmcm9tVkxRU2lnbmVkKGFWYWx1ZSkge1xuICAgIHZhciBpc05lZ2F0aXZlID0gKGFWYWx1ZSAmIDEpID09PSAxO1xuICAgIHZhciBzaGlmdGVkID0gYVZhbHVlID4+IDE7XG4gICAgcmV0dXJuIGlzTmVnYXRpdmVcbiAgICAgID8gLXNoaWZ0ZWRcbiAgICAgIDogc2hpZnRlZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBiYXNlIDY0IFZMUSBlbmNvZGVkIHZhbHVlLlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiBiYXNlNjRWTFFfZW5jb2RlKGFWYWx1ZSkge1xuICAgIHZhciBlbmNvZGVkID0gXCJcIjtcbiAgICB2YXIgZGlnaXQ7XG5cbiAgICB2YXIgdmxxID0gdG9WTFFTaWduZWQoYVZhbHVlKTtcblxuICAgIGRvIHtcbiAgICAgIGRpZ2l0ID0gdmxxICYgVkxRX0JBU0VfTUFTSztcbiAgICAgIHZscSA+Pj49IFZMUV9CQVNFX1NISUZUO1xuICAgICAgaWYgKHZscSA+IDApIHtcbiAgICAgICAgLy8gVGhlcmUgYXJlIHN0aWxsIG1vcmUgZGlnaXRzIGluIHRoaXMgdmFsdWUsIHNvIHdlIG11c3QgbWFrZSBzdXJlIHRoZVxuICAgICAgICAvLyBjb250aW51YXRpb24gYml0IGlzIG1hcmtlZC5cbiAgICAgICAgZGlnaXQgfD0gVkxRX0NPTlRJTlVBVElPTl9CSVQ7XG4gICAgICB9XG4gICAgICBlbmNvZGVkICs9IGJhc2U2NC5lbmNvZGUoZGlnaXQpO1xuICAgIH0gd2hpbGUgKHZscSA+IDApO1xuXG4gICAgcmV0dXJuIGVuY29kZWQ7XG4gIH07XG5cbiAgLyoqXG4gICAqIERlY29kZXMgdGhlIG5leHQgYmFzZSA2NCBWTFEgdmFsdWUgZnJvbSB0aGUgZ2l2ZW4gc3RyaW5nIGFuZCByZXR1cm5zIHRoZVxuICAgKiB2YWx1ZSBhbmQgdGhlIHJlc3Qgb2YgdGhlIHN0cmluZyB2aWEgdGhlIG91dCBwYXJhbWV0ZXIuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIGJhc2U2NFZMUV9kZWNvZGUoYVN0ciwgYUluZGV4LCBhT3V0UGFyYW0pIHtcbiAgICB2YXIgc3RyTGVuID0gYVN0ci5sZW5ndGg7XG4gICAgdmFyIHJlc3VsdCA9IDA7XG4gICAgdmFyIHNoaWZ0ID0gMDtcbiAgICB2YXIgY29udGludWF0aW9uLCBkaWdpdDtcblxuICAgIGRvIHtcbiAgICAgIGlmIChhSW5kZXggPj0gc3RyTGVuKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkV4cGVjdGVkIG1vcmUgZGlnaXRzIGluIGJhc2UgNjQgVkxRIHZhbHVlLlwiKTtcbiAgICAgIH1cblxuICAgICAgZGlnaXQgPSBiYXNlNjQuZGVjb2RlKGFTdHIuY2hhckNvZGVBdChhSW5kZXgrKykpO1xuICAgICAgaWYgKGRpZ2l0ID09PSAtMSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbnZhbGlkIGJhc2U2NCBkaWdpdDogXCIgKyBhU3RyLmNoYXJBdChhSW5kZXggLSAxKSk7XG4gICAgICB9XG5cbiAgICAgIGNvbnRpbnVhdGlvbiA9ICEhKGRpZ2l0ICYgVkxRX0NPTlRJTlVBVElPTl9CSVQpO1xuICAgICAgZGlnaXQgJj0gVkxRX0JBU0VfTUFTSztcbiAgICAgIHJlc3VsdCA9IHJlc3VsdCArIChkaWdpdCA8PCBzaGlmdCk7XG4gICAgICBzaGlmdCArPSBWTFFfQkFTRV9TSElGVDtcbiAgICB9IHdoaWxlIChjb250aW51YXRpb24pO1xuXG4gICAgYU91dFBhcmFtLnZhbHVlID0gZnJvbVZMUVNpZ25lZChyZXN1bHQpO1xuICAgIGFPdXRQYXJhbS5yZXN0ID0gYUluZGV4O1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9iYXNlNjQtdmxxLmpzXG4gKiogbW9kdWxlIGlkID0gNlxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgaW50VG9DaGFyTWFwID0gJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky8nLnNwbGl0KCcnKTtcblxuICAvKipcbiAgICogRW5jb2RlIGFuIGludGVnZXIgaW4gdGhlIHJhbmdlIG9mIDAgdG8gNjMgdG8gYSBzaW5nbGUgYmFzZSA2NCBkaWdpdC5cbiAgICovXG4gIGV4cG9ydHMuZW5jb2RlID0gZnVuY3Rpb24gKG51bWJlcikge1xuICAgIGlmICgwIDw9IG51bWJlciAmJiBudW1iZXIgPCBpbnRUb0NoYXJNYXAubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gaW50VG9DaGFyTWFwW251bWJlcl07XG4gICAgfVxuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJNdXN0IGJlIGJldHdlZW4gMCBhbmQgNjM6IFwiICsgbnVtYmVyKTtcbiAgfTtcblxuICAvKipcbiAgICogRGVjb2RlIGEgc2luZ2xlIGJhc2UgNjQgY2hhcmFjdGVyIGNvZGUgZGlnaXQgdG8gYW4gaW50ZWdlci4gUmV0dXJucyAtMSBvblxuICAgKiBmYWlsdXJlLlxuICAgKi9cbiAgZXhwb3J0cy5kZWNvZGUgPSBmdW5jdGlvbiAoY2hhckNvZGUpIHtcbiAgICB2YXIgYmlnQSA9IDY1OyAgICAgLy8gJ0EnXG4gICAgdmFyIGJpZ1ogPSA5MDsgICAgIC8vICdaJ1xuXG4gICAgdmFyIGxpdHRsZUEgPSA5NzsgIC8vICdhJ1xuICAgIHZhciBsaXR0bGVaID0gMTIyOyAvLyAneidcblxuICAgIHZhciB6ZXJvID0gNDg7ICAgICAvLyAnMCdcbiAgICB2YXIgbmluZSA9IDU3OyAgICAgLy8gJzknXG5cbiAgICB2YXIgcGx1cyA9IDQzOyAgICAgLy8gJysnXG4gICAgdmFyIHNsYXNoID0gNDc7ICAgIC8vICcvJ1xuXG4gICAgdmFyIGxpdHRsZU9mZnNldCA9IDI2O1xuICAgIHZhciBudW1iZXJPZmZzZXQgPSA1MjtcblxuICAgIC8vIDAgLSAyNTogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpcbiAgICBpZiAoYmlnQSA8PSBjaGFyQ29kZSAmJiBjaGFyQ29kZSA8PSBiaWdaKSB7XG4gICAgICByZXR1cm4gKGNoYXJDb2RlIC0gYmlnQSk7XG4gICAgfVxuXG4gICAgLy8gMjYgLSA1MTogYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpcbiAgICBpZiAobGl0dGxlQSA8PSBjaGFyQ29kZSAmJiBjaGFyQ29kZSA8PSBsaXR0bGVaKSB7XG4gICAgICByZXR1cm4gKGNoYXJDb2RlIC0gbGl0dGxlQSArIGxpdHRsZU9mZnNldCk7XG4gICAgfVxuXG4gICAgLy8gNTIgLSA2MTogMDEyMzQ1Njc4OVxuICAgIGlmICh6ZXJvIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IG5pbmUpIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSB6ZXJvICsgbnVtYmVyT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA2MjogK1xuICAgIGlmIChjaGFyQ29kZSA9PSBwbHVzKSB7XG4gICAgICByZXR1cm4gNjI7XG4gICAgfVxuXG4gICAgLy8gNjM6IC9cbiAgICBpZiAoY2hhckNvZGUgPT0gc2xhc2gpIHtcbiAgICAgIHJldHVybiA2MztcbiAgICB9XG5cbiAgICAvLyBJbnZhbGlkIGJhc2U2NCBkaWdpdC5cbiAgICByZXR1cm4gLTE7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2Jhc2U2NC5qc1xuICoqIG1vZHVsZSBpZCA9IDdcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgLy8gSXQgdHVybnMgb3V0IHRoYXQgc29tZSAobW9zdD8pIEphdmFTY3JpcHQgZW5naW5lcyBkb24ndCBzZWxmLWhvc3RcbiAgLy8gYEFycmF5LnByb3RvdHlwZS5zb3J0YC4gVGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlIEMrKyB3aWxsIGxpa2VseSByZW1haW5cbiAgLy8gZmFzdGVyIHRoYW4gSlMgd2hlbiBkb2luZyByYXcgQ1BVLWludGVuc2l2ZSBzb3J0aW5nLiBIb3dldmVyLCB3aGVuIHVzaW5nIGFcbiAgLy8gY3VzdG9tIGNvbXBhcmF0b3IgZnVuY3Rpb24sIGNhbGxpbmcgYmFjayBhbmQgZm9ydGggYmV0d2VlbiB0aGUgVk0ncyBDKysgYW5kXG4gIC8vIEpJVCdkIEpTIGlzIHJhdGhlciBzbG93ICphbmQqIGxvc2VzIEpJVCB0eXBlIGluZm9ybWF0aW9uLCByZXN1bHRpbmcgaW5cbiAgLy8gd29yc2UgZ2VuZXJhdGVkIGNvZGUgZm9yIHRoZSBjb21wYXJhdG9yIGZ1bmN0aW9uIHRoYW4gd291bGQgYmUgb3B0aW1hbC4gSW5cbiAgLy8gZmFjdCwgd2hlbiBzb3J0aW5nIHdpdGggYSBjb21wYXJhdG9yLCB0aGVzZSBjb3N0cyBvdXR3ZWlnaCB0aGUgYmVuZWZpdHMgb2ZcbiAgLy8gc29ydGluZyBpbiBDKysuIEJ5IHVzaW5nIG91ciBvd24gSlMtaW1wbGVtZW50ZWQgUXVpY2sgU29ydCAoYmVsb3cpLCB3ZSBnZXRcbiAgLy8gYSB+MzUwMG1zIG1lYW4gc3BlZWQtdXAgaW4gYGJlbmNoL2JlbmNoLmh0bWxgLlxuXG4gIC8qKlxuICAgKiBTd2FwIHRoZSBlbGVtZW50cyBpbmRleGVkIGJ5IGB4YCBhbmQgYHlgIGluIHRoZSBhcnJheSBgYXJ5YC5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBUaGUgYXJyYXkuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB4XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIGZpcnN0IGl0ZW0uXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB5XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIHNlY29uZCBpdGVtLlxuICAgKi9cbiAgZnVuY3Rpb24gc3dhcChhcnksIHgsIHkpIHtcbiAgICB2YXIgdGVtcCA9IGFyeVt4XTtcbiAgICBhcnlbeF0gPSBhcnlbeV07XG4gICAgYXJ5W3ldID0gdGVtcDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGEgcmFuZG9tIGludGVnZXIgd2l0aGluIHRoZSByYW5nZSBgbG93IC4uIGhpZ2hgIGluY2x1c2l2ZS5cbiAgICpcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGxvd1xuICAgKiAgICAgICAgVGhlIGxvd2VyIGJvdW5kIG9uIHRoZSByYW5nZS5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IGhpZ2hcbiAgICogICAgICAgIFRoZSB1cHBlciBib3VuZCBvbiB0aGUgcmFuZ2UuXG4gICAqL1xuICBmdW5jdGlvbiByYW5kb21JbnRJblJhbmdlKGxvdywgaGlnaCkge1xuICAgIHJldHVybiBNYXRoLnJvdW5kKGxvdyArIChNYXRoLnJhbmRvbSgpICogKGhpZ2ggLSBsb3cpKSk7XG4gIH1cblxuICAvKipcbiAgICogVGhlIFF1aWNrIFNvcnQgYWxnb3JpdGhtLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHBcbiAgICogICAgICAgIFN0YXJ0IGluZGV4IG9mIHRoZSBhcnJheVxuICAgKiBAcGFyYW0ge051bWJlcn0gclxuICAgKiAgICAgICAgRW5kIGluZGV4IG9mIHRoZSBhcnJheVxuICAgKi9cbiAgZnVuY3Rpb24gZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBwLCByKSB7XG4gICAgLy8gSWYgb3VyIGxvd2VyIGJvdW5kIGlzIGxlc3MgdGhhbiBvdXIgdXBwZXIgYm91bmQsIHdlICgxKSBwYXJ0aXRpb24gdGhlXG4gICAgLy8gYXJyYXkgaW50byB0d28gcGllY2VzIGFuZCAoMikgcmVjdXJzZSBvbiBlYWNoIGhhbGYuIElmIGl0IGlzIG5vdCwgdGhpcyBpc1xuICAgIC8vIHRoZSBlbXB0eSBhcnJheSBhbmQgb3VyIGJhc2UgY2FzZS5cblxuICAgIGlmIChwIDwgcikge1xuICAgICAgLy8gKDEpIFBhcnRpdGlvbmluZy5cbiAgICAgIC8vXG4gICAgICAvLyBUaGUgcGFydGl0aW9uaW5nIGNob29zZXMgYSBwaXZvdCBiZXR3ZWVuIGBwYCBhbmQgYHJgIGFuZCBtb3ZlcyBhbGxcbiAgICAgIC8vIGVsZW1lbnRzIHRoYXQgYXJlIGxlc3MgdGhhbiBvciBlcXVhbCB0byB0aGUgcGl2b3QgdG8gdGhlIGJlZm9yZSBpdCwgYW5kXG4gICAgICAvLyBhbGwgdGhlIGVsZW1lbnRzIHRoYXQgYXJlIGdyZWF0ZXIgdGhhbiBpdCBhZnRlciBpdC4gVGhlIGVmZmVjdCBpcyB0aGF0XG4gICAgICAvLyBvbmNlIHBhcnRpdGlvbiBpcyBkb25lLCB0aGUgcGl2b3QgaXMgaW4gdGhlIGV4YWN0IHBsYWNlIGl0IHdpbGwgYmUgd2hlblxuICAgICAgLy8gdGhlIGFycmF5IGlzIHB1dCBpbiBzb3J0ZWQgb3JkZXIsIGFuZCBpdCB3aWxsIG5vdCBuZWVkIHRvIGJlIG1vdmVkXG4gICAgICAvLyBhZ2Fpbi4gVGhpcyBydW5zIGluIE8obikgdGltZS5cblxuICAgICAgLy8gQWx3YXlzIGNob29zZSBhIHJhbmRvbSBwaXZvdCBzbyB0aGF0IGFuIGlucHV0IGFycmF5IHdoaWNoIGlzIHJldmVyc2VcbiAgICAgIC8vIHNvcnRlZCBkb2VzIG5vdCBjYXVzZSBPKG5eMikgcnVubmluZyB0aW1lLlxuICAgICAgdmFyIHBpdm90SW5kZXggPSByYW5kb21JbnRJblJhbmdlKHAsIHIpO1xuICAgICAgdmFyIGkgPSBwIC0gMTtcblxuICAgICAgc3dhcChhcnksIHBpdm90SW5kZXgsIHIpO1xuICAgICAgdmFyIHBpdm90ID0gYXJ5W3JdO1xuXG4gICAgICAvLyBJbW1lZGlhdGVseSBhZnRlciBgamAgaXMgaW5jcmVtZW50ZWQgaW4gdGhpcyBsb29wLCB0aGUgZm9sbG93aW5nIGhvbGRcbiAgICAgIC8vIHRydWU6XG4gICAgICAvL1xuICAgICAgLy8gICAqIEV2ZXJ5IGVsZW1lbnQgaW4gYGFyeVtwIC4uIGldYCBpcyBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHBpdm90LlxuICAgICAgLy9cbiAgICAgIC8vICAgKiBFdmVyeSBlbGVtZW50IGluIGBhcnlbaSsxIC4uIGotMV1gIGlzIGdyZWF0ZXIgdGhhbiB0aGUgcGl2b3QuXG4gICAgICBmb3IgKHZhciBqID0gcDsgaiA8IHI7IGorKykge1xuICAgICAgICBpZiAoY29tcGFyYXRvcihhcnlbal0sIHBpdm90KSA8PSAwKSB7XG4gICAgICAgICAgaSArPSAxO1xuICAgICAgICAgIHN3YXAoYXJ5LCBpLCBqKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBzd2FwKGFyeSwgaSArIDEsIGopO1xuICAgICAgdmFyIHEgPSBpICsgMTtcblxuICAgICAgLy8gKDIpIFJlY3Vyc2Ugb24gZWFjaCBoYWxmLlxuXG4gICAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHAsIHEgLSAxKTtcbiAgICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcSArIDEsIHIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTb3J0IHRoZSBnaXZlbiBhcnJheSBpbi1wbGFjZSB3aXRoIHRoZSBnaXZlbiBjb21wYXJhdG9yIGZ1bmN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICovXG4gIGV4cG9ydHMucXVpY2tTb3J0ID0gZnVuY3Rpb24gKGFyeSwgY29tcGFyYXRvcikge1xuICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgMCwgYXJ5Lmxlbmd0aCAtIDEpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9xdWljay1zb3J0LmpzXG4gKiogbW9kdWxlIGlkID0gOFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgYmFzZTY0VkxRID0gcmVxdWlyZSgnLi9iYXNlNjQtdmxxJyk7XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG4gIHZhciBBcnJheVNldCA9IHJlcXVpcmUoJy4vYXJyYXktc2V0JykuQXJyYXlTZXQ7XG4gIHZhciBNYXBwaW5nTGlzdCA9IHJlcXVpcmUoJy4vbWFwcGluZy1saXN0JykuTWFwcGluZ0xpc3Q7XG5cbiAgLyoqXG4gICAqIEFuIGluc3RhbmNlIG9mIHRoZSBTb3VyY2VNYXBHZW5lcmF0b3IgcmVwcmVzZW50cyBhIHNvdXJjZSBtYXAgd2hpY2ggaXNcbiAgICogYmVpbmcgYnVpbHQgaW5jcmVtZW50YWxseS4gWW91IG1heSBwYXNzIGFuIG9iamVjdCB3aXRoIHRoZSBmb2xsb3dpbmdcbiAgICogcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGZpbGU6IFRoZSBmaWxlbmFtZSBvZiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIHNvdXJjZVJvb3Q6IEEgcm9vdCBmb3IgYWxsIHJlbGF0aXZlIFVSTHMgaW4gdGhpcyBzb3VyY2UgbWFwLlxuICAgKi9cbiAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yKGFBcmdzKSB7XG4gICAgaWYgKCFhQXJncykge1xuICAgICAgYUFyZ3MgPSB7fTtcbiAgICB9XG4gICAgdGhpcy5fZmlsZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnZmlsZScsIG51bGwpO1xuICAgIHRoaXMuX3NvdXJjZVJvb3QgPSB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZVJvb3QnLCBudWxsKTtcbiAgICB0aGlzLl9za2lwVmFsaWRhdGlvbiA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc2tpcFZhbGlkYXRpb24nLCBmYWxzZSk7XG4gICAgdGhpcy5fc291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgIHRoaXMuX25hbWVzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbWFwcGluZ3MgPSBuZXcgTWFwcGluZ0xpc3QoKTtcbiAgICB0aGlzLl9zb3VyY2VzQ29udGVudHMgPSBudWxsO1xuICB9XG5cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLyoqXG4gICAqIENyZWF0ZXMgYSBuZXcgU291cmNlTWFwR2VuZXJhdG9yIGJhc2VkIG9uIGEgU291cmNlTWFwQ29uc3VtZXJcbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VNYXBDb25zdW1lciBUaGUgU291cmNlTWFwLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLmZyb21Tb3VyY2VNYXAgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9mcm9tU291cmNlTWFwKGFTb3VyY2VNYXBDb25zdW1lcikge1xuICAgICAgdmFyIHNvdXJjZVJvb3QgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlUm9vdDtcbiAgICAgIHZhciBnZW5lcmF0b3IgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgICAgZmlsZTogYVNvdXJjZU1hcENvbnN1bWVyLmZpbGUsXG4gICAgICAgIHNvdXJjZVJvb3Q6IHNvdXJjZVJvb3RcbiAgICAgIH0pO1xuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLmVhY2hNYXBwaW5nKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICAgIHZhciBuZXdNYXBwaW5nID0ge1xuICAgICAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5nZW5lcmF0ZWRMaW5lLFxuICAgICAgICAgICAgY29sdW1uOiBtYXBwaW5nLmdlbmVyYXRlZENvbHVtblxuICAgICAgICAgIH1cbiAgICAgICAgfTtcblxuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgIT0gbnVsbCkge1xuICAgICAgICAgIG5ld01hcHBpbmcuc291cmNlID0gbWFwcGluZy5zb3VyY2U7XG4gICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgbmV3TWFwcGluZy5zb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIG5ld01hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBuZXdNYXBwaW5nLm9yaWdpbmFsID0ge1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW5cbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgaWYgKG1hcHBpbmcubmFtZSAhPSBudWxsKSB7XG4gICAgICAgICAgICBuZXdNYXBwaW5nLm5hbWUgPSBtYXBwaW5nLm5hbWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgZ2VuZXJhdG9yLmFkZE1hcHBpbmcobmV3TWFwcGluZyk7XG4gICAgICB9KTtcbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VzLmZvckVhY2goZnVuY3Rpb24gKHNvdXJjZUZpbGUpIHtcbiAgICAgICAgdmFyIGNvbnRlbnQgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlQ29udGVudEZvcihzb3VyY2VGaWxlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQgIT0gbnVsbCkge1xuICAgICAgICAgIGdlbmVyYXRvci5zZXRTb3VyY2VDb250ZW50KHNvdXJjZUZpbGUsIGNvbnRlbnQpO1xuICAgICAgICB9XG4gICAgICB9KTtcbiAgICAgIHJldHVybiBnZW5lcmF0b3I7XG4gICAgfTtcblxuICAvKipcbiAgICogQWRkIGEgc2luZ2xlIG1hcHBpbmcgZnJvbSBvcmlnaW5hbCBzb3VyY2UgbGluZSBhbmQgY29sdW1uIHRvIHRoZSBnZW5lcmF0ZWRcbiAgICogc291cmNlJ3MgbGluZSBhbmQgY29sdW1uIGZvciB0aGlzIHNvdXJjZSBtYXAgYmVpbmcgY3JlYXRlZC4gVGhlIG1hcHBpbmdcbiAgICogb2JqZWN0IHNob3VsZCBoYXZlIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGdlbmVyYXRlZDogQW4gb2JqZWN0IHdpdGggdGhlIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zLlxuICAgKiAgIC0gb3JpZ2luYWw6IEFuIG9iamVjdCB3aXRoIHRoZSBvcmlnaW5hbCBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zLlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUgKHJlbGF0aXZlIHRvIHRoZSBzb3VyY2VSb290KS5cbiAgICogICAtIG5hbWU6IEFuIG9wdGlvbmFsIG9yaWdpbmFsIHRva2VuIG5hbWUgZm9yIHRoaXMgbWFwcGluZy5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYWRkTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2FkZE1hcHBpbmcoYUFyZ3MpIHtcbiAgICAgIHZhciBnZW5lcmF0ZWQgPSB1dGlsLmdldEFyZyhhQXJncywgJ2dlbmVyYXRlZCcpO1xuICAgICAgdmFyIG9yaWdpbmFsID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdvcmlnaW5hbCcsIG51bGwpO1xuICAgICAgdmFyIHNvdXJjZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJywgbnVsbCk7XG4gICAgICB2YXIgbmFtZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbmFtZScsIG51bGwpO1xuXG4gICAgICBpZiAoIXRoaXMuX3NraXBWYWxpZGF0aW9uKSB7XG4gICAgICAgIHRoaXMuX3ZhbGlkYXRlTWFwcGluZyhnZW5lcmF0ZWQsIG9yaWdpbmFsLCBzb3VyY2UsIG5hbWUpO1xuICAgICAgfVxuXG4gICAgICBpZiAoc291cmNlICE9IG51bGwgJiYgIXRoaXMuX3NvdXJjZXMuaGFzKHNvdXJjZSkpIHtcbiAgICAgICAgdGhpcy5fc291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKG5hbWUgIT0gbnVsbCAmJiAhdGhpcy5fbmFtZXMuaGFzKG5hbWUpKSB7XG4gICAgICAgIHRoaXMuX25hbWVzLmFkZChuYW1lKTtcbiAgICAgIH1cblxuICAgICAgdGhpcy5fbWFwcGluZ3MuYWRkKHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogZ2VuZXJhdGVkLmxpbmUsXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogZ2VuZXJhdGVkLmNvbHVtbixcbiAgICAgICAgb3JpZ2luYWxMaW5lOiBvcmlnaW5hbCAhPSBudWxsICYmIG9yaWdpbmFsLmxpbmUsXG4gICAgICAgIG9yaWdpbmFsQ29sdW1uOiBvcmlnaW5hbCAhPSBudWxsICYmIG9yaWdpbmFsLmNvbHVtbixcbiAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgIG5hbWU6IG5hbWVcbiAgICAgIH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGEgc291cmNlIGZpbGUuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLnNldFNvdXJjZUNvbnRlbnQgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9zZXRTb3VyY2VDb250ZW50KGFTb3VyY2VGaWxlLCBhU291cmNlQ29udGVudCkge1xuICAgICAgdmFyIHNvdXJjZSA9IGFTb3VyY2VGaWxlO1xuICAgICAgaWYgKHRoaXMuX3NvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuX3NvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChhU291cmNlQ29udGVudCAhPSBudWxsKSB7XG4gICAgICAgIC8vIEFkZCB0aGUgc291cmNlIGNvbnRlbnQgdG8gdGhlIF9zb3VyY2VzQ29udGVudHMgbWFwLlxuICAgICAgICAvLyBDcmVhdGUgYSBuZXcgX3NvdXJjZXNDb250ZW50cyBtYXAgaWYgdGhlIHByb3BlcnR5IGlzIG51bGwuXG4gICAgICAgIGlmICghdGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzID0ge307XG4gICAgICAgIH1cbiAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzW3V0aWwudG9TZXRTdHJpbmcoc291cmNlKV0gPSBhU291cmNlQ29udGVudDtcbiAgICAgIH0gZWxzZSBpZiAodGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgIC8vIFJlbW92ZSB0aGUgc291cmNlIGZpbGUgZnJvbSB0aGUgX3NvdXJjZXNDb250ZW50cyBtYXAuXG4gICAgICAgIC8vIElmIHRoZSBfc291cmNlc0NvbnRlbnRzIG1hcCBpcyBlbXB0eSwgc2V0IHRoZSBwcm9wZXJ0eSB0byBudWxsLlxuICAgICAgICBkZWxldGUgdGhpcy5fc291cmNlc0NvbnRlbnRzW3V0aWwudG9TZXRTdHJpbmcoc291cmNlKV07XG4gICAgICAgIGlmIChPYmplY3Qua2V5cyh0aGlzLl9zb3VyY2VzQ29udGVudHMpLmxlbmd0aCA9PT0gMCkge1xuICAgICAgICAgIHRoaXMuX3NvdXJjZXNDb250ZW50cyA9IG51bGw7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBcHBsaWVzIHRoZSBtYXBwaW5ncyBvZiBhIHN1Yi1zb3VyY2UtbWFwIGZvciBhIHNwZWNpZmljIHNvdXJjZSBmaWxlIHRvIHRoZVxuICAgKiBzb3VyY2UgbWFwIGJlaW5nIGdlbmVyYXRlZC4gRWFjaCBtYXBwaW5nIHRvIHRoZSBzdXBwbGllZCBzb3VyY2UgZmlsZSBpc1xuICAgKiByZXdyaXR0ZW4gdXNpbmcgdGhlIHN1cHBsaWVkIHNvdXJjZSBtYXAuIE5vdGU6IFRoZSByZXNvbHV0aW9uIGZvciB0aGVcbiAgICogcmVzdWx0aW5nIG1hcHBpbmdzIGlzIHRoZSBtaW5pbWl1bSBvZiB0aGlzIG1hcCBhbmQgdGhlIHN1cHBsaWVkIG1hcC5cbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VNYXBDb25zdW1lciBUaGUgc291cmNlIG1hcCB0byBiZSBhcHBsaWVkLlxuICAgKiBAcGFyYW0gYVNvdXJjZUZpbGUgT3B0aW9uYWwuIFRoZSBmaWxlbmFtZSBvZiB0aGUgc291cmNlIGZpbGUuXG4gICAqICAgICAgICBJZiBvbWl0dGVkLCBTb3VyY2VNYXBDb25zdW1lcidzIGZpbGUgcHJvcGVydHkgd2lsbCBiZSB1c2VkLlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcFBhdGggT3B0aW9uYWwuIFRoZSBkaXJuYW1lIG9mIHRoZSBwYXRoIHRvIHRoZSBzb3VyY2UgbWFwXG4gICAqICAgICAgICB0byBiZSBhcHBsaWVkLiBJZiByZWxhdGl2ZSwgaXQgaXMgcmVsYXRpdmUgdG8gdGhlIFNvdXJjZU1hcENvbnN1bWVyLlxuICAgKiAgICAgICAgVGhpcyBwYXJhbWV0ZXIgaXMgbmVlZGVkIHdoZW4gdGhlIHR3byBzb3VyY2UgbWFwcyBhcmVuJ3QgaW4gdGhlIHNhbWVcbiAgICogICAgICAgIGRpcmVjdG9yeSwgYW5kIHRoZSBzb3VyY2UgbWFwIHRvIGJlIGFwcGxpZWQgY29udGFpbnMgcmVsYXRpdmUgc291cmNlXG4gICAqICAgICAgICBwYXRocy4gSWYgc28sIHRob3NlIHJlbGF0aXZlIHNvdXJjZSBwYXRocyBuZWVkIHRvIGJlIHJld3JpdHRlblxuICAgKiAgICAgICAgcmVsYXRpdmUgdG8gdGhlIFNvdXJjZU1hcEdlbmVyYXRvci5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYXBwbHlTb3VyY2VNYXAgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9hcHBseVNvdXJjZU1hcChhU291cmNlTWFwQ29uc3VtZXIsIGFTb3VyY2VGaWxlLCBhU291cmNlTWFwUGF0aCkge1xuICAgICAgdmFyIHNvdXJjZUZpbGUgPSBhU291cmNlRmlsZTtcbiAgICAgIC8vIElmIGFTb3VyY2VGaWxlIGlzIG9taXR0ZWQsIHdlIHdpbGwgdXNlIHRoZSBmaWxlIHByb3BlcnR5IG9mIHRoZSBTb3VyY2VNYXBcbiAgICAgIGlmIChhU291cmNlRmlsZSA9PSBudWxsKSB7XG4gICAgICAgIGlmIChhU291cmNlTWFwQ29uc3VtZXIuZmlsZSA9PSBudWxsKSB7XG4gICAgICAgICAgdGhyb3cgbmV3IEVycm9yKFxuICAgICAgICAgICAgJ1NvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuYXBwbHlTb3VyY2VNYXAgcmVxdWlyZXMgZWl0aGVyIGFuIGV4cGxpY2l0IHNvdXJjZSBmaWxlLCAnICtcbiAgICAgICAgICAgICdvciB0aGUgc291cmNlIG1hcFxcJ3MgXCJmaWxlXCIgcHJvcGVydHkuIEJvdGggd2VyZSBvbWl0dGVkLidcbiAgICAgICAgICApO1xuICAgICAgICB9XG4gICAgICAgIHNvdXJjZUZpbGUgPSBhU291cmNlTWFwQ29uc3VtZXIuZmlsZTtcbiAgICAgIH1cbiAgICAgIHZhciBzb3VyY2VSb290ID0gdGhpcy5fc291cmNlUm9vdDtcbiAgICAgIC8vIE1ha2UgXCJzb3VyY2VGaWxlXCIgcmVsYXRpdmUgaWYgYW4gYWJzb2x1dGUgVXJsIGlzIHBhc3NlZC5cbiAgICAgIGlmIChzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgc291cmNlRmlsZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlRmlsZSk7XG4gICAgICB9XG4gICAgICAvLyBBcHBseWluZyB0aGUgU291cmNlTWFwIGNhbiBhZGQgYW5kIHJlbW92ZSBpdGVtcyBmcm9tIHRoZSBzb3VyY2VzIGFuZFxuICAgICAgLy8gdGhlIG5hbWVzIGFycmF5LlxuICAgICAgdmFyIG5ld1NvdXJjZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICAgIHZhciBuZXdOYW1lcyA9IG5ldyBBcnJheVNldCgpO1xuXG4gICAgICAvLyBGaW5kIG1hcHBpbmdzIGZvciB0aGUgXCJzb3VyY2VGaWxlXCJcbiAgICAgIHRoaXMuX21hcHBpbmdzLnVuc29ydGVkRm9yRWFjaChmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgPT09IHNvdXJjZUZpbGUgJiYgbWFwcGluZy5vcmlnaW5hbExpbmUgIT0gbnVsbCkge1xuICAgICAgICAgIC8vIENoZWNrIGlmIGl0IGNhbiBiZSBtYXBwZWQgYnkgdGhlIHNvdXJjZSBtYXAsIHRoZW4gdXBkYXRlIHRoZSBtYXBwaW5nLlxuICAgICAgICAgIHZhciBvcmlnaW5hbCA9IGFTb3VyY2VNYXBDb25zdW1lci5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgICAgICAgIGxpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgY29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uXG4gICAgICAgICAgfSk7XG4gICAgICAgICAgaWYgKG9yaWdpbmFsLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgICAvLyBDb3B5IG1hcHBpbmdcbiAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gb3JpZ2luYWwuc291cmNlO1xuICAgICAgICAgICAgaWYgKGFTb3VyY2VNYXBQYXRoICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSB1dGlsLmpvaW4oYVNvdXJjZU1hcFBhdGgsIG1hcHBpbmcuc291cmNlKVxuICAgICAgICAgICAgfVxuICAgICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgbWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgPSBvcmlnaW5hbC5saW5lO1xuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IG9yaWdpbmFsLmNvbHVtbjtcbiAgICAgICAgICAgIGlmIChvcmlnaW5hbC5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgbWFwcGluZy5uYW1lID0gb3JpZ2luYWwubmFtZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgc291cmNlID0gbWFwcGluZy5zb3VyY2U7XG4gICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiAhbmV3U291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICAgIG5ld1NvdXJjZXMuYWRkKHNvdXJjZSk7XG4gICAgICAgIH1cblxuICAgICAgICB2YXIgbmFtZSA9IG1hcHBpbmcubmFtZTtcbiAgICAgICAgaWYgKG5hbWUgIT0gbnVsbCAmJiAhbmV3TmFtZXMuaGFzKG5hbWUpKSB7XG4gICAgICAgICAgbmV3TmFtZXMuYWRkKG5hbWUpO1xuICAgICAgICB9XG5cbiAgICAgIH0sIHRoaXMpO1xuICAgICAgdGhpcy5fc291cmNlcyA9IG5ld1NvdXJjZXM7XG4gICAgICB0aGlzLl9uYW1lcyA9IG5ld05hbWVzO1xuXG4gICAgICAvLyBDb3B5IHNvdXJjZXNDb250ZW50cyBvZiBhcHBsaWVkIG1hcC5cbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VzLmZvckVhY2goZnVuY3Rpb24gKHNvdXJjZUZpbGUpIHtcbiAgICAgICAgdmFyIGNvbnRlbnQgPSBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlQ29udGVudEZvcihzb3VyY2VGaWxlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQgIT0gbnVsbCkge1xuICAgICAgICAgIGlmIChhU291cmNlTWFwUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5qb2luKGFTb3VyY2VNYXBQYXRoLCBzb3VyY2VGaWxlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlRmlsZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlRmlsZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMuc2V0U291cmNlQ29udGVudChzb3VyY2VGaWxlLCBjb250ZW50KTtcbiAgICAgICAgfVxuICAgICAgfSwgdGhpcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogQSBtYXBwaW5nIGNhbiBoYXZlIG9uZSBvZiB0aGUgdGhyZWUgbGV2ZWxzIG9mIGRhdGE6XG4gICAqXG4gICAqICAgMS4gSnVzdCB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9uLlxuICAgKiAgIDIuIFRoZSBHZW5lcmF0ZWQgcG9zaXRpb24sIG9yaWdpbmFsIHBvc2l0aW9uLCBhbmQgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIDMuIEdlbmVyYXRlZCBhbmQgb3JpZ2luYWwgcG9zaXRpb24sIG9yaWdpbmFsIHNvdXJjZSwgYXMgd2VsbCBhcyBhIG5hbWVcbiAgICogICAgICB0b2tlbi5cbiAgICpcbiAgICogVG8gbWFpbnRhaW4gY29uc2lzdGVuY3ksIHdlIHZhbGlkYXRlIHRoYXQgYW55IG5ldyBtYXBwaW5nIGJlaW5nIGFkZGVkIGZhbGxzXG4gICAqIGluIHRvIG9uZSBvZiB0aGVzZSBjYXRlZ29yaWVzLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fdmFsaWRhdGVNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfdmFsaWRhdGVNYXBwaW5nKGFHZW5lcmF0ZWQsIGFPcmlnaW5hbCwgYVNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFOYW1lKSB7XG4gICAgICBpZiAoYUdlbmVyYXRlZCAmJiAnbGluZScgaW4gYUdlbmVyYXRlZCAmJiAnY29sdW1uJyBpbiBhR2VuZXJhdGVkXG4gICAgICAgICAgJiYgYUdlbmVyYXRlZC5saW5lID4gMCAmJiBhR2VuZXJhdGVkLmNvbHVtbiA+PSAwXG4gICAgICAgICAgJiYgIWFPcmlnaW5hbCAmJiAhYVNvdXJjZSAmJiAhYU5hbWUpIHtcbiAgICAgICAgLy8gQ2FzZSAxLlxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBlbHNlIGlmIChhR2VuZXJhdGVkICYmICdsaW5lJyBpbiBhR2VuZXJhdGVkICYmICdjb2x1bW4nIGluIGFHZW5lcmF0ZWRcbiAgICAgICAgICAgICAgICYmIGFPcmlnaW5hbCAmJiAnbGluZScgaW4gYU9yaWdpbmFsICYmICdjb2x1bW4nIGluIGFPcmlnaW5hbFxuICAgICAgICAgICAgICAgJiYgYUdlbmVyYXRlZC5saW5lID4gMCAmJiBhR2VuZXJhdGVkLmNvbHVtbiA+PSAwXG4gICAgICAgICAgICAgICAmJiBhT3JpZ2luYWwubGluZSA+IDAgJiYgYU9yaWdpbmFsLmNvbHVtbiA+PSAwXG4gICAgICAgICAgICAgICAmJiBhU291cmNlKSB7XG4gICAgICAgIC8vIENhc2VzIDIgYW5kIDMuXG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ludmFsaWQgbWFwcGluZzogJyArIEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IGFHZW5lcmF0ZWQsXG4gICAgICAgICAgc291cmNlOiBhU291cmNlLFxuICAgICAgICAgIG9yaWdpbmFsOiBhT3JpZ2luYWwsXG4gICAgICAgICAgbmFtZTogYU5hbWVcbiAgICAgICAgfSkpO1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFNlcmlhbGl6ZSB0aGUgYWNjdW11bGF0ZWQgbWFwcGluZ3MgaW4gdG8gdGhlIHN0cmVhbSBvZiBiYXNlIDY0IFZMUXNcbiAgICogc3BlY2lmaWVkIGJ5IHRoZSBzb3VyY2UgbWFwIGZvcm1hdC5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX3NlcmlhbGl6ZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3Jfc2VyaWFsaXplTWFwcGluZ3MoKSB7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzR2VuZXJhdGVkTGluZSA9IDE7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbExpbmUgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzTmFtZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNTb3VyY2UgPSAwO1xuICAgICAgdmFyIHJlc3VsdCA9ICcnO1xuICAgICAgdmFyIG1hcHBpbmc7XG4gICAgICB2YXIgbmFtZUlkeDtcbiAgICAgIHZhciBzb3VyY2VJZHg7XG5cbiAgICAgIHZhciBtYXBwaW5ncyA9IHRoaXMuX21hcHBpbmdzLnRvQXJyYXkoKTtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBtYXBwaW5ncy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBtYXBwaW5nID0gbWFwcGluZ3NbaV07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSAhPT0gcHJldmlvdXNHZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgICAgIHdoaWxlIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgIT09IHByZXZpb3VzR2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgcmVzdWx0ICs9ICc7JztcbiAgICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkTGluZSsrO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBpZiAoaSA+IDApIHtcbiAgICAgICAgICAgIGlmICghdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZChtYXBwaW5nLCBtYXBwaW5nc1tpIC0gMV0pKSB7XG4gICAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgICAgfVxuICAgICAgICAgICAgcmVzdWx0ICs9ICcsJztcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShtYXBwaW5nLmdlbmVyYXRlZENvbHVtblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbjtcblxuICAgICAgICBpZiAobWFwcGluZy5zb3VyY2UgIT0gbnVsbCkge1xuICAgICAgICAgIHNvdXJjZUlkeCA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUoc291cmNlSWR4IC0gcHJldmlvdXNTb3VyY2UpO1xuICAgICAgICAgIHByZXZpb3VzU291cmNlID0gc291cmNlSWR4O1xuXG4gICAgICAgICAgLy8gbGluZXMgYXJlIHN0b3JlZCAwLWJhc2VkIGluIFNvdXJjZU1hcCBzcGVjIHZlcnNpb24gM1xuICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG1hcHBpbmcub3JpZ2luYWxMaW5lIC0gMVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNPcmlnaW5hbExpbmUpO1xuICAgICAgICAgIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gbWFwcGluZy5vcmlnaW5hbExpbmUgLSAxO1xuXG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobWFwcGluZy5vcmlnaW5hbENvbHVtblxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNPcmlnaW5hbENvbHVtbik7XG4gICAgICAgICAgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgIG5hbWVJZHggPSB0aGlzLl9uYW1lcy5pbmRleE9mKG1hcHBpbmcubmFtZSk7XG4gICAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShuYW1lSWR4IC0gcHJldmlvdXNOYW1lKTtcbiAgICAgICAgICAgIHByZXZpb3VzTmFtZSA9IG5hbWVJZHg7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiByZXN1bHQ7XG4gICAgfTtcblxuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50ID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfZ2VuZXJhdGVTb3VyY2VzQ29udGVudChhU291cmNlcywgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHJldHVybiBhU291cmNlcy5tYXAoZnVuY3Rpb24gKHNvdXJjZSkge1xuICAgICAgICBpZiAoIXRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICAgIHJldHVybiBudWxsO1xuICAgICAgICB9XG4gICAgICAgIGlmIChhU291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlID0gdXRpbC5yZWxhdGl2ZShhU291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgfVxuICAgICAgICB2YXIga2V5ID0gdXRpbC50b1NldFN0cmluZyhzb3VyY2UpO1xuICAgICAgICByZXR1cm4gT2JqZWN0LnByb3RvdHlwZS5oYXNPd25Qcm9wZXJ0eS5jYWxsKHRoaXMuX3NvdXJjZXNDb250ZW50cyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBrZXkpXG4gICAgICAgICAgPyB0aGlzLl9zb3VyY2VzQ29udGVudHNba2V5XVxuICAgICAgICAgIDogbnVsbDtcbiAgICAgIH0sIHRoaXMpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEV4dGVybmFsaXplIHRoZSBzb3VyY2UgbWFwLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS50b0pTT04gPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl90b0pTT04oKSB7XG4gICAgICB2YXIgbWFwID0ge1xuICAgICAgICB2ZXJzaW9uOiB0aGlzLl92ZXJzaW9uLFxuICAgICAgICBzb3VyY2VzOiB0aGlzLl9zb3VyY2VzLnRvQXJyYXkoKSxcbiAgICAgICAgbmFtZXM6IHRoaXMuX25hbWVzLnRvQXJyYXkoKSxcbiAgICAgICAgbWFwcGluZ3M6IHRoaXMuX3NlcmlhbGl6ZU1hcHBpbmdzKClcbiAgICAgIH07XG4gICAgICBpZiAodGhpcy5fZmlsZSAhPSBudWxsKSB7XG4gICAgICAgIG1hcC5maWxlID0gdGhpcy5fZmlsZTtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLl9zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgbWFwLnNvdXJjZVJvb3QgPSB0aGlzLl9zb3VyY2VSb290O1xuICAgICAgfVxuICAgICAgaWYgKHRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICBtYXAuc291cmNlc0NvbnRlbnQgPSB0aGlzLl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50KG1hcC5zb3VyY2VzLCBtYXAuc291cmNlUm9vdCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiBtYXA7XG4gICAgfTtcblxuICAvKipcbiAgICogUmVuZGVyIHRoZSBzb3VyY2UgbWFwIGJlaW5nIGdlbmVyYXRlZCB0byBhIHN0cmluZy5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUudG9TdHJpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl90b1N0cmluZygpIHtcbiAgICAgIHJldHVybiBKU09OLnN0cmluZ2lmeSh0aGlzLnRvSlNPTigpKTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuU291cmNlTWFwR2VuZXJhdG9yID0gU291cmNlTWFwR2VuZXJhdG9yO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvci5qc1xuICoqIG1vZHVsZSBpZCA9IDlcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxNCBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICAvKipcbiAgICogRGV0ZXJtaW5lIHdoZXRoZXIgbWFwcGluZ0IgaXMgYWZ0ZXIgbWFwcGluZ0Egd2l0aCByZXNwZWN0IHRvIGdlbmVyYXRlZFxuICAgKiBwb3NpdGlvbi5cbiAgICovXG4gIGZ1bmN0aW9uIGdlbmVyYXRlZFBvc2l0aW9uQWZ0ZXIobWFwcGluZ0EsIG1hcHBpbmdCKSB7XG4gICAgLy8gT3B0aW1pemVkIGZvciBtb3N0IGNvbW1vbiBjYXNlXG4gICAgdmFyIGxpbmVBID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZTtcbiAgICB2YXIgbGluZUIgPSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIHZhciBjb2x1bW5BID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHZhciBjb2x1bW5CID0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHJldHVybiBsaW5lQiA+IGxpbmVBIHx8IGxpbmVCID09IGxpbmVBICYmIGNvbHVtbkIgPj0gY29sdW1uQSB8fFxuICAgICAgICAgICB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikgPD0gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBIGRhdGEgc3RydWN0dXJlIHRvIHByb3ZpZGUgYSBzb3J0ZWQgdmlldyBvZiBhY2N1bXVsYXRlZCBtYXBwaW5ncyBpbiBhXG4gICAqIHBlcmZvcm1hbmNlIGNvbnNjaW91cyBtYW5uZXIuIEl0IHRyYWRlcyBhIG5lZ2xpYmFibGUgb3ZlcmhlYWQgaW4gZ2VuZXJhbFxuICAgKiBjYXNlIGZvciBhIGxhcmdlIHNwZWVkdXAgaW4gY2FzZSBvZiBtYXBwaW5ncyBiZWluZyBhZGRlZCBpbiBvcmRlci5cbiAgICovXG4gIGZ1bmN0aW9uIE1hcHBpbmdMaXN0KCkge1xuICAgIHRoaXMuX2FycmF5ID0gW107XG4gICAgdGhpcy5fc29ydGVkID0gdHJ1ZTtcbiAgICAvLyBTZXJ2ZXMgYXMgaW5maW11bVxuICAgIHRoaXMuX2xhc3QgPSB7Z2VuZXJhdGVkTGluZTogLTEsIGdlbmVyYXRlZENvbHVtbjogMH07XG4gIH1cblxuICAvKipcbiAgICogSXRlcmF0ZSB0aHJvdWdoIGludGVybmFsIGl0ZW1zLiBUaGlzIG1ldGhvZCB0YWtlcyB0aGUgc2FtZSBhcmd1bWVudHMgdGhhdFxuICAgKiBgQXJyYXkucHJvdG90eXBlLmZvckVhY2hgIHRha2VzLlxuICAgKlxuICAgKiBOT1RFOiBUaGUgb3JkZXIgb2YgdGhlIG1hcHBpbmdzIGlzIE5PVCBndWFyYW50ZWVkLlxuICAgKi9cbiAgTWFwcGluZ0xpc3QucHJvdG90eXBlLnVuc29ydGVkRm9yRWFjaCA9XG4gICAgZnVuY3Rpb24gTWFwcGluZ0xpc3RfZm9yRWFjaChhQ2FsbGJhY2ssIGFUaGlzQXJnKSB7XG4gICAgICB0aGlzLl9hcnJheS5mb3JFYWNoKGFDYWxsYmFjaywgYVRoaXNBcmcpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCB0aGUgZ2l2ZW4gc291cmNlIG1hcHBpbmcuXG4gICAqXG4gICAqIEBwYXJhbSBPYmplY3QgYU1hcHBpbmdcbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBNYXBwaW5nTGlzdF9hZGQoYU1hcHBpbmcpIHtcbiAgICBpZiAoZ2VuZXJhdGVkUG9zaXRpb25BZnRlcih0aGlzLl9sYXN0LCBhTWFwcGluZykpIHtcbiAgICAgIHRoaXMuX2xhc3QgPSBhTWFwcGluZztcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLl9zb3J0ZWQgPSBmYWxzZTtcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZmxhdCwgc29ydGVkIGFycmF5IG9mIG1hcHBpbmdzLiBUaGUgbWFwcGluZ3MgYXJlIHNvcnRlZCBieVxuICAgKiBnZW5lcmF0ZWQgcG9zaXRpb24uXG4gICAqXG4gICAqIFdBUk5JTkc6IFRoaXMgbWV0aG9kIHJldHVybnMgaW50ZXJuYWwgZGF0YSB3aXRob3V0IGNvcHlpbmcsIGZvclxuICAgKiBwZXJmb3JtYW5jZS4gVGhlIHJldHVybiB2YWx1ZSBtdXN0IE5PVCBiZSBtdXRhdGVkLCBhbmQgc2hvdWxkIGJlIHRyZWF0ZWQgYXNcbiAgICogYW4gaW1tdXRhYmxlIGJvcnJvdy4gSWYgeW91IHdhbnQgdG8gdGFrZSBvd25lcnNoaXAsIHlvdSBtdXN0IG1ha2UgeW91ciBvd25cbiAgICogY29weS5cbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS50b0FycmF5ID0gZnVuY3Rpb24gTWFwcGluZ0xpc3RfdG9BcnJheSgpIHtcbiAgICBpZiAoIXRoaXMuX3NvcnRlZCkge1xuICAgICAgdGhpcy5fYXJyYXkuc29ydCh1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKTtcbiAgICAgIHRoaXMuX3NvcnRlZCA9IHRydWU7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLl9hcnJheTtcbiAgfTtcblxuICBleHBvcnRzLk1hcHBpbmdMaXN0ID0gTWFwcGluZ0xpc3Q7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL21hcHBpbmctbGlzdC5qc1xuICoqIG1vZHVsZSBpZCA9IDEwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_source_map_generator.js b/devtools/shared/sourcemap/tests/unit/test_source_map_generator.js
new file mode 100644
index 000000000..4b5fd4976
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_source_map_generator.js
@@ -0,0 +1,4039 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var SourceMapGenerator = __webpack_require__(1).SourceMapGenerator;
+ var SourceMapConsumer = __webpack_require__(7).SourceMapConsumer;
+ var SourceNode = __webpack_require__(10).SourceNode;
+ var util = __webpack_require__(11);
+
+ exports['test some simple stuff'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'foo.js',
+ sourceRoot: '.'
+ });
+ assert.ok(true);
+
+ var map = new SourceMapGenerator().toJSON();
+ assert.ok(!('file' in map));
+ assert.ok(!('sourceRoot' in map));
+ };
+
+ exports['test JSON serialization'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'foo.js',
+ sourceRoot: '.'
+ });
+ assert.equal(map.toString(), JSON.stringify(map));
+ };
+
+ exports['test adding mappings (case 1)'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.'
+ });
+
+ assert.doesNotThrow(function () {
+ map.addMapping({
+ generated: { line: 1, column: 1 }
+ });
+ });
+ };
+
+ exports['test adding mappings (case 2)'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.'
+ });
+
+ assert.doesNotThrow(function () {
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ source: 'bar.js',
+ original: { line: 1, column: 1 }
+ });
+ });
+ };
+
+ exports['test adding mappings (case 3)'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.'
+ });
+
+ assert.doesNotThrow(function () {
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ source: 'bar.js',
+ original: { line: 1, column: 1 },
+ name: 'someToken'
+ });
+ });
+ };
+
+ exports['test adding mappings (invalid)'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.'
+ });
+
+ // Not enough info.
+ assert.throws(function () {
+ map.addMapping({});
+ });
+
+ // Original file position, but no source.
+ assert.throws(function () {
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 1, column: 1 }
+ });
+ });
+ };
+
+ exports['test adding mappings with skipValidation'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.',
+ skipValidation: true
+ });
+
+ // Not enough info, caught by `util.getArgs`
+ assert.throws(function () {
+ map.addMapping({});
+ });
+
+ // Original file position, but no source. Not checked.
+ assert.doesNotThrow(function () {
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 1, column: 1 }
+ });
+ });
+ };
+
+ exports['test that the correct mappings are being generated'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'min.js',
+ sourceRoot: '/the/root'
+ });
+
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 1, column: 1 },
+ source: 'one.js'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 5 },
+ original: { line: 1, column: 5 },
+ source: 'one.js'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 9 },
+ original: { line: 1, column: 11 },
+ source: 'one.js'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 18 },
+ original: { line: 1, column: 21 },
+ source: 'one.js',
+ name: 'bar'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 21 },
+ original: { line: 2, column: 3 },
+ source: 'one.js'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 28 },
+ original: { line: 2, column: 10 },
+ source: 'one.js',
+ name: 'baz'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 32 },
+ original: { line: 2, column: 14 },
+ source: 'one.js',
+ name: 'bar'
+ });
+
+ map.addMapping({
+ generated: { line: 2, column: 1 },
+ original: { line: 1, column: 1 },
+ source: 'two.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 5 },
+ original: { line: 1, column: 5 },
+ source: 'two.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 9 },
+ original: { line: 1, column: 11 },
+ source: 'two.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 18 },
+ original: { line: 1, column: 21 },
+ source: 'two.js',
+ name: 'n'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 21 },
+ original: { line: 2, column: 3 },
+ source: 'two.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 28 },
+ original: { line: 2, column: 10 },
+ source: 'two.js',
+ name: 'n'
+ });
+
+ map = JSON.parse(map.toString());
+
+ util.assertEqualMaps(assert, map, util.testMap);
+ };
+
+ exports['test that adding a mapping with an empty string name does not break generation'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'generated-foo.js',
+ sourceRoot: '.'
+ });
+
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ source: 'bar.js',
+ original: { line: 1, column: 1 },
+ name: ''
+ });
+
+ assert.doesNotThrow(function () {
+ JSON.parse(map.toString());
+ });
+ };
+
+ exports['test that source content can be set'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'min.js',
+ sourceRoot: '/the/root'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 1, column: 1 },
+ source: 'one.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 1 },
+ original: { line: 1, column: 1 },
+ source: 'two.js'
+ });
+ map.setSourceContent('one.js', 'one file content');
+
+ map = JSON.parse(map.toString());
+ assert.equal(map.sources[0], 'one.js');
+ assert.equal(map.sources[1], 'two.js');
+ assert.equal(map.sourcesContent[0], 'one file content');
+ assert.equal(map.sourcesContent[1], null);
+ };
+
+ exports['test .fromSourceMap'] = function (assert) {
+ var map = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(util.testMap));
+ util.assertEqualMaps(assert, map.toJSON(), util.testMap);
+ };
+
+ exports['test .fromSourceMap with sourcesContent'] = function (assert) {
+ var map = SourceMapGenerator.fromSourceMap(
+ new SourceMapConsumer(util.testMapWithSourcesContent));
+ util.assertEqualMaps(assert, map.toJSON(), util.testMapWithSourcesContent);
+ };
+
+ exports['test applySourceMap'] = function (assert) {
+ var node = new SourceNode(null, null, null, [
+ new SourceNode(2, 0, 'fileX', 'lineX2\n'),
+ 'genA1\n',
+ new SourceNode(2, 0, 'fileY', 'lineY2\n'),
+ 'genA2\n',
+ new SourceNode(1, 0, 'fileX', 'lineX1\n'),
+ 'genA3\n',
+ new SourceNode(1, 0, 'fileY', 'lineY1\n')
+ ]);
+ var mapStep1 = node.toStringWithSourceMap({
+ file: 'fileA'
+ }).map;
+ mapStep1.setSourceContent('fileX', 'lineX1\nlineX2\n');
+ mapStep1 = mapStep1.toJSON();
+
+ node = new SourceNode(null, null, null, [
+ 'gen1\n',
+ new SourceNode(1, 0, 'fileA', 'lineA1\n'),
+ new SourceNode(2, 0, 'fileA', 'lineA2\n'),
+ new SourceNode(3, 0, 'fileA', 'lineA3\n'),
+ new SourceNode(4, 0, 'fileA', 'lineA4\n'),
+ new SourceNode(1, 0, 'fileB', 'lineB1\n'),
+ new SourceNode(2, 0, 'fileB', 'lineB2\n'),
+ 'gen2\n'
+ ]);
+ var mapStep2 = node.toStringWithSourceMap({
+ file: 'fileGen'
+ }).map;
+ mapStep2.setSourceContent('fileB', 'lineB1\nlineB2\n');
+ mapStep2 = mapStep2.toJSON();
+
+ node = new SourceNode(null, null, null, [
+ 'gen1\n',
+ new SourceNode(2, 0, 'fileX', 'lineA1\n'),
+ new SourceNode(2, 0, 'fileA', 'lineA2\n'),
+ new SourceNode(2, 0, 'fileY', 'lineA3\n'),
+ new SourceNode(4, 0, 'fileA', 'lineA4\n'),
+ new SourceNode(1, 0, 'fileB', 'lineB1\n'),
+ new SourceNode(2, 0, 'fileB', 'lineB2\n'),
+ 'gen2\n'
+ ]);
+ var expectedMap = node.toStringWithSourceMap({
+ file: 'fileGen'
+ }).map;
+ expectedMap.setSourceContent('fileX', 'lineX1\nlineX2\n');
+ expectedMap.setSourceContent('fileB', 'lineB1\nlineB2\n');
+ expectedMap = expectedMap.toJSON();
+
+ // apply source map "mapStep1" to "mapStep2"
+ var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(mapStep2));
+ generator.applySourceMap(new SourceMapConsumer(mapStep1));
+ var actualMap = generator.toJSON();
+
+ util.assertEqualMaps(assert, actualMap, expectedMap);
+ };
+
+ exports['test applySourceMap throws when file is missing'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'test.js'
+ });
+ var map2 = new SourceMapGenerator();
+ assert.throws(function() {
+ map.applySourceMap(new SourceMapConsumer(map2.toJSON()));
+ });
+ };
+
+ exports['test the two additional parameters of applySourceMap'] = function (assert) {
+ // Assume the following directory structure:
+ //
+ // http://foo.org/
+ // bar.coffee
+ // app/
+ // coffee/
+ // foo.coffee
+ // temp/
+ // bundle.js
+ // temp_maps/
+ // bundle.js.map
+ // public/
+ // bundle.min.js
+ // bundle.min.js.map
+ //
+ // http://www.example.com/
+ // baz.coffee
+
+ var bundleMap = new SourceMapGenerator({
+ file: 'bundle.js'
+ });
+ bundleMap.addMapping({
+ generated: { line: 3, column: 3 },
+ original: { line: 2, column: 2 },
+ source: '../../coffee/foo.coffee'
+ });
+ bundleMap.setSourceContent('../../coffee/foo.coffee', 'foo coffee');
+ bundleMap.addMapping({
+ generated: { line: 13, column: 13 },
+ original: { line: 12, column: 12 },
+ source: '/bar.coffee'
+ });
+ bundleMap.setSourceContent('/bar.coffee', 'bar coffee');
+ bundleMap.addMapping({
+ generated: { line: 23, column: 23 },
+ original: { line: 22, column: 22 },
+ source: 'http://www.example.com/baz.coffee'
+ });
+ bundleMap.setSourceContent(
+ 'http://www.example.com/baz.coffee',
+ 'baz coffee'
+ );
+ bundleMap = new SourceMapConsumer(bundleMap.toJSON());
+
+ var minifiedMap = new SourceMapGenerator({
+ file: 'bundle.min.js',
+ sourceRoot: '..'
+ });
+ minifiedMap.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 3, column: 3 },
+ source: 'temp/bundle.js'
+ });
+ minifiedMap.addMapping({
+ generated: { line: 11, column: 11 },
+ original: { line: 13, column: 13 },
+ source: 'temp/bundle.js'
+ });
+ minifiedMap.addMapping({
+ generated: { line: 21, column: 21 },
+ original: { line: 23, column: 23 },
+ source: 'temp/bundle.js'
+ });
+ minifiedMap = new SourceMapConsumer(minifiedMap.toJSON());
+
+ var expectedMap = function (sources) {
+ var map = new SourceMapGenerator({
+ file: 'bundle.min.js',
+ sourceRoot: '..'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 2, column: 2 },
+ source: sources[0]
+ });
+ map.setSourceContent(sources[0], 'foo coffee');
+ map.addMapping({
+ generated: { line: 11, column: 11 },
+ original: { line: 12, column: 12 },
+ source: sources[1]
+ });
+ map.setSourceContent(sources[1], 'bar coffee');
+ map.addMapping({
+ generated: { line: 21, column: 21 },
+ original: { line: 22, column: 22 },
+ source: sources[2]
+ });
+ map.setSourceContent(sources[2], 'baz coffee');
+ return map.toJSON();
+ }
+
+ var actualMap = function (aSourceMapPath) {
+ var map = SourceMapGenerator.fromSourceMap(minifiedMap);
+ // Note that relying on `bundleMap.file` (which is simply 'bundle.js')
+ // instead of supplying the second parameter wouldn't work here.
+ map.applySourceMap(bundleMap, '../temp/bundle.js', aSourceMapPath);
+ return map.toJSON();
+ }
+
+ util.assertEqualMaps(assert, actualMap('../temp/temp_maps'), expectedMap([
+ 'coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ util.assertEqualMaps(assert, actualMap('/app/temp/temp_maps'), expectedMap([
+ '/app/coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ util.assertEqualMaps(assert, actualMap('http://foo.org/app/temp/temp_maps'), expectedMap([
+ 'http://foo.org/app/coffee/foo.coffee',
+ 'http://foo.org/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ // If the third parameter is omitted or set to the current working
+ // directory we get incorrect source paths:
+
+ util.assertEqualMaps(assert, actualMap(), expectedMap([
+ '../coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ util.assertEqualMaps(assert, actualMap(''), expectedMap([
+ '../coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ util.assertEqualMaps(assert, actualMap('.'), expectedMap([
+ '../coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+
+ util.assertEqualMaps(assert, actualMap('./'), expectedMap([
+ '../coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee'
+ ]));
+ };
+
+ exports['test applySourceMap name handling'] = function (assert) {
+ // Imagine some CoffeeScript code being compiled into JavaScript and then
+ // minified.
+
+ var assertName = function(coffeeName, jsName, expectedName) {
+ var minifiedMap = new SourceMapGenerator({
+ file: 'test.js.min'
+ });
+ minifiedMap.addMapping({
+ generated: { line: 1, column: 4 },
+ original: { line: 1, column: 4 },
+ source: 'test.js',
+ name: jsName
+ });
+
+ var coffeeMap = new SourceMapGenerator({
+ file: 'test.js'
+ });
+ coffeeMap.addMapping({
+ generated: { line: 1, column: 4 },
+ original: { line: 1, column: 0 },
+ source: 'test.coffee',
+ name: coffeeName
+ });
+
+ minifiedMap.applySourceMap(new SourceMapConsumer(coffeeMap.toJSON()));
+
+ new SourceMapConsumer(minifiedMap.toJSON()).eachMapping(function(mapping) {
+ assert.equal(mapping.name, expectedName);
+ });
+ };
+
+ // `foo = 1` -> `var foo = 1;` -> `var a=1`
+ // CoffeeScript doesn’t rename variables, so there’s no need for it to
+ // provide names in its source maps. Minifiers do rename variables and
+ // therefore do provide names in their source maps. So that name should be
+ // retained if the original map lacks names.
+ assertName(null, 'foo', 'foo');
+
+ // `foo = 1` -> `var coffee$foo = 1;` -> `var a=1`
+ // Imagine that CoffeeScript prefixed all variables with `coffee$`. Even
+ // though the minifier then also provides a name, the original name is
+ // what corresponds to the source.
+ assertName('foo', 'coffee$foo', 'foo');
+
+ // `foo = 1` -> `var coffee$foo = 1;` -> `var coffee$foo=1`
+ // Minifiers can turn off variable mangling. Then there’s no need to
+ // provide names in the source map, but the names from the original map are
+ // still needed.
+ assertName('foo', null, 'foo');
+
+ // `foo = 1` -> `var foo = 1;` -> `var foo=1`
+ // No renaming at all.
+ assertName(null, null, null);
+ };
+
+ exports['test sorting with duplicate generated mappings'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'test.js'
+ });
+ map.addMapping({
+ generated: { line: 3, column: 0 },
+ original: { line: 2, column: 0 },
+ source: 'a.js'
+ });
+ map.addMapping({
+ generated: { line: 2, column: 0 }
+ });
+ map.addMapping({
+ generated: { line: 2, column: 0 }
+ });
+ map.addMapping({
+ generated: { line: 1, column: 0 },
+ original: { line: 1, column: 0 },
+ source: 'a.js'
+ });
+
+ util.assertEqualMaps(assert, map.toJSON(), {
+ version: 3,
+ file: 'test.js',
+ sources: ['a.js'],
+ names: [],
+ mappings: 'AAAA;A;AACA'
+ });
+ };
+
+ exports['test ignore duplicate mappings.'] = function (assert) {
+ var init = { file: 'min.js', sourceRoot: '/the/root' };
+ var map1, map2;
+
+ // null original source location
+ var nullMapping1 = {
+ generated: { line: 1, column: 0 }
+ };
+ var nullMapping2 = {
+ generated: { line: 2, column: 2 }
+ };
+
+ map1 = new SourceMapGenerator(init);
+ map2 = new SourceMapGenerator(init);
+
+ map1.addMapping(nullMapping1);
+ map1.addMapping(nullMapping1);
+
+ map2.addMapping(nullMapping1);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+
+ map1.addMapping(nullMapping2);
+ map1.addMapping(nullMapping1);
+
+ map2.addMapping(nullMapping2);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+
+ // original source location
+ var srcMapping1 = {
+ generated: { line: 1, column: 0 },
+ original: { line: 11, column: 0 },
+ source: 'srcMapping1.js'
+ };
+ var srcMapping2 = {
+ generated: { line: 2, column: 2 },
+ original: { line: 11, column: 0 },
+ source: 'srcMapping2.js'
+ };
+
+ map1 = new SourceMapGenerator(init);
+ map2 = new SourceMapGenerator(init);
+
+ map1.addMapping(srcMapping1);
+ map1.addMapping(srcMapping1);
+
+ map2.addMapping(srcMapping1);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+
+ map1.addMapping(srcMapping2);
+ map1.addMapping(srcMapping1);
+
+ map2.addMapping(srcMapping2);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+
+ // full original source and name information
+ var fullMapping1 = {
+ generated: { line: 1, column: 0 },
+ original: { line: 11, column: 0 },
+ source: 'fullMapping1.js',
+ name: 'fullMapping1'
+ };
+ var fullMapping2 = {
+ generated: { line: 2, column: 2 },
+ original: { line: 11, column: 0 },
+ source: 'fullMapping2.js',
+ name: 'fullMapping2'
+ };
+
+ map1 = new SourceMapGenerator(init);
+ map2 = new SourceMapGenerator(init);
+
+ map1.addMapping(fullMapping1);
+ map1.addMapping(fullMapping1);
+
+ map2.addMapping(fullMapping1);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+
+ map1.addMapping(fullMapping2);
+ map1.addMapping(fullMapping1);
+
+ map2.addMapping(fullMapping2);
+
+ util.assertEqualMaps(assert, map1.toJSON(), map2.toJSON());
+ };
+
+ exports['test github issue #72, check for duplicate names or sources'] = function (assert) {
+ var map = new SourceMapGenerator({
+ file: 'test.js'
+ });
+ map.addMapping({
+ generated: { line: 1, column: 1 },
+ original: { line: 2, column: 2 },
+ source: 'a.js',
+ name: 'foo'
+ });
+ map.addMapping({
+ generated: { line: 3, column: 3 },
+ original: { line: 4, column: 4 },
+ source: 'a.js',
+ name: 'foo'
+ });
+ util.assertEqualMaps(assert, map.toJSON(), {
+ version: 3,
+ file: 'test.js',
+ sources: ['a.js'],
+ names: ['foo'],
+ mappings: 'CACEA;;GAEEA'
+ });
+ };
+
+ exports['test setting sourcesContent to null when already null'] = function (assert) {
+ var smg = new SourceMapGenerator({ file: "foo.js" });
+ assert.doesNotThrow(function() {
+ smg.setSourceContent("bar.js", null);
+ });
+ };
+
+ exports['test applySourceMap with unexact match'] = function (assert) {
+ var map1 = new SourceMapGenerator({
+ file: 'bundled-source'
+ });
+ map1.addMapping({
+ generated: { line: 1, column: 4 },
+ original: { line: 1, column: 4 },
+ source: 'transformed-source'
+ });
+ map1.addMapping({
+ generated: { line: 2, column: 4 },
+ original: { line: 2, column: 4 },
+ source: 'transformed-source'
+ });
+
+ var map2 = new SourceMapGenerator({
+ file: 'transformed-source'
+ });
+ map2.addMapping({
+ generated: { line: 2, column: 0 },
+ original: { line: 1, column: 0 },
+ source: 'original-source'
+ });
+
+ var expectedMap = new SourceMapGenerator({
+ file: 'bundled-source'
+ });
+ expectedMap.addMapping({
+ generated: { line: 1, column: 4 },
+ original: { line: 1, column: 4 },
+ source: 'transformed-source'
+ });
+ expectedMap.addMapping({
+ generated: { line: 2, column: 4 },
+ original: { line: 1, column: 0 },
+ source: 'original-source'
+ });
+
+ map1.applySourceMap(new SourceMapConsumer(map2.toJSON()));
+
+ util.assertEqualMaps(assert, map1.toJSON(), expectedMap.toJSON());
+ };
+
+ exports['test issue #192'] = function (assert) {
+ var generator = new SourceMapGenerator();
+ generator.addMapping({
+ source: 'a.js',
+ generated: { line: 1, column: 10 },
+ original: { line: 1, column: 10 },
+ });
+ generator.addMapping({
+ source: 'b.js',
+ generated: { line: 1, column: 10 },
+ original: { line: 2, column: 20 },
+ });
+
+ var consumer = new SourceMapConsumer(generator.toJSON());
+
+ var n = 0;
+ consumer.eachMapping(function () { n++ });
+
+ assert.equal(n, 2,
+ "Should not de-duplicate mappings that have the same " +
+ "generated positions, but different original positions.");
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(2);
+ var util = __webpack_require__(4);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var MappingList = __webpack_require__(6).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(3);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+ var binarySearch = __webpack_require__(8);
+ var ArraySet = __webpack_require__(5).ArraySet;
+ var base64VLQ = __webpack_require__(2);
+ var quickSort = __webpack_require__(9).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var SourceMapGenerator = __webpack_require__(1).SourceMapGenerator;
+ var util = __webpack_require__(4);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ var code = "";
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+ }
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(4);
+
+ // This is a test mapping which maps functions from two different files
+ // (one.js and two.js) to a minified generated source.
+ //
+ // Here is one.js:
+ //
+ // ONE.foo = function (bar) {
+ // return baz(bar);
+ // };
+ //
+ // Here is two.js:
+ //
+ // TWO.inc = function (n) {
+ // return n + 1;
+ // };
+ //
+ // And here is the generated code (min.js):
+ //
+ // ONE.foo=function(a){return baz(a);};
+ // TWO.inc=function(a){return a+1;};
+ exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
+ " TWO.inc=function(a){return a+1;};";
+ exports.testMap = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapNoSourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapEmptySourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ // This mapping is identical to above, but uses the indexed format instead.
+ exports.indexedTestMap = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ }
+ ]
+ };
+ exports.indexedTestMapDifferentSourceRoots = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/different/root"
+ }
+ }
+ ]
+ };
+ exports.testMapWithSourcesContent = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapRelativeSources = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['./one.js', './two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.emptyMap = {
+ version: 3,
+ file: 'min.js',
+ names: [],
+ sources: [],
+ mappings: ''
+ };
+
+
+ function assertMapping(generatedLine, generatedColumn, originalSource,
+ originalLine, originalColumn, name, bias, map, assert,
+ dontTestGenerated, dontTestOriginal) {
+ if (!dontTestOriginal) {
+ var origMapping = map.originalPositionFor({
+ line: generatedLine,
+ column: generatedColumn,
+ bias: bias
+ });
+ assert.equal(origMapping.name, name,
+ 'Incorrect name, expected ' + JSON.stringify(name)
+ + ', got ' + JSON.stringify(origMapping.name));
+ assert.equal(origMapping.line, originalLine,
+ 'Incorrect line, expected ' + JSON.stringify(originalLine)
+ + ', got ' + JSON.stringify(origMapping.line));
+ assert.equal(origMapping.column, originalColumn,
+ 'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ + ', got ' + JSON.stringify(origMapping.column));
+
+ var expectedSource;
+
+ if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
+ expectedSource = originalSource;
+ } else if (originalSource) {
+ expectedSource = map.sourceRoot
+ ? util.join(map.sourceRoot, originalSource)
+ : originalSource;
+ } else {
+ expectedSource = null;
+ }
+
+ assert.equal(origMapping.source, expectedSource,
+ 'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ + ', got ' + JSON.stringify(origMapping.source));
+ }
+
+ if (!dontTestGenerated) {
+ var genMapping = map.generatedPositionFor({
+ source: originalSource,
+ line: originalLine,
+ column: originalColumn,
+ bias: bias
+ });
+ assert.equal(genMapping.line, generatedLine,
+ 'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ + ', got ' + JSON.stringify(genMapping.line));
+ assert.equal(genMapping.column, generatedColumn,
+ 'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ + ', got ' + JSON.stringify(genMapping.column));
+ }
+ }
+ exports.assertMapping = assertMapping;
+
+ function assertEqualMaps(assert, actualMap, expectedMap) {
+ assert.equal(actualMap.version, expectedMap.version, "version mismatch");
+ assert.equal(actualMap.file, expectedMap.file, "file mismatch");
+ assert.equal(actualMap.names.length,
+ expectedMap.names.length,
+ "names length mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ for (var i = 0; i < actualMap.names.length; i++) {
+ assert.equal(actualMap.names[i],
+ expectedMap.names[i],
+ "names[" + i + "] mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ }
+ assert.equal(actualMap.sources.length,
+ expectedMap.sources.length,
+ "sources length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ for (var i = 0; i < actualMap.sources.length; i++) {
+ assert.equal(actualMap.sources[i],
+ expectedMap.sources[i],
+ "sources[" + i + "] length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ }
+ assert.equal(actualMap.sourceRoot,
+ expectedMap.sourceRoot,
+ "sourceRoot mismatch: " +
+ actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
+ assert.equal(actualMap.mappings, expectedMap.mappings,
+ "mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
+ if (actualMap.sourcesContent) {
+ assert.equal(actualMap.sourcesContent.length,
+ expectedMap.sourcesContent.length,
+ "sourcesContent length mismatch");
+ for (var i = 0; i < actualMap.sourcesContent.length; i++) {
+ assert.equal(actualMap.sourcesContent[i],
+ expectedMap.sourcesContent[i],
+ "sourcesContent[" + i + "] mismatch");
+ }
+ }
+ }
+ exports.assertEqualMaps = assertEqualMaps;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgYWYzMzMzY2Y1YzNkNDVlOWZjMGYiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LXNvdXJjZS1tYXAtZ2VuZXJhdG9yLmpzIiwid2VicGFjazovLy8uL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvci5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmFzZTY0LXZscS5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmFzZTY0LmpzIiwid2VicGFjazovLy8uL2xpYi91dGlsLmpzIiwid2VicGFjazovLy8uL2xpYi9hcnJheS1zZXQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL21hcHBpbmctbGlzdC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW1hcC1jb25zdW1lci5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYmluYXJ5LXNlYXJjaC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvcXVpY2stc29ydC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW5vZGUuanMiLCJ3ZWJwYWNrOi8vLy4vdGVzdC91dGlsLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsdUJBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQ3RDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQSxxQkFBb0I7QUFDcEIsUUFBTztBQUNQLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBLHFCQUFvQixxQkFBcUI7QUFDekM7QUFDQSxvQkFBbUI7QUFDbkIsUUFBTztBQUNQLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBLHFCQUFvQixxQkFBcUI7QUFDekM7QUFDQSxvQkFBbUIscUJBQXFCO0FBQ3hDO0FBQ0EsUUFBTztBQUNQLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBLHdCQUF1QjtBQUN2QixNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBLHFCQUFvQixxQkFBcUI7QUFDekMsb0JBQW1CO0FBQ25CLFFBQU87QUFDUCxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBLHdCQUF1QjtBQUN2QixNQUFLOztBQUVMO0FBQ0E7QUFDQTtBQUNBLHFCQUFvQixxQkFBcUI7QUFDekMsb0JBQW1CO0FBQ25CLFFBQU87QUFDUCxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIsc0JBQXNCO0FBQ3ZDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHNCQUFzQjtBQUN4QyxrQkFBaUIsc0JBQXNCO0FBQ3ZDO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDLGtCQUFpQixzQkFBc0I7QUFDdkM7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixzQkFBc0I7QUFDeEMsa0JBQWlCLHNCQUFzQjtBQUN2QztBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkMsa0JBQWlCLHFCQUFxQjtBQUN0QztBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkMsa0JBQWlCLHFCQUFxQjtBQUN0QztBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkMsa0JBQWlCLHNCQUFzQjtBQUN2QztBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixzQkFBc0I7QUFDeEMsa0JBQWlCLHNCQUFzQjtBQUN2QztBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHNCQUFzQjtBQUN4QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHNCQUFzQjtBQUN4QyxrQkFBaUIsc0JBQXNCO0FBQ3ZDO0FBQ0E7QUFDQSxNQUFLOztBQUVMOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQSxNQUFLOztBQUVMO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBLG1CQUFrQix1QkFBdUI7QUFDekMsa0JBQWlCLHVCQUF1QjtBQUN4QztBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0EsbUJBQWtCLHVCQUF1QjtBQUN6QyxrQkFBaUIsdUJBQXVCO0FBQ3hDO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHVCQUF1QjtBQUN6QyxrQkFBaUIsdUJBQXVCO0FBQ3hDO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHVCQUF1QjtBQUN6QyxrQkFBaUIsdUJBQXVCO0FBQ3hDO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0EscUJBQW9CLHFCQUFxQjtBQUN6QyxvQkFBbUIscUJBQXFCO0FBQ3hDO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQSxxQkFBb0IsdUJBQXVCO0FBQzNDLG9CQUFtQix1QkFBdUI7QUFDMUM7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBLHFCQUFvQix1QkFBdUI7QUFDM0Msb0JBQW1CLHVCQUF1QjtBQUMxQztBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLHFCQUFvQixxQkFBcUI7QUFDekMsb0JBQW1CLHFCQUFxQjtBQUN4QztBQUNBO0FBQ0EsUUFBTzs7QUFFUDtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0EscUJBQW9CLHFCQUFxQjtBQUN6QyxvQkFBbUIscUJBQXFCO0FBQ3hDO0FBQ0E7QUFDQSxRQUFPOztBQUVQOztBQUVBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUEsa0NBQWlDO0FBQ2pDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUEseUNBQXdDO0FBQ3hDO0FBQ0E7QUFDQTtBQUNBOztBQUVBLHlDQUF3QztBQUN4QztBQUNBO0FBQ0E7QUFDQTs7QUFFQSxrQ0FBaUM7QUFDakM7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkMsa0JBQWlCLHFCQUFxQjtBQUN0QztBQUNBLE1BQUs7QUFDTDtBQUNBLG1CQUFrQjtBQUNsQixNQUFLO0FBQ0w7QUFDQSxtQkFBa0I7QUFDbEIsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUJBQXNCLEVBQUU7QUFDeEIsTUFBSztBQUNMOztBQUVBO0FBQ0EsaUJBQWdCO0FBQ2hCOztBQUVBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEI7QUFDQTtBQUNBLG1CQUFrQjtBQUNsQjs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIsc0JBQXNCO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkMsa0JBQWlCLHNCQUFzQjtBQUN2QztBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBOztBQUVBO0FBQ0E7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixzQkFBc0I7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixzQkFBc0I7QUFDdkM7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QyxrQkFBaUIscUJBQXFCO0FBQ3RDO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDLGtCQUFpQixxQkFBcUI7QUFDdEM7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUJBQXdCO0FBQ3hCLE1BQUs7QUFDTDs7QUFFQTtBQUNBLHVDQUFzQyxpQkFBaUI7QUFDdkQ7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxxQkFBb0IscUJBQXFCO0FBQ3pDLG9CQUFtQixxQkFBcUI7QUFDeEM7QUFDQSxRQUFPO0FBQ1A7QUFDQSxxQkFBb0IscUJBQXFCO0FBQ3pDLG9CQUFtQixxQkFBcUI7QUFDeEM7QUFDQSxRQUFPOztBQUVQO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxxQkFBb0IscUJBQXFCO0FBQ3pDLG9CQUFtQixxQkFBcUI7QUFDeEM7QUFDQSxRQUFPOztBQUVQO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxxQkFBb0IscUJBQXFCO0FBQ3pDLG9CQUFtQixxQkFBcUI7QUFDeEM7QUFDQSxRQUFPO0FBQ1A7QUFDQSxxQkFBb0IscUJBQXFCO0FBQ3pDLG9CQUFtQixxQkFBcUI7QUFDeEM7QUFDQSxRQUFPOztBQUVQOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDLGtCQUFpQixzQkFBc0I7QUFDdkMsTUFBSztBQUNMO0FBQ0E7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDLGtCQUFpQixzQkFBc0I7QUFDdkMsTUFBSzs7QUFFTDs7QUFFQTtBQUNBLHVDQUFzQyxNQUFNOztBQUU1QztBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbnVCQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBLFFBQU87QUFDUDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsNkNBQTRDLFNBQVM7QUFDckQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0EseUJBQXdCO0FBQ3hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUMzWUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNERBQTJEO0FBQzNELHFCQUFvQjtBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQzVJQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCO0FBQ2xCLG1CQUFrQjs7QUFFbEIsc0JBQXFCO0FBQ3JCLHVCQUFzQjs7QUFFdEIsbUJBQWtCO0FBQ2xCLG1CQUFrQjs7QUFFbEIsbUJBQWtCO0FBQ2xCLG9CQUFtQjs7QUFFbkI7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQ25FQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsaURBQWdELFFBQVE7QUFDeEQ7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDaFhBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF3QyxTQUFTO0FBQ2pEO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdkdBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCO0FBQ2xCOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDL0VBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5REFBd0Q7QUFDeEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQSxzQkFBcUI7QUFDckI7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWE7O0FBRWI7QUFDQTtBQUNBLFVBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYTs7QUFFYjtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsOEJBQTZCLE1BQU07QUFDbkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5REFBd0Q7QUFDeEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPOztBQUVQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQSx5REFBd0QsWUFBWTtBQUNwRTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLHNDQUFxQztBQUNyQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsNEJBQTJCLGNBQWM7QUFDekM7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDBCQUF5Qix3Q0FBd0M7QUFDakU7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtEQUFpRCxtQkFBbUIsRUFBRTtBQUN0RTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBbUIsb0JBQW9CO0FBQ3ZDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQ0FBK0IsTUFBTTtBQUNyQztBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hELHdCQUF1QiwrQ0FBK0M7QUFDdEU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLFVBQVM7QUFDVDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLDJCQUEyQjtBQUNoRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hEO0FBQ0E7QUFDQSx3QkFBdUIsNEJBQTRCO0FBQ25EOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDempDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7Ozs7OztBQy9HQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsY0FBYSxNQUFNO0FBQ25CO0FBQ0EsY0FBYSxTQUFTO0FBQ3RCO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0EsY0FBYSxPQUFPO0FBQ3BCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsT0FBTztBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsY0FBYSxNQUFNO0FBQ25CO0FBQ0EsY0FBYSxTQUFTO0FBQ3RCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7Ozs7OztBQ2xIQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTzs7QUFFUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0NBQW1DLFFBQVE7QUFDM0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsZ0RBQStDLFNBQVM7QUFDeEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUJBQXNCO0FBQ3RCO0FBQ0E7QUFDQSx5Q0FBd0M7QUFDeEM7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0JBQWlCLFdBQVc7QUFDNUI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGtEQUFpRCxTQUFTO0FBQzFEO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0EsNENBQTJDLFNBQVM7QUFDcEQ7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFhO0FBQ2I7QUFDQTtBQUNBO0FBQ0EsY0FBYTtBQUNiO0FBQ0EsWUFBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBLCtDQUE4QyxjQUFjO0FBQzVEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsWUFBVztBQUNYO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnQkFBZTtBQUNmO0FBQ0E7QUFDQTtBQUNBLGdCQUFlO0FBQ2Y7QUFDQSxjQUFhO0FBQ2I7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQSxNQUFLOztBQUVMLGFBQVk7QUFDWjs7QUFFQTtBQUNBOzs7Ozs7O0FDeFpBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDRCQUEyQjtBQUMzQiw0QkFBMkI7QUFDM0IscURBQW9ELGdCQUFnQjtBQUNwRSxxREFBb0QsYUFBYTtBQUNqRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlDQUF3QztBQUN4QyxpQ0FBZ0M7QUFDaEMsaUJBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1Q0FBc0M7QUFDdEMsOEJBQTZCO0FBQzdCLGlCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBd0M7QUFDeEMsaUNBQWdDO0FBQ2hDLGlCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXNDO0FBQ3RDLDhCQUE2QjtBQUM3QixpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1DQUFrQztBQUNsQywyQkFBMEI7QUFDMUIsV0FBVTtBQUNWLGlDQUFnQztBQUNoQyx3QkFBdUI7QUFDdkIsV0FBVTtBQUNWO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBa0M7QUFDbEMsMkJBQTBCO0FBQzFCLFdBQVU7QUFDVixpQ0FBZ0M7QUFDaEMsd0JBQXVCO0FBQ3ZCLFdBQVU7QUFDVjtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQiw0QkFBNEI7QUFDL0M7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esb0JBQW1CLDhCQUE4QjtBQUNqRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIscUNBQXFDO0FBQzFEO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EiLCJmaWxlIjoidGVzdF9zb3VyY2VfbWFwX2dlbmVyYXRvci5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgYWYzMzMzY2Y1YzNkNDVlOWZjMGZcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBTb3VyY2VNYXBHZW5lcmF0b3IgPSByZXF1aXJlKCcuLi9saWIvc291cmNlLW1hcC1nZW5lcmF0b3InKS5Tb3VyY2VNYXBHZW5lcmF0b3I7XG4gIHZhciBTb3VyY2VNYXBDb25zdW1lciA9IHJlcXVpcmUoJy4uL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyJykuU291cmNlTWFwQ29uc3VtZXI7XG4gIHZhciBTb3VyY2VOb2RlID0gcmVxdWlyZSgnLi4vbGliL3NvdXJjZS1ub2RlJykuU291cmNlTm9kZTtcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICBleHBvcnRzWyd0ZXN0IHNvbWUgc2ltcGxlIHN0dWZmJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2Zvby5qcycsXG4gICAgICBzb3VyY2VSb290OiAnLidcbiAgICB9KTtcbiAgICBhc3NlcnQub2sodHJ1ZSk7XG5cbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcigpLnRvSlNPTigpO1xuICAgIGFzc2VydC5vayghKCdmaWxlJyBpbiBtYXApKTtcbiAgICBhc3NlcnQub2soISgnc291cmNlUm9vdCcgaW4gbWFwKSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBKU09OIHNlcmlhbGl6YXRpb24nXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnZm9vLmpzJyxcbiAgICAgIHNvdXJjZVJvb3Q6ICcuJ1xuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChtYXAudG9TdHJpbmcoKSwgSlNPTi5zdHJpbmdpZnkobWFwKSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhZGRpbmcgbWFwcGluZ3MgKGNhc2UgMSknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnZ2VuZXJhdGVkLWZvby5qcycsXG4gICAgICBzb3VyY2VSb290OiAnLidcbiAgICB9KTtcblxuICAgIGFzc2VydC5kb2VzTm90VGhyb3coZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAxIH1cbiAgICAgIH0pO1xuICAgIH0pO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYWRkaW5nIG1hcHBpbmdzIChjYXNlIDIpJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2dlbmVyYXRlZC1mb28uanMnLFxuICAgICAgc291cmNlUm9vdDogJy4nXG4gICAgfSk7XG5cbiAgICBhc3NlcnQuZG9lc05vdFRocm93KGZ1bmN0aW9uICgpIHtcbiAgICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgICBzb3VyY2U6ICdiYXIuanMnLFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhZGRpbmcgbWFwcGluZ3MgKGNhc2UgMyknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnZ2VuZXJhdGVkLWZvby5qcycsXG4gICAgICBzb3VyY2VSb290OiAnLidcbiAgICB9KTtcblxuICAgIGFzc2VydC5kb2VzTm90VGhyb3coZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICAgIHNvdXJjZTogJ2Jhci5qcycsXG4gICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgICBuYW1lOiAnc29tZVRva2VuJ1xuICAgICAgfSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBhZGRpbmcgbWFwcGluZ3MgKGludmFsaWQpJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2dlbmVyYXRlZC1mb28uanMnLFxuICAgICAgc291cmNlUm9vdDogJy4nXG4gICAgfSk7XG5cbiAgICAvLyBOb3QgZW5vdWdoIGluZm8uXG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBtYXAuYWRkTWFwcGluZyh7fSk7XG4gICAgfSk7XG5cbiAgICAvLyBPcmlnaW5hbCBmaWxlIHBvc2l0aW9uLCBidXQgbm8gc291cmNlLlxuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9XG4gICAgICB9KTtcbiAgICB9KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGFkZGluZyBtYXBwaW5ncyB3aXRoIHNraXBWYWxpZGF0aW9uJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2dlbmVyYXRlZC1mb28uanMnLFxuICAgICAgc291cmNlUm9vdDogJy4nLFxuICAgICAgc2tpcFZhbGlkYXRpb246IHRydWVcbiAgICB9KTtcblxuICAgIC8vIE5vdCBlbm91Z2ggaW5mbywgY2F1Z2h0IGJ5IGB1dGlsLmdldEFyZ3NgXG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBtYXAuYWRkTWFwcGluZyh7fSk7XG4gICAgfSk7XG5cbiAgICAvLyBPcmlnaW5hbCBmaWxlIHBvc2l0aW9uLCBidXQgbm8gc291cmNlLiBOb3QgY2hlY2tlZC5cbiAgICBhc3NlcnQuZG9lc05vdFRocm93KGZ1bmN0aW9uICgpIHtcbiAgICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfVxuICAgICAgfSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHRoZSBjb3JyZWN0IG1hcHBpbmdzIGFyZSBiZWluZyBnZW5lcmF0ZWQnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnbWluLmpzJyxcbiAgICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnXG4gICAgfSk7XG5cbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIHNvdXJjZTogJ29uZS5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiA1IH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDUgfSxcbiAgICAgIHNvdXJjZTogJ29uZS5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiA5IH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDExIH0sXG4gICAgICBzb3VyY2U6ICdvbmUuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMTggfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMjEgfSxcbiAgICAgIHNvdXJjZTogJ29uZS5qcycsXG4gICAgICBuYW1lOiAnYmFyJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDIxIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDMgfSxcbiAgICAgIHNvdXJjZTogJ29uZS5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAyOCB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAxMCB9LFxuICAgICAgc291cmNlOiAnb25lLmpzJyxcbiAgICAgIG5hbWU6ICdiYXonXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMzIgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMTQgfSxcbiAgICAgIHNvdXJjZTogJ29uZS5qcycsXG4gICAgICBuYW1lOiAnYmFyJ1xuICAgIH0pO1xuXG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBzb3VyY2U6ICd0d28uanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogNSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiA1IH0sXG4gICAgICBzb3VyY2U6ICd0d28uanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogOSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxMSB9LFxuICAgICAgc291cmNlOiAndHdvLmpzJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDE4IH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDIxIH0sXG4gICAgICBzb3VyY2U6ICd0d28uanMnLFxuICAgICAgbmFtZTogJ24nXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMjEgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMyB9LFxuICAgICAgc291cmNlOiAndHdvLmpzJ1xuICAgIH0pO1xuICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDI4IH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDEwIH0sXG4gICAgICBzb3VyY2U6ICd0d28uanMnLFxuICAgICAgbmFtZTogJ24nXG4gICAgfSk7XG5cbiAgICBtYXAgPSBKU09OLnBhcnNlKG1hcC50b1N0cmluZygpKTtcblxuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgbWFwLCB1dGlsLnRlc3RNYXApO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhhdCBhZGRpbmcgYSBtYXBwaW5nIHdpdGggYW4gZW1wdHkgc3RyaW5nIG5hbWUgZG9lcyBub3QgYnJlYWsgZ2VuZXJhdGlvbiddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdnZW5lcmF0ZWQtZm9vLmpzJyxcbiAgICAgIHNvdXJjZVJvb3Q6ICcuJ1xuICAgIH0pO1xuXG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgc291cmNlOiAnYmFyLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgbmFtZTogJydcbiAgICB9KTtcblxuICAgIGFzc2VydC5kb2VzTm90VGhyb3coZnVuY3Rpb24gKCkge1xuICAgICAgSlNPTi5wYXJzZShtYXAudG9TdHJpbmcoKSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCB0aGF0IHNvdXJjZSBjb250ZW50IGNhbiBiZSBzZXQnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnbWluLmpzJyxcbiAgICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBzb3VyY2U6ICdvbmUuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAxIH0sXG4gICAgICBzb3VyY2U6ICd0d28uanMnXG4gICAgfSk7XG4gICAgbWFwLnNldFNvdXJjZUNvbnRlbnQoJ29uZS5qcycsICdvbmUgZmlsZSBjb250ZW50Jyk7XG5cbiAgICBtYXAgPSBKU09OLnBhcnNlKG1hcC50b1N0cmluZygpKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZXNbMF0sICdvbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZXNbMV0sICd0d28uanMnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZXNDb250ZW50WzBdLCAnb25lIGZpbGUgY29udGVudCcpO1xuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlc0NvbnRlbnRbMV0sIG51bGwpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLmZyb21Tb3VyY2VNYXAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gU291cmNlTWFwR2VuZXJhdG9yLmZyb21Tb3VyY2VNYXAobmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwudGVzdE1hcCkpO1xuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgbWFwLnRvSlNPTigpLCB1dGlsLnRlc3RNYXApO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLmZyb21Tb3VyY2VNYXAgd2l0aCBzb3VyY2VzQ29udGVudCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBtYXAgPSBTb3VyY2VNYXBHZW5lcmF0b3IuZnJvbVNvdXJjZU1hcChcbiAgICAgIG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXBXaXRoU291cmNlc0NvbnRlbnQpKTtcbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIG1hcC50b0pTT04oKSwgdXRpbC50ZXN0TWFwV2l0aFNvdXJjZXNDb250ZW50KTtcbiAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGFwcGx5U291cmNlTWFwJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5vZGUgPSBuZXcgU291cmNlTm9kZShudWxsLCBudWxsLCBudWxsLCBbXG4gICAgICBuZXcgU291cmNlTm9kZSgyLCAwLCAnZmlsZVgnLCAnbGluZVgyXFxuJyksXG4gICAgICAnZ2VuQTFcXG4nLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVZJywgJ2xpbmVZMlxcbicpLFxuICAgICAgJ2dlbkEyXFxuJyxcbiAgICAgIG5ldyBTb3VyY2VOb2RlKDEsIDAsICdmaWxlWCcsICdsaW5lWDFcXG4nKSxcbiAgICAgICdnZW5BM1xcbicsXG4gICAgICBuZXcgU291cmNlTm9kZSgxLCAwLCAnZmlsZVknLCAnbGluZVkxXFxuJylcbiAgICBdKTtcbiAgICB2YXIgbWFwU3RlcDEgPSBub2RlLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZmlsZUEnXG4gICAgfSkubWFwO1xuICAgIG1hcFN0ZXAxLnNldFNvdXJjZUNvbnRlbnQoJ2ZpbGVYJywgJ2xpbmVYMVxcbmxpbmVYMlxcbicpO1xuICAgIG1hcFN0ZXAxID0gbWFwU3RlcDEudG9KU09OKCk7XG5cbiAgICBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgW1xuICAgICAgJ2dlbjFcXG4nLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgJ2ZpbGVBJywgJ2xpbmVBMVxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVBJywgJ2xpbmVBMlxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMywgMCwgJ2ZpbGVBJywgJ2xpbmVBM1xcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoNCwgMCwgJ2ZpbGVBJywgJ2xpbmVBNFxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgJ2ZpbGVCJywgJ2xpbmVCMVxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVCJywgJ2xpbmVCMlxcbicpLFxuICAgICAgJ2dlbjJcXG4nXG4gICAgXSk7XG4gICAgdmFyIG1hcFN0ZXAyID0gbm9kZS50b1N0cmluZ1dpdGhTb3VyY2VNYXAoe1xuICAgICAgZmlsZTogJ2ZpbGVHZW4nXG4gICAgfSkubWFwO1xuICAgIG1hcFN0ZXAyLnNldFNvdXJjZUNvbnRlbnQoJ2ZpbGVCJywgJ2xpbmVCMVxcbmxpbmVCMlxcbicpO1xuICAgIG1hcFN0ZXAyID0gbWFwU3RlcDIudG9KU09OKCk7XG5cbiAgICBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgW1xuICAgICAgJ2dlbjFcXG4nLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVYJywgJ2xpbmVBMVxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVBJywgJ2xpbmVBMlxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVZJywgJ2xpbmVBM1xcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoNCwgMCwgJ2ZpbGVBJywgJ2xpbmVBNFxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgJ2ZpbGVCJywgJ2xpbmVCMVxcbicpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2ZpbGVCJywgJ2xpbmVCMlxcbicpLFxuICAgICAgJ2dlbjJcXG4nXG4gICAgXSk7XG4gICAgdmFyIGV4cGVjdGVkTWFwID0gbm9kZS50b1N0cmluZ1dpdGhTb3VyY2VNYXAoe1xuICAgICAgZmlsZTogJ2ZpbGVHZW4nXG4gICAgfSkubWFwO1xuICAgIGV4cGVjdGVkTWFwLnNldFNvdXJjZUNvbnRlbnQoJ2ZpbGVYJywgJ2xpbmVYMVxcbmxpbmVYMlxcbicpO1xuICAgIGV4cGVjdGVkTWFwLnNldFNvdXJjZUNvbnRlbnQoJ2ZpbGVCJywgJ2xpbmVCMVxcbmxpbmVCMlxcbicpO1xuICAgIGV4cGVjdGVkTWFwID0gZXhwZWN0ZWRNYXAudG9KU09OKCk7XG5cbiAgICAvLyBhcHBseSBzb3VyY2UgbWFwIFwibWFwU3RlcDFcIiB0byBcIm1hcFN0ZXAyXCJcbiAgICB2YXIgZ2VuZXJhdG9yID0gU291cmNlTWFwR2VuZXJhdG9yLmZyb21Tb3VyY2VNYXAobmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcFN0ZXAyKSk7XG4gICAgZ2VuZXJhdG9yLmFwcGx5U291cmNlTWFwKG5ldyBTb3VyY2VNYXBDb25zdW1lcihtYXBTdGVwMSkpO1xuICAgIHZhciBhY3R1YWxNYXAgPSBnZW5lcmF0b3IudG9KU09OKCk7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCwgZXhwZWN0ZWRNYXApO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYXBwbHlTb3VyY2VNYXAgdGhyb3dzIHdoZW4gZmlsZSBpcyBtaXNzaW5nJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ3Rlc3QuanMnXG4gICAgfSk7XG4gICAgdmFyIG1hcDIgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKCk7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbigpIHtcbiAgICAgIG1hcC5hcHBseVNvdXJjZU1hcChuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwMi50b0pTT04oKSkpO1xuICAgIH0pO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgdGhlIHR3byBhZGRpdGlvbmFsIHBhcmFtZXRlcnMgb2YgYXBwbHlTb3VyY2VNYXAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICAvLyBBc3N1bWUgdGhlIGZvbGxvd2luZyBkaXJlY3Rvcnkgc3RydWN0dXJlOlxuICAgIC8vXG4gICAgLy8gaHR0cDovL2Zvby5vcmcvXG4gICAgLy8gICBiYXIuY29mZmVlXG4gICAgLy8gICBhcHAvXG4gICAgLy8gICAgIGNvZmZlZS9cbiAgICAvLyAgICAgICBmb28uY29mZmVlXG4gICAgLy8gICAgIHRlbXAvXG4gICAgLy8gICAgICAgYnVuZGxlLmpzXG4gICAgLy8gICAgICAgdGVtcF9tYXBzL1xuICAgIC8vICAgICAgICAgYnVuZGxlLmpzLm1hcFxuICAgIC8vICAgICBwdWJsaWMvXG4gICAgLy8gICAgICAgYnVuZGxlLm1pbi5qc1xuICAgIC8vICAgICAgIGJ1bmRsZS5taW4uanMubWFwXG4gICAgLy9cbiAgICAvLyBodHRwOi8vd3d3LmV4YW1wbGUuY29tL1xuICAgIC8vICAgYmF6LmNvZmZlZVxuXG4gICAgdmFyIGJ1bmRsZU1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ2J1bmRsZS5qcydcbiAgICB9KTtcbiAgICBidW5kbGVNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMywgY29sdW1uOiAzIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIHNvdXJjZTogJy4uLy4uL2NvZmZlZS9mb28uY29mZmVlJ1xuICAgIH0pO1xuICAgIGJ1bmRsZU1hcC5zZXRTb3VyY2VDb250ZW50KCcuLi8uLi9jb2ZmZWUvZm9vLmNvZmZlZScsICdmb28gY29mZmVlJyk7XG4gICAgYnVuZGxlTWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEzLCBjb2x1bW46IDEzIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxMiwgY29sdW1uOiAxMiB9LFxuICAgICAgc291cmNlOiAnL2Jhci5jb2ZmZWUnXG4gICAgfSk7XG4gICAgYnVuZGxlTWFwLnNldFNvdXJjZUNvbnRlbnQoJy9iYXIuY29mZmVlJywgJ2JhciBjb2ZmZWUnKTtcbiAgICBidW5kbGVNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMjMsIGNvbHVtbjogMjMgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIyLCBjb2x1bW46IDIyIH0sXG4gICAgICBzb3VyY2U6ICdodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Jhei5jb2ZmZWUnXG4gICAgfSk7XG4gICAgYnVuZGxlTWFwLnNldFNvdXJjZUNvbnRlbnQoXG4gICAgICAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJyxcbiAgICAgICdiYXogY29mZmVlJ1xuICAgICk7XG4gICAgYnVuZGxlTWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKGJ1bmRsZU1hcC50b0pTT04oKSk7XG5cbiAgICB2YXIgbWluaWZpZWRNYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdidW5kbGUubWluLmpzJyxcbiAgICAgIHNvdXJjZVJvb3Q6ICcuLidcbiAgICB9KTtcbiAgICBtaW5pZmllZE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDMsIGNvbHVtbjogMyB9LFxuICAgICAgc291cmNlOiAndGVtcC9idW5kbGUuanMnXG4gICAgfSk7XG4gICAgbWluaWZpZWRNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMTEsIGNvbHVtbjogMTEgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEzLCBjb2x1bW46IDEzIH0sXG4gICAgICBzb3VyY2U6ICd0ZW1wL2J1bmRsZS5qcydcbiAgICB9KTtcbiAgICBtaW5pZmllZE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyMSwgY29sdW1uOiAyMSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMjMsIGNvbHVtbjogMjMgfSxcbiAgICAgIHNvdXJjZTogJ3RlbXAvYnVuZGxlLmpzJ1xuICAgIH0pO1xuICAgIG1pbmlmaWVkTWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1pbmlmaWVkTWFwLnRvSlNPTigpKTtcblxuICAgIHZhciBleHBlY3RlZE1hcCA9IGZ1bmN0aW9uIChzb3VyY2VzKSB7XG4gICAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICAgIGZpbGU6ICdidW5kbGUubWluLmpzJyxcbiAgICAgICAgc291cmNlUm9vdDogJy4uJ1xuICAgICAgfSk7XG4gICAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEgfSxcbiAgICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICAgIHNvdXJjZTogc291cmNlc1swXVxuICAgICAgfSk7XG4gICAgICBtYXAuc2V0U291cmNlQ29udGVudChzb3VyY2VzWzBdLCAnZm9vIGNvZmZlZScpO1xuICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMTEsIGNvbHVtbjogMTEgfSxcbiAgICAgICAgb3JpZ2luYWw6IHsgbGluZTogMTIsIGNvbHVtbjogMTIgfSxcbiAgICAgICAgc291cmNlOiBzb3VyY2VzWzFdXG4gICAgICB9KTtcbiAgICAgIG1hcC5zZXRTb3VyY2VDb250ZW50KHNvdXJjZXNbMV0sICdiYXIgY29mZmVlJyk7XG4gICAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyMSwgY29sdW1uOiAyMSB9LFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAyMiwgY29sdW1uOiAyMiB9LFxuICAgICAgICBzb3VyY2U6IHNvdXJjZXNbMl1cbiAgICAgIH0pO1xuICAgICAgbWFwLnNldFNvdXJjZUNvbnRlbnQoc291cmNlc1syXSwgJ2JheiBjb2ZmZWUnKTtcbiAgICAgIHJldHVybiBtYXAudG9KU09OKCk7XG4gICAgfVxuXG4gICAgdmFyIGFjdHVhbE1hcCA9IGZ1bmN0aW9uIChhU291cmNlTWFwUGF0aCkge1xuICAgICAgdmFyIG1hcCA9IFNvdXJjZU1hcEdlbmVyYXRvci5mcm9tU291cmNlTWFwKG1pbmlmaWVkTWFwKTtcbiAgICAgIC8vIE5vdGUgdGhhdCByZWx5aW5nIG9uIGBidW5kbGVNYXAuZmlsZWAgKHdoaWNoIGlzIHNpbXBseSAnYnVuZGxlLmpzJylcbiAgICAgIC8vIGluc3RlYWQgb2Ygc3VwcGx5aW5nIHRoZSBzZWNvbmQgcGFyYW1ldGVyIHdvdWxkbid0IHdvcmsgaGVyZS5cbiAgICAgIG1hcC5hcHBseVNvdXJjZU1hcChidW5kbGVNYXAsICcuLi90ZW1wL2J1bmRsZS5qcycsIGFTb3VyY2VNYXBQYXRoKTtcbiAgICAgIHJldHVybiBtYXAudG9KU09OKCk7XG4gICAgfVxuXG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBhY3R1YWxNYXAoJy4uL3RlbXAvdGVtcF9tYXBzJyksIGV4cGVjdGVkTWFwKFtcbiAgICAgICdjb2ZmZWUvZm9vLmNvZmZlZScsXG4gICAgICAnL2Jhci5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYmF6LmNvZmZlZSdcbiAgICBdKSk7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCgnL2FwcC90ZW1wL3RlbXBfbWFwcycpLCBleHBlY3RlZE1hcChbXG4gICAgICAnL2FwcC9jb2ZmZWUvZm9vLmNvZmZlZScsXG4gICAgICAnL2Jhci5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYmF6LmNvZmZlZSdcbiAgICBdKSk7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCgnaHR0cDovL2Zvby5vcmcvYXBwL3RlbXAvdGVtcF9tYXBzJyksIGV4cGVjdGVkTWFwKFtcbiAgICAgICdodHRwOi8vZm9vLm9yZy9hcHAvY29mZmVlL2Zvby5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly9mb28ub3JnL2Jhci5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYmF6LmNvZmZlZSdcbiAgICBdKSk7XG5cbiAgICAvLyBJZiB0aGUgdGhpcmQgcGFyYW1ldGVyIGlzIG9taXR0ZWQgb3Igc2V0IHRvIHRoZSBjdXJyZW50IHdvcmtpbmdcbiAgICAvLyBkaXJlY3Rvcnkgd2UgZ2V0IGluY29ycmVjdCBzb3VyY2UgcGF0aHM6XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCgpLCBleHBlY3RlZE1hcChbXG4gICAgICAnLi4vY29mZmVlL2Zvby5jb2ZmZWUnLFxuICAgICAgJy9iYXIuY29mZmVlJyxcbiAgICAgICdodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Jhei5jb2ZmZWUnXG4gICAgXSkpO1xuXG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBhY3R1YWxNYXAoJycpLCBleHBlY3RlZE1hcChbXG4gICAgICAnLi4vY29mZmVlL2Zvby5jb2ZmZWUnLFxuICAgICAgJy9iYXIuY29mZmVlJyxcbiAgICAgICdodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Jhei5jb2ZmZWUnXG4gICAgXSkpO1xuXG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBhY3R1YWxNYXAoJy4nKSwgZXhwZWN0ZWRNYXAoW1xuICAgICAgJy4uL2NvZmZlZS9mb28uY29mZmVlJyxcbiAgICAgICcvYmFyLmNvZmZlZScsXG4gICAgICAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJ1xuICAgIF0pKTtcblxuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgYWN0dWFsTWFwKCcuLycpLCBleHBlY3RlZE1hcChbXG4gICAgICAnLi4vY29mZmVlL2Zvby5jb2ZmZWUnLFxuICAgICAgJy9iYXIuY29mZmVlJyxcbiAgICAgICdodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Jhei5jb2ZmZWUnXG4gICAgXSkpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYXBwbHlTb3VyY2VNYXAgbmFtZSBoYW5kbGluZyddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIC8vIEltYWdpbmUgc29tZSBDb2ZmZWVTY3JpcHQgY29kZSBiZWluZyBjb21waWxlZCBpbnRvIEphdmFTY3JpcHQgYW5kIHRoZW5cbiAgICAvLyBtaW5pZmllZC5cblxuICAgIHZhciBhc3NlcnROYW1lID0gZnVuY3Rpb24oY29mZmVlTmFtZSwganNOYW1lLCBleHBlY3RlZE5hbWUpIHtcbiAgICAgIHZhciBtaW5pZmllZE1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgICBmaWxlOiAndGVzdC5qcy5taW4nXG4gICAgICB9KTtcbiAgICAgIG1pbmlmaWVkTWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiA0IH0sXG4gICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogNCB9LFxuICAgICAgICBzb3VyY2U6ICd0ZXN0LmpzJyxcbiAgICAgICAgbmFtZToganNOYW1lXG4gICAgICB9KTtcblxuICAgICAgdmFyIGNvZmZlZU1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgICBmaWxlOiAndGVzdC5qcydcbiAgICAgIH0pO1xuICAgICAgY29mZmVlTWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiA0IH0sXG4gICAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9LFxuICAgICAgICBzb3VyY2U6ICd0ZXN0LmNvZmZlZScsXG4gICAgICAgIG5hbWU6IGNvZmZlZU5hbWVcbiAgICAgIH0pO1xuXG4gICAgICBtaW5pZmllZE1hcC5hcHBseVNvdXJjZU1hcChuZXcgU291cmNlTWFwQ29uc3VtZXIoY29mZmVlTWFwLnRvSlNPTigpKSk7XG5cbiAgICAgIG5ldyBTb3VyY2VNYXBDb25zdW1lcihtaW5pZmllZE1hcC50b0pTT04oKSkuZWFjaE1hcHBpbmcoZnVuY3Rpb24obWFwcGluZykge1xuICAgICAgICBhc3NlcnQuZXF1YWwobWFwcGluZy5uYW1lLCBleHBlY3RlZE5hbWUpO1xuICAgICAgfSk7XG4gICAgfTtcblxuICAgIC8vIGBmb28gPSAxYCAtPiBgdmFyIGZvbyA9IDE7YCAtPiBgdmFyIGE9MWBcbiAgICAvLyBDb2ZmZWVTY3JpcHQgZG9lc27igJl0IHJlbmFtZSB2YXJpYWJsZXMsIHNvIHRoZXJl4oCZcyBubyBuZWVkIGZvciBpdCB0b1xuICAgIC8vIHByb3ZpZGUgbmFtZXMgaW4gaXRzIHNvdXJjZSBtYXBzLiBNaW5pZmllcnMgZG8gcmVuYW1lIHZhcmlhYmxlcyBhbmRcbiAgICAvLyB0aGVyZWZvcmUgZG8gcHJvdmlkZSBuYW1lcyBpbiB0aGVpciBzb3VyY2UgbWFwcy4gU28gdGhhdCBuYW1lIHNob3VsZCBiZVxuICAgIC8vIHJldGFpbmVkIGlmIHRoZSBvcmlnaW5hbCBtYXAgbGFja3MgbmFtZXMuXG4gICAgYXNzZXJ0TmFtZShudWxsLCAnZm9vJywgJ2ZvbycpO1xuXG4gICAgLy8gYGZvbyA9IDFgIC0+IGB2YXIgY29mZmVlJGZvbyA9IDE7YCAtPiBgdmFyIGE9MWBcbiAgICAvLyBJbWFnaW5lIHRoYXQgQ29mZmVlU2NyaXB0IHByZWZpeGVkIGFsbCB2YXJpYWJsZXMgd2l0aCBgY29mZmVlJGAuIEV2ZW5cbiAgICAvLyB0aG91Z2ggdGhlIG1pbmlmaWVyIHRoZW4gYWxzbyBwcm92aWRlcyBhIG5hbWUsIHRoZSBvcmlnaW5hbCBuYW1lIGlzXG4gICAgLy8gd2hhdCBjb3JyZXNwb25kcyB0byB0aGUgc291cmNlLlxuICAgIGFzc2VydE5hbWUoJ2ZvbycsICdjb2ZmZWUkZm9vJywgJ2ZvbycpO1xuXG4gICAgLy8gYGZvbyA9IDFgIC0+IGB2YXIgY29mZmVlJGZvbyA9IDE7YCAtPiBgdmFyIGNvZmZlZSRmb289MWBcbiAgICAvLyBNaW5pZmllcnMgY2FuIHR1cm4gb2ZmIHZhcmlhYmxlIG1hbmdsaW5nLiBUaGVuIHRoZXJl4oCZcyBubyBuZWVkIHRvXG4gICAgLy8gcHJvdmlkZSBuYW1lcyBpbiB0aGUgc291cmNlIG1hcCwgYnV0IHRoZSBuYW1lcyBmcm9tIHRoZSBvcmlnaW5hbCBtYXAgYXJlXG4gICAgLy8gc3RpbGwgbmVlZGVkLlxuICAgIGFzc2VydE5hbWUoJ2ZvbycsIG51bGwsICdmb28nKTtcblxuICAgIC8vIGBmb28gPSAxYCAtPiBgdmFyIGZvbyA9IDE7YCAtPiBgdmFyIGZvbz0xYFxuICAgIC8vIE5vIHJlbmFtaW5nIGF0IGFsbC5cbiAgICBhc3NlcnROYW1lKG51bGwsIG51bGwsIG51bGwpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgc29ydGluZyB3aXRoIGR1cGxpY2F0ZSBnZW5lcmF0ZWQgbWFwcGluZ3MnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgbWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAndGVzdC5qcydcbiAgICB9KTtcbiAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMywgY29sdW1uOiAwIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ2EuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJ1xuICAgIH0pO1xuXG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBtYXAudG9KU09OKCksIHtcbiAgICAgIHZlcnNpb246IDMsXG4gICAgICBmaWxlOiAndGVzdC5qcycsXG4gICAgICBzb3VyY2VzOiBbJ2EuanMnXSxcbiAgICAgIG5hbWVzOiBbXSxcbiAgICAgIG1hcHBpbmdzOiAnQUFBQTtBO0FBQ0EnXG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBpZ25vcmUgZHVwbGljYXRlIG1hcHBpbmdzLiddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBpbml0ID0geyBmaWxlOiAnbWluLmpzJywgc291cmNlUm9vdDogJy90aGUvcm9vdCcgfTtcbiAgICB2YXIgbWFwMSwgbWFwMjtcblxuICAgIC8vIG51bGwgb3JpZ2luYWwgc291cmNlIGxvY2F0aW9uXG4gICAgdmFyIG51bGxNYXBwaW5nMSA9IHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDAgfVxuICAgIH07XG4gICAgdmFyIG51bGxNYXBwaW5nMiA9IHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfVxuICAgIH07XG5cbiAgICBtYXAxID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcihpbml0KTtcbiAgICBtYXAyID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcihpbml0KTtcblxuICAgIG1hcDEuYWRkTWFwcGluZyhudWxsTWFwcGluZzEpO1xuICAgIG1hcDEuYWRkTWFwcGluZyhudWxsTWFwcGluZzEpO1xuXG4gICAgbWFwMi5hZGRNYXBwaW5nKG51bGxNYXBwaW5nMSk7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIG1hcDEudG9KU09OKCksIG1hcDIudG9KU09OKCkpO1xuXG4gICAgbWFwMS5hZGRNYXBwaW5nKG51bGxNYXBwaW5nMik7XG4gICAgbWFwMS5hZGRNYXBwaW5nKG51bGxNYXBwaW5nMSk7XG5cbiAgICBtYXAyLmFkZE1hcHBpbmcobnVsbE1hcHBpbmcyKTtcblxuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgbWFwMS50b0pTT04oKSwgbWFwMi50b0pTT04oKSk7XG5cbiAgICAvLyBvcmlnaW5hbCBzb3VyY2UgbG9jYXRpb25cbiAgICB2YXIgc3JjTWFwcGluZzEgPSB7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAwIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdzcmNNYXBwaW5nMS5qcydcbiAgICB9O1xuICAgIHZhciBzcmNNYXBwaW5nMiA9IHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDExLCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ3NyY01hcHBpbmcyLmpzJ1xuICAgIH07XG5cbiAgICBtYXAxID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcihpbml0KTtcbiAgICBtYXAyID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcihpbml0KTtcblxuICAgIG1hcDEuYWRkTWFwcGluZyhzcmNNYXBwaW5nMSk7XG4gICAgbWFwMS5hZGRNYXBwaW5nKHNyY01hcHBpbmcxKTtcblxuICAgIG1hcDIuYWRkTWFwcGluZyhzcmNNYXBwaW5nMSk7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIG1hcDEudG9KU09OKCksIG1hcDIudG9KU09OKCkpO1xuXG4gICAgbWFwMS5hZGRNYXBwaW5nKHNyY01hcHBpbmcyKTtcbiAgICBtYXAxLmFkZE1hcHBpbmcoc3JjTWFwcGluZzEpO1xuXG4gICAgbWFwMi5hZGRNYXBwaW5nKHNyY01hcHBpbmcyKTtcblxuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgbWFwMS50b0pTT04oKSwgbWFwMi50b0pTT04oKSk7XG5cbiAgICAvLyBmdWxsIG9yaWdpbmFsIHNvdXJjZSBhbmQgbmFtZSBpbmZvcm1hdGlvblxuICAgIHZhciBmdWxsTWFwcGluZzEgPSB7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAwIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdmdWxsTWFwcGluZzEuanMnLFxuICAgICAgbmFtZTogJ2Z1bGxNYXBwaW5nMSdcbiAgICB9O1xuICAgIHZhciBmdWxsTWFwcGluZzIgPSB7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdmdWxsTWFwcGluZzIuanMnLFxuICAgICAgbmFtZTogJ2Z1bGxNYXBwaW5nMidcbiAgICB9O1xuXG4gICAgbWFwMSA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3IoaW5pdCk7XG4gICAgbWFwMiA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3IoaW5pdCk7XG5cbiAgICBtYXAxLmFkZE1hcHBpbmcoZnVsbE1hcHBpbmcxKTtcbiAgICBtYXAxLmFkZE1hcHBpbmcoZnVsbE1hcHBpbmcxKTtcblxuICAgIG1hcDIuYWRkTWFwcGluZyhmdWxsTWFwcGluZzEpO1xuXG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBtYXAxLnRvSlNPTigpLCBtYXAyLnRvSlNPTigpKTtcblxuICAgIG1hcDEuYWRkTWFwcGluZyhmdWxsTWFwcGluZzIpO1xuICAgIG1hcDEuYWRkTWFwcGluZyhmdWxsTWFwcGluZzEpO1xuXG4gICAgbWFwMi5hZGRNYXBwaW5nKGZ1bGxNYXBwaW5nMik7XG5cbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIG1hcDEudG9KU09OKCksIG1hcDIudG9KU09OKCkpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgZ2l0aHViIGlzc3VlICM3MiwgY2hlY2sgZm9yIGR1cGxpY2F0ZSBuYW1lcyBvciBzb3VyY2VzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG1hcCA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgZmlsZTogJ3Rlc3QuanMnXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMSB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJyxcbiAgICAgIG5hbWU6ICdmb28nXG4gICAgfSk7XG4gICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDMsIGNvbHVtbjogMyB9LFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogNCwgY29sdW1uOiA0IH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJyxcbiAgICAgIG5hbWU6ICdmb28nXG4gICAgfSk7XG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBtYXAudG9KU09OKCksIHtcbiAgICAgIHZlcnNpb246IDMsXG4gICAgICBmaWxlOiAndGVzdC5qcycsXG4gICAgICBzb3VyY2VzOiBbJ2EuanMnXSxcbiAgICAgIG5hbWVzOiBbJ2ZvbyddLFxuICAgICAgbWFwcGluZ3M6ICdDQUNFQTs7R0FFRUEnXG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBzZXR0aW5nIHNvdXJjZXNDb250ZW50IHRvIG51bGwgd2hlbiBhbHJlYWR5IG51bGwnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgc21nID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7IGZpbGU6IFwiZm9vLmpzXCIgfSk7XG4gICAgYXNzZXJ0LmRvZXNOb3RUaHJvdyhmdW5jdGlvbigpIHtcbiAgICAgIHNtZy5zZXRTb3VyY2VDb250ZW50KFwiYmFyLmpzXCIsIG51bGwpO1xuICAgIH0pO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgYXBwbHlTb3VyY2VNYXAgd2l0aCB1bmV4YWN0IG1hdGNoJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgICB2YXIgbWFwMSA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgICBmaWxlOiAnYnVuZGxlZC1zb3VyY2UnXG4gICAgICB9KTtcbiAgICAgIG1hcDEuYWRkTWFwcGluZyh7XG4gICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDQgfSxcbiAgICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiA0IH0sXG4gICAgICAgIHNvdXJjZTogJ3RyYW5zZm9ybWVkLXNvdXJjZSdcbiAgICAgIH0pO1xuICAgICAgbWFwMS5hZGRNYXBwaW5nKHtcbiAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogNCB9LFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDQgfSxcbiAgICAgICAgc291cmNlOiAndHJhbnNmb3JtZWQtc291cmNlJ1xuICAgICAgfSk7XG5cbiAgICAgIHZhciBtYXAyID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICAgIGZpbGU6ICd0cmFuc2Zvcm1lZC1zb3VyY2UnXG4gICAgICB9KTtcbiAgICAgIG1hcDIuYWRkTWFwcGluZyh7XG4gICAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDAgfSxcbiAgICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAwIH0sXG4gICAgICAgIHNvdXJjZTogJ29yaWdpbmFsLXNvdXJjZSdcbiAgICAgIH0pO1xuXG4gICAgICB2YXIgZXhwZWN0ZWRNYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgICAgZmlsZTogJ2J1bmRsZWQtc291cmNlJ1xuICAgICAgfSk7XG4gICAgICBleHBlY3RlZE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogNCB9LFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDQgfSxcbiAgICAgICAgc291cmNlOiAndHJhbnNmb3JtZWQtc291cmNlJ1xuICAgICAgfSk7XG4gICAgICBleHBlY3RlZE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogNCB9LFxuICAgICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDAgfSxcbiAgICAgICAgc291cmNlOiAnb3JpZ2luYWwtc291cmNlJ1xuICAgICAgfSk7XG5cbiAgICAgIG1hcDEuYXBwbHlTb3VyY2VNYXAobmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcDIudG9KU09OKCkpKTtcblxuICAgICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBtYXAxLnRvSlNPTigpLCBleHBlY3RlZE1hcC50b0pTT04oKSk7XG4gICAgfTtcblxuICBleHBvcnRzWyd0ZXN0IGlzc3VlICMxOTInXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgZ2VuZXJhdG9yID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcigpO1xuICAgIGdlbmVyYXRvci5hZGRNYXBwaW5nKHtcbiAgICAgIHNvdXJjZTogJ2EuanMnLFxuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMTAgfSxcbiAgICB9KTtcbiAgICBnZW5lcmF0b3IuYWRkTWFwcGluZyh7XG4gICAgICBzb3VyY2U6ICdiLmpzJyxcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDEwIH0sXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIwIH0sXG4gICAgfSk7XG5cbiAgICB2YXIgY29uc3VtZXIgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIoZ2VuZXJhdG9yLnRvSlNPTigpKTtcblxuICAgIHZhciBuID0gMDtcbiAgICBjb25zdW1lci5lYWNoTWFwcGluZyhmdW5jdGlvbiAoKSB7IG4rKyB9KTtcblxuICAgIGFzc2VydC5lcXVhbChuLCAyLFxuICAgICAgICAgICAgICAgICBcIlNob3VsZCBub3QgZGUtZHVwbGljYXRlIG1hcHBpbmdzIHRoYXQgaGF2ZSB0aGUgc2FtZSBcIiArXG4gICAgICAgICAgICAgICAgIFwiZ2VuZXJhdGVkIHBvc2l0aW9ucywgYnV0IGRpZmZlcmVudCBvcmlnaW5hbCBwb3NpdGlvbnMuXCIpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1zb3VyY2UtbWFwLWdlbmVyYXRvci5qc1xuICoqIG1vZHVsZSBpZCA9IDBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIGJhc2U2NFZMUSA9IHJlcXVpcmUoJy4vYmFzZTY0LXZscScpO1xuICB2YXIgdXRpbCA9IHJlcXVpcmUoJy4vdXRpbCcpO1xuICB2YXIgQXJyYXlTZXQgPSByZXF1aXJlKCcuL2FycmF5LXNldCcpLkFycmF5U2V0O1xuICB2YXIgTWFwcGluZ0xpc3QgPSByZXF1aXJlKCcuL21hcHBpbmctbGlzdCcpLk1hcHBpbmdMaXN0O1xuXG4gIC8qKlxuICAgKiBBbiBpbnN0YW5jZSBvZiB0aGUgU291cmNlTWFwR2VuZXJhdG9yIHJlcHJlc2VudHMgYSBzb3VyY2UgbWFwIHdoaWNoIGlzXG4gICAqIGJlaW5nIGJ1aWx0IGluY3JlbWVudGFsbHkuIFlvdSBtYXkgcGFzcyBhbiBvYmplY3Qgd2l0aCB0aGUgZm9sbG93aW5nXG4gICAqIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBmaWxlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBzb3VyY2VSb290OiBBIHJvb3QgZm9yIGFsbCByZWxhdGl2ZSBVUkxzIGluIHRoaXMgc291cmNlIG1hcC5cbiAgICovXG4gIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcihhQXJncykge1xuICAgIGlmICghYUFyZ3MpIHtcbiAgICAgIGFBcmdzID0ge307XG4gICAgfVxuICAgIHRoaXMuX2ZpbGUgPSB1dGlsLmdldEFyZyhhQXJncywgJ2ZpbGUnLCBudWxsKTtcbiAgICB0aGlzLl9zb3VyY2VSb290ID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2VSb290JywgbnVsbCk7XG4gICAgdGhpcy5fc2tpcFZhbGlkYXRpb24gPSB1dGlsLmdldEFyZyhhQXJncywgJ3NraXBWYWxpZGF0aW9uJywgZmFsc2UpO1xuICAgIHRoaXMuX3NvdXJjZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICB0aGlzLl9uYW1lcyA9IG5ldyBBcnJheVNldCgpO1xuICAgIHRoaXMuX21hcHBpbmdzID0gbmV3IE1hcHBpbmdMaXN0KCk7XG4gICAgdGhpcy5fc291cmNlc0NvbnRlbnRzID0gbnVsbDtcbiAgfVxuXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8qKlxuICAgKiBDcmVhdGVzIGEgbmV3IFNvdXJjZU1hcEdlbmVyYXRvciBiYXNlZCBvbiBhIFNvdXJjZU1hcENvbnN1bWVyXG4gICAqXG4gICAqIEBwYXJhbSBhU291cmNlTWFwQ29uc3VtZXIgVGhlIFNvdXJjZU1hcC5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5mcm9tU291cmNlTWFwID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfZnJvbVNvdXJjZU1hcChhU291cmNlTWFwQ29uc3VtZXIpIHtcbiAgICAgIHZhciBzb3VyY2VSb290ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZVJvb3Q7XG4gICAgICB2YXIgZ2VuZXJhdG9yID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICAgIGZpbGU6IGFTb3VyY2VNYXBDb25zdW1lci5maWxlLFxuICAgICAgICBzb3VyY2VSb290OiBzb3VyY2VSb290XG4gICAgICB9KTtcbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICB2YXIgbmV3TWFwcGluZyA9IHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IHtcbiAgICAgICAgICAgIGxpbmU6IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSxcbiAgICAgICAgICAgIGNvbHVtbjogbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW5cbiAgICAgICAgICB9XG4gICAgICAgIH07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuc291cmNlICE9IG51bGwpIHtcbiAgICAgICAgICBuZXdNYXBwaW5nLnNvdXJjZSA9IG1hcHBpbmcuc291cmNlO1xuICAgICAgICAgIGlmIChzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICAgIG5ld01hcHBpbmcuc291cmNlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBuZXdNYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgbmV3TWFwcGluZy5vcmlnaW5hbCA9IHtcbiAgICAgICAgICAgIGxpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgY29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uXG4gICAgICAgICAgfTtcblxuICAgICAgICAgIGlmIChtYXBwaW5nLm5hbWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgbmV3TWFwcGluZy5uYW1lID0gbWFwcGluZy5uYW1lO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIGdlbmVyYXRvci5hZGRNYXBwaW5nKG5ld01hcHBpbmcpO1xuICAgICAgfSk7XG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGZ1bmN0aW9uIChzb3VyY2VGaWxlKSB7XG4gICAgICAgIHZhciBjb250ZW50ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlRmlsZSk7XG4gICAgICAgIGlmIChjb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgICBnZW5lcmF0b3Iuc2V0U291cmNlQ29udGVudChzb3VyY2VGaWxlLCBjb250ZW50KTtcbiAgICAgICAgfVxuICAgICAgfSk7XG4gICAgICByZXR1cm4gZ2VuZXJhdG9yO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIHNpbmdsZSBtYXBwaW5nIGZyb20gb3JpZ2luYWwgc291cmNlIGxpbmUgYW5kIGNvbHVtbiB0byB0aGUgZ2VuZXJhdGVkXG4gICAqIHNvdXJjZSdzIGxpbmUgYW5kIGNvbHVtbiBmb3IgdGhpcyBzb3VyY2UgbWFwIGJlaW5nIGNyZWF0ZWQuIFRoZSBtYXBwaW5nXG4gICAqIG9iamVjdCBzaG91bGQgaGF2ZSB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBnZW5lcmF0ZWQ6IEFuIG9iamVjdCB3aXRoIHRoZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIHBvc2l0aW9ucy5cbiAgICogICAtIG9yaWdpbmFsOiBBbiBvYmplY3Qgd2l0aCB0aGUgb3JpZ2luYWwgbGluZSBhbmQgY29sdW1uIHBvc2l0aW9ucy5cbiAgICogICAtIHNvdXJjZTogVGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlIChyZWxhdGl2ZSB0byB0aGUgc291cmNlUm9vdCkuXG4gICAqICAgLSBuYW1lOiBBbiBvcHRpb25hbCBvcmlnaW5hbCB0b2tlbiBuYW1lIGZvciB0aGlzIG1hcHBpbmcuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLmFkZE1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9hZGRNYXBwaW5nKGFBcmdzKSB7XG4gICAgICB2YXIgZ2VuZXJhdGVkID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdnZW5lcmF0ZWQnKTtcbiAgICAgIHZhciBvcmlnaW5hbCA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnb3JpZ2luYWwnLCBudWxsKTtcbiAgICAgIHZhciBzb3VyY2UgPSB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScsIG51bGwpO1xuICAgICAgdmFyIG5hbWUgPSB1dGlsLmdldEFyZyhhQXJncywgJ25hbWUnLCBudWxsKTtcblxuICAgICAgaWYgKCF0aGlzLl9za2lwVmFsaWRhdGlvbikge1xuICAgICAgICB0aGlzLl92YWxpZGF0ZU1hcHBpbmcoZ2VuZXJhdGVkLCBvcmlnaW5hbCwgc291cmNlLCBuYW1lKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHNvdXJjZSAhPSBudWxsICYmICF0aGlzLl9zb3VyY2VzLmhhcyhzb3VyY2UpKSB7XG4gICAgICAgIHRoaXMuX3NvdXJjZXMuYWRkKHNvdXJjZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChuYW1lICE9IG51bGwgJiYgIXRoaXMuX25hbWVzLmhhcyhuYW1lKSkge1xuICAgICAgICB0aGlzLl9uYW1lcy5hZGQobmFtZSk7XG4gICAgICB9XG5cbiAgICAgIHRoaXMuX21hcHBpbmdzLmFkZCh7XG4gICAgICAgIGdlbmVyYXRlZExpbmU6IGdlbmVyYXRlZC5saW5lLFxuICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IGdlbmVyYXRlZC5jb2x1bW4sXG4gICAgICAgIG9yaWdpbmFsTGluZTogb3JpZ2luYWwgIT0gbnVsbCAmJiBvcmlnaW5hbC5saW5lLFxuICAgICAgICBvcmlnaW5hbENvbHVtbjogb3JpZ2luYWwgIT0gbnVsbCAmJiBvcmlnaW5hbC5jb2x1bW4sXG4gICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICBuYW1lOiBuYW1lXG4gICAgICB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBTZXQgdGhlIHNvdXJjZSBjb250ZW50IGZvciBhIHNvdXJjZSBmaWxlLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5zZXRTb3VyY2VDb250ZW50ID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3Jfc2V0U291cmNlQ29udGVudChhU291cmNlRmlsZSwgYVNvdXJjZUNvbnRlbnQpIHtcbiAgICAgIHZhciBzb3VyY2UgPSBhU291cmNlRmlsZTtcbiAgICAgIGlmICh0aGlzLl9zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgc291cmNlID0gdXRpbC5yZWxhdGl2ZSh0aGlzLl9zb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgfVxuXG4gICAgICBpZiAoYVNvdXJjZUNvbnRlbnQgIT0gbnVsbCkge1xuICAgICAgICAvLyBBZGQgdGhlIHNvdXJjZSBjb250ZW50IHRvIHRoZSBfc291cmNlc0NvbnRlbnRzIG1hcC5cbiAgICAgICAgLy8gQ3JlYXRlIGEgbmV3IF9zb3VyY2VzQ29udGVudHMgbWFwIGlmIHRoZSBwcm9wZXJ0eSBpcyBudWxsLlxuICAgICAgICBpZiAoIXRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICAgIHRoaXMuX3NvdXJjZXNDb250ZW50cyA9IHt9O1xuICAgICAgICB9XG4gICAgICAgIHRoaXMuX3NvdXJjZXNDb250ZW50c1t1dGlsLnRvU2V0U3RyaW5nKHNvdXJjZSldID0gYVNvdXJjZUNvbnRlbnQ7XG4gICAgICB9IGVsc2UgaWYgKHRoaXMuX3NvdXJjZXNDb250ZW50cykge1xuICAgICAgICAvLyBSZW1vdmUgdGhlIHNvdXJjZSBmaWxlIGZyb20gdGhlIF9zb3VyY2VzQ29udGVudHMgbWFwLlxuICAgICAgICAvLyBJZiB0aGUgX3NvdXJjZXNDb250ZW50cyBtYXAgaXMgZW1wdHksIHNldCB0aGUgcHJvcGVydHkgdG8gbnVsbC5cbiAgICAgICAgZGVsZXRlIHRoaXMuX3NvdXJjZXNDb250ZW50c1t1dGlsLnRvU2V0U3RyaW5nKHNvdXJjZSldO1xuICAgICAgICBpZiAoT2JqZWN0LmtleXModGhpcy5fc291cmNlc0NvbnRlbnRzKS5sZW5ndGggPT09IDApIHtcbiAgICAgICAgICB0aGlzLl9zb3VyY2VzQ29udGVudHMgPSBudWxsO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogQXBwbGllcyB0aGUgbWFwcGluZ3Mgb2YgYSBzdWItc291cmNlLW1hcCBmb3IgYSBzcGVjaWZpYyBzb3VyY2UgZmlsZSB0byB0aGVcbiAgICogc291cmNlIG1hcCBiZWluZyBnZW5lcmF0ZWQuIEVhY2ggbWFwcGluZyB0byB0aGUgc3VwcGxpZWQgc291cmNlIGZpbGUgaXNcbiAgICogcmV3cml0dGVuIHVzaW5nIHRoZSBzdXBwbGllZCBzb3VyY2UgbWFwLiBOb3RlOiBUaGUgcmVzb2x1dGlvbiBmb3IgdGhlXG4gICAqIHJlc3VsdGluZyBtYXBwaW5ncyBpcyB0aGUgbWluaW1pdW0gb2YgdGhpcyBtYXAgYW5kIHRoZSBzdXBwbGllZCBtYXAuXG4gICAqXG4gICAqIEBwYXJhbSBhU291cmNlTWFwQ29uc3VtZXIgVGhlIHNvdXJjZSBtYXAgdG8gYmUgYXBwbGllZC5cbiAgICogQHBhcmFtIGFTb3VyY2VGaWxlIE9wdGlvbmFsLiBUaGUgZmlsZW5hbWUgb2YgdGhlIHNvdXJjZSBmaWxlLlxuICAgKiAgICAgICAgSWYgb21pdHRlZCwgU291cmNlTWFwQ29uc3VtZXIncyBmaWxlIHByb3BlcnR5IHdpbGwgYmUgdXNlZC5cbiAgICogQHBhcmFtIGFTb3VyY2VNYXBQYXRoIE9wdGlvbmFsLiBUaGUgZGlybmFtZSBvZiB0aGUgcGF0aCB0byB0aGUgc291cmNlIG1hcFxuICAgKiAgICAgICAgdG8gYmUgYXBwbGllZC4gSWYgcmVsYXRpdmUsIGl0IGlzIHJlbGF0aXZlIHRvIHRoZSBTb3VyY2VNYXBDb25zdW1lci5cbiAgICogICAgICAgIFRoaXMgcGFyYW1ldGVyIGlzIG5lZWRlZCB3aGVuIHRoZSB0d28gc291cmNlIG1hcHMgYXJlbid0IGluIHRoZSBzYW1lXG4gICAqICAgICAgICBkaXJlY3RvcnksIGFuZCB0aGUgc291cmNlIG1hcCB0byBiZSBhcHBsaWVkIGNvbnRhaW5zIHJlbGF0aXZlIHNvdXJjZVxuICAgKiAgICAgICAgcGF0aHMuIElmIHNvLCB0aG9zZSByZWxhdGl2ZSBzb3VyY2UgcGF0aHMgbmVlZCB0byBiZSByZXdyaXR0ZW5cbiAgICogICAgICAgIHJlbGF0aXZlIHRvIHRoZSBTb3VyY2VNYXBHZW5lcmF0b3IuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLmFwcGx5U291cmNlTWFwID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfYXBwbHlTb3VyY2VNYXAoYVNvdXJjZU1hcENvbnN1bWVyLCBhU291cmNlRmlsZSwgYVNvdXJjZU1hcFBhdGgpIHtcbiAgICAgIHZhciBzb3VyY2VGaWxlID0gYVNvdXJjZUZpbGU7XG4gICAgICAvLyBJZiBhU291cmNlRmlsZSBpcyBvbWl0dGVkLCB3ZSB3aWxsIHVzZSB0aGUgZmlsZSBwcm9wZXJ0eSBvZiB0aGUgU291cmNlTWFwXG4gICAgICBpZiAoYVNvdXJjZUZpbGUgPT0gbnVsbCkge1xuICAgICAgICBpZiAoYVNvdXJjZU1hcENvbnN1bWVyLmZpbGUgPT0gbnVsbCkge1xuICAgICAgICAgIHRocm93IG5ldyBFcnJvcihcbiAgICAgICAgICAgICdTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLmFwcGx5U291cmNlTWFwIHJlcXVpcmVzIGVpdGhlciBhbiBleHBsaWNpdCBzb3VyY2UgZmlsZSwgJyArXG4gICAgICAgICAgICAnb3IgdGhlIHNvdXJjZSBtYXBcXCdzIFwiZmlsZVwiIHByb3BlcnR5LiBCb3RoIHdlcmUgb21pdHRlZC4nXG4gICAgICAgICAgKTtcbiAgICAgICAgfVxuICAgICAgICBzb3VyY2VGaWxlID0gYVNvdXJjZU1hcENvbnN1bWVyLmZpbGU7XG4gICAgICB9XG4gICAgICB2YXIgc291cmNlUm9vdCA9IHRoaXMuX3NvdXJjZVJvb3Q7XG4gICAgICAvLyBNYWtlIFwic291cmNlRmlsZVwiIHJlbGF0aXZlIGlmIGFuIGFic29sdXRlIFVybCBpcyBwYXNzZWQuXG4gICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIHNvdXJjZUZpbGUgPSB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIHNvdXJjZUZpbGUpO1xuICAgICAgfVxuICAgICAgLy8gQXBwbHlpbmcgdGhlIFNvdXJjZU1hcCBjYW4gYWRkIGFuZCByZW1vdmUgaXRlbXMgZnJvbSB0aGUgc291cmNlcyBhbmRcbiAgICAgIC8vIHRoZSBuYW1lcyBhcnJheS5cbiAgICAgIHZhciBuZXdTb3VyY2VzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgICB2YXIgbmV3TmFtZXMgPSBuZXcgQXJyYXlTZXQoKTtcblxuICAgICAgLy8gRmluZCBtYXBwaW5ncyBmb3IgdGhlIFwic291cmNlRmlsZVwiXG4gICAgICB0aGlzLl9tYXBwaW5ncy51bnNvcnRlZEZvckVhY2goZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgICAgaWYgKG1hcHBpbmcuc291cmNlID09PSBzb3VyY2VGaWxlICYmIG1hcHBpbmcub3JpZ2luYWxMaW5lICE9IG51bGwpIHtcbiAgICAgICAgICAvLyBDaGVjayBpZiBpdCBjYW4gYmUgbWFwcGVkIGJ5IHRoZSBzb3VyY2UgbWFwLCB0aGVuIHVwZGF0ZSB0aGUgbWFwcGluZy5cbiAgICAgICAgICB2YXIgb3JpZ2luYWwgPSBhU291cmNlTWFwQ29uc3VtZXIub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICAgICAgICBsaW5lOiBtYXBwaW5nLm9yaWdpbmFsTGluZSxcbiAgICAgICAgICAgIGNvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtblxuICAgICAgICAgIH0pO1xuICAgICAgICAgIGlmIChvcmlnaW5hbC5zb3VyY2UgIT0gbnVsbCkge1xuICAgICAgICAgICAgLy8gQ29weSBtYXBwaW5nXG4gICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IG9yaWdpbmFsLnNvdXJjZTtcbiAgICAgICAgICAgIGlmIChhU291cmNlTWFwUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gdXRpbC5qb2luKGFTb3VyY2VNYXBQYXRoLCBtYXBwaW5nLnNvdXJjZSlcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIGlmIChzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIG1hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lID0gb3JpZ2luYWwubGluZTtcbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPSBvcmlnaW5hbC5jb2x1bW47XG4gICAgICAgICAgICBpZiAob3JpZ2luYWwubmFtZSAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIG1hcHBpbmcubmFtZSA9IG9yaWdpbmFsLm5hbWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgdmFyIHNvdXJjZSA9IG1hcHBpbmcuc291cmNlO1xuICAgICAgICBpZiAoc291cmNlICE9IG51bGwgJiYgIW5ld1NvdXJjZXMuaGFzKHNvdXJjZSkpIHtcbiAgICAgICAgICBuZXdTb3VyY2VzLmFkZChzb3VyY2UpO1xuICAgICAgICB9XG5cbiAgICAgICAgdmFyIG5hbWUgPSBtYXBwaW5nLm5hbWU7XG4gICAgICAgIGlmIChuYW1lICE9IG51bGwgJiYgIW5ld05hbWVzLmhhcyhuYW1lKSkge1xuICAgICAgICAgIG5ld05hbWVzLmFkZChuYW1lKTtcbiAgICAgICAgfVxuXG4gICAgICB9LCB0aGlzKTtcbiAgICAgIHRoaXMuX3NvdXJjZXMgPSBuZXdTb3VyY2VzO1xuICAgICAgdGhpcy5fbmFtZXMgPSBuZXdOYW1lcztcblxuICAgICAgLy8gQ29weSBzb3VyY2VzQ29udGVudHMgb2YgYXBwbGllZCBtYXAuXG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGZ1bmN0aW9uIChzb3VyY2VGaWxlKSB7XG4gICAgICAgIHZhciBjb250ZW50ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlRmlsZSk7XG4gICAgICAgIGlmIChjb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgICBpZiAoYVNvdXJjZU1hcFBhdGggIT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlRmlsZSA9IHV0aWwuam9pbihhU291cmNlTWFwUGF0aCwgc291cmNlRmlsZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIGlmIChzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZUZpbGUgPSB1dGlsLnJlbGF0aXZlKHNvdXJjZVJvb3QsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICB0aGlzLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0sIHRoaXMpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEEgbWFwcGluZyBjYW4gaGF2ZSBvbmUgb2YgdGhlIHRocmVlIGxldmVscyBvZiBkYXRhOlxuICAgKlxuICAgKiAgIDEuIEp1c3QgdGhlIGdlbmVyYXRlZCBwb3NpdGlvbi5cbiAgICogICAyLiBUaGUgR2VuZXJhdGVkIHBvc2l0aW9uLCBvcmlnaW5hbCBwb3NpdGlvbiwgYW5kIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAzLiBHZW5lcmF0ZWQgYW5kIG9yaWdpbmFsIHBvc2l0aW9uLCBvcmlnaW5hbCBzb3VyY2UsIGFzIHdlbGwgYXMgYSBuYW1lXG4gICAqICAgICAgdG9rZW4uXG4gICAqXG4gICAqIFRvIG1haW50YWluIGNvbnNpc3RlbmN5LCB3ZSB2YWxpZGF0ZSB0aGF0IGFueSBuZXcgbWFwcGluZyBiZWluZyBhZGRlZCBmYWxsc1xuICAgKiBpbiB0byBvbmUgb2YgdGhlc2UgY2F0ZWdvcmllcy5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX3ZhbGlkYXRlTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3ZhbGlkYXRlTWFwcGluZyhhR2VuZXJhdGVkLCBhT3JpZ2luYWwsIGFTb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhTmFtZSkge1xuICAgICAgaWYgKGFHZW5lcmF0ZWQgJiYgJ2xpbmUnIGluIGFHZW5lcmF0ZWQgJiYgJ2NvbHVtbicgaW4gYUdlbmVyYXRlZFxuICAgICAgICAgICYmIGFHZW5lcmF0ZWQubGluZSA+IDAgJiYgYUdlbmVyYXRlZC5jb2x1bW4gPj0gMFxuICAgICAgICAgICYmICFhT3JpZ2luYWwgJiYgIWFTb3VyY2UgJiYgIWFOYW1lKSB7XG4gICAgICAgIC8vIENhc2UgMS5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgZWxzZSBpZiAoYUdlbmVyYXRlZCAmJiAnbGluZScgaW4gYUdlbmVyYXRlZCAmJiAnY29sdW1uJyBpbiBhR2VuZXJhdGVkXG4gICAgICAgICAgICAgICAmJiBhT3JpZ2luYWwgJiYgJ2xpbmUnIGluIGFPcmlnaW5hbCAmJiAnY29sdW1uJyBpbiBhT3JpZ2luYWxcbiAgICAgICAgICAgICAgICYmIGFHZW5lcmF0ZWQubGluZSA+IDAgJiYgYUdlbmVyYXRlZC5jb2x1bW4gPj0gMFxuICAgICAgICAgICAgICAgJiYgYU9yaWdpbmFsLmxpbmUgPiAwICYmIGFPcmlnaW5hbC5jb2x1bW4gPj0gMFxuICAgICAgICAgICAgICAgJiYgYVNvdXJjZSkge1xuICAgICAgICAvLyBDYXNlcyAyIGFuZCAzLlxuICAgICAgICByZXR1cm47XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdJbnZhbGlkIG1hcHBpbmc6ICcgKyBKU09OLnN0cmluZ2lmeSh7XG4gICAgICAgICAgZ2VuZXJhdGVkOiBhR2VuZXJhdGVkLFxuICAgICAgICAgIHNvdXJjZTogYVNvdXJjZSxcbiAgICAgICAgICBvcmlnaW5hbDogYU9yaWdpbmFsLFxuICAgICAgICAgIG5hbWU6IGFOYW1lXG4gICAgICAgIH0pKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBTZXJpYWxpemUgdGhlIGFjY3VtdWxhdGVkIG1hcHBpbmdzIGluIHRvIHRoZSBzdHJlYW0gb2YgYmFzZSA2NCBWTFFzXG4gICAqIHNwZWNpZmllZCBieSB0aGUgc291cmNlIG1hcCBmb3JtYXQuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl9zZXJpYWxpemVNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3NlcmlhbGl6ZU1hcHBpbmdzKCkge1xuICAgICAgdmFyIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c0dlbmVyYXRlZExpbmUgPSAxO1xuICAgICAgdmFyIHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gMDtcbiAgICAgIHZhciBwcmV2aW91c05hbWUgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzU291cmNlID0gMDtcbiAgICAgIHZhciByZXN1bHQgPSAnJztcbiAgICAgIHZhciBtYXBwaW5nO1xuICAgICAgdmFyIG5hbWVJZHg7XG4gICAgICB2YXIgc291cmNlSWR4O1xuXG4gICAgICB2YXIgbWFwcGluZ3MgPSB0aGlzLl9tYXBwaW5ncy50b0FycmF5KCk7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gbWFwcGluZ3MubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgICAgbWFwcGluZyA9IG1hcHBpbmdzW2ldO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgIT09IHByZXZpb3VzR2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgICB3aGlsZSAobWFwcGluZy5nZW5lcmF0ZWRMaW5lICE9PSBwcmV2aW91c0dlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICAgIHJlc3VsdCArPSAnOyc7XG4gICAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZExpbmUrKztcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgaWYgKGkgPiAwKSB7XG4gICAgICAgICAgICBpZiAoIXV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQobWFwcGluZywgbWFwcGluZ3NbaSAtIDFdKSkge1xuICAgICAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICAgIHJlc3VsdCArPSAnLCc7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobWFwcGluZy5nZW5lcmF0ZWRDb2x1bW5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBwcmV2aW91c0dlbmVyYXRlZENvbHVtbik7XG4gICAgICAgIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuc291cmNlICE9IG51bGwpIHtcbiAgICAgICAgICBzb3VyY2VJZHggPSB0aGlzLl9zb3VyY2VzLmluZGV4T2YobWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKHNvdXJjZUlkeCAtIHByZXZpb3VzU291cmNlKTtcbiAgICAgICAgICBwcmV2aW91c1NvdXJjZSA9IHNvdXJjZUlkeDtcblxuICAgICAgICAgIC8vIGxpbmVzIGFyZSBzdG9yZWQgMC1iYXNlZCBpbiBTb3VyY2VNYXAgc3BlYyB2ZXJzaW9uIDNcbiAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShtYXBwaW5nLm9yaWdpbmFsTGluZSAtIDFcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIHByZXZpb3VzT3JpZ2luYWxMaW5lKTtcbiAgICAgICAgICBwcmV2aW91c09yaWdpbmFsTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lIC0gMTtcblxuICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG1hcHBpbmcub3JpZ2luYWxDb2x1bW5cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAtIHByZXZpb3VzT3JpZ2luYWxDb2x1bW4pO1xuICAgICAgICAgIHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gPSBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuXG4gICAgICAgICAgaWYgKG1hcHBpbmcubmFtZSAhPSBudWxsKSB7XG4gICAgICAgICAgICBuYW1lSWR4ID0gdGhpcy5fbmFtZXMuaW5kZXhPZihtYXBwaW5nLm5hbWUpO1xuICAgICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobmFtZUlkeCAtIHByZXZpb3VzTmFtZSk7XG4gICAgICAgICAgICBwcmV2aW91c05hbWUgPSBuYW1lSWR4O1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gcmVzdWx0O1xuICAgIH07XG5cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fZ2VuZXJhdGVTb3VyY2VzQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2dlbmVyYXRlU291cmNlc0NvbnRlbnQoYVNvdXJjZXMsIGFTb3VyY2VSb290KSB7XG4gICAgICByZXR1cm4gYVNvdXJjZXMubWFwKGZ1bmN0aW9uIChzb3VyY2UpIHtcbiAgICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgICAgfVxuICAgICAgICBpZiAoYVNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICAgIHNvdXJjZSA9IHV0aWwucmVsYXRpdmUoYVNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgIH1cbiAgICAgICAgdmFyIGtleSA9IHV0aWwudG9TZXRTdHJpbmcoc291cmNlKTtcbiAgICAgICAgcmV0dXJuIE9iamVjdC5wcm90b3R5cGUuaGFzT3duUHJvcGVydHkuY2FsbCh0aGlzLl9zb3VyY2VzQ29udGVudHMsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAga2V5KVxuICAgICAgICAgID8gdGhpcy5fc291cmNlc0NvbnRlbnRzW2tleV1cbiAgICAgICAgICA6IG51bGw7XG4gICAgICB9LCB0aGlzKTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBFeHRlcm5hbGl6ZSB0aGUgc291cmNlIG1hcC5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUudG9KU09OID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfdG9KU09OKCkge1xuICAgICAgdmFyIG1hcCA9IHtcbiAgICAgICAgdmVyc2lvbjogdGhpcy5fdmVyc2lvbixcbiAgICAgICAgc291cmNlczogdGhpcy5fc291cmNlcy50b0FycmF5KCksXG4gICAgICAgIG5hbWVzOiB0aGlzLl9uYW1lcy50b0FycmF5KCksXG4gICAgICAgIG1hcHBpbmdzOiB0aGlzLl9zZXJpYWxpemVNYXBwaW5ncygpXG4gICAgICB9O1xuICAgICAgaWYgKHRoaXMuX2ZpbGUgIT0gbnVsbCkge1xuICAgICAgICBtYXAuZmlsZSA9IHRoaXMuX2ZpbGU7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5fc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIG1hcC5zb3VyY2VSb290ID0gdGhpcy5fc291cmNlUm9vdDtcbiAgICAgIH1cbiAgICAgIGlmICh0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgbWFwLnNvdXJjZXNDb250ZW50ID0gdGhpcy5fZ2VuZXJhdGVTb3VyY2VzQ29udGVudChtYXAuc291cmNlcywgbWFwLnNvdXJjZVJvb3QpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gbWFwO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJlbmRlciB0aGUgc291cmNlIG1hcCBiZWluZyBnZW5lcmF0ZWQgdG8gYSBzdHJpbmcuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLnRvU3RyaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfdG9TdHJpbmcoKSB7XG4gICAgICByZXR1cm4gSlNPTi5zdHJpbmdpZnkodGhpcy50b0pTT04oKSk7XG4gICAgfTtcblxuICBleHBvcnRzLlNvdXJjZU1hcEdlbmVyYXRvciA9IFNvdXJjZU1hcEdlbmVyYXRvcjtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvc291cmNlLW1hcC1nZW5lcmF0b3IuanNcbiAqKiBtb2R1bGUgaWQgPSAxXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICpcbiAqIEJhc2VkIG9uIHRoZSBCYXNlIDY0IFZMUSBpbXBsZW1lbnRhdGlvbiBpbiBDbG9zdXJlIENvbXBpbGVyOlxuICogaHR0cHM6Ly9jb2RlLmdvb2dsZS5jb20vcC9jbG9zdXJlLWNvbXBpbGVyL3NvdXJjZS9icm93c2UvdHJ1bmsvc3JjL2NvbS9nb29nbGUvZGVidWdnaW5nL3NvdXJjZW1hcC9CYXNlNjRWTFEuamF2YVxuICpcbiAqIENvcHlyaWdodCAyMDExIFRoZSBDbG9zdXJlIENvbXBpbGVyIEF1dGhvcnMuIEFsbCByaWdodHMgcmVzZXJ2ZWQuXG4gKiBSZWRpc3RyaWJ1dGlvbiBhbmQgdXNlIGluIHNvdXJjZSBhbmQgYmluYXJ5IGZvcm1zLCB3aXRoIG9yIHdpdGhvdXRcbiAqIG1vZGlmaWNhdGlvbiwgYXJlIHBlcm1pdHRlZCBwcm92aWRlZCB0aGF0IHRoZSBmb2xsb3dpbmcgY29uZGl0aW9ucyBhcmVcbiAqIG1ldDpcbiAqXG4gKiAgKiBSZWRpc3RyaWJ1dGlvbnMgb2Ygc291cmNlIGNvZGUgbXVzdCByZXRhaW4gdGhlIGFib3ZlIGNvcHlyaWdodFxuICogICAgbm90aWNlLCB0aGlzIGxpc3Qgb2YgY29uZGl0aW9ucyBhbmQgdGhlIGZvbGxvd2luZyBkaXNjbGFpbWVyLlxuICogICogUmVkaXN0cmlidXRpb25zIGluIGJpbmFyeSBmb3JtIG11c3QgcmVwcm9kdWNlIHRoZSBhYm92ZVxuICogICAgY29weXJpZ2h0IG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmdcbiAqICAgIGRpc2NsYWltZXIgaW4gdGhlIGRvY3VtZW50YXRpb24gYW5kL29yIG90aGVyIG1hdGVyaWFscyBwcm92aWRlZFxuICogICAgd2l0aCB0aGUgZGlzdHJpYnV0aW9uLlxuICogICogTmVpdGhlciB0aGUgbmFtZSBvZiBHb29nbGUgSW5jLiBub3IgdGhlIG5hbWVzIG9mIGl0c1xuICogICAgY29udHJpYnV0b3JzIG1heSBiZSB1c2VkIHRvIGVuZG9yc2Ugb3IgcHJvbW90ZSBwcm9kdWN0cyBkZXJpdmVkXG4gKiAgICBmcm9tIHRoaXMgc29mdHdhcmUgd2l0aG91dCBzcGVjaWZpYyBwcmlvciB3cml0dGVuIHBlcm1pc3Npb24uXG4gKlxuICogVEhJUyBTT0ZUV0FSRSBJUyBQUk9WSURFRCBCWSBUSEUgQ09QWVJJR0hUIEhPTERFUlMgQU5EIENPTlRSSUJVVE9SU1xuICogXCJBUyBJU1wiIEFORCBBTlkgRVhQUkVTUyBPUiBJTVBMSUVEIFdBUlJBTlRJRVMsIElOQ0xVRElORywgQlVUIE5PVFxuICogTElNSVRFRCBUTywgVEhFIElNUExJRUQgV0FSUkFOVElFUyBPRiBNRVJDSEFOVEFCSUxJVFkgQU5EIEZJVE5FU1MgRk9SXG4gKiBBIFBBUlRJQ1VMQVIgUFVSUE9TRSBBUkUgRElTQ0xBSU1FRC4gSU4gTk8gRVZFTlQgU0hBTEwgVEhFIENPUFlSSUdIVFxuICogT1dORVIgT1IgQ09OVFJJQlVUT1JTIEJFIExJQUJMRSBGT1IgQU5ZIERJUkVDVCwgSU5ESVJFQ1QsIElOQ0lERU5UQUwsXG4gKiBTUEVDSUFMLCBFWEVNUExBUlksIE9SIENPTlNFUVVFTlRJQUwgREFNQUdFUyAoSU5DTFVESU5HLCBCVVQgTk9UXG4gKiBMSU1JVEVEIFRPLCBQUk9DVVJFTUVOVCBPRiBTVUJTVElUVVRFIEdPT0RTIE9SIFNFUlZJQ0VTOyBMT1NTIE9GIFVTRSxcbiAqIERBVEEsIE9SIFBST0ZJVFM7IE9SIEJVU0lORVNTIElOVEVSUlVQVElPTikgSE9XRVZFUiBDQVVTRUQgQU5EIE9OIEFOWVxuICogVEhFT1JZIE9GIExJQUJJTElUWSwgV0hFVEhFUiBJTiBDT05UUkFDVCwgU1RSSUNUIExJQUJJTElUWSwgT1IgVE9SVFxuICogKElOQ0xVRElORyBORUdMSUdFTkNFIE9SIE9USEVSV0lTRSkgQVJJU0lORyBJTiBBTlkgV0FZIE9VVCBPRiBUSEUgVVNFXG4gKiBPRiBUSElTIFNPRlRXQVJFLCBFVkVOIElGIEFEVklTRUQgT0YgVEhFIFBPU1NJQklMSVRZIE9GIFNVQ0ggREFNQUdFLlxuICovXG57XG4gIHZhciBiYXNlNjQgPSByZXF1aXJlKCcuL2Jhc2U2NCcpO1xuXG4gIC8vIEEgc2luZ2xlIGJhc2UgNjQgZGlnaXQgY2FuIGNvbnRhaW4gNiBiaXRzIG9mIGRhdGEuIEZvciB0aGUgYmFzZSA2NCB2YXJpYWJsZVxuICAvLyBsZW5ndGggcXVhbnRpdGllcyB3ZSB1c2UgaW4gdGhlIHNvdXJjZSBtYXAgc3BlYywgdGhlIGZpcnN0IGJpdCBpcyB0aGUgc2lnbixcbiAgLy8gdGhlIG5leHQgZm91ciBiaXRzIGFyZSB0aGUgYWN0dWFsIHZhbHVlLCBhbmQgdGhlIDZ0aCBiaXQgaXMgdGhlXG4gIC8vIGNvbnRpbnVhdGlvbiBiaXQuIFRoZSBjb250aW51YXRpb24gYml0IHRlbGxzIHVzIHdoZXRoZXIgdGhlcmUgYXJlIG1vcmVcbiAgLy8gZGlnaXRzIGluIHRoaXMgdmFsdWUgZm9sbG93aW5nIHRoaXMgZGlnaXQuXG4gIC8vXG4gIC8vICAgQ29udGludWF0aW9uXG4gIC8vICAgfCAgICBTaWduXG4gIC8vICAgfCAgICB8XG4gIC8vICAgViAgICBWXG4gIC8vICAgMTAxMDExXG5cbiAgdmFyIFZMUV9CQVNFX1NISUZUID0gNTtcblxuICAvLyBiaW5hcnk6IDEwMDAwMFxuICB2YXIgVkxRX0JBU0UgPSAxIDw8IFZMUV9CQVNFX1NISUZUO1xuXG4gIC8vIGJpbmFyeTogMDExMTExXG4gIHZhciBWTFFfQkFTRV9NQVNLID0gVkxRX0JBU0UgLSAxO1xuXG4gIC8vIGJpbmFyeTogMTAwMDAwXG4gIHZhciBWTFFfQ09OVElOVUFUSU9OX0JJVCA9IFZMUV9CQVNFO1xuXG4gIC8qKlxuICAgKiBDb252ZXJ0cyBmcm9tIGEgdHdvLWNvbXBsZW1lbnQgdmFsdWUgdG8gYSB2YWx1ZSB3aGVyZSB0aGUgc2lnbiBiaXQgaXNcbiAgICogcGxhY2VkIGluIHRoZSBsZWFzdCBzaWduaWZpY2FudCBiaXQuICBGb3IgZXhhbXBsZSwgYXMgZGVjaW1hbHM6XG4gICAqICAgMSBiZWNvbWVzIDIgKDEwIGJpbmFyeSksIC0xIGJlY29tZXMgMyAoMTEgYmluYXJ5KVxuICAgKiAgIDIgYmVjb21lcyA0ICgxMDAgYmluYXJ5KSwgLTIgYmVjb21lcyA1ICgxMDEgYmluYXJ5KVxuICAgKi9cbiAgZnVuY3Rpb24gdG9WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgcmV0dXJuIGFWYWx1ZSA8IDBcbiAgICAgID8gKCgtYVZhbHVlKSA8PCAxKSArIDFcbiAgICAgIDogKGFWYWx1ZSA8PCAxKSArIDA7XG4gIH1cblxuICAvKipcbiAgICogQ29udmVydHMgdG8gYSB0d28tY29tcGxlbWVudCB2YWx1ZSBmcm9tIGEgdmFsdWUgd2hlcmUgdGhlIHNpZ24gYml0IGlzXG4gICAqIHBsYWNlZCBpbiB0aGUgbGVhc3Qgc2lnbmlmaWNhbnQgYml0LiAgRm9yIGV4YW1wbGUsIGFzIGRlY2ltYWxzOlxuICAgKiAgIDIgKDEwIGJpbmFyeSkgYmVjb21lcyAxLCAzICgxMSBiaW5hcnkpIGJlY29tZXMgLTFcbiAgICogICA0ICgxMDAgYmluYXJ5KSBiZWNvbWVzIDIsIDUgKDEwMSBiaW5hcnkpIGJlY29tZXMgLTJcbiAgICovXG4gIGZ1bmN0aW9uIGZyb21WTFFTaWduZWQoYVZhbHVlKSB7XG4gICAgdmFyIGlzTmVnYXRpdmUgPSAoYVZhbHVlICYgMSkgPT09IDE7XG4gICAgdmFyIHNoaWZ0ZWQgPSBhVmFsdWUgPj4gMTtcbiAgICByZXR1cm4gaXNOZWdhdGl2ZVxuICAgICAgPyAtc2hpZnRlZFxuICAgICAgOiBzaGlmdGVkO1xuICB9XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGJhc2UgNjQgVkxRIGVuY29kZWQgdmFsdWUuXG4gICAqL1xuICBleHBvcnRzLmVuY29kZSA9IGZ1bmN0aW9uIGJhc2U2NFZMUV9lbmNvZGUoYVZhbHVlKSB7XG4gICAgdmFyIGVuY29kZWQgPSBcIlwiO1xuICAgIHZhciBkaWdpdDtcblxuICAgIHZhciB2bHEgPSB0b1ZMUVNpZ25lZChhVmFsdWUpO1xuXG4gICAgZG8ge1xuICAgICAgZGlnaXQgPSB2bHEgJiBWTFFfQkFTRV9NQVNLO1xuICAgICAgdmxxID4+Pj0gVkxRX0JBU0VfU0hJRlQ7XG4gICAgICBpZiAodmxxID4gMCkge1xuICAgICAgICAvLyBUaGVyZSBhcmUgc3RpbGwgbW9yZSBkaWdpdHMgaW4gdGhpcyB2YWx1ZSwgc28gd2UgbXVzdCBtYWtlIHN1cmUgdGhlXG4gICAgICAgIC8vIGNvbnRpbnVhdGlvbiBiaXQgaXMgbWFya2VkLlxuICAgICAgICBkaWdpdCB8PSBWTFFfQ09OVElOVUFUSU9OX0JJVDtcbiAgICAgIH1cbiAgICAgIGVuY29kZWQgKz0gYmFzZTY0LmVuY29kZShkaWdpdCk7XG4gICAgfSB3aGlsZSAodmxxID4gMCk7XG5cbiAgICByZXR1cm4gZW5jb2RlZDtcbiAgfTtcblxuICAvKipcbiAgICogRGVjb2RlcyB0aGUgbmV4dCBiYXNlIDY0IFZMUSB2YWx1ZSBmcm9tIHRoZSBnaXZlbiBzdHJpbmcgYW5kIHJldHVybnMgdGhlXG4gICAqIHZhbHVlIGFuZCB0aGUgcmVzdCBvZiB0aGUgc3RyaW5nIHZpYSB0aGUgb3V0IHBhcmFtZXRlci5cbiAgICovXG4gIGV4cG9ydHMuZGVjb2RlID0gZnVuY3Rpb24gYmFzZTY0VkxRX2RlY29kZShhU3RyLCBhSW5kZXgsIGFPdXRQYXJhbSkge1xuICAgIHZhciBzdHJMZW4gPSBhU3RyLmxlbmd0aDtcbiAgICB2YXIgcmVzdWx0ID0gMDtcbiAgICB2YXIgc2hpZnQgPSAwO1xuICAgIHZhciBjb250aW51YXRpb24sIGRpZ2l0O1xuXG4gICAgZG8ge1xuICAgICAgaWYgKGFJbmRleCA+PSBzdHJMZW4pIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiRXhwZWN0ZWQgbW9yZSBkaWdpdHMgaW4gYmFzZSA2NCBWTFEgdmFsdWUuXCIpO1xuICAgICAgfVxuXG4gICAgICBkaWdpdCA9IGJhc2U2NC5kZWNvZGUoYVN0ci5jaGFyQ29kZUF0KGFJbmRleCsrKSk7XG4gICAgICBpZiAoZGlnaXQgPT09IC0xKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkludmFsaWQgYmFzZTY0IGRpZ2l0OiBcIiArIGFTdHIuY2hhckF0KGFJbmRleCAtIDEpKTtcbiAgICAgIH1cblxuICAgICAgY29udGludWF0aW9uID0gISEoZGlnaXQgJiBWTFFfQ09OVElOVUFUSU9OX0JJVCk7XG4gICAgICBkaWdpdCAmPSBWTFFfQkFTRV9NQVNLO1xuICAgICAgcmVzdWx0ID0gcmVzdWx0ICsgKGRpZ2l0IDw8IHNoaWZ0KTtcbiAgICAgIHNoaWZ0ICs9IFZMUV9CQVNFX1NISUZUO1xuICAgIH0gd2hpbGUgKGNvbnRpbnVhdGlvbik7XG5cbiAgICBhT3V0UGFyYW0udmFsdWUgPSBmcm9tVkxRU2lnbmVkKHJlc3VsdCk7XG4gICAgYU91dFBhcmFtLnJlc3QgPSBhSW5kZXg7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2Jhc2U2NC12bHEuanNcbiAqKiBtb2R1bGUgaWQgPSAyXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBpbnRUb0NoYXJNYXAgPSAnQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVphYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5ejAxMjM0NTY3ODkrLycuc3BsaXQoJycpO1xuXG4gIC8qKlxuICAgKiBFbmNvZGUgYW4gaW50ZWdlciBpbiB0aGUgcmFuZ2Ugb2YgMCB0byA2MyB0byBhIHNpbmdsZSBiYXNlIDY0IGRpZ2l0LlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiAobnVtYmVyKSB7XG4gICAgaWYgKDAgPD0gbnVtYmVyICYmIG51bWJlciA8IGludFRvQ2hhck1hcC5sZW5ndGgpIHtcbiAgICAgIHJldHVybiBpbnRUb0NoYXJNYXBbbnVtYmVyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcIk11c3QgYmUgYmV0d2VlbiAwIGFuZCA2MzogXCIgKyBudW1iZXIpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBEZWNvZGUgYSBzaW5nbGUgYmFzZSA2NCBjaGFyYWN0ZXIgY29kZSBkaWdpdCB0byBhbiBpbnRlZ2VyLiBSZXR1cm5zIC0xIG9uXG4gICAqIGZhaWx1cmUuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIChjaGFyQ29kZSkge1xuICAgIHZhciBiaWdBID0gNjU7ICAgICAvLyAnQSdcbiAgICB2YXIgYmlnWiA9IDkwOyAgICAgLy8gJ1onXG5cbiAgICB2YXIgbGl0dGxlQSA9IDk3OyAgLy8gJ2EnXG4gICAgdmFyIGxpdHRsZVogPSAxMjI7IC8vICd6J1xuXG4gICAgdmFyIHplcm8gPSA0ODsgICAgIC8vICcwJ1xuICAgIHZhciBuaW5lID0gNTc7ICAgICAvLyAnOSdcblxuICAgIHZhciBwbHVzID0gNDM7ICAgICAvLyAnKydcbiAgICB2YXIgc2xhc2ggPSA0NzsgICAgLy8gJy8nXG5cbiAgICB2YXIgbGl0dGxlT2Zmc2V0ID0gMjY7XG4gICAgdmFyIG51bWJlck9mZnNldCA9IDUyO1xuXG4gICAgLy8gMCAtIDI1OiBBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWlxuICAgIGlmIChiaWdBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGJpZ1opIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBiaWdBKTtcbiAgICB9XG5cbiAgICAvLyAyNiAtIDUxOiBhYmNkZWZnaGlqa2xtbm9wcXJzdHV2d3h5elxuICAgIGlmIChsaXR0bGVBIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IGxpdHRsZVopIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSBsaXR0bGVBICsgbGl0dGxlT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA1MiAtIDYxOiAwMTIzNDU2Nzg5XG4gICAgaWYgKHplcm8gPD0gY2hhckNvZGUgJiYgY2hhckNvZGUgPD0gbmluZSkge1xuICAgICAgcmV0dXJuIChjaGFyQ29kZSAtIHplcm8gKyBudW1iZXJPZmZzZXQpO1xuICAgIH1cblxuICAgIC8vIDYyOiArXG4gICAgaWYgKGNoYXJDb2RlID09IHBsdXMpIHtcbiAgICAgIHJldHVybiA2MjtcbiAgICB9XG5cbiAgICAvLyA2MzogL1xuICAgIGlmIChjaGFyQ29kZSA9PSBzbGFzaCkge1xuICAgICAgcmV0dXJuIDYzO1xuICAgIH1cblxuICAgIC8vIEludmFsaWQgYmFzZTY0IGRpZ2l0LlxuICAgIHJldHVybiAtMTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmFzZTY0LmpzXG4gKiogbW9kdWxlIGlkID0gM1xuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICAvKipcbiAgICogVGhpcyBpcyBhIGhlbHBlciBmdW5jdGlvbiBmb3IgZ2V0dGluZyB2YWx1ZXMgZnJvbSBwYXJhbWV0ZXIvb3B0aW9uc1xuICAgKiBvYmplY3RzLlxuICAgKlxuICAgKiBAcGFyYW0gYXJncyBUaGUgb2JqZWN0IHdlIGFyZSBleHRyYWN0aW5nIHZhbHVlcyBmcm9tXG4gICAqIEBwYXJhbSBuYW1lIFRoZSBuYW1lIG9mIHRoZSBwcm9wZXJ0eSB3ZSBhcmUgZ2V0dGluZy5cbiAgICogQHBhcmFtIGRlZmF1bHRWYWx1ZSBBbiBvcHRpb25hbCB2YWx1ZSB0byByZXR1cm4gaWYgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmdcbiAgICogZnJvbSB0aGUgb2JqZWN0LiBJZiB0aGlzIGlzIG5vdCBzcGVjaWZpZWQgYW5kIHRoZSBwcm9wZXJ0eSBpcyBtaXNzaW5nLCBhblxuICAgKiBlcnJvciB3aWxsIGJlIHRocm93bi5cbiAgICovXG4gIGZ1bmN0aW9uIGdldEFyZyhhQXJncywgYU5hbWUsIGFEZWZhdWx0VmFsdWUpIHtcbiAgICBpZiAoYU5hbWUgaW4gYUFyZ3MpIHtcbiAgICAgIHJldHVybiBhQXJnc1thTmFtZV07XG4gICAgfSBlbHNlIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAzKSB7XG4gICAgICByZXR1cm4gYURlZmF1bHRWYWx1ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhTmFtZSArICdcIiBpcyBhIHJlcXVpcmVkIGFyZ3VtZW50LicpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmdldEFyZyA9IGdldEFyZztcblxuICB2YXIgdXJsUmVnZXhwID0gL14oPzooW1xcdytcXC0uXSspOik/XFwvXFwvKD86KFxcdys6XFx3KylAKT8oW1xcdy5dKikoPzo6KFxcZCspKT8oXFxTKikkLztcbiAgdmFyIGRhdGFVcmxSZWdleHAgPSAvXmRhdGE6LitcXCwuKyQvO1xuXG4gIGZ1bmN0aW9uIHVybFBhcnNlKGFVcmwpIHtcbiAgICB2YXIgbWF0Y2ggPSBhVXJsLm1hdGNoKHVybFJlZ2V4cCk7XG4gICAgaWYgKCFtYXRjaCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBzY2hlbWU6IG1hdGNoWzFdLFxuICAgICAgYXV0aDogbWF0Y2hbMl0sXG4gICAgICBob3N0OiBtYXRjaFszXSxcbiAgICAgIHBvcnQ6IG1hdGNoWzRdLFxuICAgICAgcGF0aDogbWF0Y2hbNV1cbiAgICB9O1xuICB9XG4gIGV4cG9ydHMudXJsUGFyc2UgPSB1cmxQYXJzZTtcblxuICBmdW5jdGlvbiB1cmxHZW5lcmF0ZShhUGFyc2VkVXJsKSB7XG4gICAgdmFyIHVybCA9ICcnO1xuICAgIGlmIChhUGFyc2VkVXJsLnNjaGVtZSkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuc2NoZW1lICsgJzonO1xuICAgIH1cbiAgICB1cmwgKz0gJy8vJztcbiAgICBpZiAoYVBhcnNlZFVybC5hdXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5hdXRoICsgJ0AnO1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5ob3N0KSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5ob3N0O1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wb3J0KSB7XG4gICAgICB1cmwgKz0gXCI6XCIgKyBhUGFyc2VkVXJsLnBvcnRcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwucGF0aCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwucGF0aDtcbiAgICB9XG4gICAgcmV0dXJuIHVybDtcbiAgfVxuICBleHBvcnRzLnVybEdlbmVyYXRlID0gdXJsR2VuZXJhdGU7XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBwYXRoLCBvciB0aGUgcGF0aCBwb3J0aW9uIG9mIGEgVVJMOlxuICAgKlxuICAgKiAtIFJlcGxhY2VzIGNvbnNlcXV0aXZlIHNsYXNoZXMgd2l0aCBvbmUgc2xhc2guXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnLicgcGFydHMuXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnPGRpcj4vLi4nIHBhcnRzLlxuICAgKlxuICAgKiBCYXNlZCBvbiBjb2RlIGluIHRoZSBOb2RlLmpzICdwYXRoJyBjb3JlIG1vZHVsZS5cbiAgICpcbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIHVybCB0byBub3JtYWxpemUuXG4gICAqL1xuICBmdW5jdGlvbiBub3JtYWxpemUoYVBhdGgpIHtcbiAgICB2YXIgcGF0aCA9IGFQYXRoO1xuICAgIHZhciB1cmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgaWYgKHVybCkge1xuICAgICAgaWYgKCF1cmwucGF0aCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG4gICAgICBwYXRoID0gdXJsLnBhdGg7XG4gICAgfVxuICAgIHZhciBpc0Fic29sdXRlID0gZXhwb3J0cy5pc0Fic29sdXRlKHBhdGgpO1xuXG4gICAgdmFyIHBhcnRzID0gcGF0aC5zcGxpdCgvXFwvKy8pO1xuICAgIGZvciAodmFyIHBhcnQsIHVwID0gMCwgaSA9IHBhcnRzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICBwYXJ0ID0gcGFydHNbaV07XG4gICAgICBpZiAocGFydCA9PT0gJy4nKSB7XG4gICAgICAgIHBhcnRzLnNwbGljZShpLCAxKTtcbiAgICAgIH0gZWxzZSBpZiAocGFydCA9PT0gJy4uJykge1xuICAgICAgICB1cCsrO1xuICAgICAgfSBlbHNlIGlmICh1cCA+IDApIHtcbiAgICAgICAgaWYgKHBhcnQgPT09ICcnKSB7XG4gICAgICAgICAgLy8gVGhlIGZpcnN0IHBhcnQgaXMgYmxhbmsgaWYgdGhlIHBhdGggaXMgYWJzb2x1dGUuIFRyeWluZyB0byBnb1xuICAgICAgICAgIC8vIGFib3ZlIHRoZSByb290IGlzIGEgbm8tb3AuIFRoZXJlZm9yZSB3ZSBjYW4gcmVtb3ZlIGFsbCAnLi4nIHBhcnRzXG4gICAgICAgICAgLy8gZGlyZWN0bHkgYWZ0ZXIgdGhlIHJvb3QuXG4gICAgICAgICAgcGFydHMuc3BsaWNlKGkgKyAxLCB1cCk7XG4gICAgICAgICAgdXAgPSAwO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHBhcnRzLnNwbGljZShpLCAyKTtcbiAgICAgICAgICB1cC0tO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHBhdGggPSBwYXJ0cy5qb2luKCcvJyk7XG5cbiAgICBpZiAocGF0aCA9PT0gJycpIHtcbiAgICAgIHBhdGggPSBpc0Fic29sdXRlID8gJy8nIDogJy4nO1xuICAgIH1cblxuICAgIGlmICh1cmwpIHtcbiAgICAgIHVybC5wYXRoID0gcGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZSh1cmwpO1xuICAgIH1cbiAgICByZXR1cm4gcGF0aDtcbiAgfVxuICBleHBvcnRzLm5vcm1hbGl6ZSA9IG5vcm1hbGl6ZTtcblxuICAvKipcbiAgICogSm9pbnMgdHdvIHBhdGhzL1VSTHMuXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBqb2luZWQgd2l0aCB0aGUgcm9vdC5cbiAgICpcbiAgICogLSBJZiBhUGF0aCBpcyBhIFVSTCBvciBhIGRhdGEgVVJJLCBhUGF0aCBpcyByZXR1cm5lZCwgdW5sZXNzIGFQYXRoIGlzIGFcbiAgICogICBzY2hlbWUtcmVsYXRpdmUgVVJMOiBUaGVuIHRoZSBzY2hlbWUgb2YgYVJvb3QsIGlmIGFueSwgaXMgcHJlcGVuZGVkXG4gICAqICAgZmlyc3QuXG4gICAqIC0gT3RoZXJ3aXNlIGFQYXRoIGlzIGEgcGF0aC4gSWYgYVJvb3QgaXMgYSBVUkwsIHRoZW4gaXRzIHBhdGggcG9ydGlvblxuICAgKiAgIGlzIHVwZGF0ZWQgd2l0aCB0aGUgcmVzdWx0IGFuZCBhUm9vdCBpcyByZXR1cm5lZC4gT3RoZXJ3aXNlIHRoZSByZXN1bHRcbiAgICogICBpcyByZXR1cm5lZC5cbiAgICogICAtIElmIGFQYXRoIGlzIGFic29sdXRlLCB0aGUgcmVzdWx0IGlzIGFQYXRoLlxuICAgKiAgIC0gT3RoZXJ3aXNlIHRoZSB0d28gcGF0aHMgYXJlIGpvaW5lZCB3aXRoIGEgc2xhc2guXG4gICAqIC0gSm9pbmluZyBmb3IgZXhhbXBsZSAnaHR0cDovLycgYW5kICd3d3cuZXhhbXBsZS5jb20nIGlzIGFsc28gc3VwcG9ydGVkLlxuICAgKi9cbiAgZnVuY3Rpb24gam9pbihhUm9vdCwgYVBhdGgpIHtcbiAgICBpZiAoYVJvb3QgPT09IFwiXCIpIHtcbiAgICAgIGFSb290ID0gXCIuXCI7XG4gICAgfVxuICAgIGlmIChhUGF0aCA9PT0gXCJcIikge1xuICAgICAgYVBhdGggPSBcIi5cIjtcbiAgICB9XG4gICAgdmFyIGFQYXRoVXJsID0gdXJsUGFyc2UoYVBhdGgpO1xuICAgIHZhciBhUm9vdFVybCA9IHVybFBhcnNlKGFSb290KTtcbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290ID0gYVJvb3RVcmwucGF0aCB8fCAnLyc7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oZm9vLCAnLy93d3cuZXhhbXBsZS5vcmcnKWBcbiAgICBpZiAoYVBhdGhVcmwgJiYgIWFQYXRoVXJsLnNjaGVtZSkge1xuICAgICAgaWYgKGFSb290VXJsKSB7XG4gICAgICAgIGFQYXRoVXJsLnNjaGVtZSA9IGFSb290VXJsLnNjaGVtZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUGF0aFVybCk7XG4gICAgfVxuXG4gICAgaWYgKGFQYXRoVXJsIHx8IGFQYXRoLm1hdGNoKGRhdGFVcmxSZWdleHApKSB7XG4gICAgICByZXR1cm4gYVBhdGg7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oJ2h0dHA6Ly8nLCAnd3d3LmV4YW1wbGUuY29tJylgXG4gICAgaWYgKGFSb290VXJsICYmICFhUm9vdFVybC5ob3N0ICYmICFhUm9vdFVybC5wYXRoKSB7XG4gICAgICBhUm9vdFVybC5ob3N0ID0gYVBhdGg7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cblxuICAgIHZhciBqb2luZWQgPSBhUGF0aC5jaGFyQXQoMCkgPT09ICcvJ1xuICAgICAgPyBhUGF0aFxuICAgICAgOiBub3JtYWxpemUoYVJvb3QucmVwbGFjZSgvXFwvKyQvLCAnJykgKyAnLycgKyBhUGF0aCk7XG5cbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290VXJsLnBhdGggPSBqb2luZWQ7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cbiAgICByZXR1cm4gam9pbmVkO1xuICB9XG4gIGV4cG9ydHMuam9pbiA9IGpvaW47XG5cbiAgZXhwb3J0cy5pc0Fic29sdXRlID0gZnVuY3Rpb24gKGFQYXRoKSB7XG4gICAgcmV0dXJuIGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nIHx8ICEhYVBhdGgubWF0Y2godXJsUmVnZXhwKTtcbiAgfTtcblxuICAvKipcbiAgICogTWFrZSBhIHBhdGggcmVsYXRpdmUgdG8gYSBVUkwgb3IgYW5vdGhlciBwYXRoLlxuICAgKlxuICAgKiBAcGFyYW0gYVJvb3QgVGhlIHJvb3QgcGF0aCBvciBVUkwuXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciBVUkwgdG8gYmUgbWFkZSByZWxhdGl2ZSB0byBhUm9vdC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlbGF0aXZlKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG5cbiAgICBhUm9vdCA9IGFSb290LnJlcGxhY2UoL1xcLyQvLCAnJyk7XG5cbiAgICAvLyBJdCBpcyBwb3NzaWJsZSBmb3IgdGhlIHBhdGggdG8gYmUgYWJvdmUgdGhlIHJvb3QuIEluIHRoaXMgY2FzZSwgc2ltcGx5XG4gICAgLy8gY2hlY2tpbmcgd2hldGhlciB0aGUgcm9vdCBpcyBhIHByZWZpeCBvZiB0aGUgcGF0aCB3b24ndCB3b3JrLiBJbnN0ZWFkLCB3ZVxuICAgIC8vIG5lZWQgdG8gcmVtb3ZlIGNvbXBvbmVudHMgZnJvbSB0aGUgcm9vdCBvbmUgYnkgb25lLCB1bnRpbCBlaXRoZXIgd2UgZmluZFxuICAgIC8vIGEgcHJlZml4IHRoYXQgZml0cywgb3Igd2UgcnVuIG91dCBvZiBjb21wb25lbnRzIHRvIHJlbW92ZS5cbiAgICB2YXIgbGV2ZWwgPSAwO1xuICAgIHdoaWxlIChhUGF0aC5pbmRleE9mKGFSb290ICsgJy8nKSAhPT0gMCkge1xuICAgICAgdmFyIGluZGV4ID0gYVJvb3QubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgIC8vIElmIHRoZSBvbmx5IHBhcnQgb2YgdGhlIHJvb3QgdGhhdCBpcyBsZWZ0IGlzIHRoZSBzY2hlbWUgKGkuZS4gaHR0cDovLyxcbiAgICAgIC8vIGZpbGU6Ly8vLCBldGMuKSwgb25lIG9yIG1vcmUgc2xhc2hlcyAoLyksIG9yIHNpbXBseSBub3RoaW5nIGF0IGFsbCwgd2VcbiAgICAgIC8vIGhhdmUgZXhoYXVzdGVkIGFsbCBjb21wb25lbnRzLCBzbyB0aGUgcGF0aCBpcyBub3QgcmVsYXRpdmUgdG8gdGhlIHJvb3QuXG4gICAgICBhUm9vdCA9IGFSb290LnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIGlmIChhUm9vdC5tYXRjaCgvXihbXlxcL10rOlxcLyk/XFwvKiQvKSkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgICsrbGV2ZWw7XG4gICAgfVxuXG4gICAgLy8gTWFrZSBzdXJlIHdlIGFkZCBhIFwiLi4vXCIgZm9yIGVhY2ggY29tcG9uZW50IHdlIHJlbW92ZWQgZnJvbSB0aGUgcm9vdC5cbiAgICByZXR1cm4gQXJyYXkobGV2ZWwgKyAxKS5qb2luKFwiLi4vXCIpICsgYVBhdGguc3Vic3RyKGFSb290Lmxlbmd0aCArIDEpO1xuICB9XG4gIGV4cG9ydHMucmVsYXRpdmUgPSByZWxhdGl2ZTtcblxuICAvKipcbiAgICogQmVjYXVzZSBiZWhhdmlvciBnb2VzIHdhY2t5IHdoZW4geW91IHNldCBgX19wcm90b19fYCBvbiBvYmplY3RzLCB3ZVxuICAgKiBoYXZlIHRvIHByZWZpeCBhbGwgdGhlIHN0cmluZ3MgaW4gb3VyIHNldCB3aXRoIGFuIGFyYml0cmFyeSBjaGFyYWN0ZXIuXG4gICAqXG4gICAqIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL3B1bGwvMzEgYW5kXG4gICAqIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvaXNzdWVzLzMwXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgZnVuY3Rpb24gdG9TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiAnJCcgKyBhU3RyO1xuICB9XG4gIGV4cG9ydHMudG9TZXRTdHJpbmcgPSB0b1NldFN0cmluZztcblxuICBmdW5jdGlvbiBmcm9tU2V0U3RyaW5nKGFTdHIpIHtcbiAgICByZXR1cm4gYVN0ci5zdWJzdHIoMSk7XG4gIH1cbiAgZXhwb3J0cy5mcm9tU2V0U3RyaW5nID0gZnJvbVNldFN0cmluZztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aGVyZSB0aGUgb3JpZ2luYWwgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiwgYnV0IGRpZmZlcmVudCBnZW5lcmF0ZWRcbiAgICogbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGEgbWFwcGluZyB3aXRoIGFcbiAgICogc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zID0gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnM7XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2l0aCBkZWZsYXRlZCBzb3VyY2UgYW5kIG5hbWUgaW5kaWNlcyB3aGVyZVxuICAgKiB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqXG4gICAqIE9wdGlvbmFsbHkgcGFzcyBpbiBgdHJ1ZWAgYXMgYG9ubHlDb21wYXJlR2VuZXJhdGVkYCB0byBjb25zaWRlciB0d29cbiAgICogbWFwcGluZ3Mgd2l0aCB0aGUgc2FtZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uLCBidXQgZGlmZmVyZW50XG4gICAqIHNvdXJjZS9uYW1lL29yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiB0aGUgc2FtZS4gVXNlZnVsIHdoZW4gc2VhcmNoaW5nIGZvciBhXG4gICAqIG1hcHBpbmcgd2l0aCBhIHN0dWJiZWQgb3V0IG1hcHBpbmcuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IsIG9ubHlDb21wYXJlR2VuZXJhdGVkKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Euc291cmNlIC0gbWFwcGluZ0Iuc291cmNlO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQ7XG5cbiAgZnVuY3Rpb24gc3RyY21wKGFTdHIxLCBhU3RyMikge1xuICAgIGlmIChhU3RyMSA9PT0gYVN0cjIpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cblxuICAgIGlmIChhU3RyMSA+IGFTdHIyKSB7XG4gICAgICByZXR1cm4gMTtcbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG4gIH1cblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGluZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBzdHJpbmdzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IHN0cmNtcChtYXBwaW5nQS5zb3VyY2UsIG1hcHBpbmdCLnNvdXJjZSk7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHN0cmNtcChtYXBwaW5nQS5uYW1lLCBtYXBwaW5nQi5uYW1lKTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3V0aWwuanNcbiAqKiBtb2R1bGUgaWQgPSA0XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLyoqXG4gICAqIEEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggaXMgYSBjb21iaW5hdGlvbiBvZiBhbiBhcnJheSBhbmQgYSBzZXQuIEFkZGluZyBhIG5ld1xuICAgKiBtZW1iZXIgaXMgTygxKSwgdGVzdGluZyBmb3IgbWVtYmVyc2hpcCBpcyBPKDEpLCBhbmQgZmluZGluZyB0aGUgaW5kZXggb2YgYW5cbiAgICogZWxlbWVudCBpcyBPKDEpLiBSZW1vdmluZyBlbGVtZW50cyBmcm9tIHRoZSBzZXQgaXMgbm90IHN1cHBvcnRlZC4gT25seVxuICAgKiBzdHJpbmdzIGFyZSBzdXBwb3J0ZWQgZm9yIG1lbWJlcnNoaXAuXG4gICAqL1xuICBmdW5jdGlvbiBBcnJheVNldCgpIHtcbiAgICB0aGlzLl9hcnJheSA9IFtdO1xuICAgIHRoaXMuX3NldCA9IHt9O1xuICB9XG5cbiAgLyoqXG4gICAqIFN0YXRpYyBtZXRob2QgZm9yIGNyZWF0aW5nIEFycmF5U2V0IGluc3RhbmNlcyBmcm9tIGFuIGV4aXN0aW5nIGFycmF5LlxuICAgKi9cbiAgQXJyYXlTZXQuZnJvbUFycmF5ID0gZnVuY3Rpb24gQXJyYXlTZXRfZnJvbUFycmF5KGFBcnJheSwgYUFsbG93RHVwbGljYXRlcykge1xuICAgIHZhciBzZXQgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gYUFycmF5Lmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICBzZXQuYWRkKGFBcnJheVtpXSwgYUFsbG93RHVwbGljYXRlcyk7XG4gICAgfVxuICAgIHJldHVybiBzZXQ7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybiBob3cgbWFueSB1bmlxdWUgaXRlbXMgYXJlIGluIHRoaXMgQXJyYXlTZXQuIElmIGR1cGxpY2F0ZXMgaGF2ZSBiZWVuXG4gICAqIGFkZGVkLCB0aGFuIHRob3NlIGRvIG5vdCBjb3VudCB0b3dhcmRzIHRoZSBzaXplLlxuICAgKlxuICAgKiBAcmV0dXJucyBOdW1iZXJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5zaXplID0gZnVuY3Rpb24gQXJyYXlTZXRfc2l6ZSgpIHtcbiAgICByZXR1cm4gT2JqZWN0LmdldE93blByb3BlcnR5TmFtZXModGhpcy5fc2V0KS5sZW5ndGg7XG4gIH07XG5cbiAgLyoqXG4gICAqIEFkZCB0aGUgZ2l2ZW4gc3RyaW5nIHRvIHRoaXMgc2V0LlxuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBBcnJheVNldF9hZGQoYVN0ciwgYUFsbG93RHVwbGljYXRlcykge1xuICAgIHZhciBzU3RyID0gdXRpbC50b1NldFN0cmluZyhhU3RyKTtcbiAgICB2YXIgaXNEdXBsaWNhdGUgPSB0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cik7XG4gICAgdmFyIGlkeCA9IHRoaXMuX2FycmF5Lmxlbmd0aDtcbiAgICBpZiAoIWlzRHVwbGljYXRlIHx8IGFBbGxvd0R1cGxpY2F0ZXMpIHtcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYVN0cik7XG4gICAgfVxuICAgIGlmICghaXNEdXBsaWNhdGUpIHtcbiAgICAgIHRoaXMuX3NldFtzU3RyXSA9IGlkeDtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIElzIHRoZSBnaXZlbiBzdHJpbmcgYSBtZW1iZXIgb2YgdGhpcyBzZXQ/XG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmhhcyA9IGZ1bmN0aW9uIEFycmF5U2V0X2hhcyhhU3RyKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIHJldHVybiB0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cik7XG4gIH07XG5cbiAgLyoqXG4gICAqIFdoYXQgaXMgdGhlIGluZGV4IG9mIHRoZSBnaXZlbiBzdHJpbmcgaW4gdGhlIGFycmF5P1xuICAgKlxuICAgKiBAcGFyYW0gU3RyaW5nIGFTdHJcbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS5pbmRleE9mID0gZnVuY3Rpb24gQXJyYXlTZXRfaW5kZXhPZihhU3RyKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIGlmICh0aGlzLl9zZXQuaGFzT3duUHJvcGVydHkoc1N0cikpIHtcbiAgICAgIHJldHVybiB0aGlzLl9zZXRbc1N0cl07XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYVN0ciArICdcIiBpcyBub3QgaW4gdGhlIHNldC4nKTtcbiAgfTtcblxuICAvKipcbiAgICogV2hhdCBpcyB0aGUgZWxlbWVudCBhdCB0aGUgZ2l2ZW4gaW5kZXg/XG4gICAqXG4gICAqIEBwYXJhbSBOdW1iZXIgYUlkeFxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmF0ID0gZnVuY3Rpb24gQXJyYXlTZXRfYXQoYUlkeCkge1xuICAgIGlmIChhSWR4ID49IDAgJiYgYUlkeCA8IHRoaXMuX2FycmF5Lmxlbmd0aCkge1xuICAgICAgcmV0dXJuIHRoaXMuX2FycmF5W2FJZHhdO1xuICAgIH1cbiAgICB0aHJvdyBuZXcgRXJyb3IoJ05vIGVsZW1lbnQgaW5kZXhlZCBieSAnICsgYUlkeCk7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGFycmF5IHJlcHJlc2VudGF0aW9uIG9mIHRoaXMgc2V0ICh3aGljaCBoYXMgdGhlIHByb3BlciBpbmRpY2VzXG4gICAqIGluZGljYXRlZCBieSBpbmRleE9mKS4gTm90ZSB0aGF0IHRoaXMgaXMgYSBjb3B5IG9mIHRoZSBpbnRlcm5hbCBhcnJheSB1c2VkXG4gICAqIGZvciBzdG9yaW5nIHRoZSBtZW1iZXJzIHNvIHRoYXQgbm8gb25lIGNhbiBtZXNzIHdpdGggaW50ZXJuYWwgc3RhdGUuXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUudG9BcnJheSA9IGZ1bmN0aW9uIEFycmF5U2V0X3RvQXJyYXkoKSB7XG4gICAgcmV0dXJuIHRoaXMuX2FycmF5LnNsaWNlKCk7XG4gIH07XG5cbiAgZXhwb3J0cy5BcnJheVNldCA9IEFycmF5U2V0O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9hcnJheS1zZXQuanNcbiAqKiBtb2R1bGUgaWQgPSA1XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTQgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLyoqXG4gICAqIERldGVybWluZSB3aGV0aGVyIG1hcHBpbmdCIGlzIGFmdGVyIG1hcHBpbmdBIHdpdGggcmVzcGVjdCB0byBnZW5lcmF0ZWRcbiAgICogcG9zaXRpb24uXG4gICAqL1xuICBmdW5jdGlvbiBnZW5lcmF0ZWRQb3NpdGlvbkFmdGVyKG1hcHBpbmdBLCBtYXBwaW5nQikge1xuICAgIC8vIE9wdGltaXplZCBmb3IgbW9zdCBjb21tb24gY2FzZVxuICAgIHZhciBsaW5lQSA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmU7XG4gICAgdmFyIGxpbmVCID0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICB2YXIgY29sdW1uQSA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbjtcbiAgICB2YXIgY29sdW1uQiA9IG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICByZXR1cm4gbGluZUIgPiBsaW5lQSB8fCBsaW5lQiA9PSBsaW5lQSAmJiBjb2x1bW5CID49IGNvbHVtbkEgfHxcbiAgICAgICAgICAgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IpIDw9IDA7XG4gIH1cblxuICAvKipcbiAgICogQSBkYXRhIHN0cnVjdHVyZSB0byBwcm92aWRlIGEgc29ydGVkIHZpZXcgb2YgYWNjdW11bGF0ZWQgbWFwcGluZ3MgaW4gYVxuICAgKiBwZXJmb3JtYW5jZSBjb25zY2lvdXMgbWFubmVyLiBJdCB0cmFkZXMgYSBuZWdsaWJhYmxlIG92ZXJoZWFkIGluIGdlbmVyYWxcbiAgICogY2FzZSBmb3IgYSBsYXJnZSBzcGVlZHVwIGluIGNhc2Ugb2YgbWFwcGluZ3MgYmVpbmcgYWRkZWQgaW4gb3JkZXIuXG4gICAqL1xuICBmdW5jdGlvbiBNYXBwaW5nTGlzdCgpIHtcbiAgICB0aGlzLl9hcnJheSA9IFtdO1xuICAgIHRoaXMuX3NvcnRlZCA9IHRydWU7XG4gICAgLy8gU2VydmVzIGFzIGluZmltdW1cbiAgICB0aGlzLl9sYXN0ID0ge2dlbmVyYXRlZExpbmU6IC0xLCBnZW5lcmF0ZWRDb2x1bW46IDB9O1xuICB9XG5cbiAgLyoqXG4gICAqIEl0ZXJhdGUgdGhyb3VnaCBpbnRlcm5hbCBpdGVtcy4gVGhpcyBtZXRob2QgdGFrZXMgdGhlIHNhbWUgYXJndW1lbnRzIHRoYXRcbiAgICogYEFycmF5LnByb3RvdHlwZS5mb3JFYWNoYCB0YWtlcy5cbiAgICpcbiAgICogTk9URTogVGhlIG9yZGVyIG9mIHRoZSBtYXBwaW5ncyBpcyBOT1QgZ3VhcmFudGVlZC5cbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS51bnNvcnRlZEZvckVhY2ggPVxuICAgIGZ1bmN0aW9uIE1hcHBpbmdMaXN0X2ZvckVhY2goYUNhbGxiYWNrLCBhVGhpc0FyZykge1xuICAgICAgdGhpcy5fYXJyYXkuZm9yRWFjaChhQ2FsbGJhY2ssIGFUaGlzQXJnKTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBZGQgdGhlIGdpdmVuIHNvdXJjZSBtYXBwaW5nLlxuICAgKlxuICAgKiBAcGFyYW0gT2JqZWN0IGFNYXBwaW5nXG4gICAqL1xuICBNYXBwaW5nTGlzdC5wcm90b3R5cGUuYWRkID0gZnVuY3Rpb24gTWFwcGluZ0xpc3RfYWRkKGFNYXBwaW5nKSB7XG4gICAgaWYgKGdlbmVyYXRlZFBvc2l0aW9uQWZ0ZXIodGhpcy5fbGFzdCwgYU1hcHBpbmcpKSB7XG4gICAgICB0aGlzLl9sYXN0ID0gYU1hcHBpbmc7XG4gICAgICB0aGlzLl9hcnJheS5wdXNoKGFNYXBwaW5nKTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhpcy5fc29ydGVkID0gZmFsc2U7XG4gICAgICB0aGlzLl9hcnJheS5wdXNoKGFNYXBwaW5nKTtcbiAgICB9XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGZsYXQsIHNvcnRlZCBhcnJheSBvZiBtYXBwaW5ncy4gVGhlIG1hcHBpbmdzIGFyZSBzb3J0ZWQgYnlcbiAgICogZ2VuZXJhdGVkIHBvc2l0aW9uLlxuICAgKlxuICAgKiBXQVJOSU5HOiBUaGlzIG1ldGhvZCByZXR1cm5zIGludGVybmFsIGRhdGEgd2l0aG91dCBjb3B5aW5nLCBmb3JcbiAgICogcGVyZm9ybWFuY2UuIFRoZSByZXR1cm4gdmFsdWUgbXVzdCBOT1QgYmUgbXV0YXRlZCwgYW5kIHNob3VsZCBiZSB0cmVhdGVkIGFzXG4gICAqIGFuIGltbXV0YWJsZSBib3Jyb3cuIElmIHlvdSB3YW50IHRvIHRha2Ugb3duZXJzaGlwLCB5b3UgbXVzdCBtYWtlIHlvdXIgb3duXG4gICAqIGNvcHkuXG4gICAqL1xuICBNYXBwaW5nTGlzdC5wcm90b3R5cGUudG9BcnJheSA9IGZ1bmN0aW9uIE1hcHBpbmdMaXN0X3RvQXJyYXkoKSB7XG4gICAgaWYgKCF0aGlzLl9zb3J0ZWQpIHtcbiAgICAgIHRoaXMuX2FycmF5LnNvcnQodXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZCk7XG4gICAgICB0aGlzLl9zb3J0ZWQgPSB0cnVlO1xuICAgIH1cbiAgICByZXR1cm4gdGhpcy5fYXJyYXk7XG4gIH07XG5cbiAgZXhwb3J0cy5NYXBwaW5nTGlzdCA9IE1hcHBpbmdMaXN0O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9tYXBwaW5nLWxpc3QuanNcbiAqKiBtb2R1bGUgaWQgPSA2XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG4gIHZhciBiaW5hcnlTZWFyY2ggPSByZXF1aXJlKCcuL2JpbmFyeS1zZWFyY2gnKTtcbiAgdmFyIEFycmF5U2V0ID0gcmVxdWlyZSgnLi9hcnJheS1zZXQnKS5BcnJheVNldDtcbiAgdmFyIGJhc2U2NFZMUSA9IHJlcXVpcmUoJy4vYmFzZTY0LXZscScpO1xuICB2YXIgcXVpY2tTb3J0ID0gcmVxdWlyZSgnLi9xdWljay1zb3J0JykucXVpY2tTb3J0O1xuXG4gIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyKGFTb3VyY2VNYXApIHtcbiAgICB2YXIgc291cmNlTWFwID0gYVNvdXJjZU1hcDtcbiAgICBpZiAodHlwZW9mIGFTb3VyY2VNYXAgPT09ICdzdHJpbmcnKSB7XG4gICAgICBzb3VyY2VNYXAgPSBKU09OLnBhcnNlKGFTb3VyY2VNYXAucmVwbGFjZSgvXlxcKVxcXVxcfScvLCAnJykpO1xuICAgIH1cblxuICAgIHJldHVybiBzb3VyY2VNYXAuc2VjdGlvbnMgIT0gbnVsbFxuICAgICAgPyBuZXcgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyKHNvdXJjZU1hcClcbiAgICAgIDogbmV3IEJhc2ljU291cmNlTWFwQ29uc3VtZXIoc291cmNlTWFwKTtcbiAgfVxuXG4gIFNvdXJjZU1hcENvbnN1bWVyLmZyb21Tb3VyY2VNYXAgPSBmdW5jdGlvbihhU291cmNlTWFwKSB7XG4gICAgcmV0dXJuIEJhc2ljU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcChhU291cmNlTWFwKTtcbiAgfVxuXG4gIC8qKlxuICAgKiBUaGUgdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcHBpbmcgc3BlYyB0aGF0IHdlIGFyZSBjb25zdW1pbmcuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8vIGBfX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmQgYF9fb3JpZ2luYWxNYXBwaW5nc2AgYXJlIGFycmF5cyB0aGF0IGhvbGQgdGhlXG4gIC8vIHBhcnNlZCBtYXBwaW5nIGNvb3JkaW5hdGVzIGZyb20gdGhlIHNvdXJjZSBtYXAncyBcIm1hcHBpbmdzXCIgYXR0cmlidXRlLiBUaGV5XG4gIC8vIGFyZSBsYXppbHkgaW5zdGFudGlhdGVkLCBhY2Nlc3NlZCB2aWEgdGhlIGBfZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAvLyBgX29yaWdpbmFsTWFwcGluZ3NgIGdldHRlcnMgcmVzcGVjdGl2ZWx5LCBhbmQgd2Ugb25seSBwYXJzZSB0aGUgbWFwcGluZ3NcbiAgLy8gYW5kIGNyZWF0ZSB0aGVzZSBhcnJheXMgb25jZSBxdWVyaWVkIGZvciBhIHNvdXJjZSBsb2NhdGlvbi4gV2UganVtcCB0aHJvdWdoXG4gIC8vIHRoZXNlIGhvb3BzIGJlY2F1c2UgdGhlcmUgY2FuIGJlIG1hbnkgdGhvdXNhbmRzIG9mIG1hcHBpbmdzLCBhbmQgcGFyc2luZ1xuICAvLyB0aGVtIGlzIGV4cGVuc2l2ZSwgc28gd2Ugb25seSB3YW50IHRvIGRvIGl0IGlmIHdlIG11c3QuXG4gIC8vXG4gIC8vIEVhY2ggb2JqZWN0IGluIHRoZSBhcnJheXMgaXMgb2YgdGhlIGZvcm06XG4gIC8vXG4gIC8vICAgICB7XG4gIC8vICAgICAgIGdlbmVyYXRlZExpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIGdlbmVyYXRlZENvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBzb3VyY2U6IFRoZSBwYXRoIHRvIHRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSB0aGF0IGdlbmVyYXRlZCB0aGlzXG4gIC8vICAgICAgICAgICAgICAgY2h1bmsgb2YgY29kZSxcbiAgLy8gICAgICAgb3JpZ2luYWxMaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSB0aGF0XG4gIC8vICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZHMgdG8gdGhpcyBjaHVuayBvZiBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgb3JpZ2luYWxDb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UgdGhhdFxuICAvLyAgICAgICAgICAgICAgICAgICAgICAgY29ycmVzcG9uZHMgdG8gdGhpcyBjaHVuayBvZiBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgbmFtZTogVGhlIG5hbWUgb2YgdGhlIG9yaWdpbmFsIHN5bWJvbCB3aGljaCBnZW5lcmF0ZWQgdGhpcyBjaHVuayBvZlxuICAvLyAgICAgICAgICAgICBjb2RlLlxuICAvLyAgICAgfVxuICAvL1xuICAvLyBBbGwgcHJvcGVydGllcyBleGNlcHQgZm9yIGBnZW5lcmF0ZWRMaW5lYCBhbmQgYGdlbmVyYXRlZENvbHVtbmAgY2FuIGJlXG4gIC8vIGBudWxsYC5cbiAgLy9cbiAgLy8gYF9nZW5lcmF0ZWRNYXBwaW5nc2AgaXMgb3JkZXJlZCBieSB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucy5cbiAgLy9cbiAgLy8gYF9vcmlnaW5hbE1hcHBpbmdzYCBpcyBvcmRlcmVkIGJ5IHRoZSBvcmlnaW5hbCBwb3NpdGlvbnMuXG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBudWxsO1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnX2dlbmVyYXRlZE1hcHBpbmdzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgaWYgKCF0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MpIHtcbiAgICAgICAgdGhpcy5fcGFyc2VNYXBwaW5ncyh0aGlzLl9tYXBwaW5ncywgdGhpcy5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncztcbiAgICB9XG4gIH0pO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fX29yaWdpbmFsTWFwcGluZ3MgPSBudWxsO1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnX29yaWdpbmFsTWFwcGluZ3MnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICBpZiAoIXRoaXMuX19vcmlnaW5hbE1hcHBpbmdzKSB7XG4gICAgICAgIHRoaXMuX3BhcnNlTWFwcGluZ3ModGhpcy5fbWFwcGluZ3MsIHRoaXMuc291cmNlUm9vdCk7XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncztcbiAgICB9XG4gIH0pO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fY2hhcklzTWFwcGluZ1NlcGFyYXRvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfY2hhcklzTWFwcGluZ1NlcGFyYXRvcihhU3RyLCBpbmRleCkge1xuICAgICAgdmFyIGMgPSBhU3RyLmNoYXJBdChpbmRleCk7XG4gICAgICByZXR1cm4gYyA9PT0gXCI7XCIgfHwgYyA9PT0gXCIsXCI7XG4gICAgfTtcblxuICAvKipcbiAgICogUGFyc2UgdGhlIG1hcHBpbmdzIGluIGEgc3RyaW5nIGluIHRvIGEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggd2UgY2FuIGVhc2lseVxuICAgKiBxdWVyeSAodGhlIG9yZGVyZWQgYXJyYXlzIGluIHRoZSBgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgICogYHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzYCBwcm9wZXJ0aWVzKS5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKFwiU3ViY2xhc3NlcyBtdXN0IGltcGxlbWVudCBfcGFyc2VNYXBwaW5nc1wiKTtcbiAgICB9O1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUiA9IDE7XG4gIFNvdXJjZU1hcENvbnN1bWVyLk9SSUdJTkFMX09SREVSID0gMjtcblxuICBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCA9IDE7XG4gIFNvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5EID0gMjtcblxuICAvKipcbiAgICogSXRlcmF0ZSBvdmVyIGVhY2ggbWFwcGluZyBiZXR3ZWVuIGFuIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiBhbmQgYVxuICAgKiBnZW5lcmF0ZWQgbGluZS9jb2x1bW4gaW4gdGhpcyBzb3VyY2UgbWFwLlxuICAgKlxuICAgKiBAcGFyYW0gRnVuY3Rpb24gYUNhbGxiYWNrXG4gICAqICAgICAgICBUaGUgZnVuY3Rpb24gdGhhdCBpcyBjYWxsZWQgd2l0aCBlYWNoIG1hcHBpbmcuXG4gICAqIEBwYXJhbSBPYmplY3QgYUNvbnRleHRcbiAgICogICAgICAgIE9wdGlvbmFsLiBJZiBzcGVjaWZpZWQsIHRoaXMgb2JqZWN0IHdpbGwgYmUgdGhlIHZhbHVlIG9mIGB0aGlzYCBldmVyeVxuICAgKiAgICAgICAgdGltZSB0aGF0IGBhQ2FsbGJhY2tgIGlzIGNhbGxlZC5cbiAgICogQHBhcmFtIGFPcmRlclxuICAgKiAgICAgICAgRWl0aGVyIGBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVJgIG9yXG4gICAqICAgICAgICBgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVJgLiBTcGVjaWZpZXMgd2hldGhlciB5b3Ugd2FudCB0b1xuICAgKiAgICAgICAgaXRlcmF0ZSBvdmVyIHRoZSBtYXBwaW5ncyBzb3J0ZWQgYnkgdGhlIGdlbmVyYXRlZCBmaWxlJ3MgbGluZS9jb2x1bW5cbiAgICogICAgICAgIG9yZGVyIG9yIHRoZSBvcmlnaW5hbCdzIHNvdXJjZS9saW5lL2NvbHVtbiBvcmRlciwgcmVzcGVjdGl2ZWx5LiBEZWZhdWx0cyB0b1xuICAgKiAgICAgICAgYFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUmAuXG4gICAqL1xuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuZWFjaE1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2VhY2hNYXBwaW5nKGFDYWxsYmFjaywgYUNvbnRleHQsIGFPcmRlcikge1xuICAgICAgdmFyIGNvbnRleHQgPSBhQ29udGV4dCB8fCBudWxsO1xuICAgICAgdmFyIG9yZGVyID0gYU9yZGVyIHx8IFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUjtcblxuICAgICAgdmFyIG1hcHBpbmdzO1xuICAgICAgc3dpdGNoIChvcmRlcikge1xuICAgICAgY2FzZSBTb3VyY2VNYXBDb25zdW1lci5HRU5FUkFURURfT1JERVI6XG4gICAgICAgIG1hcHBpbmdzID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3M7XG4gICAgICAgIGJyZWFrO1xuICAgICAgY2FzZSBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUjpcbiAgICAgICAgbWFwcGluZ3MgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzO1xuICAgICAgICBicmVhaztcbiAgICAgIGRlZmF1bHQ6XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIlVua25vd24gb3JkZXIgb2YgaXRlcmF0aW9uLlwiKTtcbiAgICAgIH1cblxuICAgICAgdmFyIHNvdXJjZVJvb3QgPSB0aGlzLnNvdXJjZVJvb3Q7XG4gICAgICBtYXBwaW5ncy5tYXAoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgICAgdmFyIHNvdXJjZSA9IG1hcHBpbmcuc291cmNlID09PSBudWxsID8gbnVsbCA6IHRoaXMuX3NvdXJjZXMuYXQobWFwcGluZy5zb3VyY2UpO1xuICAgICAgICBpZiAoc291cmNlICE9IG51bGwgJiYgc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgIH1cbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUsXG4gICAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbixcbiAgICAgICAgICBvcmlnaW5hbExpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgIG9yaWdpbmFsQ29sdW1uOiBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uLFxuICAgICAgICAgIG5hbWU6IG1hcHBpbmcubmFtZSA9PT0gbnVsbCA/IG51bGwgOiB0aGlzLl9uYW1lcy5hdChtYXBwaW5nLm5hbWUpXG4gICAgICAgIH07XG4gICAgICB9LCB0aGlzKS5mb3JFYWNoKGFDYWxsYmFjaywgY29udGV4dCk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyBhbGwgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwcm92aWRlZC4gSWYgbm8gY29sdW1uIGlzIHByb3ZpZGVkLCByZXR1cm5zIGFsbCBtYXBwaW5nc1xuICAgKiBjb3JyZXNwb25kaW5nIHRvIGEgZWl0aGVyIHRoZSBsaW5lIHdlIGFyZSBzZWFyY2hpbmcgZm9yIG9yIHRoZSBuZXh0XG4gICAqIGNsb3Nlc3QgbGluZSB0aGF0IGhhcyBhbnkgbWFwcGluZ3MuIE90aGVyd2lzZSwgcmV0dXJucyBhbGwgbWFwcGluZ3NcbiAgICogY29ycmVzcG9uZGluZyB0byB0aGUgZ2l2ZW4gbGluZSBhbmQgZWl0aGVyIHRoZSBjb2x1bW4gd2UgYXJlIHNlYXJjaGluZyBmb3JcbiAgICogb3IgdGhlIG5leHQgY2xvc2VzdCBjb2x1bW4gdGhhdCBoYXMgYW55IG9mZnNldHMuXG4gICAqXG4gICAqIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogT3B0aW9uYWwuIHRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqXG4gICAqIGFuZCBhbiBhcnJheSBvZiBvYmplY3RzIGlzIHJldHVybmVkLCBlYWNoIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5hbGxHZW5lcmF0ZWRQb3NpdGlvbnNGb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2FsbEdlbmVyYXRlZFBvc2l0aW9uc0ZvcihhQXJncykge1xuICAgICAgdmFyIGxpbmUgPSB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKTtcblxuICAgICAgLy8gV2hlbiB0aGVyZSBpcyBubyBleGFjdCBtYXRjaCwgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX2ZpbmRNYXBwaW5nXG4gICAgICAvLyByZXR1cm5zIHRoZSBpbmRleCBvZiB0aGUgY2xvc2VzdCBtYXBwaW5nIGxlc3MgdGhhbiB0aGUgbmVlZGxlLiBCeVxuICAgICAgLy8gc2V0dGluZyBuZWVkbGUub3JpZ2luYWxDb2x1bW4gdG8gMCwgd2UgdGh1cyBmaW5kIHRoZSBsYXN0IG1hcHBpbmcgZm9yXG4gICAgICAvLyB0aGUgZ2l2ZW4gbGluZSwgcHJvdmlkZWQgc3VjaCBhIG1hcHBpbmcgZXhpc3RzLlxuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgc291cmNlOiB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpLFxuICAgICAgICBvcmlnaW5hbExpbmU6IGxpbmUsXG4gICAgICAgIG9yaWdpbmFsQ29sdW1uOiB1dGlsLmdldEFyZyhhQXJncywgJ2NvbHVtbicsIDApXG4gICAgICB9O1xuXG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgbmVlZGxlLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBuZWVkbGUuc291cmNlKTtcbiAgICAgIH1cbiAgICAgIGlmICghdGhpcy5fc291cmNlcy5oYXMobmVlZGxlLnNvdXJjZSkpIHtcbiAgICAgICAgcmV0dXJuIFtdO1xuICAgICAgfVxuICAgICAgbmVlZGxlLnNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihuZWVkbGUuc291cmNlKTtcblxuICAgICAgdmFyIG1hcHBpbmdzID0gW107XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKG5lZWRsZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRoaXMuX29yaWdpbmFsTWFwcGluZ3MsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIm9yaWdpbmFsTGluZVwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCJvcmlnaW5hbENvbHVtblwiLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCk7XG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChhQXJncy5jb2x1bW4gPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIHZhciBvcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZTtcblxuICAgICAgICAgIC8vIEl0ZXJhdGUgdW50aWwgZWl0aGVyIHdlIHJ1biBvdXQgb2YgbWFwcGluZ3MsIG9yIHdlIHJ1biBpbnRvXG4gICAgICAgICAgLy8gYSBtYXBwaW5nIGZvciBhIGRpZmZlcmVudCBsaW5lIHRoYW4gdGhlIG9uZSB3ZSBmb3VuZC4gU2luY2VcbiAgICAgICAgICAvLyBtYXBwaW5ncyBhcmUgc29ydGVkLCB0aGlzIGlzIGd1YXJhbnRlZWQgdG8gZmluZCBhbGwgbWFwcGluZ3MgZm9yXG4gICAgICAgICAgLy8gdGhlIGxpbmUgd2UgZm91bmQuXG4gICAgICAgICAgd2hpbGUgKG1hcHBpbmcgJiYgbWFwcGluZy5vcmlnaW5hbExpbmUgPT09IG9yaWdpbmFsTGluZSkge1xuICAgICAgICAgICAgbWFwcGluZ3MucHVzaCh7XG4gICAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRMaW5lJywgbnVsbCksXG4gICAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgICBsYXN0Q29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbGFzdEdlbmVyYXRlZENvbHVtbicsIG51bGwpXG4gICAgICAgICAgICB9KTtcblxuICAgICAgICAgICAgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbKytpbmRleF07XG4gICAgICAgICAgfVxuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhciBvcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICAvLyBJdGVyYXRlIHVudGlsIGVpdGhlciB3ZSBydW4gb3V0IG9mIG1hcHBpbmdzLCBvciB3ZSBydW4gaW50b1xuICAgICAgICAgIC8vIGEgbWFwcGluZyBmb3IgYSBkaWZmZXJlbnQgbGluZSB0aGFuIHRoZSBvbmUgd2Ugd2VyZSBzZWFyY2hpbmcgZm9yLlxuICAgICAgICAgIC8vIFNpbmNlIG1hcHBpbmdzIGFyZSBzb3J0ZWQsIHRoaXMgaXMgZ3VhcmFudGVlZCB0byBmaW5kIGFsbCBtYXBwaW5ncyBmb3JcbiAgICAgICAgICAvLyB0aGUgbGluZSB3ZSBhcmUgc2VhcmNoaW5nIGZvci5cbiAgICAgICAgICB3aGlsZSAobWFwcGluZyAmJlxuICAgICAgICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gbGluZSAmJlxuICAgICAgICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID09IG9yaWdpbmFsQ29sdW1uKSB7XG4gICAgICAgICAgICBtYXBwaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1srK2luZGV4XTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG1hcHBpbmdzO1xuICAgIH07XG5cbiAgZXhwb3J0cy5Tb3VyY2VNYXBDb25zdW1lciA9IFNvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBBIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgaW5zdGFuY2UgcmVwcmVzZW50cyBhIHBhcnNlZCBzb3VyY2UgbWFwIHdoaWNoIHdlIGNhblxuICAgKiBxdWVyeSBmb3IgaW5mb3JtYXRpb24gYWJvdXQgdGhlIG9yaWdpbmFsIGZpbGUgcG9zaXRpb25zIGJ5IGdpdmluZyBpdCBhIGZpbGVcbiAgICogcG9zaXRpb24gaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqXG4gICAqIFRoZSBvbmx5IHBhcmFtZXRlciBpcyB0aGUgcmF3IHNvdXJjZSBtYXAgKGVpdGhlciBhcyBhIEpTT04gc3RyaW5nLCBvclxuICAgKiBhbHJlYWR5IHBhcnNlZCB0byBhbiBvYmplY3QpLiBBY2NvcmRpbmcgdG8gdGhlIHNwZWMsIHNvdXJjZSBtYXBzIGhhdmUgdGhlXG4gICAqIGZvbGxvd2luZyBhdHRyaWJ1dGVzOlxuICAgKlxuICAgKiAgIC0gdmVyc2lvbjogV2hpY2ggdmVyc2lvbiBvZiB0aGUgc291cmNlIG1hcCBzcGVjIHRoaXMgbWFwIGlzIGZvbGxvd2luZy5cbiAgICogICAtIHNvdXJjZXM6IEFuIGFycmF5IG9mIFVSTHMgdG8gdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlcy5cbiAgICogICAtIG5hbWVzOiBBbiBhcnJheSBvZiBpZGVudGlmaWVycyB3aGljaCBjYW4gYmUgcmVmZXJyZW5jZWQgYnkgaW5kaXZpZHVhbCBtYXBwaW5ncy5cbiAgICogICAtIHNvdXJjZVJvb3Q6IE9wdGlvbmFsLiBUaGUgVVJMIHJvb3QgZnJvbSB3aGljaCBhbGwgc291cmNlcyBhcmUgcmVsYXRpdmUuXG4gICAqICAgLSBzb3VyY2VzQ29udGVudDogT3B0aW9uYWwuIEFuIGFycmF5IG9mIGNvbnRlbnRzIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZXMuXG4gICAqICAgLSBtYXBwaW5nczogQSBzdHJpbmcgb2YgYmFzZTY0IFZMUXMgd2hpY2ggY29udGFpbiB0aGUgYWN0dWFsIG1hcHBpbmdzLlxuICAgKiAgIC0gZmlsZTogT3B0aW9uYWwuIFRoZSBnZW5lcmF0ZWQgZmlsZSB0aGlzIHNvdXJjZSBtYXAgaXMgYXNzb2NpYXRlZCB3aXRoLlxuICAgKlxuICAgKiBIZXJlIGlzIGFuIGV4YW1wbGUgc291cmNlIG1hcCwgdGFrZW4gZnJvbSB0aGUgc291cmNlIG1hcCBzcGVjWzBdOlxuICAgKlxuICAgKiAgICAge1xuICAgKiAgICAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgICAgZmlsZTogXCJvdXQuanNcIixcbiAgICogICAgICAgc291cmNlUm9vdCA6IFwiXCIsXG4gICAqICAgICAgIHNvdXJjZXM6IFtcImZvby5qc1wiLCBcImJhci5qc1wiXSxcbiAgICogICAgICAgbmFtZXM6IFtcInNyY1wiLCBcIm1hcHNcIiwgXCJhcmVcIiwgXCJmdW5cIl0sXG4gICAqICAgICAgIG1hcHBpbmdzOiBcIkFBLEFCOztBQkNERTtcIlxuICAgKiAgICAgfVxuICAgKlxuICAgKiBbMF06IGh0dHBzOi8vZG9jcy5nb29nbGUuY29tL2RvY3VtZW50L2QvMVUxUkdBZWhRd1J5cFVUb3ZGMUtSbHBpT0Z6ZTBiLV8yZ2M2ZkFIMEtZMGsvZWRpdD9wbGk9MSNcbiAgICovXG4gIGZ1bmN0aW9uIEJhc2ljU291cmNlTWFwQ29uc3VtZXIoYVNvdXJjZU1hcCkge1xuICAgIHZhciBzb3VyY2VNYXAgPSBhU291cmNlTWFwO1xuICAgIGlmICh0eXBlb2YgYVNvdXJjZU1hcCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHNvdXJjZU1hcCA9IEpTT04ucGFyc2UoYVNvdXJjZU1hcC5yZXBsYWNlKC9eXFwpXFxdXFx9Jy8sICcnKSk7XG4gICAgfVxuXG4gICAgdmFyIHZlcnNpb24gPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICd2ZXJzaW9uJyk7XG4gICAgdmFyIHNvdXJjZXMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VzJyk7XG4gICAgLy8gU2FzcyAzLjMgbGVhdmVzIG91dCB0aGUgJ25hbWVzJyBhcnJheSwgc28gd2UgZGV2aWF0ZSBmcm9tIHRoZSBzcGVjICh3aGljaFxuICAgIC8vIHJlcXVpcmVzIHRoZSBhcnJheSkgdG8gcGxheSBuaWNlIGhlcmUuXG4gICAgdmFyIG5hbWVzID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnbmFtZXMnLCBbXSk7XG4gICAgdmFyIHNvdXJjZVJvb3QgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VSb290JywgbnVsbCk7XG4gICAgdmFyIHNvdXJjZXNDb250ZW50ID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnc291cmNlc0NvbnRlbnQnLCBudWxsKTtcbiAgICB2YXIgbWFwcGluZ3MgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdtYXBwaW5ncycpO1xuICAgIHZhciBmaWxlID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAnZmlsZScsIG51bGwpO1xuXG4gICAgLy8gT25jZSBhZ2FpbiwgU2FzcyBkZXZpYXRlcyBmcm9tIHRoZSBzcGVjIGFuZCBzdXBwbGllcyB0aGUgdmVyc2lvbiBhcyBhXG4gICAgLy8gc3RyaW5nIHJhdGhlciB0aGFuIGEgbnVtYmVyLCBzbyB3ZSB1c2UgbG9vc2UgZXF1YWxpdHkgY2hlY2tpbmcgaGVyZS5cbiAgICBpZiAodmVyc2lvbiAhPSB0aGlzLl92ZXJzaW9uKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIHZlcnNpb246ICcgKyB2ZXJzaW9uKTtcbiAgICB9XG5cbiAgICBzb3VyY2VzID0gc291cmNlc1xuICAgICAgLy8gU29tZSBzb3VyY2UgbWFwcyBwcm9kdWNlIHJlbGF0aXZlIHNvdXJjZSBwYXRocyBsaWtlIFwiLi9mb28uanNcIiBpbnN0ZWFkIG9mXG4gICAgICAvLyBcImZvby5qc1wiLiAgTm9ybWFsaXplIHRoZXNlIGZpcnN0IHNvIHRoYXQgZnV0dXJlIGNvbXBhcmlzb25zIHdpbGwgc3VjY2VlZC5cbiAgICAgIC8vIFNlZSBidWd6aWwubGEvMTA5MDc2OC5cbiAgICAgIC5tYXAodXRpbC5ub3JtYWxpemUpXG4gICAgICAvLyBBbHdheXMgZW5zdXJlIHRoYXQgYWJzb2x1dGUgc291cmNlcyBhcmUgaW50ZXJuYWxseSBzdG9yZWQgcmVsYXRpdmUgdG9cbiAgICAgIC8vIHRoZSBzb3VyY2Ugcm9vdCwgaWYgdGhlIHNvdXJjZSByb290IGlzIGFic29sdXRlLiBOb3QgZG9pbmcgdGhpcyB3b3VsZFxuICAgICAgLy8gYmUgcGFydGljdWxhcmx5IHByb2JsZW1hdGljIHdoZW4gdGhlIHNvdXJjZSByb290IGlzIGEgcHJlZml4IG9mIHRoZVxuICAgICAgLy8gc291cmNlICh2YWxpZCwgYnV0IHdoeT8/KS4gU2VlIGdpdGh1YiBpc3N1ZSAjMTk5IGFuZCBidWd6aWwubGEvMTE4ODk4Mi5cbiAgICAgIC5tYXAoZnVuY3Rpb24gKHNvdXJjZSkge1xuICAgICAgICByZXR1cm4gc291cmNlUm9vdCAmJiB1dGlsLmlzQWJzb2x1dGUoc291cmNlUm9vdCkgJiYgdXRpbC5pc0Fic29sdXRlKHNvdXJjZSlcbiAgICAgICAgICA/IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgc291cmNlKVxuICAgICAgICAgIDogc291cmNlO1xuICAgICAgfSk7XG5cbiAgICAvLyBQYXNzIGB0cnVlYCBiZWxvdyB0byBhbGxvdyBkdXBsaWNhdGUgbmFtZXMgYW5kIHNvdXJjZXMuIFdoaWxlIHNvdXJjZSBtYXBzXG4gICAgLy8gYXJlIGludGVuZGVkIHRvIGJlIGNvbXByZXNzZWQgYW5kIGRlZHVwbGljYXRlZCwgdGhlIFR5cGVTY3JpcHQgY29tcGlsZXJcbiAgICAvLyBzb21ldGltZXMgZ2VuZXJhdGVzIHNvdXJjZSBtYXBzIHdpdGggZHVwbGljYXRlcyBpbiB0aGVtLiBTZWUgR2l0aHViIGlzc3VlXG4gICAgLy8gIzcyIGFuZCBidWd6aWwubGEvODg5NDkyLlxuICAgIHRoaXMuX25hbWVzID0gQXJyYXlTZXQuZnJvbUFycmF5KG5hbWVzLCB0cnVlKTtcbiAgICB0aGlzLl9zb3VyY2VzID0gQXJyYXlTZXQuZnJvbUFycmF5KHNvdXJjZXMsIHRydWUpO1xuXG4gICAgdGhpcy5zb3VyY2VSb290ID0gc291cmNlUm9vdDtcbiAgICB0aGlzLnNvdXJjZXNDb250ZW50ID0gc291cmNlc0NvbnRlbnQ7XG4gICAgdGhpcy5fbWFwcGluZ3MgPSBtYXBwaW5ncztcbiAgICB0aGlzLmZpbGUgPSBmaWxlO1xuICB9XG5cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmNvbnN1bWVyID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIENyZWF0ZSBhIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgZnJvbSBhIFNvdXJjZU1hcEdlbmVyYXRvci5cbiAgICpcbiAgICogQHBhcmFtIFNvdXJjZU1hcEdlbmVyYXRvciBhU291cmNlTWFwXG4gICAqICAgICAgICBUaGUgc291cmNlIG1hcCB0aGF0IHdpbGwgYmUgY29uc3VtZWQuXG4gICAqIEByZXR1cm5zIEJhc2ljU291cmNlTWFwQ29uc3VtZXJcbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZnJvbVNvdXJjZU1hcChhU291cmNlTWFwKSB7XG4gICAgICB2YXIgc21jID0gT2JqZWN0LmNyZWF0ZShCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG5cbiAgICAgIHZhciBuYW1lcyA9IHNtYy5fbmFtZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoYVNvdXJjZU1hcC5fbmFtZXMudG9BcnJheSgpLCB0cnVlKTtcbiAgICAgIHZhciBzb3VyY2VzID0gc21jLl9zb3VyY2VzID0gQXJyYXlTZXQuZnJvbUFycmF5KGFTb3VyY2VNYXAuX3NvdXJjZXMudG9BcnJheSgpLCB0cnVlKTtcbiAgICAgIHNtYy5zb3VyY2VSb290ID0gYVNvdXJjZU1hcC5fc291cmNlUm9vdDtcbiAgICAgIHNtYy5zb3VyY2VzQ29udGVudCA9IGFTb3VyY2VNYXAuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQoc21jLl9zb3VyY2VzLnRvQXJyYXkoKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc21jLnNvdXJjZVJvb3QpO1xuICAgICAgc21jLmZpbGUgPSBhU291cmNlTWFwLl9maWxlO1xuXG4gICAgICAvLyBCZWNhdXNlIHdlIGFyZSBtb2RpZnlpbmcgdGhlIGVudHJpZXMgKGJ5IGNvbnZlcnRpbmcgc3RyaW5nIHNvdXJjZXMgYW5kXG4gICAgICAvLyBuYW1lcyB0byBpbmRpY2VzIGludG8gdGhlIHNvdXJjZXMgYW5kIG5hbWVzIEFycmF5U2V0cyksIHdlIGhhdmUgdG8gbWFrZVxuICAgICAgLy8gYSBjb3B5IG9mIHRoZSBlbnRyeSBvciBlbHNlIGJhZCB0aGluZ3MgaGFwcGVuLiBTaGFyZWQgbXV0YWJsZSBzdGF0ZVxuICAgICAgLy8gc3RyaWtlcyBhZ2FpbiEgU2VlIGdpdGh1YiBpc3N1ZSAjMTkxLlxuXG4gICAgICB2YXIgZ2VuZXJhdGVkTWFwcGluZ3MgPSBhU291cmNlTWFwLl9tYXBwaW5ncy50b0FycmF5KCkuc2xpY2UoKTtcbiAgICAgIHZhciBkZXN0R2VuZXJhdGVkTWFwcGluZ3MgPSBzbWMuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIGRlc3RPcmlnaW5hbE1hcHBpbmdzID0gc21jLl9fb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuXG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuZ3RoID0gZ2VuZXJhdGVkTWFwcGluZ3MubGVuZ3RoOyBpIDwgbGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNyY01hcHBpbmcgPSBnZW5lcmF0ZWRNYXBwaW5nc1tpXTtcbiAgICAgICAgdmFyIGRlc3RNYXBwaW5nID0gbmV3IE1hcHBpbmc7XG4gICAgICAgIGRlc3RNYXBwaW5nLmdlbmVyYXRlZExpbmUgPSBzcmNNYXBwaW5nLmdlbmVyYXRlZExpbmU7XG4gICAgICAgIGRlc3RNYXBwaW5nLmdlbmVyYXRlZENvbHVtbiA9IHNyY01hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgIGlmIChzcmNNYXBwaW5nLnNvdXJjZSkge1xuICAgICAgICAgIGRlc3RNYXBwaW5nLnNvdXJjZSA9IHNvdXJjZXMuaW5kZXhPZihzcmNNYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgZGVzdE1hcHBpbmcub3JpZ2luYWxMaW5lID0gc3JjTWFwcGluZy5vcmlnaW5hbExpbmU7XG4gICAgICAgICAgZGVzdE1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPSBzcmNNYXBwaW5nLm9yaWdpbmFsQ29sdW1uO1xuXG4gICAgICAgICAgaWYgKHNyY01hcHBpbmcubmFtZSkge1xuICAgICAgICAgICAgZGVzdE1hcHBpbmcubmFtZSA9IG5hbWVzLmluZGV4T2Yoc3JjTWFwcGluZy5uYW1lKTtcbiAgICAgICAgICB9XG5cbiAgICAgICAgICBkZXN0T3JpZ2luYWxNYXBwaW5ncy5wdXNoKGRlc3RNYXBwaW5nKTtcbiAgICAgICAgfVxuXG4gICAgICAgIGRlc3RHZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKGRlc3RNYXBwaW5nKTtcbiAgICAgIH1cblxuICAgICAgcXVpY2tTb3J0KHNtYy5fX29yaWdpbmFsTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMpO1xuXG4gICAgICByZXR1cm4gc21jO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvKipcbiAgICogVGhlIGxpc3Qgb2Ygb3JpZ2luYWwgc291cmNlcy5cbiAgICovXG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ3NvdXJjZXMnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc291cmNlcy50b0FycmF5KCkubWFwKGZ1bmN0aW9uIChzKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCA/IHV0aWwuam9pbih0aGlzLnNvdXJjZVJvb3QsIHMpIDogcztcbiAgICAgIH0sIHRoaXMpO1xuICAgIH1cbiAgfSk7XG5cbiAgLyoqXG4gICAqIFByb3ZpZGUgdGhlIEpJVCB3aXRoIGEgbmljZSBzaGFwZSAvIGhpZGRlbiBjbGFzcy5cbiAgICovXG4gIGZ1bmN0aW9uIE1hcHBpbmcoKSB7XG4gICAgdGhpcy5nZW5lcmF0ZWRMaW5lID0gMDtcbiAgICB0aGlzLmdlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgdGhpcy5zb3VyY2UgPSBudWxsO1xuICAgIHRoaXMub3JpZ2luYWxMaW5lID0gbnVsbDtcbiAgICB0aGlzLm9yaWdpbmFsQ29sdW1uID0gbnVsbDtcbiAgICB0aGlzLm5hbWUgPSBudWxsO1xuICB9XG5cbiAgLyoqXG4gICAqIFBhcnNlIHRoZSBtYXBwaW5ncyBpbiBhIHN0cmluZyBpbiB0byBhIGRhdGEgc3RydWN0dXJlIHdoaWNoIHdlIGNhbiBlYXNpbHlcbiAgICogcXVlcnkgKHRoZSBvcmRlcmVkIGFycmF5cyBpbiB0aGUgYHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gICAqIGB0aGlzLl9fb3JpZ2luYWxNYXBwaW5nc2AgcHJvcGVydGllcykuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdmFyIGdlbmVyYXRlZExpbmUgPSAxO1xuICAgICAgdmFyIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsTGluZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNTb3VyY2UgPSAwO1xuICAgICAgdmFyIHByZXZpb3VzTmFtZSA9IDA7XG4gICAgICB2YXIgbGVuZ3RoID0gYVN0ci5sZW5ndGg7XG4gICAgICB2YXIgaW5kZXggPSAwO1xuICAgICAgdmFyIGNhY2hlZFNlZ21lbnRzID0ge307XG4gICAgICB2YXIgdGVtcCA9IHt9O1xuICAgICAgdmFyIG9yaWdpbmFsTWFwcGluZ3MgPSBbXTtcbiAgICAgIHZhciBnZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIG1hcHBpbmcsIHN0ciwgc2VnbWVudCwgZW5kLCB2YWx1ZTtcblxuICAgICAgd2hpbGUgKGluZGV4IDwgbGVuZ3RoKSB7XG4gICAgICAgIGlmIChhU3RyLmNoYXJBdChpbmRleCkgPT09ICc7Jykge1xuICAgICAgICAgIGdlbmVyYXRlZExpbmUrKztcbiAgICAgICAgICBpbmRleCsrO1xuICAgICAgICAgIHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgfVxuICAgICAgICBlbHNlIGlmIChhU3RyLmNoYXJBdChpbmRleCkgPT09ICcsJykge1xuICAgICAgICAgIGluZGV4Kys7XG4gICAgICAgIH1cbiAgICAgICAgZWxzZSB7XG4gICAgICAgICAgbWFwcGluZyA9IG5ldyBNYXBwaW5nKCk7XG4gICAgICAgICAgbWFwcGluZy5nZW5lcmF0ZWRMaW5lID0gZ2VuZXJhdGVkTGluZTtcblxuICAgICAgICAgIC8vIEJlY2F1c2UgZWFjaCBvZmZzZXQgaXMgZW5jb2RlZCByZWxhdGl2ZSB0byB0aGUgcHJldmlvdXMgb25lLFxuICAgICAgICAgIC8vIG1hbnkgc2VnbWVudHMgb2Z0ZW4gaGF2ZSB0aGUgc2FtZSBlbmNvZGluZy4gV2UgY2FuIGV4cGxvaXQgdGhpc1xuICAgICAgICAgIC8vIGZhY3QgYnkgY2FjaGluZyB0aGUgcGFyc2VkIHZhcmlhYmxlIGxlbmd0aCBmaWVsZHMgb2YgZWFjaCBzZWdtZW50LFxuICAgICAgICAgIC8vIGFsbG93aW5nIHVzIHRvIGF2b2lkIGEgc2Vjb25kIHBhcnNlIGlmIHdlIGVuY291bnRlciB0aGUgc2FtZVxuICAgICAgICAgIC8vIHNlZ21lbnQgYWdhaW4uXG4gICAgICAgICAgZm9yIChlbmQgPSBpbmRleDsgZW5kIDwgbGVuZ3RoOyBlbmQrKykge1xuICAgICAgICAgICAgaWYgKHRoaXMuX2NoYXJJc01hcHBpbmdTZXBhcmF0b3IoYVN0ciwgZW5kKSkge1xuICAgICAgICAgICAgICBicmVhaztcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgc3RyID0gYVN0ci5zbGljZShpbmRleCwgZW5kKTtcblxuICAgICAgICAgIHNlZ21lbnQgPSBjYWNoZWRTZWdtZW50c1tzdHJdO1xuICAgICAgICAgIGlmIChzZWdtZW50KSB7XG4gICAgICAgICAgICBpbmRleCArPSBzdHIubGVuZ3RoO1xuICAgICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgICBzZWdtZW50ID0gW107XG4gICAgICAgICAgICB3aGlsZSAoaW5kZXggPCBlbmQpIHtcbiAgICAgICAgICAgICAgYmFzZTY0VkxRLmRlY29kZShhU3RyLCBpbmRleCwgdGVtcCk7XG4gICAgICAgICAgICAgIHZhbHVlID0gdGVtcC52YWx1ZTtcbiAgICAgICAgICAgICAgaW5kZXggPSB0ZW1wLnJlc3Q7XG4gICAgICAgICAgICAgIHNlZ21lbnQucHVzaCh2YWx1ZSk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA9PT0gMikge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ZvdW5kIGEgc291cmNlLCBidXQgbm8gbGluZSBhbmQgY29sdW1uJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA9PT0gMykge1xuICAgICAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ0ZvdW5kIGEgc291cmNlIGFuZCBsaW5lLCBidXQgbm8gY29sdW1uJyk7XG4gICAgICAgICAgICB9XG5cbiAgICAgICAgICAgIGNhY2hlZFNlZ21lbnRzW3N0cl0gPSBzZWdtZW50O1xuICAgICAgICAgIH1cblxuICAgICAgICAgIC8vIEdlbmVyYXRlZCBjb2x1bW4uXG4gICAgICAgICAgbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gPSBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiArIHNlZ21lbnRbMF07XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbjtcblxuICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA+IDEpIHtcbiAgICAgICAgICAgIC8vIE9yaWdpbmFsIHNvdXJjZS5cbiAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gcHJldmlvdXNTb3VyY2UgKyBzZWdtZW50WzFdO1xuICAgICAgICAgICAgcHJldmlvdXNTb3VyY2UgKz0gc2VnbWVudFsxXTtcblxuICAgICAgICAgICAgLy8gT3JpZ2luYWwgbGluZS5cbiAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lID0gcHJldmlvdXNPcmlnaW5hbExpbmUgKyBzZWdtZW50WzJdO1xuICAgICAgICAgICAgcHJldmlvdXNPcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZTtcbiAgICAgICAgICAgIC8vIExpbmVzIGFyZSBzdG9yZWQgMC1iYXNlZFxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgKz0gMTtcblxuICAgICAgICAgICAgLy8gT3JpZ2luYWwgY29sdW1uLlxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IHByZXZpb3VzT3JpZ2luYWxDb2x1bW4gKyBzZWdtZW50WzNdO1xuICAgICAgICAgICAgcHJldmlvdXNPcmlnaW5hbENvbHVtbiA9IG1hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICAgIGlmIChzZWdtZW50Lmxlbmd0aCA+IDQpIHtcbiAgICAgICAgICAgICAgLy8gT3JpZ2luYWwgbmFtZS5cbiAgICAgICAgICAgICAgbWFwcGluZy5uYW1lID0gcHJldmlvdXNOYW1lICsgc2VnbWVudFs0XTtcbiAgICAgICAgICAgICAgcHJldmlvdXNOYW1lICs9IHNlZ21lbnRbNF07XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgZ2VuZXJhdGVkTWFwcGluZ3MucHVzaChtYXBwaW5nKTtcbiAgICAgICAgICBpZiAodHlwZW9mIG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSAnbnVtYmVyJykge1xuICAgICAgICAgICAgb3JpZ2luYWxNYXBwaW5ncy5wdXNoKG1hcHBpbmcpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQoZ2VuZXJhdGVkTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQpO1xuICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gZ2VuZXJhdGVkTWFwcGluZ3M7XG5cbiAgICAgIHF1aWNrU29ydChvcmlnaW5hbE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKTtcbiAgICAgIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzID0gb3JpZ2luYWxNYXBwaW5ncztcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBGaW5kIHRoZSBtYXBwaW5nIHRoYXQgYmVzdCBtYXRjaGVzIHRoZSBoeXBvdGhldGljYWwgXCJuZWVkbGVcIiBtYXBwaW5nIHRoYXRcbiAgICogd2UgYXJlIHNlYXJjaGluZyBmb3IgaW4gdGhlIGdpdmVuIFwiaGF5c3RhY2tcIiBvZiBtYXBwaW5ncy5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9maW5kTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZmluZE1hcHBpbmcoYU5lZWRsZSwgYU1hcHBpbmdzLCBhTGluZU5hbWUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYUNvbHVtbk5hbWUsIGFDb21wYXJhdG9yLCBhQmlhcykge1xuICAgICAgLy8gVG8gcmV0dXJuIHRoZSBwb3NpdGlvbiB3ZSBhcmUgc2VhcmNoaW5nIGZvciwgd2UgbXVzdCBmaXJzdCBmaW5kIHRoZVxuICAgICAgLy8gbWFwcGluZyBmb3IgdGhlIGdpdmVuIHBvc2l0aW9uIGFuZCB0aGVuIHJldHVybiB0aGUgb3Bwb3NpdGUgcG9zaXRpb24gaXRcbiAgICAgIC8vIHBvaW50cyB0by4gQmVjYXVzZSB0aGUgbWFwcGluZ3MgYXJlIHNvcnRlZCwgd2UgY2FuIHVzZSBiaW5hcnkgc2VhcmNoIHRvXG4gICAgICAvLyBmaW5kIHRoZSBiZXN0IG1hcHBpbmcuXG5cbiAgICAgIGlmIChhTmVlZGxlW2FMaW5lTmFtZV0gPD0gMCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdMaW5lIG11c3QgYmUgZ3JlYXRlciB0aGFuIG9yIGVxdWFsIHRvIDEsIGdvdCAnXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgKyBhTmVlZGxlW2FMaW5lTmFtZV0pO1xuICAgICAgfVxuICAgICAgaWYgKGFOZWVkbGVbYUNvbHVtbk5hbWVdIDwgMCkge1xuICAgICAgICB0aHJvdyBuZXcgVHlwZUVycm9yKCdDb2x1bW4gbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMCwgZ290ICdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICArIGFOZWVkbGVbYUNvbHVtbk5hbWVdKTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIGJpbmFyeVNlYXJjaC5zZWFyY2goYU5lZWRsZSwgYU1hcHBpbmdzLCBhQ29tcGFyYXRvciwgYUJpYXMpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIENvbXB1dGUgdGhlIGxhc3QgY29sdW1uIGZvciBlYWNoIGdlbmVyYXRlZCBtYXBwaW5nLiBUaGUgbGFzdCBjb2x1bW4gaXNcbiAgICogaW5jbHVzaXZlLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29tcHV0ZUNvbHVtblNwYW5zID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9jb21wdXRlQ29sdW1uU3BhbnMoKSB7XG4gICAgICBmb3IgKHZhciBpbmRleCA9IDA7IGluZGV4IDwgdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3MubGVuZ3RoOyArK2luZGV4KSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIC8vIE1hcHBpbmdzIGRvIG5vdCBjb250YWluIGEgZmllbGQgZm9yIHRoZSBsYXN0IGdlbmVyYXRlZCBjb2x1bW50LiBXZVxuICAgICAgICAvLyBjYW4gY29tZSB1cCB3aXRoIGFuIG9wdGltaXN0aWMgZXN0aW1hdGUsIGhvd2V2ZXIsIGJ5IGFzc3VtaW5nIHRoYXRcbiAgICAgICAgLy8gbWFwcGluZ3MgYXJlIGNvbnRpZ3VvdXMgKGkuZS4gZ2l2ZW4gdHdvIGNvbnNlY3V0aXZlIG1hcHBpbmdzLCB0aGVcbiAgICAgICAgLy8gZmlyc3QgbWFwcGluZyBlbmRzIHdoZXJlIHRoZSBzZWNvbmQgb25lIHN0YXJ0cykuXG4gICAgICAgIGlmIChpbmRleCArIDEgPCB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGgpIHtcbiAgICAgICAgICB2YXIgbmV4dE1hcHBpbmcgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5nc1tpbmRleCArIDFdO1xuXG4gICAgICAgICAgaWYgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9PT0gbmV4dE1hcHBpbmcuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgbWFwcGluZy5sYXN0R2VuZXJhdGVkQ29sdW1uID0gbmV4dE1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uIC0gMTtcbiAgICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIC8vIFRoZSBsYXN0IG1hcHBpbmcgZm9yIGVhY2ggbGluZSBzcGFucyB0aGUgZW50aXJlIGxpbmUuXG4gICAgICAgIG1hcHBpbmcubGFzdEdlbmVyYXRlZENvbHVtbiA9IEluZmluaXR5O1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSwgbGluZSwgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3RcbiAgICogd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gYmlhczogRWl0aGVyICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJy5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlLCBvciBudWxsLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBuYW1lOiBUaGUgb3JpZ2luYWwgaWRlbnRpZmllciwgb3IgbnVsbC5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLm9yaWdpbmFsUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX29yaWdpbmFsUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIGdlbmVyYXRlZExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKFxuICAgICAgICBuZWVkbGUsXG4gICAgICAgIHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzLFxuICAgICAgICBcImdlbmVyYXRlZExpbmVcIixcbiAgICAgICAgXCJnZW5lcmF0ZWRDb2x1bW5cIixcbiAgICAgICAgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCxcbiAgICAgICAgdXRpbC5nZXRBcmcoYUFyZ3MsICdiaWFzJywgU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQpXG4gICAgICApO1xuXG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzW2luZGV4XTtcblxuICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lID09PSBuZWVkbGUuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgIHZhciBzb3VyY2UgPSB1dGlsLmdldEFyZyhtYXBwaW5nLCAnc291cmNlJywgbnVsbCk7XG4gICAgICAgICAgaWYgKHNvdXJjZSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5hdChzb3VyY2UpO1xuICAgICAgICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIHNvdXJjZSA9IHV0aWwuam9pbih0aGlzLnNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgfVxuICAgICAgICAgIHZhciBuYW1lID0gdXRpbC5nZXRBcmcobWFwcGluZywgJ25hbWUnLCBudWxsKTtcbiAgICAgICAgICBpZiAobmFtZSAhPT0gbnVsbCkge1xuICAgICAgICAgICAgbmFtZSA9IHRoaXMuX25hbWVzLmF0KG5hbWUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICByZXR1cm4ge1xuICAgICAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnb3JpZ2luYWxMaW5lJywgbnVsbCksXG4gICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdvcmlnaW5hbENvbHVtbicsIG51bGwpLFxuICAgICAgICAgICAgbmFtZTogbmFtZVxuICAgICAgICAgIH07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgc291cmNlOiBudWxsLFxuICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgIG5hbWU6IG51bGxcbiAgICAgIH07XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIHRydWUgaWYgd2UgaGF2ZSB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGV2ZXJ5IHNvdXJjZSBpbiB0aGUgc291cmNlXG4gICAqIG1hcCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMgPVxuICAgIGZ1bmN0aW9uIEJhc2ljU291cmNlTWFwQ29uc3VtZXJfaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKSB7XG4gICAgICBpZiAoIXRoaXMuc291cmNlc0NvbnRlbnQpIHtcbiAgICAgICAgcmV0dXJuIGZhbHNlO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnQubGVuZ3RoID49IHRoaXMuX3NvdXJjZXMuc2l6ZSgpICYmXG4gICAgICAgICF0aGlzLnNvdXJjZXNDb250ZW50LnNvbWUoZnVuY3Rpb24gKHNjKSB7IHJldHVybiBzYyA9PSBudWxsOyB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UgY29udGVudC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgdGhlIHVybCBvZiB0aGVcbiAgICogb3JpZ2luYWwgc291cmNlIGZpbGUuIFJldHVybnMgbnVsbCBpZiBubyBvcmlnaW5hbCBzb3VyY2UgY29udGVudCBpc1xuICAgKiBhdmFpbGlibGUuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5zb3VyY2VDb250ZW50Rm9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIG51bGxPbk1pc3NpbmcpIHtcbiAgICAgIGlmICghdGhpcy5zb3VyY2VzQ29udGVudCkge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cblxuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIGFTb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuc291cmNlUm9vdCwgYVNvdXJjZSk7XG4gICAgICB9XG5cbiAgICAgIGlmICh0aGlzLl9zb3VyY2VzLmhhcyhhU291cmNlKSkge1xuICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudFt0aGlzLl9zb3VyY2VzLmluZGV4T2YoYVNvdXJjZSldO1xuICAgICAgfVxuXG4gICAgICB2YXIgdXJsO1xuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsXG4gICAgICAgICAgJiYgKHVybCA9IHV0aWwudXJsUGFyc2UodGhpcy5zb3VyY2VSb290KSkpIHtcbiAgICAgICAgLy8gWFhYOiBmaWxlOi8vIFVSSXMgYW5kIGFic29sdXRlIHBhdGhzIGxlYWQgdG8gdW5leHBlY3RlZCBiZWhhdmlvciBmb3JcbiAgICAgICAgLy8gbWFueSB1c2Vycy4gV2UgY2FuIGhlbHAgdGhlbSBvdXQgd2hlbiB0aGV5IGV4cGVjdCBmaWxlOi8vIFVSSXMgdG9cbiAgICAgICAgLy8gYmVoYXZlIGxpa2UgaXQgd291bGQgaWYgdGhleSB3ZXJlIHJ1bm5pbmcgYSBsb2NhbCBIVFRQIHNlcnZlci4gU2VlXG4gICAgICAgIC8vIGh0dHBzOi8vYnVnemlsbGEubW96aWxsYS5vcmcvc2hvd19idWcuY2dpP2lkPTg4NTU5Ny5cbiAgICAgICAgdmFyIGZpbGVVcmlBYnNQYXRoID0gYVNvdXJjZS5yZXBsYWNlKC9eZmlsZTpcXC9cXC8vLCBcIlwiKTtcbiAgICAgICAgaWYgKHVybC5zY2hlbWUgPT0gXCJmaWxlXCJcbiAgICAgICAgICAgICYmIHRoaXMuX3NvdXJjZXMuaGFzKGZpbGVVcmlBYnNQYXRoKSkge1xuICAgICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihmaWxlVXJpQWJzUGF0aCldXG4gICAgICAgIH1cblxuICAgICAgICBpZiAoKCF1cmwucGF0aCB8fCB1cmwucGF0aCA9PSBcIi9cIilcbiAgICAgICAgICAgICYmIHRoaXMuX3NvdXJjZXMuaGFzKFwiL1wiICsgYVNvdXJjZSkpIHtcbiAgICAgICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudFt0aGlzLl9zb3VyY2VzLmluZGV4T2YoXCIvXCIgKyBhU291cmNlKV07XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgLy8gVGhpcyBmdW5jdGlvbiBpcyB1c2VkIHJlY3Vyc2l2ZWx5IGZyb21cbiAgICAgIC8vIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuc291cmNlQ29udGVudEZvci4gSW4gdGhhdCBjYXNlLCB3ZVxuICAgICAgLy8gZG9uJ3Qgd2FudCB0byB0aHJvdyBpZiB3ZSBjYW4ndCBmaW5kIHRoZSBzb3VyY2UgLSB3ZSBqdXN0IHdhbnQgdG9cbiAgICAgIC8vIHJldHVybiBudWxsLCBzbyB3ZSBwcm92aWRlIGEgZmxhZyB0byBleGl0IGdyYWNlZnVsbHkuXG4gICAgICBpZiAobnVsbE9uTWlzc2luZykge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTb3VyY2UgKyAnXCIgaXMgbm90IGluIHRoZSBTb3VyY2VNYXAuJyk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoXG4gICAqIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGJpYXM6IEVpdGhlciAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnIG9yXG4gICAqICAgICAnU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICogICAgIERlZmF1bHRzIHRvICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuZ2VuZXJhdGVkUG9zaXRpb25Gb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2dlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgc291cmNlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnKTtcbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKHRoaXMuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgIH1cbiAgICAgIGlmICghdGhpcy5fc291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICAgIGxhc3RDb2x1bW46IG51bGxcbiAgICAgICAgfTtcbiAgICAgIH1cbiAgICAgIHNvdXJjZSA9IHRoaXMuX3NvdXJjZXMuaW5kZXhPZihzb3VyY2UpO1xuXG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgb3JpZ2luYWxMaW5lOiB1dGlsLmdldEFyZyhhQXJncywgJ2xpbmUnKSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIHZhciBpbmRleCA9IHRoaXMuX2ZpbmRNYXBwaW5nKFxuICAgICAgICBuZWVkbGUsXG4gICAgICAgIHRoaXMuX29yaWdpbmFsTWFwcGluZ3MsXG4gICAgICAgIFwib3JpZ2luYWxMaW5lXCIsXG4gICAgICAgIFwib3JpZ2luYWxDb2x1bW5cIixcbiAgICAgICAgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyxcbiAgICAgICAgdXRpbC5nZXRBcmcoYUFyZ3MsICdiaWFzJywgU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQpXG4gICAgICApO1xuXG4gICAgICBpZiAoaW5kZXggPj0gMCkge1xuICAgICAgICB2YXIgbWFwcGluZyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSA9PT0gbmVlZGxlLnNvdXJjZSkge1xuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkTGluZScsIG51bGwpLFxuICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICBsYXN0Q29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbGFzdEdlbmVyYXRlZENvbHVtbicsIG51bGwpXG4gICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBsaW5lOiBudWxsLFxuICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgIGxhc3RDb2x1bW46IG51bGxcbiAgICAgIH07XG4gICAgfTtcblxuICBleHBvcnRzLkJhc2ljU291cmNlTWFwQ29uc3VtZXIgPSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyO1xuXG4gIC8qKlxuICAgKiBBbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIgaW5zdGFuY2UgcmVwcmVzZW50cyBhIHBhcnNlZCBzb3VyY2UgbWFwIHdoaWNoXG4gICAqIHdlIGNhbiBxdWVyeSBmb3IgaW5mb3JtYXRpb24uIEl0IGRpZmZlcnMgZnJvbSBCYXNpY1NvdXJjZU1hcENvbnN1bWVyIGluXG4gICAqIHRoYXQgaXQgdGFrZXMgXCJpbmRleGVkXCIgc291cmNlIG1hcHMgKGkuZS4gb25lcyB3aXRoIGEgXCJzZWN0aW9uc1wiIGZpZWxkKSBhc1xuICAgKiBpbnB1dC5cbiAgICpcbiAgICogVGhlIG9ubHkgcGFyYW1ldGVyIGlzIGEgcmF3IHNvdXJjZSBtYXAgKGVpdGhlciBhcyBhIEpTT04gc3RyaW5nLCBvciBhbHJlYWR5XG4gICAqIHBhcnNlZCB0byBhbiBvYmplY3QpLiBBY2NvcmRpbmcgdG8gdGhlIHNwZWMgZm9yIGluZGV4ZWQgc291cmNlIG1hcHMsIHRoZXlcbiAgICogaGF2ZSB0aGUgZm9sbG93aW5nIGF0dHJpYnV0ZXM6XG4gICAqXG4gICAqICAgLSB2ZXJzaW9uOiBXaGljaCB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwIHNwZWMgdGhpcyBtYXAgaXMgZm9sbG93aW5nLlxuICAgKiAgIC0gZmlsZTogT3B0aW9uYWwuIFRoZSBnZW5lcmF0ZWQgZmlsZSB0aGlzIHNvdXJjZSBtYXAgaXMgYXNzb2NpYXRlZCB3aXRoLlxuICAgKiAgIC0gc2VjdGlvbnM6IEEgbGlzdCBvZiBzZWN0aW9uIGRlZmluaXRpb25zLlxuICAgKlxuICAgKiBFYWNoIHZhbHVlIHVuZGVyIHRoZSBcInNlY3Rpb25zXCIgZmllbGQgaGFzIHR3byBmaWVsZHM6XG4gICAqICAgLSBvZmZzZXQ6IFRoZSBvZmZzZXQgaW50byB0aGUgb3JpZ2luYWwgc3BlY2lmaWVkIGF0IHdoaWNoIHRoaXMgc2VjdGlvblxuICAgKiAgICAgICBiZWdpbnMgdG8gYXBwbHksIGRlZmluZWQgYXMgYW4gb2JqZWN0IHdpdGggYSBcImxpbmVcIiBhbmQgXCJjb2x1bW5cIlxuICAgKiAgICAgICBmaWVsZC5cbiAgICogICAtIG1hcDogQSBzb3VyY2UgbWFwIGRlZmluaXRpb24uIFRoaXMgc291cmNlIG1hcCBjb3VsZCBhbHNvIGJlIGluZGV4ZWQsXG4gICAqICAgICAgIGJ1dCBkb2Vzbid0IGhhdmUgdG8gYmUuXG4gICAqXG4gICAqIEluc3RlYWQgb2YgdGhlIFwibWFwXCIgZmllbGQsIGl0J3MgYWxzbyBwb3NzaWJsZSB0byBoYXZlIGEgXCJ1cmxcIiBmaWVsZFxuICAgKiBzcGVjaWZ5aW5nIGEgVVJMIHRvIHJldHJpZXZlIGEgc291cmNlIG1hcCBmcm9tLCBidXQgdGhhdCdzIGN1cnJlbnRseVxuICAgKiB1bnN1cHBvcnRlZC5cbiAgICpcbiAgICogSGVyZSdzIGFuIGV4YW1wbGUgc291cmNlIG1hcCwgdGFrZW4gZnJvbSB0aGUgc291cmNlIG1hcCBzcGVjWzBdLCBidXRcbiAgICogbW9kaWZpZWQgdG8gb21pdCBhIHNlY3Rpb24gd2hpY2ggdXNlcyB0aGUgXCJ1cmxcIiBmaWVsZC5cbiAgICpcbiAgICogIHtcbiAgICogICAgdmVyc2lvbiA6IDMsXG4gICAqICAgIGZpbGU6IFwiYXBwLmpzXCIsXG4gICAqICAgIHNlY3Rpb25zOiBbe1xuICAgKiAgICAgIG9mZnNldDoge2xpbmU6MTAwLCBjb2x1bW46MTB9LFxuICAgKiAgICAgIG1hcDoge1xuICAgKiAgICAgICAgdmVyc2lvbiA6IDMsXG4gICAqICAgICAgICBmaWxlOiBcInNlY3Rpb24uanNcIixcbiAgICogICAgICAgIHNvdXJjZXM6IFtcImZvby5qc1wiLCBcImJhci5qc1wiXSxcbiAgICogICAgICAgIG5hbWVzOiBbXCJzcmNcIiwgXCJtYXBzXCIsIFwiYXJlXCIsIFwiZnVuXCJdLFxuICAgKiAgICAgICAgbWFwcGluZ3M6IFwiQUFBQSxFOztBQkNERTtcIlxuICAgKiAgICAgIH1cbiAgICogICAgfV0sXG4gICAqICB9XG4gICAqXG4gICAqIFswXTogaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vZG9jdW1lbnQvZC8xVTFSR0FlaFF3UnlwVVRvdkYxS1JscGlPRnplMGItXzJnYzZmQUgwS1kway9lZGl0I2hlYWRpbmc9aC41MzVlczN4ZXByZ3RcbiAgICovXG4gIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcihhU291cmNlTWFwKSB7XG4gICAgdmFyIHNvdXJjZU1hcCA9IGFTb3VyY2VNYXA7XG4gICAgaWYgKHR5cGVvZiBhU291cmNlTWFwID09PSAnc3RyaW5nJykge1xuICAgICAgc291cmNlTWFwID0gSlNPTi5wYXJzZShhU291cmNlTWFwLnJlcGxhY2UoL15cXClcXF1cXH0nLywgJycpKTtcbiAgICB9XG5cbiAgICB2YXIgdmVyc2lvbiA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3ZlcnNpb24nKTtcbiAgICB2YXIgc2VjdGlvbnMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzZWN0aW9ucycpO1xuXG4gICAgaWYgKHZlcnNpb24gIT0gdGhpcy5fdmVyc2lvbikge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdVbnN1cHBvcnRlZCB2ZXJzaW9uOiAnICsgdmVyc2lvbik7XG4gICAgfVxuXG4gICAgdGhpcy5fc291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgIHRoaXMuX25hbWVzID0gbmV3IEFycmF5U2V0KCk7XG5cbiAgICB2YXIgbGFzdE9mZnNldCA9IHtcbiAgICAgIGxpbmU6IC0xLFxuICAgICAgY29sdW1uOiAwXG4gICAgfTtcbiAgICB0aGlzLl9zZWN0aW9ucyA9IHNlY3Rpb25zLm1hcChmdW5jdGlvbiAocykge1xuICAgICAgaWYgKHMudXJsKSB7XG4gICAgICAgIC8vIFRoZSB1cmwgZmllbGQgd2lsbCByZXF1aXJlIHN1cHBvcnQgZm9yIGFzeW5jaHJvbmljaXR5LlxuICAgICAgICAvLyBTZWUgaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc291cmNlLW1hcC9pc3N1ZXMvMTZcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTdXBwb3J0IGZvciB1cmwgZmllbGQgaW4gc2VjdGlvbnMgbm90IGltcGxlbWVudGVkLicpO1xuICAgICAgfVxuICAgICAgdmFyIG9mZnNldCA9IHV0aWwuZ2V0QXJnKHMsICdvZmZzZXQnKTtcbiAgICAgIHZhciBvZmZzZXRMaW5lID0gdXRpbC5nZXRBcmcob2Zmc2V0LCAnbGluZScpO1xuICAgICAgdmFyIG9mZnNldENvbHVtbiA9IHV0aWwuZ2V0QXJnKG9mZnNldCwgJ2NvbHVtbicpO1xuXG4gICAgICBpZiAob2Zmc2V0TGluZSA8IGxhc3RPZmZzZXQubGluZSB8fFxuICAgICAgICAgIChvZmZzZXRMaW5lID09PSBsYXN0T2Zmc2V0LmxpbmUgJiYgb2Zmc2V0Q29sdW1uIDwgbGFzdE9mZnNldC5jb2x1bW4pKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignU2VjdGlvbiBvZmZzZXRzIG11c3QgYmUgb3JkZXJlZCBhbmQgbm9uLW92ZXJsYXBwaW5nLicpO1xuICAgICAgfVxuICAgICAgbGFzdE9mZnNldCA9IG9mZnNldDtcblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgZ2VuZXJhdGVkT2Zmc2V0OiB7XG4gICAgICAgICAgLy8gVGhlIG9mZnNldCBmaWVsZHMgYXJlIDAtYmFzZWQsIGJ1dCB3ZSB1c2UgMS1iYXNlZCBpbmRpY2VzIHdoZW5cbiAgICAgICAgICAvLyBlbmNvZGluZy9kZWNvZGluZyBmcm9tIFZMUS5cbiAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBvZmZzZXRMaW5lICsgMSxcbiAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG9mZnNldENvbHVtbiArIDFcbiAgICAgICAgfSxcbiAgICAgICAgY29uc3VtZXI6IG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLmdldEFyZyhzLCAnbWFwJykpXG4gICAgICB9XG4gICAgfSk7XG4gIH1cblxuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlID0gT2JqZWN0LmNyZWF0ZShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUpO1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmNvbnN0cnVjdG9yID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8qKlxuICAgKiBUaGUgbGlzdCBvZiBvcmlnaW5hbCBzb3VyY2VzLlxuICAgKi9cbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdzb3VyY2VzJywge1xuICAgIGdldDogZnVuY3Rpb24gKCkge1xuICAgICAgdmFyIHNvdXJjZXMgPSBbXTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgZm9yICh2YXIgaiA9IDA7IGogPCB0aGlzLl9zZWN0aW9uc1tpXS5jb25zdW1lci5zb3VyY2VzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgc291cmNlcy5wdXNoKHRoaXMuX3NlY3Rpb25zW2ldLmNvbnN1bWVyLnNvdXJjZXNbal0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICByZXR1cm4gc291cmNlcztcbiAgICB9XG4gIH0pO1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UsIGxpbmUsIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBnZW5lcmF0ZWRcbiAgICogc291cmNlJ3MgbGluZSBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0XG4gICAqIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlLCBvciBudWxsLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBuYW1lOiBUaGUgb3JpZ2luYWwgaWRlbnRpZmllciwgb3IgbnVsbC5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUub3JpZ2luYWxQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX29yaWdpbmFsUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIGdlbmVyYXRlZExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJylcbiAgICAgIH07XG5cbiAgICAgIC8vIEZpbmQgdGhlIHNlY3Rpb24gY29udGFpbmluZyB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9uIHdlJ3JlIHRyeWluZyB0byBtYXBcbiAgICAgIC8vIHRvIGFuIG9yaWdpbmFsIHBvc2l0aW9uLlxuICAgICAgdmFyIHNlY3Rpb25JbmRleCA9IGJpbmFyeVNlYXJjaC5zZWFyY2gobmVlZGxlLCB0aGlzLl9zZWN0aW9ucyxcbiAgICAgICAgZnVuY3Rpb24obmVlZGxlLCBzZWN0aW9uKSB7XG4gICAgICAgICAgdmFyIGNtcCA9IG5lZWRsZS5nZW5lcmF0ZWRMaW5lIC0gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZTtcbiAgICAgICAgICBpZiAoY21wKSB7XG4gICAgICAgICAgICByZXR1cm4gY21wO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIHJldHVybiAobmVlZGxlLmdlbmVyYXRlZENvbHVtbiAtXG4gICAgICAgICAgICAgICAgICBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICB9KTtcbiAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbc2VjdGlvbkluZGV4XTtcblxuICAgICAgaWYgKCFzZWN0aW9uKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgc291cmNlOiBudWxsLFxuICAgICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgICAgY29sdW1uOiBudWxsLFxuICAgICAgICAgIG5hbWU6IG51bGxcbiAgICAgICAgfTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHNlY3Rpb24uY29uc3VtZXIub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICAgIGxpbmU6IG5lZWRsZS5nZW5lcmF0ZWRMaW5lIC1cbiAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICBjb2x1bW46IG5lZWRsZS5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBuZWVkbGUuZ2VuZXJhdGVkTGluZVxuICAgICAgICAgICA/IHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbiAtIDFcbiAgICAgICAgICAgOiAwKSxcbiAgICAgICAgYmlhczogYUFyZ3MuYmlhc1xuICAgICAgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIHRydWUgaWYgd2UgaGF2ZSB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGV2ZXJ5IHNvdXJjZSBpbiB0aGUgc291cmNlXG4gICAqIG1hcCwgZmFsc2Ugb3RoZXJ3aXNlLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5oYXNDb250ZW50c09mQWxsU291cmNlcyA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX2hhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzKCkge1xuICAgICAgcmV0dXJuIHRoaXMuX3NlY3Rpb25zLmV2ZXJ5KGZ1bmN0aW9uIChzKSB7XG4gICAgICAgIHJldHVybiBzLmNvbnN1bWVyLmhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzKCk7XG4gICAgICB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBvcmlnaW5hbCBzb3VyY2UgY29udGVudC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgdGhlIHVybCBvZiB0aGVcbiAgICogb3JpZ2luYWwgc291cmNlIGZpbGUuIFJldHVybnMgbnVsbCBpZiBubyBvcmlnaW5hbCBzb3VyY2UgY29udGVudCBpc1xuICAgKiBhdmFpbGFibGUuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLnNvdXJjZUNvbnRlbnRGb3IgPVxuICAgIGZ1bmN0aW9uIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcl9zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIG51bGxPbk1pc3NpbmcpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcblxuICAgICAgICB2YXIgY29udGVudCA9IHNlY3Rpb24uY29uc3VtZXIuc291cmNlQ29udGVudEZvcihhU291cmNlLCB0cnVlKTtcbiAgICAgICAgaWYgKGNvbnRlbnQpIHtcbiAgICAgICAgICByZXR1cm4gY29udGVudDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgICAgaWYgKG51bGxPbk1pc3NpbmcpIHtcbiAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICB9XG4gICAgICBlbHNlIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhU291cmNlICsgJ1wiIGlzIG5vdCBpbiB0aGUgU291cmNlTWFwLicpO1xuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4gaW5mb3JtYXRpb24gZm9yIHRoZSBvcmlnaW5hbCBzb3VyY2UsXG4gICAqIGxpbmUsIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3Qgd2l0aFxuICAgKiB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBzb3VyY2U6IFRoZSBmaWxlbmFtZSBvZiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gbGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqXG4gICAqIGFuZCBhbiBvYmplY3QgaXMgcmV0dXJuZWQgd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5nZW5lcmF0ZWRQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX2dlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKSB7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IHRoaXMuX3NlY3Rpb25zLmxlbmd0aDsgaSsrKSB7XG4gICAgICAgIHZhciBzZWN0aW9uID0gdGhpcy5fc2VjdGlvbnNbaV07XG5cbiAgICAgICAgLy8gT25seSBjb25zaWRlciB0aGlzIHNlY3Rpb24gaWYgdGhlIHJlcXVlc3RlZCBzb3VyY2UgaXMgaW4gdGhlIGxpc3Qgb2ZcbiAgICAgICAgLy8gc291cmNlcyBvZiB0aGUgY29uc3VtZXIuXG4gICAgICAgIGlmIChzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZXMuaW5kZXhPZih1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpKSA9PT0gLTEpIHtcbiAgICAgICAgICBjb250aW51ZTtcbiAgICAgICAgfVxuICAgICAgICB2YXIgZ2VuZXJhdGVkUG9zaXRpb24gPSBzZWN0aW9uLmNvbnN1bWVyLmdlbmVyYXRlZFBvc2l0aW9uRm9yKGFBcmdzKTtcbiAgICAgICAgaWYgKGdlbmVyYXRlZFBvc2l0aW9uKSB7XG4gICAgICAgICAgdmFyIHJldCA9IHtcbiAgICAgICAgICAgIGxpbmU6IGdlbmVyYXRlZFBvc2l0aW9uLmxpbmUgK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWRQb3NpdGlvbi5jb2x1bW4gK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSA9PT0gZ2VuZXJhdGVkUG9zaXRpb24ubGluZVxuICAgICAgICAgICAgICAgPyBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4gLSAxXG4gICAgICAgICAgICAgICA6IDApXG4gICAgICAgICAgfTtcbiAgICAgICAgICByZXR1cm4gcmV0O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBQYXJzZSB0aGUgbWFwcGluZ3MgaW4gYSBzdHJpbmcgaW4gdG8gYSBkYXRhIHN0cnVjdHVyZSB3aGljaCB3ZSBjYW4gZWFzaWx5XG4gICAqIHF1ZXJ5ICh0aGUgb3JkZXJlZCBhcnJheXMgaW4gdGhlIGB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAgKiBgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3NgIHByb3BlcnRpZXMpLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fcGFyc2VNYXBwaW5ncyA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX3BhcnNlTWFwcGluZ3MoYVN0ciwgYVNvdXJjZVJvb3QpIHtcbiAgICAgIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IFtdO1xuICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MgPSBbXTtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcbiAgICAgICAgdmFyIHNlY3Rpb25NYXBwaW5ncyA9IHNlY3Rpb24uY29uc3VtZXIuX2dlbmVyYXRlZE1hcHBpbmdzO1xuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHNlY3Rpb25NYXBwaW5ncy5sZW5ndGg7IGorKykge1xuICAgICAgICAgIHZhciBtYXBwaW5nID0gc2VjdGlvbk1hcHBpbmdzW2ldO1xuXG4gICAgICAgICAgdmFyIHNvdXJjZSA9IHNlY3Rpb24uY29uc3VtZXIuX3NvdXJjZXMuYXQobWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIGlmIChzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZVJvb3QgIT09IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZSA9IHV0aWwuam9pbihzZWN0aW9uLmNvbnN1bWVyLnNvdXJjZVJvb3QsIHNvdXJjZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHRoaXMuX3NvdXJjZXMuYWRkKHNvdXJjZSk7XG4gICAgICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKHNvdXJjZSk7XG5cbiAgICAgICAgICB2YXIgbmFtZSA9IHNlY3Rpb24uY29uc3VtZXIuX25hbWVzLmF0KG1hcHBpbmcubmFtZSk7XG4gICAgICAgICAgdGhpcy5fbmFtZXMuYWRkKG5hbWUpO1xuICAgICAgICAgIG5hbWUgPSB0aGlzLl9uYW1lcy5pbmRleE9mKG5hbWUpO1xuXG4gICAgICAgICAgLy8gVGhlIG1hcHBpbmdzIGNvbWluZyBmcm9tIHRoZSBjb25zdW1lciBmb3IgdGhlIHNlY3Rpb24gaGF2ZVxuICAgICAgICAgIC8vIGdlbmVyYXRlZCBwb3NpdGlvbnMgcmVsYXRpdmUgdG8gdGhlIHN0YXJ0IG9mIHRoZSBzZWN0aW9uLCBzbyB3ZVxuICAgICAgICAgIC8vIG5lZWQgdG8gb2Zmc2V0IHRoZW0gdG8gYmUgcmVsYXRpdmUgdG8gdGhlIHN0YXJ0IG9mIHRoZSBjb25jYXRlbmF0ZWRcbiAgICAgICAgICAvLyBnZW5lcmF0ZWQgZmlsZS5cbiAgICAgICAgICB2YXIgYWRqdXN0ZWRNYXBwaW5nID0ge1xuICAgICAgICAgICAgc291cmNlOiBzb3VyY2UsXG4gICAgICAgICAgICBnZW5lcmF0ZWRMaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUgK1xuICAgICAgICAgICAgICAoc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkTGluZSAtIDEpLFxuICAgICAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBtYXBwaW5nLmNvbHVtbiArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBtYXBwaW5nLmdlbmVyYXRlZExpbmUpXG4gICAgICAgICAgICAgID8gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uIC0gMVxuICAgICAgICAgICAgICA6IDAsXG4gICAgICAgICAgICBvcmlnaW5hbExpbmU6IG1hcHBpbmcub3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgb3JpZ2luYWxDb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgICBuYW1lOiBuYW1lXG4gICAgICAgICAgfTtcblxuICAgICAgICAgIHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKGFkanVzdGVkTWFwcGluZyk7XG4gICAgICAgICAgaWYgKHR5cGVvZiBhZGp1c3RlZE1hcHBpbmcub3JpZ2luYWxMaW5lID09PSAnbnVtYmVyJykge1xuICAgICAgICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MucHVzaChhZGp1c3RlZE1hcHBpbmcpO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQodGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkKTtcbiAgICAgIHF1aWNrU29ydCh0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyk7XG4gICAgfTtcblxuICBleHBvcnRzLkluZGV4ZWRTb3VyY2VNYXBDb25zdW1lciA9IEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lcjtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvc291cmNlLW1hcC1jb25zdW1lci5qc1xuICoqIG1vZHVsZSBpZCA9IDdcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgZXhwb3J0cy5HUkVBVEVTVF9MT1dFUl9CT1VORCA9IDE7XG4gIGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQgPSAyO1xuXG4gIC8qKlxuICAgKiBSZWN1cnNpdmUgaW1wbGVtZW50YXRpb24gb2YgYmluYXJ5IHNlYXJjaC5cbiAgICpcbiAgICogQHBhcmFtIGFMb3cgSW5kaWNlcyBoZXJlIGFuZCBsb3dlciBkbyBub3QgY29udGFpbiB0aGUgbmVlZGxlLlxuICAgKiBAcGFyYW0gYUhpZ2ggSW5kaWNlcyBoZXJlIGFuZCBoaWdoZXIgZG8gbm90IGNvbnRhaW4gdGhlIG5lZWRsZS5cbiAgICogQHBhcmFtIGFOZWVkbGUgVGhlIGVsZW1lbnQgYmVpbmcgc2VhcmNoZWQgZm9yLlxuICAgKiBAcGFyYW0gYUhheXN0YWNrIFRoZSBub24tZW1wdHkgYXJyYXkgYmVpbmcgc2VhcmNoZWQuXG4gICAqIEBwYXJhbSBhQ29tcGFyZSBGdW5jdGlvbiB3aGljaCB0YWtlcyB0d28gZWxlbWVudHMgYW5kIHJldHVybnMgLTEsIDAsIG9yIDEuXG4gICAqIEBwYXJhbSBhQmlhcyBFaXRoZXIgJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlY3Vyc2l2ZVNlYXJjaChhTG93LCBhSGlnaCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpIHtcbiAgICAvLyBUaGlzIGZ1bmN0aW9uIHRlcm1pbmF0ZXMgd2hlbiBvbmUgb2YgdGhlIGZvbGxvd2luZyBpcyB0cnVlOlxuICAgIC8vXG4gICAgLy8gICAxLiBXZSBmaW5kIHRoZSBleGFjdCBlbGVtZW50IHdlIGFyZSBsb29raW5nIGZvci5cbiAgICAvL1xuICAgIC8vICAgMi4gV2UgZGlkIG5vdCBmaW5kIHRoZSBleGFjdCBlbGVtZW50LCBidXQgd2UgY2FuIHJldHVybiB0aGUgaW5kZXggb2ZcbiAgICAvLyAgICAgIHRoZSBuZXh0LWNsb3Nlc3QgZWxlbWVudC5cbiAgICAvL1xuICAgIC8vICAgMy4gV2UgZGlkIG5vdCBmaW5kIHRoZSBleGFjdCBlbGVtZW50LCBhbmQgdGhlcmUgaXMgbm8gbmV4dC1jbG9zZXN0XG4gICAgLy8gICAgICBlbGVtZW50IHRoYW4gdGhlIG9uZSB3ZSBhcmUgc2VhcmNoaW5nIGZvciwgc28gd2UgcmV0dXJuIC0xLlxuICAgIHZhciBtaWQgPSBNYXRoLmZsb29yKChhSGlnaCAtIGFMb3cpIC8gMikgKyBhTG93O1xuICAgIHZhciBjbXAgPSBhQ29tcGFyZShhTmVlZGxlLCBhSGF5c3RhY2tbbWlkXSwgdHJ1ZSk7XG4gICAgaWYgKGNtcCA9PT0gMCkge1xuICAgICAgLy8gRm91bmQgdGhlIGVsZW1lbnQgd2UgYXJlIGxvb2tpbmcgZm9yLlxuICAgICAgcmV0dXJuIG1pZDtcbiAgICB9XG4gICAgZWxzZSBpZiAoY21wID4gMCkge1xuICAgICAgLy8gT3VyIG5lZWRsZSBpcyBncmVhdGVyIHRoYW4gYUhheXN0YWNrW21pZF0uXG4gICAgICBpZiAoYUhpZ2ggLSBtaWQgPiAxKSB7XG4gICAgICAgIC8vIFRoZSBlbGVtZW50IGlzIGluIHRoZSB1cHBlciBoYWxmLlxuICAgICAgICByZXR1cm4gcmVjdXJzaXZlU2VhcmNoKG1pZCwgYUhpZ2gsIGFOZWVkbGUsIGFIYXlzdGFjaywgYUNvbXBhcmUsIGFCaWFzKTtcbiAgICAgIH1cblxuICAgICAgLy8gVGhlIGV4YWN0IG5lZWRsZSBlbGVtZW50IHdhcyBub3QgZm91bmQgaW4gdGhpcyBoYXlzdGFjay4gRGV0ZXJtaW5lIGlmXG4gICAgICAvLyB3ZSBhcmUgaW4gdGVybWluYXRpb24gY2FzZSAoMykgb3IgKDIpIGFuZCByZXR1cm4gdGhlIGFwcHJvcHJpYXRlIHRoaW5nLlxuICAgICAgaWYgKGFCaWFzID09IGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQpIHtcbiAgICAgICAgcmV0dXJuIGFIaWdoIDwgYUhheXN0YWNrLmxlbmd0aCA/IGFIaWdoIDogLTE7XG4gICAgICB9IGVsc2Uge1xuICAgICAgICByZXR1cm4gbWlkO1xuICAgICAgfVxuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIC8vIE91ciBuZWVkbGUgaXMgbGVzcyB0aGFuIGFIYXlzdGFja1ttaWRdLlxuICAgICAgaWYgKG1pZCAtIGFMb3cgPiAxKSB7XG4gICAgICAgIC8vIFRoZSBlbGVtZW50IGlzIGluIHRoZSBsb3dlciBoYWxmLlxuICAgICAgICByZXR1cm4gcmVjdXJzaXZlU2VhcmNoKGFMb3csIG1pZCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpO1xuICAgICAgfVxuXG4gICAgICAvLyB3ZSBhcmUgaW4gdGVybWluYXRpb24gY2FzZSAoMykgb3IgKDIpIGFuZCByZXR1cm4gdGhlIGFwcHJvcHJpYXRlIHRoaW5nLlxuICAgICAgaWYgKGFCaWFzID09IGV4cG9ydHMuTEVBU1RfVVBQRVJfQk9VTkQpIHtcbiAgICAgICAgcmV0dXJuIG1pZDtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBhTG93IDwgMCA/IC0xIDogYUxvdztcbiAgICAgIH1cbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogVGhpcyBpcyBhbiBpbXBsZW1lbnRhdGlvbiBvZiBiaW5hcnkgc2VhcmNoIHdoaWNoIHdpbGwgYWx3YXlzIHRyeSBhbmQgcmV0dXJuXG4gICAqIHRoZSBpbmRleCBvZiB0aGUgY2xvc2VzdCBlbGVtZW50IGlmIHRoZXJlIGlzIG5vIGV4YWN0IGhpdC4gVGhpcyBpcyBiZWNhdXNlXG4gICAqIG1hcHBpbmdzIGJldHdlZW4gb3JpZ2luYWwgYW5kIGdlbmVyYXRlZCBsaW5lL2NvbCBwYWlycyBhcmUgc2luZ2xlIHBvaW50cyxcbiAgICogYW5kIHRoZXJlIGlzIGFuIGltcGxpY2l0IHJlZ2lvbiBiZXR3ZWVuIGVhY2ggb2YgdGhlbSwgc28gYSBtaXNzIGp1c3QgbWVhbnNcbiAgICogdGhhdCB5b3UgYXJlbid0IG9uIHRoZSB2ZXJ5IHN0YXJ0IG9mIGEgcmVnaW9uLlxuICAgKlxuICAgKiBAcGFyYW0gYU5lZWRsZSBUaGUgZWxlbWVudCB5b3UgYXJlIGxvb2tpbmcgZm9yLlxuICAgKiBAcGFyYW0gYUhheXN0YWNrIFRoZSBhcnJheSB0aGF0IGlzIGJlaW5nIHNlYXJjaGVkLlxuICAgKiBAcGFyYW0gYUNvbXBhcmUgQSBmdW5jdGlvbiB3aGljaCB0YWtlcyB0aGUgbmVlZGxlIGFuZCBhbiBlbGVtZW50IGluIHRoZVxuICAgKiAgICAgYXJyYXkgYW5kIHJldHVybnMgLTEsIDAsIG9yIDEgZGVwZW5kaW5nIG9uIHdoZXRoZXIgdGhlIG5lZWRsZSBpcyBsZXNzXG4gICAqICAgICB0aGFuLCBlcXVhbCB0bywgb3IgZ3JlYXRlciB0aGFuIHRoZSBlbGVtZW50LCByZXNwZWN0aXZlbHkuXG4gICAqIEBwYXJhbSBhQmlhcyBFaXRoZXIgJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdiaW5hcnlTZWFyY2guTEVBU1RfVVBQRVJfQk9VTkQnLiBTcGVjaWZpZXMgd2hldGhlciB0byByZXR1cm4gdGhlXG4gICAqICAgICBjbG9zZXN0IGVsZW1lbnQgdGhhdCBpcyBzbWFsbGVyIHRoYW4gb3IgZ3JlYXRlciB0aGFuIHRoZSBvbmUgd2UgYXJlXG4gICAqICAgICBzZWFyY2hpbmcgZm9yLCByZXNwZWN0aXZlbHksIGlmIHRoZSBleGFjdCBlbGVtZW50IGNhbm5vdCBiZSBmb3VuZC5cbiAgICogICAgIERlZmF1bHRzIHRvICdiaW5hcnlTZWFyY2guR1JFQVRFU1RfTE9XRVJfQk9VTkQnLlxuICAgKi9cbiAgZXhwb3J0cy5zZWFyY2ggPSBmdW5jdGlvbiBzZWFyY2goYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpIHtcbiAgICBpZiAoYUhheXN0YWNrLmxlbmd0aCA9PT0gMCkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cblxuICAgIHZhciBpbmRleCA9IHJlY3Vyc2l2ZVNlYXJjaCgtMSwgYUhheXN0YWNrLmxlbmd0aCwgYU5lZWRsZSwgYUhheXN0YWNrLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhQ29tcGFyZSwgYUJpYXMgfHwgZXhwb3J0cy5HUkVBVEVTVF9MT1dFUl9CT1VORCk7XG4gICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgcmV0dXJuIC0xO1xuICAgIH1cblxuICAgIC8vIFdlIGhhdmUgZm91bmQgZWl0aGVyIHRoZSBleGFjdCBlbGVtZW50LCBvciB0aGUgbmV4dC1jbG9zZXN0IGVsZW1lbnQgdGhhblxuICAgIC8vIHRoZSBvbmUgd2UgYXJlIHNlYXJjaGluZyBmb3IuIEhvd2V2ZXIsIHRoZXJlIG1heSBiZSBtb3JlIHRoYW4gb25lIHN1Y2hcbiAgICAvLyBlbGVtZW50LiBNYWtlIHN1cmUgd2UgYWx3YXlzIHJldHVybiB0aGUgc21hbGxlc3Qgb2YgdGhlc2UuXG4gICAgd2hpbGUgKGluZGV4IC0gMSA+PSAwKSB7XG4gICAgICBpZiAoYUNvbXBhcmUoYUhheXN0YWNrW2luZGV4XSwgYUhheXN0YWNrW2luZGV4IC0gMV0sIHRydWUpICE9PSAwKSB7XG4gICAgICAgIGJyZWFrO1xuICAgICAgfVxuICAgICAgLS1pbmRleDtcbiAgICB9XG5cbiAgICByZXR1cm4gaW5kZXg7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2JpbmFyeS1zZWFyY2guanNcbiAqKiBtb2R1bGUgaWQgPSA4XG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIC8vIEl0IHR1cm5zIG91dCB0aGF0IHNvbWUgKG1vc3Q/KSBKYXZhU2NyaXB0IGVuZ2luZXMgZG9uJ3Qgc2VsZi1ob3N0XG4gIC8vIGBBcnJheS5wcm90b3R5cGUuc29ydGAuIFRoaXMgbWFrZXMgc2Vuc2UgYmVjYXVzZSBDKysgd2lsbCBsaWtlbHkgcmVtYWluXG4gIC8vIGZhc3RlciB0aGFuIEpTIHdoZW4gZG9pbmcgcmF3IENQVS1pbnRlbnNpdmUgc29ydGluZy4gSG93ZXZlciwgd2hlbiB1c2luZyBhXG4gIC8vIGN1c3RvbSBjb21wYXJhdG9yIGZ1bmN0aW9uLCBjYWxsaW5nIGJhY2sgYW5kIGZvcnRoIGJldHdlZW4gdGhlIFZNJ3MgQysrIGFuZFxuICAvLyBKSVQnZCBKUyBpcyByYXRoZXIgc2xvdyAqYW5kKiBsb3NlcyBKSVQgdHlwZSBpbmZvcm1hdGlvbiwgcmVzdWx0aW5nIGluXG4gIC8vIHdvcnNlIGdlbmVyYXRlZCBjb2RlIGZvciB0aGUgY29tcGFyYXRvciBmdW5jdGlvbiB0aGFuIHdvdWxkIGJlIG9wdGltYWwuIEluXG4gIC8vIGZhY3QsIHdoZW4gc29ydGluZyB3aXRoIGEgY29tcGFyYXRvciwgdGhlc2UgY29zdHMgb3V0d2VpZ2ggdGhlIGJlbmVmaXRzIG9mXG4gIC8vIHNvcnRpbmcgaW4gQysrLiBCeSB1c2luZyBvdXIgb3duIEpTLWltcGxlbWVudGVkIFF1aWNrIFNvcnQgKGJlbG93KSwgd2UgZ2V0XG4gIC8vIGEgfjM1MDBtcyBtZWFuIHNwZWVkLXVwIGluIGBiZW5jaC9iZW5jaC5odG1sYC5cblxuICAvKipcbiAgICogU3dhcCB0aGUgZWxlbWVudHMgaW5kZXhlZCBieSBgeGAgYW5kIGB5YCBpbiB0aGUgYXJyYXkgYGFyeWAuXG4gICAqXG4gICAqIEBwYXJhbSB7QXJyYXl9IGFyeVxuICAgKiAgICAgICAgVGhlIGFycmF5LlxuICAgKiBAcGFyYW0ge051bWJlcn0geFxuICAgKiAgICAgICAgVGhlIGluZGV4IG9mIHRoZSBmaXJzdCBpdGVtLlxuICAgKiBAcGFyYW0ge051bWJlcn0geVxuICAgKiAgICAgICAgVGhlIGluZGV4IG9mIHRoZSBzZWNvbmQgaXRlbS5cbiAgICovXG4gIGZ1bmN0aW9uIHN3YXAoYXJ5LCB4LCB5KSB7XG4gICAgdmFyIHRlbXAgPSBhcnlbeF07XG4gICAgYXJ5W3hdID0gYXJ5W3ldO1xuICAgIGFyeVt5XSA9IHRlbXA7XG4gIH1cblxuICAvKipcbiAgICogUmV0dXJucyBhIHJhbmRvbSBpbnRlZ2VyIHdpdGhpbiB0aGUgcmFuZ2UgYGxvdyAuLiBoaWdoYCBpbmNsdXNpdmUuXG4gICAqXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBsb3dcbiAgICogICAgICAgIFRoZSBsb3dlciBib3VuZCBvbiB0aGUgcmFuZ2UuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBoaWdoXG4gICAqICAgICAgICBUaGUgdXBwZXIgYm91bmQgb24gdGhlIHJhbmdlLlxuICAgKi9cbiAgZnVuY3Rpb24gcmFuZG9tSW50SW5SYW5nZShsb3csIGhpZ2gpIHtcbiAgICByZXR1cm4gTWF0aC5yb3VuZChsb3cgKyAoTWF0aC5yYW5kb20oKSAqIChoaWdoIC0gbG93KSkpO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSBRdWljayBTb3J0IGFsZ29yaXRobS5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBBbiBhcnJheSB0byBzb3J0LlxuICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjb21wYXJhdG9yXG4gICAqICAgICAgICBGdW5jdGlvbiB0byB1c2UgdG8gY29tcGFyZSB0d28gaXRlbXMuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSBwXG4gICAqICAgICAgICBTdGFydCBpbmRleCBvZiB0aGUgYXJyYXlcbiAgICogQHBhcmFtIHtOdW1iZXJ9IHJcbiAgICogICAgICAgIEVuZCBpbmRleCBvZiB0aGUgYXJyYXlcbiAgICovXG4gIGZ1bmN0aW9uIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcCwgcikge1xuICAgIC8vIElmIG91ciBsb3dlciBib3VuZCBpcyBsZXNzIHRoYW4gb3VyIHVwcGVyIGJvdW5kLCB3ZSAoMSkgcGFydGl0aW9uIHRoZVxuICAgIC8vIGFycmF5IGludG8gdHdvIHBpZWNlcyBhbmQgKDIpIHJlY3Vyc2Ugb24gZWFjaCBoYWxmLiBJZiBpdCBpcyBub3QsIHRoaXMgaXNcbiAgICAvLyB0aGUgZW1wdHkgYXJyYXkgYW5kIG91ciBiYXNlIGNhc2UuXG5cbiAgICBpZiAocCA8IHIpIHtcbiAgICAgIC8vICgxKSBQYXJ0aXRpb25pbmcuXG4gICAgICAvL1xuICAgICAgLy8gVGhlIHBhcnRpdGlvbmluZyBjaG9vc2VzIGEgcGl2b3QgYmV0d2VlbiBgcGAgYW5kIGByYCBhbmQgbW92ZXMgYWxsXG4gICAgICAvLyBlbGVtZW50cyB0aGF0IGFyZSBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHBpdm90IHRvIHRoZSBiZWZvcmUgaXQsIGFuZFxuICAgICAgLy8gYWxsIHRoZSBlbGVtZW50cyB0aGF0IGFyZSBncmVhdGVyIHRoYW4gaXQgYWZ0ZXIgaXQuIFRoZSBlZmZlY3QgaXMgdGhhdFxuICAgICAgLy8gb25jZSBwYXJ0aXRpb24gaXMgZG9uZSwgdGhlIHBpdm90IGlzIGluIHRoZSBleGFjdCBwbGFjZSBpdCB3aWxsIGJlIHdoZW5cbiAgICAgIC8vIHRoZSBhcnJheSBpcyBwdXQgaW4gc29ydGVkIG9yZGVyLCBhbmQgaXQgd2lsbCBub3QgbmVlZCB0byBiZSBtb3ZlZFxuICAgICAgLy8gYWdhaW4uIFRoaXMgcnVucyBpbiBPKG4pIHRpbWUuXG5cbiAgICAgIC8vIEFsd2F5cyBjaG9vc2UgYSByYW5kb20gcGl2b3Qgc28gdGhhdCBhbiBpbnB1dCBhcnJheSB3aGljaCBpcyByZXZlcnNlXG4gICAgICAvLyBzb3J0ZWQgZG9lcyBub3QgY2F1c2UgTyhuXjIpIHJ1bm5pbmcgdGltZS5cbiAgICAgIHZhciBwaXZvdEluZGV4ID0gcmFuZG9tSW50SW5SYW5nZShwLCByKTtcbiAgICAgIHZhciBpID0gcCAtIDE7XG5cbiAgICAgIHN3YXAoYXJ5LCBwaXZvdEluZGV4LCByKTtcbiAgICAgIHZhciBwaXZvdCA9IGFyeVtyXTtcblxuICAgICAgLy8gSW1tZWRpYXRlbHkgYWZ0ZXIgYGpgIGlzIGluY3JlbWVudGVkIGluIHRoaXMgbG9vcCwgdGhlIGZvbGxvd2luZyBob2xkXG4gICAgICAvLyB0cnVlOlxuICAgICAgLy9cbiAgICAgIC8vICAgKiBFdmVyeSBlbGVtZW50IGluIGBhcnlbcCAuLiBpXWAgaXMgbGVzcyB0aGFuIG9yIGVxdWFsIHRvIHRoZSBwaXZvdC5cbiAgICAgIC8vXG4gICAgICAvLyAgICogRXZlcnkgZWxlbWVudCBpbiBgYXJ5W2krMSAuLiBqLTFdYCBpcyBncmVhdGVyIHRoYW4gdGhlIHBpdm90LlxuICAgICAgZm9yICh2YXIgaiA9IHA7IGogPCByOyBqKyspIHtcbiAgICAgICAgaWYgKGNvbXBhcmF0b3IoYXJ5W2pdLCBwaXZvdCkgPD0gMCkge1xuICAgICAgICAgIGkgKz0gMTtcbiAgICAgICAgICBzd2FwKGFyeSwgaSwgaik7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgc3dhcChhcnksIGkgKyAxLCBqKTtcbiAgICAgIHZhciBxID0gaSArIDE7XG5cbiAgICAgIC8vICgyKSBSZWN1cnNlIG9uIGVhY2ggaGFsZi5cblxuICAgICAgZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBwLCBxIC0gMSk7XG4gICAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHEgKyAxLCByKTtcbiAgICB9XG4gIH1cblxuICAvKipcbiAgICogU29ydCB0aGUgZ2l2ZW4gYXJyYXkgaW4tcGxhY2Ugd2l0aCB0aGUgZ2l2ZW4gY29tcGFyYXRvciBmdW5jdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBBbiBhcnJheSB0byBzb3J0LlxuICAgKiBAcGFyYW0ge2Z1bmN0aW9ufSBjb21wYXJhdG9yXG4gICAqICAgICAgICBGdW5jdGlvbiB0byB1c2UgdG8gY29tcGFyZSB0d28gaXRlbXMuXG4gICAqL1xuICBleHBvcnRzLnF1aWNrU29ydCA9IGZ1bmN0aW9uIChhcnksIGNvbXBhcmF0b3IpIHtcbiAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIDAsIGFyeS5sZW5ndGggLSAxKTtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvcXVpY2stc29ydC5qc1xuICoqIG1vZHVsZSBpZCA9IDlcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIFNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4vc291cmNlLW1hcC1nZW5lcmF0b3InKS5Tb3VyY2VNYXBHZW5lcmF0b3I7XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLy8gTWF0Y2hlcyBhIFdpbmRvd3Mtc3R5bGUgYFxcclxcbmAgbmV3bGluZSBvciBhIGBcXG5gIG5ld2xpbmUgdXNlZCBieSBhbGwgb3RoZXJcbiAgLy8gb3BlcmF0aW5nIHN5c3RlbXMgdGhlc2UgZGF5cyAoY2FwdHVyaW5nIHRoZSByZXN1bHQpLlxuICB2YXIgUkVHRVhfTkVXTElORSA9IC8oXFxyP1xcbikvO1xuXG4gIC8vIE5ld2xpbmUgY2hhcmFjdGVyIGNvZGUgZm9yIGNoYXJDb2RlQXQoKSBjb21wYXJpc29uc1xuICB2YXIgTkVXTElORV9DT0RFID0gMTA7XG5cbiAgLy8gUHJpdmF0ZSBzeW1ib2wgZm9yIGlkZW50aWZ5aW5nIGBTb3VyY2VOb2RlYHMgd2hlbiBtdWx0aXBsZSB2ZXJzaW9ucyBvZlxuICAvLyB0aGUgc291cmNlLW1hcCBsaWJyYXJ5IGFyZSBsb2FkZWQuIFRoaXMgTVVTVCBOT1QgQ0hBTkdFIGFjcm9zc1xuICAvLyB2ZXJzaW9ucyFcbiAgdmFyIGlzU291cmNlTm9kZSA9IFwiJCQkaXNTb3VyY2VOb2RlJCQkXCI7XG5cbiAgLyoqXG4gICAqIFNvdXJjZU5vZGVzIHByb3ZpZGUgYSB3YXkgdG8gYWJzdHJhY3Qgb3ZlciBpbnRlcnBvbGF0aW5nL2NvbmNhdGVuYXRpbmdcbiAgICogc25pcHBldHMgb2YgZ2VuZXJhdGVkIEphdmFTY3JpcHQgc291cmNlIGNvZGUgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGxpbmUgYW5kXG4gICAqIGNvbHVtbiBpbmZvcm1hdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhlIG9yaWdpbmFsIHNvdXJjZSBjb2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUxpbmUgVGhlIG9yaWdpbmFsIGxpbmUgbnVtYmVyLlxuICAgKiBAcGFyYW0gYUNvbHVtbiBUaGUgb3JpZ2luYWwgY29sdW1uIG51bWJlci5cbiAgICogQHBhcmFtIGFTb3VyY2UgVGhlIG9yaWdpbmFsIHNvdXJjZSdzIGZpbGVuYW1lLlxuICAgKiBAcGFyYW0gYUNodW5rcyBPcHRpb25hbC4gQW4gYXJyYXkgb2Ygc3RyaW5ncyB3aGljaCBhcmUgc25pcHBldHMgb2ZcbiAgICogICAgICAgIGdlbmVyYXRlZCBKUywgb3Igb3RoZXIgU291cmNlTm9kZXMuXG4gICAqIEBwYXJhbSBhTmFtZSBUaGUgb3JpZ2luYWwgaWRlbnRpZmllci5cbiAgICovXG4gIGZ1bmN0aW9uIFNvdXJjZU5vZGUoYUxpbmUsIGFDb2x1bW4sIGFTb3VyY2UsIGFDaHVua3MsIGFOYW1lKSB7XG4gICAgdGhpcy5jaGlsZHJlbiA9IFtdO1xuICAgIHRoaXMuc291cmNlQ29udGVudHMgPSB7fTtcbiAgICB0aGlzLmxpbmUgPSBhTGluZSA9PSBudWxsID8gbnVsbCA6IGFMaW5lO1xuICAgIHRoaXMuY29sdW1uID0gYUNvbHVtbiA9PSBudWxsID8gbnVsbCA6IGFDb2x1bW47XG4gICAgdGhpcy5zb3VyY2UgPSBhU291cmNlID09IG51bGwgPyBudWxsIDogYVNvdXJjZTtcbiAgICB0aGlzLm5hbWUgPSBhTmFtZSA9PSBudWxsID8gbnVsbCA6IGFOYW1lO1xuICAgIHRoaXNbaXNTb3VyY2VOb2RlXSA9IHRydWU7XG4gICAgaWYgKGFDaHVua3MgIT0gbnVsbCkgdGhpcy5hZGQoYUNodW5rcyk7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyBhIFNvdXJjZU5vZGUgZnJvbSBnZW5lcmF0ZWQgY29kZSBhbmQgYSBTb3VyY2VNYXBDb25zdW1lci5cbiAgICpcbiAgICogQHBhcmFtIGFHZW5lcmF0ZWRDb2RlIFRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBTb3VyY2VNYXAgZm9yIHRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVJlbGF0aXZlUGF0aCBPcHRpb25hbC4gVGhlIHBhdGggdGhhdCByZWxhdGl2ZSBzb3VyY2VzIGluIHRoZVxuICAgKiAgICAgICAgU291cmNlTWFwQ29uc3VtZXIgc2hvdWxkIGJlIHJlbGF0aXZlIHRvLlxuICAgKi9cbiAgU291cmNlTm9kZS5mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcChhR2VuZXJhdGVkQ29kZSwgYVNvdXJjZU1hcENvbnN1bWVyLCBhUmVsYXRpdmVQYXRoKSB7XG4gICAgICAvLyBUaGUgU291cmNlTm9kZSB3ZSB3YW50IHRvIGZpbGwgd2l0aCB0aGUgZ2VuZXJhdGVkIGNvZGVcbiAgICAgIC8vIGFuZCB0aGUgU291cmNlTWFwXG4gICAgICB2YXIgbm9kZSA9IG5ldyBTb3VyY2VOb2RlKCk7XG5cbiAgICAgIC8vIEFsbCBldmVuIGluZGljZXMgb2YgdGhpcyBhcnJheSBhcmUgb25lIGxpbmUgb2YgdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAgICAgLy8gd2hpbGUgYWxsIG9kZCBpbmRpY2VzIGFyZSB0aGUgbmV3bGluZXMgYmV0d2VlbiB0d28gYWRqYWNlbnQgbGluZXNcbiAgICAgIC8vIChzaW5jZSBgUkVHRVhfTkVXTElORWAgY2FwdHVyZXMgaXRzIG1hdGNoKS5cbiAgICAgIC8vIFByb2Nlc3NlZCBmcmFnbWVudHMgYXJlIHJlbW92ZWQgZnJvbSB0aGlzIGFycmF5LCBieSBjYWxsaW5nIGBzaGlmdE5leHRMaW5lYC5cbiAgICAgIHZhciByZW1haW5pbmdMaW5lcyA9IGFHZW5lcmF0ZWRDb2RlLnNwbGl0KFJFR0VYX05FV0xJTkUpO1xuICAgICAgdmFyIHNoaWZ0TmV4dExpbmUgPSBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGxpbmVDb250ZW50cyA9IHJlbWFpbmluZ0xpbmVzLnNoaWZ0KCk7XG4gICAgICAgIC8vIFRoZSBsYXN0IGxpbmUgb2YgYSBmaWxlIG1pZ2h0IG5vdCBoYXZlIGEgbmV3bGluZS5cbiAgICAgICAgdmFyIG5ld0xpbmUgPSByZW1haW5pbmdMaW5lcy5zaGlmdCgpIHx8IFwiXCI7XG4gICAgICAgIHJldHVybiBsaW5lQ29udGVudHMgKyBuZXdMaW5lO1xuICAgICAgfTtcblxuICAgICAgLy8gV2UgbmVlZCB0byByZW1lbWJlciB0aGUgcG9zaXRpb24gb2YgXCJyZW1haW5pbmdMaW5lc1wiXG4gICAgICB2YXIgbGFzdEdlbmVyYXRlZExpbmUgPSAxLCBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcblxuICAgICAgLy8gVGhlIGdlbmVyYXRlIFNvdXJjZU5vZGVzIHdlIG5lZWQgYSBjb2RlIHJhbmdlLlxuICAgICAgLy8gVG8gZXh0cmFjdCBpdCBjdXJyZW50IGFuZCBsYXN0IG1hcHBpbmcgaXMgdXNlZC5cbiAgICAgIC8vIEhlcmUgd2Ugc3RvcmUgdGhlIGxhc3QgbWFwcGluZy5cbiAgICAgIHZhciBsYXN0TWFwcGluZyA9IG51bGw7XG5cbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcgIT09IG51bGwpIHtcbiAgICAgICAgICAvLyBXZSBhZGQgdGhlIGNvZGUgZnJvbSBcImxhc3RNYXBwaW5nXCIgdG8gXCJtYXBwaW5nXCI6XG4gICAgICAgICAgLy8gRmlyc3QgY2hlY2sgaWYgdGhlcmUgaXMgYSBuZXcgbGluZSBpbiBiZXR3ZWVuLlxuICAgICAgICAgIGlmIChsYXN0R2VuZXJhdGVkTGluZSA8IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgdmFyIGNvZGUgPSBcIlwiO1xuICAgICAgICAgICAgLy8gQXNzb2NpYXRlIGZpcnN0IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICAgIGFkZE1hcHBpbmdXaXRoQ29kZShsYXN0TWFwcGluZywgc2hpZnROZXh0TGluZSgpKTtcbiAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgICAgIC8vIFRoZSByZW1haW5pbmcgY29kZSBpcyBhZGRlZCB3aXRob3V0IG1hcHBpbmdcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gVGhlcmUgaXMgbm8gbmV3IGxpbmUgaW4gYmV0d2Vlbi5cbiAgICAgICAgICAgIC8vIEFzc29jaWF0ZSB0aGUgY29kZSBiZXR3ZWVuIFwibGFzdEdlbmVyYXRlZENvbHVtblwiIGFuZFxuICAgICAgICAgICAgLy8gXCJtYXBwaW5nLmdlbmVyYXRlZENvbHVtblwiIHdpdGggXCJsYXN0TWFwcGluZ1wiXG4gICAgICAgICAgICB2YXIgbmV4dExpbmUgPSByZW1haW5pbmdMaW5lc1swXTtcbiAgICAgICAgICAgIHZhciBjb2RlID0gbmV4dExpbmUuc3Vic3RyKDAsIG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICAgICAgcmVtYWluaW5nTGluZXNbMF0gPSBuZXh0TGluZS5zdWJzdHIobWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdEdlbmVyYXRlZENvbHVtbik7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIGNvZGUpO1xuICAgICAgICAgICAgLy8gTm8gbW9yZSByZW1haW5pbmcgY29kZSwgY29udGludWVcbiAgICAgICAgICAgIGxhc3RNYXBwaW5nID0gbWFwcGluZztcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gV2UgYWRkIHRoZSBnZW5lcmF0ZWQgY29kZSB1bnRpbCB0aGUgZmlyc3QgbWFwcGluZ1xuICAgICAgICAvLyB0byB0aGUgU291cmNlTm9kZSB3aXRob3V0IGFueSBtYXBwaW5nLlxuICAgICAgICAvLyBFYWNoIGxpbmUgaXMgYWRkZWQgYXMgc2VwYXJhdGUgc3RyaW5nLlxuICAgICAgICB3aGlsZSAobGFzdEdlbmVyYXRlZExpbmUgPCBtYXBwaW5nLmdlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICBub2RlLmFkZChzaGlmdE5leHRMaW5lKCkpO1xuICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxhc3RHZW5lcmF0ZWRDb2x1bW4gPCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikge1xuICAgICAgICAgIHZhciBuZXh0TGluZSA9IHJlbWFpbmluZ0xpbmVzWzBdO1xuICAgICAgICAgIG5vZGUuYWRkKG5leHRMaW5lLnN1YnN0cigwLCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikpO1xuICAgICAgICAgIHJlbWFpbmluZ0xpbmVzWzBdID0gbmV4dExpbmUuc3Vic3RyKG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgIH1cbiAgICAgICAgbGFzdE1hcHBpbmcgPSBtYXBwaW5nO1xuICAgICAgfSwgdGhpcyk7XG4gICAgICAvLyBXZSBoYXZlIHByb2Nlc3NlZCBhbGwgbWFwcGluZ3MuXG4gICAgICBpZiAocmVtYWluaW5nTGluZXMubGVuZ3RoID4gMCkge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcpIHtcbiAgICAgICAgICAvLyBBc3NvY2lhdGUgdGhlIHJlbWFpbmluZyBjb2RlIGluIHRoZSBjdXJyZW50IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIHNoaWZ0TmV4dExpbmUoKSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gYW5kIGFkZCB0aGUgcmVtYWluaW5nIGxpbmVzIHdpdGhvdXQgYW55IG1hcHBpbmdcbiAgICAgICAgbm9kZS5hZGQocmVtYWluaW5nTGluZXMuam9pbihcIlwiKSk7XG4gICAgICB9XG5cbiAgICAgIC8vIENvcHkgc291cmNlc0NvbnRlbnQgaW50byBTb3VyY2VOb2RlXG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGZ1bmN0aW9uIChzb3VyY2VGaWxlKSB7XG4gICAgICAgIHZhciBjb250ZW50ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlRmlsZSk7XG4gICAgICAgIGlmIChjb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgICBpZiAoYVJlbGF0aXZlUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5qb2luKGFSZWxhdGl2ZVBhdGgsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBub2RlLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4gbm9kZTtcblxuICAgICAgZnVuY3Rpb24gYWRkTWFwcGluZ1dpdGhDb2RlKG1hcHBpbmcsIGNvZGUpIHtcbiAgICAgICAgaWYgKG1hcHBpbmcgPT09IG51bGwgfHwgbWFwcGluZy5zb3VyY2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIG5vZGUuYWRkKGNvZGUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhciBzb3VyY2UgPSBhUmVsYXRpdmVQYXRoXG4gICAgICAgICAgICA/IHV0aWwuam9pbihhUmVsYXRpdmVQYXRoLCBtYXBwaW5nLnNvdXJjZSlcbiAgICAgICAgICAgIDogbWFwcGluZy5zb3VyY2U7XG4gICAgICAgICAgbm9kZS5hZGQobmV3IFNvdXJjZU5vZGUobWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGlzIHNvdXJjZSBub2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUNodW5rIEEgc3RyaW5nIHNuaXBwZXQgb2YgZ2VuZXJhdGVkIEpTIGNvZGUsIGFub3RoZXIgaW5zdGFuY2Ugb2ZcbiAgICogICAgICAgIFNvdXJjZU5vZGUsIG9yIGFuIGFycmF5IHdoZXJlIGVhY2ggbWVtYmVyIGlzIG9uZSBvZiB0aG9zZSB0aGluZ3MuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX2FkZChhQ2h1bmspIHtcbiAgICBpZiAoQXJyYXkuaXNBcnJheShhQ2h1bmspKSB7XG4gICAgICBhQ2h1bmsuZm9yRWFjaChmdW5jdGlvbiAoY2h1bmspIHtcbiAgICAgICAgdGhpcy5hZGQoY2h1bmspO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIGlmIChhQ2h1bmspIHtcbiAgICAgICAgdGhpcy5jaGlsZHJlbi5wdXNoKGFDaHVuayk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgXCJFeHBlY3RlZCBhIFNvdXJjZU5vZGUsIHN0cmluZywgb3IgYW4gYXJyYXkgb2YgU291cmNlTm9kZXMgYW5kIHN0cmluZ3MuIEdvdCBcIiArIGFDaHVua1xuICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGUgYmVnaW5uaW5nIG9mIHRoaXMgc291cmNlIG5vZGUuXG4gICAqXG4gICAqIEBwYXJhbSBhQ2h1bmsgQSBzdHJpbmcgc25pcHBldCBvZiBnZW5lcmF0ZWQgSlMgY29kZSwgYW5vdGhlciBpbnN0YW5jZSBvZlxuICAgKiAgICAgICAgU291cmNlTm9kZSwgb3IgYW4gYXJyYXkgd2hlcmUgZWFjaCBtZW1iZXIgaXMgb25lIG9mIHRob3NlIHRoaW5ncy5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnByZXBlbmQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX3ByZXBlbmQoYUNodW5rKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkoYUNodW5rKSkge1xuICAgICAgZm9yICh2YXIgaSA9IGFDaHVuay5sZW5ndGgtMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgICAgdGhpcy5wcmVwZW5kKGFDaHVua1tpXSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4udW5zaGlmdChhQ2h1bmspO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXG4gICAgICAgIFwiRXhwZWN0ZWQgYSBTb3VyY2VOb2RlLCBzdHJpbmcsIG9yIGFuIGFycmF5IG9mIFNvdXJjZU5vZGVzIGFuZCBzdHJpbmdzLiBHb3QgXCIgKyBhQ2h1bmtcbiAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgSlMgc25pcHBldHMgaW4gdGhpcyBub2RlIGFuZCBpdHMgY2hpbGRyZW4uIFRoZVxuICAgKiB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBvbmNlIGZvciBlYWNoIHNuaXBwZXQgb2YgSlMgYW5kIGlzIHBhc3NlZCB0aGF0XG4gICAqIHNuaXBwZXQgYW5kIHRoZSBpdHMgb3JpZ2luYWwgYXNzb2NpYXRlZCBzb3VyY2UncyBsaW5lL2NvbHVtbiBsb2NhdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2FsayA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfd2FsayhhRm4pIHtcbiAgICB2YXIgY2h1bms7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGNodW5rID0gdGhpcy5jaGlsZHJlbltpXTtcbiAgICAgIGlmIChjaHVua1tpc1NvdXJjZU5vZGVdKSB7XG4gICAgICAgIGNodW5rLndhbGsoYUZuKTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICBpZiAoY2h1bmsgIT09ICcnKSB7XG4gICAgICAgICAgYUZuKGNodW5rLCB7IHNvdXJjZTogdGhpcy5zb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgIGxpbmU6IHRoaXMubGluZSxcbiAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uOiB0aGlzLmNvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogdGhpcy5uYW1lIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBMaWtlIGBTdHJpbmcucHJvdG90eXBlLmpvaW5gIGV4Y2VwdCBmb3IgU291cmNlTm9kZXMuIEluc2VydHMgYGFTdHJgIGJldHdlZW5cbiAgICogZWFjaCBvZiBgdGhpcy5jaGlsZHJlbmAuXG4gICAqXG4gICAqIEBwYXJhbSBhU2VwIFRoZSBzZXBhcmF0b3IuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5qb2luID0gZnVuY3Rpb24gU291cmNlTm9kZV9qb2luKGFTZXApIHtcbiAgICB2YXIgbmV3Q2hpbGRyZW47XG4gICAgdmFyIGk7XG4gICAgdmFyIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoO1xuICAgIGlmIChsZW4gPiAwKSB7XG4gICAgICBuZXdDaGlsZHJlbiA9IFtdO1xuICAgICAgZm9yIChpID0gMDsgaSA8IGxlbi0xOyBpKyspIHtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaCh0aGlzLmNoaWxkcmVuW2ldKTtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaChhU2VwKTtcbiAgICAgIH1cbiAgICAgIG5ld0NoaWxkcmVuLnB1c2godGhpcy5jaGlsZHJlbltpXSk7XG4gICAgICB0aGlzLmNoaWxkcmVuID0gbmV3Q2hpbGRyZW47XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBDYWxsIFN0cmluZy5wcm90b3R5cGUucmVwbGFjZSBvbiB0aGUgdmVyeSByaWdodC1tb3N0IHNvdXJjZSBzbmlwcGV0LiBVc2VmdWxcbiAgICogZm9yIHRyaW1taW5nIHdoaXRlc3BhY2UgZnJvbSB0aGUgZW5kIG9mIGEgc291cmNlIG5vZGUsIGV0Yy5cbiAgICpcbiAgICogQHBhcmFtIGFQYXR0ZXJuIFRoZSBwYXR0ZXJuIHRvIHJlcGxhY2UuXG4gICAqIEBwYXJhbSBhUmVwbGFjZW1lbnQgVGhlIHRoaW5nIHRvIHJlcGxhY2UgdGhlIHBhdHRlcm4gd2l0aC5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnJlcGxhY2VSaWdodCA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfcmVwbGFjZVJpZ2h0KGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpIHtcbiAgICB2YXIgbGFzdENoaWxkID0gdGhpcy5jaGlsZHJlblt0aGlzLmNoaWxkcmVuLmxlbmd0aCAtIDFdO1xuICAgIGlmIChsYXN0Q2hpbGRbaXNTb3VyY2VOb2RlXSkge1xuICAgICAgbGFzdENoaWxkLnJlcGxhY2VSaWdodChhUGF0dGVybiwgYVJlcGxhY2VtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAodHlwZW9mIGxhc3RDaGlsZCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW5bdGhpcy5jaGlsZHJlbi5sZW5ndGggLSAxXSA9IGxhc3RDaGlsZC5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4ucHVzaCgnJy5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGEgc291cmNlIGZpbGUuIFRoaXMgd2lsbCBiZSBhZGRlZCB0byB0aGUgU291cmNlTWFwR2VuZXJhdG9yXG4gICAqIGluIHRoZSBzb3VyY2VzQ29udGVudCBmaWVsZC5cbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VGaWxlIFRoZSBmaWxlbmFtZSBvZiB0aGUgc291cmNlIGZpbGVcbiAgICogQHBhcmFtIGFTb3VyY2VDb250ZW50IFRoZSBjb250ZW50IG9mIHRoZSBzb3VyY2UgZmlsZVxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUuc2V0U291cmNlQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9zZXRTb3VyY2VDb250ZW50KGFTb3VyY2VGaWxlLCBhU291cmNlQ29udGVudCkge1xuICAgICAgdGhpcy5zb3VyY2VDb250ZW50c1t1dGlsLnRvU2V0U3RyaW5nKGFTb3VyY2VGaWxlKV0gPSBhU291cmNlQ29udGVudDtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgU291cmNlTm9kZXMuIFRoZSB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBmb3IgZWFjaFxuICAgKiBzb3VyY2UgZmlsZSBjb250ZW50IGFuZCBpcyBwYXNzZWQgdGhlIGZpbGVuYW1lIGFuZCBzb3VyY2UgY29udGVudC5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2Fsa1NvdXJjZUNvbnRlbnRzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VOb2RlX3dhbGtTb3VyY2VDb250ZW50cyhhRm4pIHtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSB0aGlzLmNoaWxkcmVuLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGlmICh0aGlzLmNoaWxkcmVuW2ldW2lzU291cmNlTm9kZV0pIHtcbiAgICAgICAgICB0aGlzLmNoaWxkcmVuW2ldLndhbGtTb3VyY2VDb250ZW50cyhhRm4pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBzb3VyY2VzID0gT2JqZWN0LmtleXModGhpcy5zb3VyY2VDb250ZW50cyk7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gc291cmNlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBhRm4odXRpbC5mcm9tU2V0U3RyaW5nKHNvdXJjZXNbaV0pLCB0aGlzLnNvdXJjZUNvbnRlbnRzW3NvdXJjZXNbaV1dKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlLiBXYWxrcyBvdmVyIHRoZSB0cmVlXG4gICAqIGFuZCBjb25jYXRlbmF0ZXMgYWxsIHRoZSB2YXJpb3VzIHNuaXBwZXRzIHRvZ2V0aGVyIHRvIG9uZSBzdHJpbmcuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS50b1N0cmluZyA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfdG9TdHJpbmcoKSB7XG4gICAgdmFyIHN0ciA9IFwiXCI7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaykge1xuICAgICAgc3RyICs9IGNodW5rO1xuICAgIH0pO1xuICAgIHJldHVybiBzdHI7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlIGFsb25nIHdpdGggYSBzb3VyY2VcbiAgICogbWFwLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUudG9TdHJpbmdXaXRoU291cmNlTWFwID0gZnVuY3Rpb24gU291cmNlTm9kZV90b1N0cmluZ1dpdGhTb3VyY2VNYXAoYUFyZ3MpIHtcbiAgICB2YXIgZ2VuZXJhdGVkID0ge1xuICAgICAgY29kZTogXCJcIixcbiAgICAgIGxpbmU6IDEsXG4gICAgICBjb2x1bW46IDBcbiAgICB9O1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKGFBcmdzKTtcbiAgICB2YXIgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgIHZhciBsYXN0T3JpZ2luYWxTb3VyY2UgPSBudWxsO1xuICAgIHZhciBsYXN0T3JpZ2luYWxMaW5lID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsQ29sdW1uID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsTmFtZSA9IG51bGw7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaywgb3JpZ2luYWwpIHtcbiAgICAgIGdlbmVyYXRlZC5jb2RlICs9IGNodW5rO1xuICAgICAgaWYgKG9yaWdpbmFsLnNvdXJjZSAhPT0gbnVsbFxuICAgICAgICAgICYmIG9yaWdpbmFsLmxpbmUgIT09IG51bGxcbiAgICAgICAgICAmJiBvcmlnaW5hbC5jb2x1bW4gIT09IG51bGwpIHtcbiAgICAgICAgaWYobGFzdE9yaWdpbmFsU291cmNlICE9PSBvcmlnaW5hbC5zb3VyY2VcbiAgICAgICAgICAgfHwgbGFzdE9yaWdpbmFsTGluZSAhPT0gb3JpZ2luYWwubGluZVxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxDb2x1bW4gIT09IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxOYW1lICE9PSBvcmlnaW5hbC5uYW1lKSB7XG4gICAgICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICAgICAgc291cmNlOiBvcmlnaW5hbC5zb3VyY2UsXG4gICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICBsaW5lOiBvcmlnaW5hbC5saW5lLFxuICAgICAgICAgICAgICBjb2x1bW46IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICAgICAgICBsaW5lOiBnZW5lcmF0ZWQubGluZSxcbiAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbmFtZTogb3JpZ2luYWwubmFtZVxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG9yaWdpbmFsLnNvdXJjZTtcbiAgICAgICAgbGFzdE9yaWdpbmFsTGluZSA9IG9yaWdpbmFsLmxpbmU7XG4gICAgICAgIGxhc3RPcmlnaW5hbENvbHVtbiA9IG9yaWdpbmFsLmNvbHVtbjtcbiAgICAgICAgbGFzdE9yaWdpbmFsTmFtZSA9IG9yaWdpbmFsLm5hbWU7XG4gICAgICAgIHNvdXJjZU1hcHBpbmdBY3RpdmUgPSB0cnVlO1xuICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IHtcbiAgICAgICAgICAgIGxpbmU6IGdlbmVyYXRlZC5saW5lLFxuICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgbGFzdE9yaWdpbmFsU291cmNlID0gbnVsbDtcbiAgICAgICAgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgICAgfVxuICAgICAgZm9yICh2YXIgaWR4ID0gMCwgbGVuZ3RoID0gY2h1bmsubGVuZ3RoOyBpZHggPCBsZW5ndGg7IGlkeCsrKSB7XG4gICAgICAgIGlmIChjaHVuay5jaGFyQ29kZUF0KGlkeCkgPT09IE5FV0xJTkVfQ09ERSkge1xuICAgICAgICAgIGdlbmVyYXRlZC5saW5lKys7XG4gICAgICAgICAgZ2VuZXJhdGVkLmNvbHVtbiA9IDA7XG4gICAgICAgICAgLy8gTWFwcGluZ3MgZW5kIGF0IGVvbFxuICAgICAgICAgIGlmIChpZHggKyAxID09PSBsZW5ndGgpIHtcbiAgICAgICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG51bGw7XG4gICAgICAgICAgICBzb3VyY2VNYXBwaW5nQWN0aXZlID0gZmFsc2U7XG4gICAgICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgICAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICAgICAgICAgIHNvdXJjZTogb3JpZ2luYWwuc291cmNlLFxuICAgICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICAgIGxpbmU6IG9yaWdpbmFsLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBvcmlnaW5hbC5jb2x1bW5cbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgZ2VuZXJhdGVkOiB7XG4gICAgICAgICAgICAgICAgbGluZTogZ2VuZXJhdGVkLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIG5hbWU6IG9yaWdpbmFsLm5hbWVcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBnZW5lcmF0ZWQuY29sdW1uKys7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgICB0aGlzLndhbGtTb3VyY2VDb250ZW50cyhmdW5jdGlvbiAoc291cmNlRmlsZSwgc291cmNlQ29udGVudCkge1xuICAgICAgbWFwLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgc291cmNlQ29udGVudCk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4geyBjb2RlOiBnZW5lcmF0ZWQuY29kZSwgbWFwOiBtYXAgfTtcbiAgfTtcblxuICBleHBvcnRzLlNvdXJjZU5vZGUgPSBTb3VyY2VOb2RlO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2Utbm9kZS5qc1xuICoqIG1vZHVsZSBpZCA9IDEwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi4vbGliL3V0aWwnKTtcblxuICAvLyBUaGlzIGlzIGEgdGVzdCBtYXBwaW5nIHdoaWNoIG1hcHMgZnVuY3Rpb25zIGZyb20gdHdvIGRpZmZlcmVudCBmaWxlc1xuICAvLyAob25lLmpzIGFuZCB0d28uanMpIHRvIGEgbWluaWZpZWQgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgLy9cbiAgLy8gSGVyZSBpcyBvbmUuanM6XG4gIC8vXG4gIC8vICAgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcbiAgLy8gICAgIHJldHVybiBiYXooYmFyKTtcbiAgLy8gICB9O1xuICAvL1xuICAvLyBIZXJlIGlzIHR3by5qczpcbiAgLy9cbiAgLy8gICBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcbiAgLy8gICAgIHJldHVybiBuICsgMTtcbiAgLy8gICB9O1xuICAvL1xuICAvLyBBbmQgaGVyZSBpcyB0aGUgZ2VuZXJhdGVkIGNvZGUgKG1pbi5qcyk6XG4gIC8vXG4gIC8vICAgT05FLmZvbz1mdW5jdGlvbihhKXtyZXR1cm4gYmF6KGEpO307XG4gIC8vICAgVFdPLmluYz1mdW5jdGlvbihhKXtyZXR1cm4gYSsxO307XG4gIGV4cG9ydHMudGVzdEdlbmVyYXRlZENvZGUgPSBcIiBPTkUuZm9vPWZ1bmN0aW9uKGEpe3JldHVybiBiYXooYSk7fTtcXG5cIitcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwiIFRXTy5pbmM9ZnVuY3Rpb24oYSl7cmV0dXJuIGErMTt9O1wiO1xuICBleHBvcnRzLnRlc3RNYXAgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VSb290OiAnL3RoZS9yb290JyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLnRlc3RNYXBOb1NvdXJjZVJvb3QgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLnRlc3RNYXBFbXB0eVNvdXJjZVJvb3QgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VSb290OiAnJyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICAvLyBUaGlzIG1hcHBpbmcgaXMgaWRlbnRpY2FsIHRvIGFib3ZlLCBidXQgdXNlcyB0aGUgaW5kZXhlZCBmb3JtYXQgaW5zdGVhZC5cbiAgZXhwb3J0cy5pbmRleGVkVGVzdE1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIHNlY3Rpb25zOiBbXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDAsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJvbmUuanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAgICAgICAnIH07JyxcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcImJhclwiLFxuICAgICAgICAgICAgXCJiYXpcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSURcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL3RoZS9yb290XCJcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMSxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcInR3by5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAgICAgICAnIH07J1xuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiblwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQVwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvdGhlL3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgXVxuICB9O1xuICBleHBvcnRzLmluZGV4ZWRUZXN0TWFwRGlmZmVyZW50U291cmNlUm9vdHMgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBzZWN0aW9uczogW1xuICAgICAge1xuICAgICAgICBvZmZzZXQ6IHtcbiAgICAgICAgICBsaW5lOiAwLFxuICAgICAgICAgIGNvbHVtbjogMFxuICAgICAgICB9LFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICB2ZXJzaW9uOiAzLFxuICAgICAgICAgIHNvdXJjZXM6IFtcbiAgICAgICAgICAgIFwib25lLmpzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAgICAgICAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuJyArXG4gICAgICAgICAgICAnICAgcmV0dXJuIGJheihiYXIpO1xcbicgK1xuICAgICAgICAgICAgJyB9OycsXG4gICAgICAgICAgXSxcbiAgICAgICAgICBuYW1lczogW1xuICAgICAgICAgICAgXCJiYXJcIixcbiAgICAgICAgICAgIFwiYmF6XCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIG1hcHBpbmdzOiBcIkNBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEXCIsXG4gICAgICAgICAgZmlsZTogXCJtaW4uanNcIixcbiAgICAgICAgICBzb3VyY2VSb290OiBcIi90aGUvcm9vdFwiXG4gICAgICAgIH1cbiAgICAgIH0sXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDEsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJ0d28uanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuJyArXG4gICAgICAgICAgICAnICAgcmV0dXJuIG4gKyAxO1xcbicgK1xuICAgICAgICAgICAgJyB9OydcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcIm5cIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0FcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL2RpZmZlcmVudC9yb290XCJcbiAgICAgICAgfVxuICAgICAgfVxuICAgIF1cbiAgfTtcbiAgZXhwb3J0cy50ZXN0TWFwV2l0aFNvdXJjZXNDb250ZW50ID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFsnYmFyJywgJ2JheicsICduJ10sXG4gICAgc291cmNlczogWydvbmUuanMnLCAndHdvLmpzJ10sXG4gICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAnIH07JyxcbiAgICAgICcgVFdPLmluYyA9IGZ1bmN0aW9uIChuKSB7XFxuJyArXG4gICAgICAnICAgcmV0dXJuIG4gKyAxO1xcbicgK1xuICAgICAgJyB9OydcbiAgICBdLFxuICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcFJlbGF0aXZlU291cmNlcyA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnLi9vbmUuanMnLCAnLi90d28uanMnXSxcbiAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICcgfTsnLFxuICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAnIH07J1xuICAgIF0sXG4gICAgc291cmNlUm9vdDogJy90aGUvcm9vdCcsXG4gICAgbWFwcGluZ3M6ICdDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRDtDQ0RiLElBQUksSUFBTSxTQUFVRSxHQUNsQixPQUFPQSdcbiAgfTtcbiAgZXhwb3J0cy5lbXB0eU1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbXSxcbiAgICBzb3VyY2VzOiBbXSxcbiAgICBtYXBwaW5nczogJydcbiAgfTtcblxuXG4gIGZ1bmN0aW9uIGFzc2VydE1hcHBpbmcoZ2VuZXJhdGVkTGluZSwgZ2VuZXJhdGVkQ29sdW1uLCBvcmlnaW5hbFNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICBvcmlnaW5hbExpbmUsIG9yaWdpbmFsQ29sdW1uLCBuYW1lLCBiaWFzLCBtYXAsIGFzc2VydCxcbiAgICAgICAgICAgICAgICAgICAgICAgICBkb250VGVzdEdlbmVyYXRlZCwgZG9udFRlc3RPcmlnaW5hbCkge1xuICAgIGlmICghZG9udFRlc3RPcmlnaW5hbCkge1xuICAgICAgdmFyIG9yaWdNYXBwaW5nID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgICBsaW5lOiBnZW5lcmF0ZWRMaW5lLFxuICAgICAgICBjb2x1bW46IGdlbmVyYXRlZENvbHVtbixcbiAgICAgICAgYmlhczogYmlhc1xuICAgICAgfSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcubmFtZSwgbmFtZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IG5hbWUsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShuYW1lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5uYW1lKSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcubGluZSwgb3JpZ2luYWxMaW5lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbGluZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdpbmFsTGluZSlcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcubGluZSkpO1xuICAgICAgYXNzZXJ0LmVxdWFsKG9yaWdNYXBwaW5nLmNvbHVtbiwgb3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBjb2x1bW4sIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShvcmlnaW5hbENvbHVtbilcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcuY29sdW1uKSk7XG5cbiAgICAgIHZhciBleHBlY3RlZFNvdXJjZTtcblxuICAgICAgaWYgKG9yaWdpbmFsU291cmNlICYmIG1hcC5zb3VyY2VSb290ICYmIG9yaWdpbmFsU291cmNlLmluZGV4T2YobWFwLnNvdXJjZVJvb3QpID09PSAwKSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gb3JpZ2luYWxTb3VyY2U7XG4gICAgICB9IGVsc2UgaWYgKG9yaWdpbmFsU291cmNlKSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gbWFwLnNvdXJjZVJvb3RcbiAgICAgICAgICA/IHV0aWwuam9pbihtYXAuc291cmNlUm9vdCwgb3JpZ2luYWxTb3VyY2UpXG4gICAgICAgICAgOiBvcmlnaW5hbFNvdXJjZTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIGV4cGVjdGVkU291cmNlID0gbnVsbDtcbiAgICAgIH1cblxuICAgICAgYXNzZXJ0LmVxdWFsKG9yaWdNYXBwaW5nLnNvdXJjZSwgZXhwZWN0ZWRTb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBzb3VyY2UsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShleHBlY3RlZFNvdXJjZSlcbiAgICAgICAgICAgICAgICAgICArICcsIGdvdCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ01hcHBpbmcuc291cmNlKSk7XG4gICAgfVxuXG4gICAgaWYgKCFkb250VGVzdEdlbmVyYXRlZCkge1xuICAgICAgdmFyIGdlbk1hcHBpbmcgPSBtYXAuZ2VuZXJhdGVkUG9zaXRpb25Gb3Ioe1xuICAgICAgICBzb3VyY2U6IG9yaWdpbmFsU291cmNlLFxuICAgICAgICBsaW5lOiBvcmlnaW5hbExpbmUsXG4gICAgICAgIGNvbHVtbjogb3JpZ2luYWxDb2x1bW4sXG4gICAgICAgIGJpYXM6IGJpYXNcbiAgICAgIH0pO1xuICAgICAgYXNzZXJ0LmVxdWFsKGdlbk1hcHBpbmcubGluZSwgZ2VuZXJhdGVkTGluZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IGxpbmUsIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShnZW5lcmF0ZWRMaW5lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShnZW5NYXBwaW5nLmxpbmUpKTtcbiAgICAgIGFzc2VydC5lcXVhbChnZW5NYXBwaW5nLmNvbHVtbiwgZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgY29sdW1uLCBleHBlY3RlZCAnICsgSlNPTi5zdHJpbmdpZnkoZ2VuZXJhdGVkQ29sdW1uKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShnZW5NYXBwaW5nLmNvbHVtbikpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmFzc2VydE1hcHBpbmcgPSBhc3NlcnRNYXBwaW5nO1xuXG4gIGZ1bmN0aW9uIGFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIGFjdHVhbE1hcCwgZXhwZWN0ZWRNYXApIHtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnZlcnNpb24sIGV4cGVjdGVkTWFwLnZlcnNpb24sIFwidmVyc2lvbiBtaXNtYXRjaFwiKTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLmZpbGUsIGV4cGVjdGVkTWFwLmZpbGUsIFwiZmlsZSBtaXNtYXRjaFwiKTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLm5hbWVzLmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAubmFtZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBcIm5hbWVzIGxlbmd0aCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5uYW1lcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAubmFtZXMubGVuZ3RoOyBpKyspIHtcbiAgICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAubmFtZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAubmFtZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgXCJuYW1lc1tcIiArIGkgKyBcIl0gbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5uYW1lcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSk7XG4gICAgfVxuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAuc291cmNlcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBcInNvdXJjZXMgbGVuZ3RoIG1pc21hdGNoOiBcIiArXG4gICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpICsgXCIgIT0gXCIgKyBleHBlY3RlZE1hcC5zb3VyY2VzLmpvaW4oXCIsIFwiKSk7XG4gICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAuc291cmNlcy5sZW5ndGg7IGkrKykge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzW2ldLFxuICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzW1wiICsgaSArIFwiXSBsZW5ndGggbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpKTtcbiAgICB9XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VSb290LFxuICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5zb3VyY2VSb290LFxuICAgICAgICAgICAgICAgICBcInNvdXJjZVJvb3QgbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlUm9vdCArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAuc291cmNlUm9vdCk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5tYXBwaW5ncywgZXhwZWN0ZWRNYXAubWFwcGluZ3MsXG4gICAgICAgICAgICAgICAgIFwibWFwcGluZ3MgbWlzbWF0Y2g6XFxuQWN0dWFsOiAgIFwiICsgYWN0dWFsTWFwLm1hcHBpbmdzICsgXCJcXG5FeHBlY3RlZDogXCIgKyBleHBlY3RlZE1hcC5tYXBwaW5ncyk7XG4gICAgaWYgKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudCkge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc0NvbnRlbnQubGVuZ3RoLFxuICAgICAgICAgICAgICAgICAgIFwic291cmNlc0NvbnRlbnQgbGVuZ3RoIG1pc21hdGNoXCIpO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCBhY3R1YWxNYXAuc291cmNlc0NvbnRlbnQubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudFtpXSxcbiAgICAgICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZXNDb250ZW50W2ldLFxuICAgICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzQ29udGVudFtcIiArIGkgKyBcIl0gbWlzbWF0Y2hcIik7XG4gICAgICB9XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuYXNzZXJ0RXF1YWxNYXBzID0gYXNzZXJ0RXF1YWxNYXBzO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdXRpbC5qc1xuICoqIG1vZHVsZSBpZCA9IDExXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_source_node.js b/devtools/shared/sourcemap/tests/unit/test_source_node.js
new file mode 100644
index 000000000..f41b2d80d
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_source_node.js
@@ -0,0 +1,3908 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(1);
+ var SourceMapGenerator = __webpack_require__(3).SourceMapGenerator;
+ var SourceMapConsumer = __webpack_require__(8).SourceMapConsumer;
+ var SourceNode = __webpack_require__(11).SourceNode;
+
+ function forEachNewline(fn) {
+ return function (assert) {
+ ['\n', '\r\n'].forEach(fn.bind(null, assert));
+ }
+ }
+
+ exports['test .add()'] = function (assert) {
+ var node = new SourceNode(null, null, null);
+
+ // Adding a string works.
+ node.add('function noop() {}');
+
+ // Adding another source node works.
+ node.add(new SourceNode(null, null, null));
+
+ // Adding an array works.
+ node.add(['function foo() {',
+ new SourceNode(null, null, null,
+ 'return 10;'),
+ '}']);
+
+ // Adding other stuff doesn't.
+ assert.throws(function () {
+ node.add({});
+ });
+ assert.throws(function () {
+ node.add(function () {});
+ });
+ };
+
+ exports['test .prepend()'] = function (assert) {
+ var node = new SourceNode(null, null, null);
+
+ // Prepending a string works.
+ node.prepend('function noop() {}');
+ assert.equal(node.children[0], 'function noop() {}');
+ assert.equal(node.children.length, 1);
+
+ // Prepending another source node works.
+ node.prepend(new SourceNode(null, null, null));
+ assert.equal(node.children[0], '');
+ assert.equal(node.children[1], 'function noop() {}');
+ assert.equal(node.children.length, 2);
+
+ // Prepending an array works.
+ node.prepend(['function foo() {',
+ new SourceNode(null, null, null,
+ 'return 10;'),
+ '}']);
+ assert.equal(node.children[0], 'function foo() {');
+ assert.equal(node.children[1], 'return 10;');
+ assert.equal(node.children[2], '}');
+ assert.equal(node.children[3], '');
+ assert.equal(node.children[4], 'function noop() {}');
+ assert.equal(node.children.length, 5);
+
+ // Prepending other stuff doesn't.
+ assert.throws(function () {
+ node.prepend({});
+ });
+ assert.throws(function () {
+ node.prepend(function () {});
+ });
+ };
+
+ exports['test .toString()'] = function (assert) {
+ assert.equal((new SourceNode(null, null, null,
+ ['function foo() {',
+ new SourceNode(null, null, null, 'return 10;'),
+ '}'])).toString(),
+ 'function foo() {return 10;}');
+ };
+
+ exports['test .join()'] = function (assert) {
+ assert.equal((new SourceNode(null, null, null,
+ ['a', 'b', 'c', 'd'])).join(', ').toString(),
+ 'a, b, c, d');
+ };
+
+ exports['test .walk()'] = function (assert) {
+ var node = new SourceNode(null, null, null,
+ ['(function () {\n',
+ ' ', new SourceNode(1, 0, 'a.js', ['someCall()']), ';\n',
+ ' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';\n',
+ '}());']);
+ var expected = [
+ { str: '(function () {\n', source: null, line: null, column: null },
+ { str: ' ', source: null, line: null, column: null },
+ { str: 'someCall()', source: 'a.js', line: 1, column: 0 },
+ { str: ';\n', source: null, line: null, column: null },
+ { str: ' ', source: null, line: null, column: null },
+ { str: 'if (foo) bar()', source: 'b.js', line: 2, column: 0 },
+ { str: ';\n', source: null, line: null, column: null },
+ { str: '}());', source: null, line: null, column: null },
+ ];
+ var i = 0;
+ node.walk(function (chunk, loc) {
+ assert.equal(expected[i].str, chunk);
+ assert.equal(expected[i].source, loc.source);
+ assert.equal(expected[i].line, loc.line);
+ assert.equal(expected[i].column, loc.column);
+ i++;
+ });
+ };
+
+ exports['test .replaceRight'] = function (assert) {
+ var node;
+
+ // Not nested
+ node = new SourceNode(null, null, null, 'hello world');
+ node.replaceRight(/world/, 'universe');
+ assert.equal(node.toString(), 'hello universe');
+
+ // Nested
+ node = new SourceNode(null, null, null,
+ [new SourceNode(null, null, null, 'hey sexy mama, '),
+ new SourceNode(null, null, null, 'want to kill all humans?')]);
+ node.replaceRight(/kill all humans/, 'watch Futurama');
+ assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?');
+ };
+
+ exports['test .toStringWithSourceMap()'] = forEachNewline(function (assert, nl) {
+ var node = new SourceNode(null, null, null,
+ ['(function () {' + nl,
+ ' ',
+ new SourceNode(1, 0, 'a.js', 'someCall', 'originalCall'),
+ new SourceNode(1, 8, 'a.js', '()'),
+ ';' + nl,
+ ' ', new SourceNode(2, 0, 'b.js', ['if (foo) bar()']), ';' + nl,
+ '}());']);
+ var result = node.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+
+ assert.equal(result.code, [
+ '(function () {',
+ ' someCall();',
+ ' if (foo) bar();',
+ '}());'
+ ].join(nl));
+
+ var map = result.map;
+ var mapWithoutOptions = node.toStringWithSourceMap().map;
+
+ assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+ assert.ok(mapWithoutOptions instanceof SourceMapGenerator, 'mapWithoutOptions instanceof SourceMapGenerator');
+ assert.ok(!('file' in mapWithoutOptions));
+ mapWithoutOptions._file = 'foo.js';
+ util.assertEqualMaps(assert, map.toJSON(), mapWithoutOptions.toJSON());
+
+ map = new SourceMapConsumer(map.toString());
+
+ var actual;
+
+ actual = map.originalPositionFor({
+ line: 1,
+ column: 4
+ });
+ assert.equal(actual.source, null);
+ assert.equal(actual.line, null);
+ assert.equal(actual.column, null);
+
+ actual = map.originalPositionFor({
+ line: 2,
+ column: 2
+ });
+ assert.equal(actual.source, 'a.js');
+ assert.equal(actual.line, 1);
+ assert.equal(actual.column, 0);
+ assert.equal(actual.name, 'originalCall');
+
+ actual = map.originalPositionFor({
+ line: 3,
+ column: 2
+ });
+ assert.equal(actual.source, 'b.js');
+ assert.equal(actual.line, 2);
+ assert.equal(actual.column, 0);
+
+ actual = map.originalPositionFor({
+ line: 3,
+ column: 16
+ });
+ assert.equal(actual.source, null);
+ assert.equal(actual.line, null);
+ assert.equal(actual.column, null);
+
+ actual = map.originalPositionFor({
+ line: 4,
+ column: 2
+ });
+ assert.equal(actual.source, null);
+ assert.equal(actual.line, null);
+ assert.equal(actual.column, null);
+ });
+
+ exports['test .fromStringWithSourceMap()'] = forEachNewline(function (assert, nl) {
+ var testCode = util.testGeneratedCode.replace(/\n/g, nl);
+ var node = SourceNode.fromStringWithSourceMap(
+ testCode,
+ new SourceMapConsumer(util.testMap));
+
+ var result = node.toStringWithSourceMap({
+ file: 'min.js'
+ });
+ var map = result.map;
+ var code = result.code;
+
+ assert.equal(code, testCode);
+ assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+ map = map.toJSON();
+ assert.equal(map.version, util.testMap.version);
+ assert.equal(map.file, util.testMap.file);
+ assert.equal(map.mappings, util.testMap.mappings);
+ });
+
+ exports['test .fromStringWithSourceMap() empty map'] = forEachNewline(function (assert, nl) {
+ var node = SourceNode.fromStringWithSourceMap(
+ util.testGeneratedCode.replace(/\n/g, nl),
+ new SourceMapConsumer(util.emptyMap));
+ var result = node.toStringWithSourceMap({
+ file: 'min.js'
+ });
+ var map = result.map;
+ var code = result.code;
+
+ assert.equal(code, util.testGeneratedCode.replace(/\n/g, nl));
+ assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+ map = map.toJSON();
+ assert.equal(map.version, util.emptyMap.version);
+ assert.equal(map.file, util.emptyMap.file);
+ assert.equal(map.mappings.length, util.emptyMap.mappings.length);
+ assert.equal(map.mappings, util.emptyMap.mappings);
+ });
+
+ exports['test .fromStringWithSourceMap() complex version'] = forEachNewline(function (assert, nl) {
+ var input = new SourceNode(null, null, null, [
+ "(function() {" + nl,
+ " var Test = {};" + nl,
+ " ", new SourceNode(1, 0, "a.js", "Test.A = { value: 1234 };" + nl),
+ " ", new SourceNode(2, 0, "a.js", "Test.A.x = 'xyz';"), nl,
+ "}());" + nl,
+ "/* Generated Source */"]);
+ input = input.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+
+ var node = SourceNode.fromStringWithSourceMap(
+ input.code,
+ new SourceMapConsumer(input.map.toString()));
+
+ var result = node.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+ var map = result.map;
+ var code = result.code;
+
+ assert.equal(code, input.code);
+ assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+ map = map.toJSON();
+ var inputMap = input.map.toJSON();
+ util.assertEqualMaps(assert, map, inputMap);
+ });
+
+ exports['test .fromStringWithSourceMap() third argument'] = function (assert) {
+ // Assume the following directory structure:
+ //
+ // http://foo.org/
+ // bar.coffee
+ // app/
+ // coffee/
+ // foo.coffee
+ // coffeeBundle.js # Made from {foo,bar,baz}.coffee
+ // maps/
+ // coffeeBundle.js.map
+ // js/
+ // foo.js
+ // public/
+ // app.js # Made from {foo,coffeeBundle}.js
+ // app.js.map
+ //
+ // http://www.example.com/
+ // baz.coffee
+
+ var coffeeBundle = new SourceNode(1, 0, 'foo.coffee', 'foo(coffee);\n');
+ coffeeBundle.setSourceContent('foo.coffee', 'foo coffee');
+ coffeeBundle.add(new SourceNode(2, 0, '/bar.coffee', 'bar(coffee);\n'));
+ coffeeBundle.add(new SourceNode(3, 0, 'http://www.example.com/baz.coffee', 'baz(coffee);'));
+ coffeeBundle = coffeeBundle.toStringWithSourceMap({
+ file: 'foo.js',
+ sourceRoot: '..'
+ });
+
+ var foo = new SourceNode(1, 0, 'foo.js', 'foo(js);');
+
+ var test = function(relativePath, expectedSources) {
+ var app = new SourceNode();
+ app.add(SourceNode.fromStringWithSourceMap(
+ coffeeBundle.code,
+ new SourceMapConsumer(coffeeBundle.map.toString()),
+ relativePath));
+ app.add(foo);
+ var i = 0;
+ app.walk(function (chunk, loc) {
+ assert.equal(loc.source, expectedSources[i]);
+ i++;
+ });
+ app.walkSourceContents(function (sourceFile, sourceContent) {
+ assert.equal(sourceFile, expectedSources[0]);
+ assert.equal(sourceContent, 'foo coffee');
+ })
+ };
+
+ test('../coffee/maps', [
+ '../coffee/foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee',
+ 'foo.js'
+ ]);
+
+ // If the third parameter is omitted or set to the current working
+ // directory we get incorrect source paths:
+
+ test(undefined, [
+ '../foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee',
+ 'foo.js'
+ ]);
+
+ test('', [
+ '../foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee',
+ 'foo.js'
+ ]);
+
+ test('.', [
+ '../foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee',
+ 'foo.js'
+ ]);
+
+ test('./', [
+ '../foo.coffee',
+ '/bar.coffee',
+ 'http://www.example.com/baz.coffee',
+ 'foo.js'
+ ]);
+ };
+
+ exports['test .toStringWithSourceMap() merging duplicate mappings'] = forEachNewline(function (assert, nl) {
+ var input = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "(function"),
+ new SourceNode(1, 0, "a.js", "() {" + nl),
+ " ",
+ new SourceNode(1, 0, "a.js", "var Test = "),
+ new SourceNode(1, 0, "b.js", "{};" + nl),
+ new SourceNode(2, 0, "b.js", "Test"),
+ new SourceNode(2, 0, "b.js", ".A", "A"),
+ new SourceNode(2, 20, "b.js", " = { value: ", "A"),
+ "1234",
+ new SourceNode(2, 40, "b.js", " };" + nl, "A"),
+ "}());" + nl,
+ "/* Generated Source */"
+ ]);
+ input = input.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+
+ assert.equal(input.code, [
+ "(function() {",
+ " var Test = {};",
+ "Test.A = { value: 1234 };",
+ "}());",
+ "/* Generated Source */"
+ ].join(nl))
+
+ var correctMap = new SourceMapGenerator({
+ file: 'foo.js'
+ });
+ correctMap.addMapping({
+ generated: { line: 1, column: 0 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ // Here is no need for a empty mapping,
+ // because mappings ends at eol
+ correctMap.addMapping({
+ generated: { line: 2, column: 2 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 2, column: 13 },
+ source: 'b.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 0 },
+ source: 'b.js',
+ original: { line: 2, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 4 },
+ source: 'b.js',
+ name: 'A',
+ original: { line: 2, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 6 },
+ source: 'b.js',
+ name: 'A',
+ original: { line: 2, column: 20 }
+ });
+ // This empty mapping is required,
+ // because there is a hole in the middle of the line
+ correctMap.addMapping({
+ generated: { line: 3, column: 18 }
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 22 },
+ source: 'b.js',
+ name: 'A',
+ original: { line: 2, column: 40 }
+ });
+ // Here is no need for a empty mapping,
+ // because mappings ends at eol
+
+ var inputMap = input.map.toJSON();
+ correctMap = correctMap.toJSON();
+ util.assertEqualMaps(assert, inputMap, correctMap);
+ });
+
+ exports['test .toStringWithSourceMap() multi-line SourceNodes'] = forEachNewline(function (assert, nl) {
+ var input = new SourceNode(null, null, null, [
+ new SourceNode(1, 0, "a.js", "(function() {" + nl + "var nextLine = 1;" + nl + "anotherLine();" + nl),
+ new SourceNode(2, 2, "b.js", "Test.call(this, 123);" + nl),
+ new SourceNode(2, 2, "b.js", "this['stuff'] = 'v';" + nl),
+ new SourceNode(2, 2, "b.js", "anotherLine();" + nl),
+ "/*" + nl + "Generated" + nl + "Source" + nl + "*/" + nl,
+ new SourceNode(3, 4, "c.js", "anotherLine();" + nl),
+ "/*" + nl + "Generated" + nl + "Source" + nl + "*/"
+ ]);
+ input = input.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+
+ assert.equal(input.code, [
+ "(function() {",
+ "var nextLine = 1;",
+ "anotherLine();",
+ "Test.call(this, 123);",
+ "this['stuff'] = 'v';",
+ "anotherLine();",
+ "/*",
+ "Generated",
+ "Source",
+ "*/",
+ "anotherLine();",
+ "/*",
+ "Generated",
+ "Source",
+ "*/"
+ ].join(nl));
+
+ var correctMap = new SourceMapGenerator({
+ file: 'foo.js'
+ });
+ correctMap.addMapping({
+ generated: { line: 1, column: 0 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 2, column: 0 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 0 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 4, column: 0 },
+ source: 'b.js',
+ original: { line: 2, column: 2 }
+ });
+ correctMap.addMapping({
+ generated: { line: 5, column: 0 },
+ source: 'b.js',
+ original: { line: 2, column: 2 }
+ });
+ correctMap.addMapping({
+ generated: { line: 6, column: 0 },
+ source: 'b.js',
+ original: { line: 2, column: 2 }
+ });
+ correctMap.addMapping({
+ generated: { line: 11, column: 0 },
+ source: 'c.js',
+ original: { line: 3, column: 4 }
+ });
+
+ var inputMap = input.map.toJSON();
+ correctMap = correctMap.toJSON();
+ util.assertEqualMaps(assert, inputMap, correctMap);
+ });
+
+ exports['test .toStringWithSourceMap() with empty string'] = function (assert) {
+ var node = new SourceNode(1, 0, 'empty.js', '');
+ var result = node.toStringWithSourceMap();
+ assert.equal(result.code, '');
+ };
+
+ exports['test .toStringWithSourceMap() with consecutive newlines'] = forEachNewline(function (assert, nl) {
+ var input = new SourceNode(null, null, null, [
+ "/***/" + nl + nl,
+ new SourceNode(1, 0, "a.js", "'use strict';" + nl),
+ new SourceNode(2, 0, "a.js", "a();"),
+ ]);
+ input = input.toStringWithSourceMap({
+ file: 'foo.js'
+ });
+
+ assert.equal(input.code, [
+ "/***/",
+ "",
+ "'use strict';",
+ "a();",
+ ].join(nl));
+
+ var correctMap = new SourceMapGenerator({
+ file: 'foo.js'
+ });
+ correctMap.addMapping({
+ generated: { line: 3, column: 0 },
+ source: 'a.js',
+ original: { line: 1, column: 0 }
+ });
+ correctMap.addMapping({
+ generated: { line: 4, column: 0 },
+ source: 'a.js',
+ original: { line: 2, column: 0 }
+ });
+
+ var inputMap = input.map.toJSON();
+ correctMap = correctMap.toJSON();
+ util.assertEqualMaps(assert, inputMap, correctMap);
+ });
+
+ exports['test setSourceContent with toStringWithSourceMap'] = function (assert) {
+ var aNode = new SourceNode(1, 1, 'a.js', 'a');
+ aNode.setSourceContent('a.js', 'someContent');
+ var node = new SourceNode(null, null, null,
+ ['(function () {\n',
+ ' ', aNode,
+ ' ', new SourceNode(1, 1, 'b.js', 'b'),
+ '}());']);
+ node.setSourceContent('b.js', 'otherContent');
+ var map = node.toStringWithSourceMap({
+ file: 'foo.js'
+ }).map;
+
+ assert.ok(map instanceof SourceMapGenerator, 'map instanceof SourceMapGenerator');
+ map = new SourceMapConsumer(map.toString());
+
+ assert.equal(map.sources.length, 2);
+ assert.equal(map.sources[0], 'a.js');
+ assert.equal(map.sources[1], 'b.js');
+ assert.equal(map.sourcesContent.length, 2);
+ assert.equal(map.sourcesContent[0], 'someContent');
+ assert.equal(map.sourcesContent[1], 'otherContent');
+ };
+
+ exports['test walkSourceContents'] = function (assert) {
+ var aNode = new SourceNode(1, 1, 'a.js', 'a');
+ aNode.setSourceContent('a.js', 'someContent');
+ var node = new SourceNode(null, null, null,
+ ['(function () {\n',
+ ' ', aNode,
+ ' ', new SourceNode(1, 1, 'b.js', 'b'),
+ '}());']);
+ node.setSourceContent('b.js', 'otherContent');
+ var results = [];
+ node.walkSourceContents(function (sourceFile, sourceContent) {
+ results.push([sourceFile, sourceContent]);
+ });
+ assert.equal(results.length, 2);
+ assert.equal(results[0][0], 'a.js');
+ assert.equal(results[0][1], 'someContent');
+ assert.equal(results[1][0], 'b.js');
+ assert.equal(results[1][1], 'otherContent');
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ // This is a test mapping which maps functions from two different files
+ // (one.js and two.js) to a minified generated source.
+ //
+ // Here is one.js:
+ //
+ // ONE.foo = function (bar) {
+ // return baz(bar);
+ // };
+ //
+ // Here is two.js:
+ //
+ // TWO.inc = function (n) {
+ // return n + 1;
+ // };
+ //
+ // And here is the generated code (min.js):
+ //
+ // ONE.foo=function(a){return baz(a);};
+ // TWO.inc=function(a){return a+1;};
+ exports.testGeneratedCode = " ONE.foo=function(a){return baz(a);};\n"+
+ " TWO.inc=function(a){return a+1;};";
+ exports.testMap = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapNoSourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapEmptySourceRoot = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourceRoot: '',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ // This mapping is identical to above, but uses the indexed format instead.
+ exports.indexedTestMap = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ }
+ ]
+ };
+ exports.indexedTestMapDifferentSourceRoots = {
+ version: 3,
+ file: 'min.js',
+ sections: [
+ {
+ offset: {
+ line: 0,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "one.js"
+ ],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ],
+ names: [
+ "bar",
+ "baz"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID",
+ file: "min.js",
+ sourceRoot: "/the/root"
+ }
+ },
+ {
+ offset: {
+ line: 1,
+ column: 0
+ },
+ map: {
+ version: 3,
+ sources: [
+ "two.js"
+ ],
+ sourcesContent: [
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ names: [
+ "n"
+ ],
+ mappings: "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA",
+ file: "min.js",
+ sourceRoot: "/different/root"
+ }
+ }
+ ]
+ };
+ exports.testMapWithSourcesContent = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['one.js', 'two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.testMapRelativeSources = {
+ version: 3,
+ file: 'min.js',
+ names: ['bar', 'baz', 'n'],
+ sources: ['./one.js', './two.js'],
+ sourcesContent: [
+ ' ONE.foo = function (bar) {\n' +
+ ' return baz(bar);\n' +
+ ' };',
+ ' TWO.inc = function (n) {\n' +
+ ' return n + 1;\n' +
+ ' };'
+ ],
+ sourceRoot: '/the/root',
+ mappings: 'CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA'
+ };
+ exports.emptyMap = {
+ version: 3,
+ file: 'min.js',
+ names: [],
+ sources: [],
+ mappings: ''
+ };
+
+
+ function assertMapping(generatedLine, generatedColumn, originalSource,
+ originalLine, originalColumn, name, bias, map, assert,
+ dontTestGenerated, dontTestOriginal) {
+ if (!dontTestOriginal) {
+ var origMapping = map.originalPositionFor({
+ line: generatedLine,
+ column: generatedColumn,
+ bias: bias
+ });
+ assert.equal(origMapping.name, name,
+ 'Incorrect name, expected ' + JSON.stringify(name)
+ + ', got ' + JSON.stringify(origMapping.name));
+ assert.equal(origMapping.line, originalLine,
+ 'Incorrect line, expected ' + JSON.stringify(originalLine)
+ + ', got ' + JSON.stringify(origMapping.line));
+ assert.equal(origMapping.column, originalColumn,
+ 'Incorrect column, expected ' + JSON.stringify(originalColumn)
+ + ', got ' + JSON.stringify(origMapping.column));
+
+ var expectedSource;
+
+ if (originalSource && map.sourceRoot && originalSource.indexOf(map.sourceRoot) === 0) {
+ expectedSource = originalSource;
+ } else if (originalSource) {
+ expectedSource = map.sourceRoot
+ ? util.join(map.sourceRoot, originalSource)
+ : originalSource;
+ } else {
+ expectedSource = null;
+ }
+
+ assert.equal(origMapping.source, expectedSource,
+ 'Incorrect source, expected ' + JSON.stringify(expectedSource)
+ + ', got ' + JSON.stringify(origMapping.source));
+ }
+
+ if (!dontTestGenerated) {
+ var genMapping = map.generatedPositionFor({
+ source: originalSource,
+ line: originalLine,
+ column: originalColumn,
+ bias: bias
+ });
+ assert.equal(genMapping.line, generatedLine,
+ 'Incorrect line, expected ' + JSON.stringify(generatedLine)
+ + ', got ' + JSON.stringify(genMapping.line));
+ assert.equal(genMapping.column, generatedColumn,
+ 'Incorrect column, expected ' + JSON.stringify(generatedColumn)
+ + ', got ' + JSON.stringify(genMapping.column));
+ }
+ }
+ exports.assertMapping = assertMapping;
+
+ function assertEqualMaps(assert, actualMap, expectedMap) {
+ assert.equal(actualMap.version, expectedMap.version, "version mismatch");
+ assert.equal(actualMap.file, expectedMap.file, "file mismatch");
+ assert.equal(actualMap.names.length,
+ expectedMap.names.length,
+ "names length mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ for (var i = 0; i < actualMap.names.length; i++) {
+ assert.equal(actualMap.names[i],
+ expectedMap.names[i],
+ "names[" + i + "] mismatch: " +
+ actualMap.names.join(", ") + " != " + expectedMap.names.join(", "));
+ }
+ assert.equal(actualMap.sources.length,
+ expectedMap.sources.length,
+ "sources length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ for (var i = 0; i < actualMap.sources.length; i++) {
+ assert.equal(actualMap.sources[i],
+ expectedMap.sources[i],
+ "sources[" + i + "] length mismatch: " +
+ actualMap.sources.join(", ") + " != " + expectedMap.sources.join(", "));
+ }
+ assert.equal(actualMap.sourceRoot,
+ expectedMap.sourceRoot,
+ "sourceRoot mismatch: " +
+ actualMap.sourceRoot + " != " + expectedMap.sourceRoot);
+ assert.equal(actualMap.mappings, expectedMap.mappings,
+ "mappings mismatch:\nActual: " + actualMap.mappings + "\nExpected: " + expectedMap.mappings);
+ if (actualMap.sourcesContent) {
+ assert.equal(actualMap.sourcesContent.length,
+ expectedMap.sourcesContent.length,
+ "sourcesContent length mismatch");
+ for (var i = 0; i < actualMap.sourcesContent.length; i++) {
+ assert.equal(actualMap.sourcesContent[i],
+ expectedMap.sourcesContent[i],
+ "sourcesContent[" + i + "] mismatch");
+ }
+ }
+ }
+ exports.assertEqualMaps = assertEqualMaps;
+ }
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var base64VLQ = __webpack_require__(4);
+ var util = __webpack_require__(2);
+ var ArraySet = __webpack_require__(6).ArraySet;
+ var MappingList = __webpack_require__(7).MappingList;
+
+ /**
+ * An instance of the SourceMapGenerator represents a source map which is
+ * being built incrementally. You may pass an object with the following
+ * properties:
+ *
+ * - file: The filename of the generated source.
+ * - sourceRoot: A root for all relative URLs in this source map.
+ */
+ function SourceMapGenerator(aArgs) {
+ if (!aArgs) {
+ aArgs = {};
+ }
+ this._file = util.getArg(aArgs, 'file', null);
+ this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null);
+ this._skipValidation = util.getArg(aArgs, 'skipValidation', false);
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+ this._mappings = new MappingList();
+ this._sourcesContents = null;
+ }
+
+ SourceMapGenerator.prototype._version = 3;
+
+ /**
+ * Creates a new SourceMapGenerator based on a SourceMapConsumer
+ *
+ * @param aSourceMapConsumer The SourceMap.
+ */
+ SourceMapGenerator.fromSourceMap =
+ function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) {
+ var sourceRoot = aSourceMapConsumer.sourceRoot;
+ var generator = new SourceMapGenerator({
+ file: aSourceMapConsumer.file,
+ sourceRoot: sourceRoot
+ });
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ var newMapping = {
+ generated: {
+ line: mapping.generatedLine,
+ column: mapping.generatedColumn
+ }
+ };
+
+ if (mapping.source != null) {
+ newMapping.source = mapping.source;
+ if (sourceRoot != null) {
+ newMapping.source = util.relative(sourceRoot, newMapping.source);
+ }
+
+ newMapping.original = {
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ };
+
+ if (mapping.name != null) {
+ newMapping.name = mapping.name;
+ }
+ }
+
+ generator.addMapping(newMapping);
+ });
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ generator.setSourceContent(sourceFile, content);
+ }
+ });
+ return generator;
+ };
+
+ /**
+ * Add a single mapping from original source line and column to the generated
+ * source's line and column for this source map being created. The mapping
+ * object should have the following properties:
+ *
+ * - generated: An object with the generated line and column positions.
+ * - original: An object with the original line and column positions.
+ * - source: The original source file (relative to the sourceRoot).
+ * - name: An optional original token name for this mapping.
+ */
+ SourceMapGenerator.prototype.addMapping =
+ function SourceMapGenerator_addMapping(aArgs) {
+ var generated = util.getArg(aArgs, 'generated');
+ var original = util.getArg(aArgs, 'original', null);
+ var source = util.getArg(aArgs, 'source', null);
+ var name = util.getArg(aArgs, 'name', null);
+
+ if (!this._skipValidation) {
+ this._validateMapping(generated, original, source, name);
+ }
+
+ if (source != null && !this._sources.has(source)) {
+ this._sources.add(source);
+ }
+
+ if (name != null && !this._names.has(name)) {
+ this._names.add(name);
+ }
+
+ this._mappings.add({
+ generatedLine: generated.line,
+ generatedColumn: generated.column,
+ originalLine: original != null && original.line,
+ originalColumn: original != null && original.column,
+ source: source,
+ name: name
+ });
+ };
+
+ /**
+ * Set the source content for a source file.
+ */
+ SourceMapGenerator.prototype.setSourceContent =
+ function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) {
+ var source = aSourceFile;
+ if (this._sourceRoot != null) {
+ source = util.relative(this._sourceRoot, source);
+ }
+
+ if (aSourceContent != null) {
+ // Add the source content to the _sourcesContents map.
+ // Create a new _sourcesContents map if the property is null.
+ if (!this._sourcesContents) {
+ this._sourcesContents = {};
+ }
+ this._sourcesContents[util.toSetString(source)] = aSourceContent;
+ } else if (this._sourcesContents) {
+ // Remove the source file from the _sourcesContents map.
+ // If the _sourcesContents map is empty, set the property to null.
+ delete this._sourcesContents[util.toSetString(source)];
+ if (Object.keys(this._sourcesContents).length === 0) {
+ this._sourcesContents = null;
+ }
+ }
+ };
+
+ /**
+ * Applies the mappings of a sub-source-map for a specific source file to the
+ * source map being generated. Each mapping to the supplied source file is
+ * rewritten using the supplied source map. Note: The resolution for the
+ * resulting mappings is the minimium of this map and the supplied map.
+ *
+ * @param aSourceMapConsumer The source map to be applied.
+ * @param aSourceFile Optional. The filename of the source file.
+ * If omitted, SourceMapConsumer's file property will be used.
+ * @param aSourceMapPath Optional. The dirname of the path to the source map
+ * to be applied. If relative, it is relative to the SourceMapConsumer.
+ * This parameter is needed when the two source maps aren't in the same
+ * directory, and the source map to be applied contains relative source
+ * paths. If so, those relative source paths need to be rewritten
+ * relative to the SourceMapGenerator.
+ */
+ SourceMapGenerator.prototype.applySourceMap =
+ function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) {
+ var sourceFile = aSourceFile;
+ // If aSourceFile is omitted, we will use the file property of the SourceMap
+ if (aSourceFile == null) {
+ if (aSourceMapConsumer.file == null) {
+ throw new Error(
+ 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' +
+ 'or the source map\'s "file" property. Both were omitted.'
+ );
+ }
+ sourceFile = aSourceMapConsumer.file;
+ }
+ var sourceRoot = this._sourceRoot;
+ // Make "sourceFile" relative if an absolute Url is passed.
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ // Applying the SourceMap can add and remove items from the sources and
+ // the names array.
+ var newSources = new ArraySet();
+ var newNames = new ArraySet();
+
+ // Find mappings for the "sourceFile"
+ this._mappings.unsortedForEach(function (mapping) {
+ if (mapping.source === sourceFile && mapping.originalLine != null) {
+ // Check if it can be mapped by the source map, then update the mapping.
+ var original = aSourceMapConsumer.originalPositionFor({
+ line: mapping.originalLine,
+ column: mapping.originalColumn
+ });
+ if (original.source != null) {
+ // Copy mapping
+ mapping.source = original.source;
+ if (aSourceMapPath != null) {
+ mapping.source = util.join(aSourceMapPath, mapping.source)
+ }
+ if (sourceRoot != null) {
+ mapping.source = util.relative(sourceRoot, mapping.source);
+ }
+ mapping.originalLine = original.line;
+ mapping.originalColumn = original.column;
+ if (original.name != null) {
+ mapping.name = original.name;
+ }
+ }
+ }
+
+ var source = mapping.source;
+ if (source != null && !newSources.has(source)) {
+ newSources.add(source);
+ }
+
+ var name = mapping.name;
+ if (name != null && !newNames.has(name)) {
+ newNames.add(name);
+ }
+
+ }, this);
+ this._sources = newSources;
+ this._names = newNames;
+
+ // Copy sourcesContents of applied map.
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aSourceMapPath != null) {
+ sourceFile = util.join(aSourceMapPath, sourceFile);
+ }
+ if (sourceRoot != null) {
+ sourceFile = util.relative(sourceRoot, sourceFile);
+ }
+ this.setSourceContent(sourceFile, content);
+ }
+ }, this);
+ };
+
+ /**
+ * A mapping can have one of the three levels of data:
+ *
+ * 1. Just the generated position.
+ * 2. The Generated position, original position, and original source.
+ * 3. Generated and original position, original source, as well as a name
+ * token.
+ *
+ * To maintain consistency, we validate that any new mapping being added falls
+ * in to one of these categories.
+ */
+ SourceMapGenerator.prototype._validateMapping =
+ function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource,
+ aName) {
+ if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && !aOriginal && !aSource && !aName) {
+ // Case 1.
+ return;
+ }
+ else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated
+ && aOriginal && 'line' in aOriginal && 'column' in aOriginal
+ && aGenerated.line > 0 && aGenerated.column >= 0
+ && aOriginal.line > 0 && aOriginal.column >= 0
+ && aSource) {
+ // Cases 2 and 3.
+ return;
+ }
+ else {
+ throw new Error('Invalid mapping: ' + JSON.stringify({
+ generated: aGenerated,
+ source: aSource,
+ original: aOriginal,
+ name: aName
+ }));
+ }
+ };
+
+ /**
+ * Serialize the accumulated mappings in to the stream of base 64 VLQs
+ * specified by the source map format.
+ */
+ SourceMapGenerator.prototype._serializeMappings =
+ function SourceMapGenerator_serializeMappings() {
+ var previousGeneratedColumn = 0;
+ var previousGeneratedLine = 1;
+ var previousOriginalColumn = 0;
+ var previousOriginalLine = 0;
+ var previousName = 0;
+ var previousSource = 0;
+ var result = '';
+ var mapping;
+ var nameIdx;
+ var sourceIdx;
+
+ var mappings = this._mappings.toArray();
+ for (var i = 0, len = mappings.length; i < len; i++) {
+ mapping = mappings[i];
+
+ if (mapping.generatedLine !== previousGeneratedLine) {
+ previousGeneratedColumn = 0;
+ while (mapping.generatedLine !== previousGeneratedLine) {
+ result += ';';
+ previousGeneratedLine++;
+ }
+ }
+ else {
+ if (i > 0) {
+ if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) {
+ continue;
+ }
+ result += ',';
+ }
+ }
+
+ result += base64VLQ.encode(mapping.generatedColumn
+ - previousGeneratedColumn);
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (mapping.source != null) {
+ sourceIdx = this._sources.indexOf(mapping.source);
+ result += base64VLQ.encode(sourceIdx - previousSource);
+ previousSource = sourceIdx;
+
+ // lines are stored 0-based in SourceMap spec version 3
+ result += base64VLQ.encode(mapping.originalLine - 1
+ - previousOriginalLine);
+ previousOriginalLine = mapping.originalLine - 1;
+
+ result += base64VLQ.encode(mapping.originalColumn
+ - previousOriginalColumn);
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (mapping.name != null) {
+ nameIdx = this._names.indexOf(mapping.name);
+ result += base64VLQ.encode(nameIdx - previousName);
+ previousName = nameIdx;
+ }
+ }
+ }
+
+ return result;
+ };
+
+ SourceMapGenerator.prototype._generateSourcesContent =
+ function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) {
+ return aSources.map(function (source) {
+ if (!this._sourcesContents) {
+ return null;
+ }
+ if (aSourceRoot != null) {
+ source = util.relative(aSourceRoot, source);
+ }
+ var key = util.toSetString(source);
+ return Object.prototype.hasOwnProperty.call(this._sourcesContents,
+ key)
+ ? this._sourcesContents[key]
+ : null;
+ }, this);
+ };
+
+ /**
+ * Externalize the source map.
+ */
+ SourceMapGenerator.prototype.toJSON =
+ function SourceMapGenerator_toJSON() {
+ var map = {
+ version: this._version,
+ sources: this._sources.toArray(),
+ names: this._names.toArray(),
+ mappings: this._serializeMappings()
+ };
+ if (this._file != null) {
+ map.file = this._file;
+ }
+ if (this._sourceRoot != null) {
+ map.sourceRoot = this._sourceRoot;
+ }
+ if (this._sourcesContents) {
+ map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot);
+ }
+
+ return map;
+ };
+
+ /**
+ * Render the source map being generated to a string.
+ */
+ SourceMapGenerator.prototype.toString =
+ function SourceMapGenerator_toString() {
+ return JSON.stringify(this.toJSON());
+ };
+
+ exports.SourceMapGenerator = SourceMapGenerator;
+ }
+
+
+/***/ },
+/* 4 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ *
+ * Based on the Base 64 VLQ implementation in Closure Compiler:
+ * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java
+ *
+ * Copyright 2011 The Closure Compiler Authors. All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ * * Neither the name of Google Inc. nor the names of its
+ * contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+ {
+ var base64 = __webpack_require__(5);
+
+ // A single base 64 digit can contain 6 bits of data. For the base 64 variable
+ // length quantities we use in the source map spec, the first bit is the sign,
+ // the next four bits are the actual value, and the 6th bit is the
+ // continuation bit. The continuation bit tells us whether there are more
+ // digits in this value following this digit.
+ //
+ // Continuation
+ // | Sign
+ // | |
+ // V V
+ // 101011
+
+ var VLQ_BASE_SHIFT = 5;
+
+ // binary: 100000
+ var VLQ_BASE = 1 << VLQ_BASE_SHIFT;
+
+ // binary: 011111
+ var VLQ_BASE_MASK = VLQ_BASE - 1;
+
+ // binary: 100000
+ var VLQ_CONTINUATION_BIT = VLQ_BASE;
+
+ /**
+ * Converts from a two-complement value to a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary)
+ * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary)
+ */
+ function toVLQSigned(aValue) {
+ return aValue < 0
+ ? ((-aValue) << 1) + 1
+ : (aValue << 1) + 0;
+ }
+
+ /**
+ * Converts to a two-complement value from a value where the sign bit is
+ * placed in the least significant bit. For example, as decimals:
+ * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1
+ * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2
+ */
+ function fromVLQSigned(aValue) {
+ var isNegative = (aValue & 1) === 1;
+ var shifted = aValue >> 1;
+ return isNegative
+ ? -shifted
+ : shifted;
+ }
+
+ /**
+ * Returns the base 64 VLQ encoded value.
+ */
+ exports.encode = function base64VLQ_encode(aValue) {
+ var encoded = "";
+ var digit;
+
+ var vlq = toVLQSigned(aValue);
+
+ do {
+ digit = vlq & VLQ_BASE_MASK;
+ vlq >>>= VLQ_BASE_SHIFT;
+ if (vlq > 0) {
+ // There are still more digits in this value, so we must make sure the
+ // continuation bit is marked.
+ digit |= VLQ_CONTINUATION_BIT;
+ }
+ encoded += base64.encode(digit);
+ } while (vlq > 0);
+
+ return encoded;
+ };
+
+ /**
+ * Decodes the next base 64 VLQ value from the given string and returns the
+ * value and the rest of the string via the out parameter.
+ */
+ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) {
+ var strLen = aStr.length;
+ var result = 0;
+ var shift = 0;
+ var continuation, digit;
+
+ do {
+ if (aIndex >= strLen) {
+ throw new Error("Expected more digits in base 64 VLQ value.");
+ }
+
+ digit = base64.decode(aStr.charCodeAt(aIndex++));
+ if (digit === -1) {
+ throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1));
+ }
+
+ continuation = !!(digit & VLQ_CONTINUATION_BIT);
+ digit &= VLQ_BASE_MASK;
+ result = result + (digit << shift);
+ shift += VLQ_BASE_SHIFT;
+ } while (continuation);
+
+ aOutParam.value = fromVLQSigned(result);
+ aOutParam.rest = aIndex;
+ };
+ }
+
+
+/***/ },
+/* 5 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split('');
+
+ /**
+ * Encode an integer in the range of 0 to 63 to a single base 64 digit.
+ */
+ exports.encode = function (number) {
+ if (0 <= number && number < intToCharMap.length) {
+ return intToCharMap[number];
+ }
+ throw new TypeError("Must be between 0 and 63: " + number);
+ };
+
+ /**
+ * Decode a single base 64 character code digit to an integer. Returns -1 on
+ * failure.
+ */
+ exports.decode = function (charCode) {
+ var bigA = 65; // 'A'
+ var bigZ = 90; // 'Z'
+
+ var littleA = 97; // 'a'
+ var littleZ = 122; // 'z'
+
+ var zero = 48; // '0'
+ var nine = 57; // '9'
+
+ var plus = 43; // '+'
+ var slash = 47; // '/'
+
+ var littleOffset = 26;
+ var numberOffset = 52;
+
+ // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ
+ if (bigA <= charCode && charCode <= bigZ) {
+ return (charCode - bigA);
+ }
+
+ // 26 - 51: abcdefghijklmnopqrstuvwxyz
+ if (littleA <= charCode && charCode <= littleZ) {
+ return (charCode - littleA + littleOffset);
+ }
+
+ // 52 - 61: 0123456789
+ if (zero <= charCode && charCode <= nine) {
+ return (charCode - zero + numberOffset);
+ }
+
+ // 62: +
+ if (charCode == plus) {
+ return 62;
+ }
+
+ // 63: /
+ if (charCode == slash) {
+ return 63;
+ }
+
+ // Invalid base64 digit.
+ return -1;
+ };
+ }
+
+
+/***/ },
+/* 6 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * A data structure which is a combination of an array and a set. Adding a new
+ * member is O(1), testing for membership is O(1), and finding the index of an
+ * element is O(1). Removing elements from the set is not supported. Only
+ * strings are supported for membership.
+ */
+ function ArraySet() {
+ this._array = [];
+ this._set = {};
+ }
+
+ /**
+ * Static method for creating ArraySet instances from an existing array.
+ */
+ ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) {
+ var set = new ArraySet();
+ for (var i = 0, len = aArray.length; i < len; i++) {
+ set.add(aArray[i], aAllowDuplicates);
+ }
+ return set;
+ };
+
+ /**
+ * Return how many unique items are in this ArraySet. If duplicates have been
+ * added, than those do not count towards the size.
+ *
+ * @returns Number
+ */
+ ArraySet.prototype.size = function ArraySet_size() {
+ return Object.getOwnPropertyNames(this._set).length;
+ };
+
+ /**
+ * Add the given string to this set.
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) {
+ var sStr = util.toSetString(aStr);
+ var isDuplicate = this._set.hasOwnProperty(sStr);
+ var idx = this._array.length;
+ if (!isDuplicate || aAllowDuplicates) {
+ this._array.push(aStr);
+ }
+ if (!isDuplicate) {
+ this._set[sStr] = idx;
+ }
+ };
+
+ /**
+ * Is the given string a member of this set?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.has = function ArraySet_has(aStr) {
+ var sStr = util.toSetString(aStr);
+ return this._set.hasOwnProperty(sStr);
+ };
+
+ /**
+ * What is the index of the given string in the array?
+ *
+ * @param String aStr
+ */
+ ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) {
+ var sStr = util.toSetString(aStr);
+ if (this._set.hasOwnProperty(sStr)) {
+ return this._set[sStr];
+ }
+ throw new Error('"' + aStr + '" is not in the set.');
+ };
+
+ /**
+ * What is the element at the given index?
+ *
+ * @param Number aIdx
+ */
+ ArraySet.prototype.at = function ArraySet_at(aIdx) {
+ if (aIdx >= 0 && aIdx < this._array.length) {
+ return this._array[aIdx];
+ }
+ throw new Error('No element indexed by ' + aIdx);
+ };
+
+ /**
+ * Returns the array representation of this set (which has the proper indices
+ * indicated by indexOf). Note that this is a copy of the internal array used
+ * for storing the members so that no one can mess with internal state.
+ */
+ ArraySet.prototype.toArray = function ArraySet_toArray() {
+ return this._array.slice();
+ };
+
+ exports.ArraySet = ArraySet;
+ }
+
+
+/***/ },
+/* 7 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+
+ /**
+ * Determine whether mappingB is after mappingA with respect to generated
+ * position.
+ */
+ function generatedPositionAfter(mappingA, mappingB) {
+ // Optimized for most common case
+ var lineA = mappingA.generatedLine;
+ var lineB = mappingB.generatedLine;
+ var columnA = mappingA.generatedColumn;
+ var columnB = mappingB.generatedColumn;
+ return lineB > lineA || lineB == lineA && columnB >= columnA ||
+ util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0;
+ }
+
+ /**
+ * A data structure to provide a sorted view of accumulated mappings in a
+ * performance conscious manner. It trades a neglibable overhead in general
+ * case for a large speedup in case of mappings being added in order.
+ */
+ function MappingList() {
+ this._array = [];
+ this._sorted = true;
+ // Serves as infimum
+ this._last = {generatedLine: -1, generatedColumn: 0};
+ }
+
+ /**
+ * Iterate through internal items. This method takes the same arguments that
+ * `Array.prototype.forEach` takes.
+ *
+ * NOTE: The order of the mappings is NOT guaranteed.
+ */
+ MappingList.prototype.unsortedForEach =
+ function MappingList_forEach(aCallback, aThisArg) {
+ this._array.forEach(aCallback, aThisArg);
+ };
+
+ /**
+ * Add the given source mapping.
+ *
+ * @param Object aMapping
+ */
+ MappingList.prototype.add = function MappingList_add(aMapping) {
+ if (generatedPositionAfter(this._last, aMapping)) {
+ this._last = aMapping;
+ this._array.push(aMapping);
+ } else {
+ this._sorted = false;
+ this._array.push(aMapping);
+ }
+ };
+
+ /**
+ * Returns the flat, sorted array of mappings. The mappings are sorted by
+ * generated position.
+ *
+ * WARNING: This method returns internal data without copying, for
+ * performance. The return value must NOT be mutated, and should be treated as
+ * an immutable borrow. If you want to take ownership, you must make your own
+ * copy.
+ */
+ MappingList.prototype.toArray = function MappingList_toArray() {
+ if (!this._sorted) {
+ this._array.sort(util.compareByGeneratedPositionsInflated);
+ this._sorted = true;
+ }
+ return this._array;
+ };
+
+ exports.MappingList = MappingList;
+ }
+
+
+/***/ },
+/* 8 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var util = __webpack_require__(2);
+ var binarySearch = __webpack_require__(9);
+ var ArraySet = __webpack_require__(6).ArraySet;
+ var base64VLQ = __webpack_require__(4);
+ var quickSort = __webpack_require__(10).quickSort;
+
+ function SourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ return sourceMap.sections != null
+ ? new IndexedSourceMapConsumer(sourceMap)
+ : new BasicSourceMapConsumer(sourceMap);
+ }
+
+ SourceMapConsumer.fromSourceMap = function(aSourceMap) {
+ return BasicSourceMapConsumer.fromSourceMap(aSourceMap);
+ }
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ SourceMapConsumer.prototype._version = 3;
+
+ // `__generatedMappings` and `__originalMappings` are arrays that hold the
+ // parsed mapping coordinates from the source map's "mappings" attribute. They
+ // are lazily instantiated, accessed via the `_generatedMappings` and
+ // `_originalMappings` getters respectively, and we only parse the mappings
+ // and create these arrays once queried for a source location. We jump through
+ // these hoops because there can be many thousands of mappings, and parsing
+ // them is expensive, so we only want to do it if we must.
+ //
+ // Each object in the arrays is of the form:
+ //
+ // {
+ // generatedLine: The line number in the generated code,
+ // generatedColumn: The column number in the generated code,
+ // source: The path to the original source file that generated this
+ // chunk of code,
+ // originalLine: The line number in the original source that
+ // corresponds to this chunk of generated code,
+ // originalColumn: The column number in the original source that
+ // corresponds to this chunk of generated code,
+ // name: The name of the original symbol which generated this chunk of
+ // code.
+ // }
+ //
+ // All properties except for `generatedLine` and `generatedColumn` can be
+ // `null`.
+ //
+ // `_generatedMappings` is ordered by the generated positions.
+ //
+ // `_originalMappings` is ordered by the original positions.
+
+ SourceMapConsumer.prototype.__generatedMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', {
+ get: function () {
+ if (!this.__generatedMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__generatedMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype.__originalMappings = null;
+ Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', {
+ get: function () {
+ if (!this.__originalMappings) {
+ this._parseMappings(this._mappings, this.sourceRoot);
+ }
+
+ return this.__originalMappings;
+ }
+ });
+
+ SourceMapConsumer.prototype._charIsMappingSeparator =
+ function SourceMapConsumer_charIsMappingSeparator(aStr, index) {
+ var c = aStr.charAt(index);
+ return c === ";" || c === ",";
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ SourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ throw new Error("Subclasses must implement _parseMappings");
+ };
+
+ SourceMapConsumer.GENERATED_ORDER = 1;
+ SourceMapConsumer.ORIGINAL_ORDER = 2;
+
+ SourceMapConsumer.GREATEST_LOWER_BOUND = 1;
+ SourceMapConsumer.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Iterate over each mapping between an original source/line/column and a
+ * generated line/column in this source map.
+ *
+ * @param Function aCallback
+ * The function that is called with each mapping.
+ * @param Object aContext
+ * Optional. If specified, this object will be the value of `this` every
+ * time that `aCallback` is called.
+ * @param aOrder
+ * Either `SourceMapConsumer.GENERATED_ORDER` or
+ * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to
+ * iterate over the mappings sorted by the generated file's line/column
+ * order or the original's source/line/column order, respectively. Defaults to
+ * `SourceMapConsumer.GENERATED_ORDER`.
+ */
+ SourceMapConsumer.prototype.eachMapping =
+ function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) {
+ var context = aContext || null;
+ var order = aOrder || SourceMapConsumer.GENERATED_ORDER;
+
+ var mappings;
+ switch (order) {
+ case SourceMapConsumer.GENERATED_ORDER:
+ mappings = this._generatedMappings;
+ break;
+ case SourceMapConsumer.ORIGINAL_ORDER:
+ mappings = this._originalMappings;
+ break;
+ default:
+ throw new Error("Unknown order of iteration.");
+ }
+
+ var sourceRoot = this.sourceRoot;
+ mappings.map(function (mapping) {
+ var source = mapping.source === null ? null : this._sources.at(mapping.source);
+ if (source != null && sourceRoot != null) {
+ source = util.join(sourceRoot, source);
+ }
+ return {
+ source: source,
+ generatedLine: mapping.generatedLine,
+ generatedColumn: mapping.generatedColumn,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: mapping.name === null ? null : this._names.at(mapping.name)
+ };
+ }, this).forEach(aCallback, context);
+ };
+
+ /**
+ * Returns all generated line and column information for the original source,
+ * line, and column provided. If no column is provided, returns all mappings
+ * corresponding to a either the line we are searching for or the next
+ * closest line that has any mappings. Otherwise, returns all mappings
+ * corresponding to the given line and either the column we are searching for
+ * or the next closest column that has any offsets.
+ *
+ * The only argument is an object with the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: Optional. the column number in the original source.
+ *
+ * and an array of objects is returned, each with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ SourceMapConsumer.prototype.allGeneratedPositionsFor =
+ function SourceMapConsumer_allGeneratedPositionsFor(aArgs) {
+ var line = util.getArg(aArgs, 'line');
+
+ // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping
+ // returns the index of the closest mapping less than the needle. By
+ // setting needle.originalColumn to 0, we thus find the last mapping for
+ // the given line, provided such a mapping exists.
+ var needle = {
+ source: util.getArg(aArgs, 'source'),
+ originalLine: line,
+ originalColumn: util.getArg(aArgs, 'column', 0)
+ };
+
+ if (this.sourceRoot != null) {
+ needle.source = util.relative(this.sourceRoot, needle.source);
+ }
+ if (!this._sources.has(needle.source)) {
+ return [];
+ }
+ needle.source = this._sources.indexOf(needle.source);
+
+ var mappings = [];
+
+ var index = this._findMapping(needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ binarySearch.LEAST_UPPER_BOUND);
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (aArgs.column === undefined) {
+ var originalLine = mapping.originalLine;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we found. Since
+ // mappings are sorted, this is guaranteed to find all mappings for
+ // the line we found.
+ while (mapping && mapping.originalLine === originalLine) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ } else {
+ var originalColumn = mapping.originalColumn;
+
+ // Iterate until either we run out of mappings, or we run into
+ // a mapping for a different line than the one we were searching for.
+ // Since mappings are sorted, this is guaranteed to find all mappings for
+ // the line we are searching for.
+ while (mapping &&
+ mapping.originalLine === line &&
+ mapping.originalColumn == originalColumn) {
+ mappings.push({
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ });
+
+ mapping = this._originalMappings[++index];
+ }
+ }
+ }
+
+ return mappings;
+ };
+
+ exports.SourceMapConsumer = SourceMapConsumer;
+
+ /**
+ * A BasicSourceMapConsumer instance represents a parsed source map which we can
+ * query for information about the original file positions by giving it a file
+ * position in the generated source.
+ *
+ * The only parameter is the raw source map (either as a JSON string, or
+ * already parsed to an object). According to the spec, source maps have the
+ * following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - sources: An array of URLs to the original source files.
+ * - names: An array of identifiers which can be referrenced by individual mappings.
+ * - sourceRoot: Optional. The URL root from which all sources are relative.
+ * - sourcesContent: Optional. An array of contents of the original source files.
+ * - mappings: A string of base64 VLQs which contain the actual mappings.
+ * - file: Optional. The generated file this source map is associated with.
+ *
+ * Here is an example source map, taken from the source map spec[0]:
+ *
+ * {
+ * version : 3,
+ * file: "out.js",
+ * sourceRoot : "",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AA,AB;;ABCDE;"
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1#
+ */
+ function BasicSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sources = util.getArg(sourceMap, 'sources');
+ // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which
+ // requires the array) to play nice here.
+ var names = util.getArg(sourceMap, 'names', []);
+ var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null);
+ var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null);
+ var mappings = util.getArg(sourceMap, 'mappings');
+ var file = util.getArg(sourceMap, 'file', null);
+
+ // Once again, Sass deviates from the spec and supplies the version as a
+ // string rather than a number, so we use loose equality checking here.
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ sources = sources
+ // Some source maps produce relative source paths like "./foo.js" instead of
+ // "foo.js". Normalize these first so that future comparisons will succeed.
+ // See bugzil.la/1090768.
+ .map(util.normalize)
+ // Always ensure that absolute sources are internally stored relative to
+ // the source root, if the source root is absolute. Not doing this would
+ // be particularly problematic when the source root is a prefix of the
+ // source (valid, but why??). See github issue #199 and bugzil.la/1188982.
+ .map(function (source) {
+ return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source)
+ ? util.relative(sourceRoot, source)
+ : source;
+ });
+
+ // Pass `true` below to allow duplicate names and sources. While source maps
+ // are intended to be compressed and deduplicated, the TypeScript compiler
+ // sometimes generates source maps with duplicates in them. See Github issue
+ // #72 and bugzil.la/889492.
+ this._names = ArraySet.fromArray(names, true);
+ this._sources = ArraySet.fromArray(sources, true);
+
+ this.sourceRoot = sourceRoot;
+ this.sourcesContent = sourcesContent;
+ this._mappings = mappings;
+ this.file = file;
+ }
+
+ BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer;
+
+ /**
+ * Create a BasicSourceMapConsumer from a SourceMapGenerator.
+ *
+ * @param SourceMapGenerator aSourceMap
+ * The source map that will be consumed.
+ * @returns BasicSourceMapConsumer
+ */
+ BasicSourceMapConsumer.fromSourceMap =
+ function SourceMapConsumer_fromSourceMap(aSourceMap) {
+ var smc = Object.create(BasicSourceMapConsumer.prototype);
+
+ var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true);
+ var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true);
+ smc.sourceRoot = aSourceMap._sourceRoot;
+ smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(),
+ smc.sourceRoot);
+ smc.file = aSourceMap._file;
+
+ // Because we are modifying the entries (by converting string sources and
+ // names to indices into the sources and names ArraySets), we have to make
+ // a copy of the entry or else bad things happen. Shared mutable state
+ // strikes again! See github issue #191.
+
+ var generatedMappings = aSourceMap._mappings.toArray().slice();
+ var destGeneratedMappings = smc.__generatedMappings = [];
+ var destOriginalMappings = smc.__originalMappings = [];
+
+ for (var i = 0, length = generatedMappings.length; i < length; i++) {
+ var srcMapping = generatedMappings[i];
+ var destMapping = new Mapping;
+ destMapping.generatedLine = srcMapping.generatedLine;
+ destMapping.generatedColumn = srcMapping.generatedColumn;
+
+ if (srcMapping.source) {
+ destMapping.source = sources.indexOf(srcMapping.source);
+ destMapping.originalLine = srcMapping.originalLine;
+ destMapping.originalColumn = srcMapping.originalColumn;
+
+ if (srcMapping.name) {
+ destMapping.name = names.indexOf(srcMapping.name);
+ }
+
+ destOriginalMappings.push(destMapping);
+ }
+
+ destGeneratedMappings.push(destMapping);
+ }
+
+ quickSort(smc.__originalMappings, util.compareByOriginalPositions);
+
+ return smc;
+ };
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ BasicSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ return this._sources.toArray().map(function (s) {
+ return this.sourceRoot != null ? util.join(this.sourceRoot, s) : s;
+ }, this);
+ }
+ });
+
+ /**
+ * Provide the JIT with a nice shape / hidden class.
+ */
+ function Mapping() {
+ this.generatedLine = 0;
+ this.generatedColumn = 0;
+ this.source = null;
+ this.originalLine = null;
+ this.originalColumn = null;
+ this.name = null;
+ }
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ BasicSourceMapConsumer.prototype._parseMappings =
+ function SourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ var generatedLine = 1;
+ var previousGeneratedColumn = 0;
+ var previousOriginalLine = 0;
+ var previousOriginalColumn = 0;
+ var previousSource = 0;
+ var previousName = 0;
+ var length = aStr.length;
+ var index = 0;
+ var cachedSegments = {};
+ var temp = {};
+ var originalMappings = [];
+ var generatedMappings = [];
+ var mapping, str, segment, end, value;
+
+ while (index < length) {
+ if (aStr.charAt(index) === ';') {
+ generatedLine++;
+ index++;
+ previousGeneratedColumn = 0;
+ }
+ else if (aStr.charAt(index) === ',') {
+ index++;
+ }
+ else {
+ mapping = new Mapping();
+ mapping.generatedLine = generatedLine;
+
+ // Because each offset is encoded relative to the previous one,
+ // many segments often have the same encoding. We can exploit this
+ // fact by caching the parsed variable length fields of each segment,
+ // allowing us to avoid a second parse if we encounter the same
+ // segment again.
+ for (end = index; end < length; end++) {
+ if (this._charIsMappingSeparator(aStr, end)) {
+ break;
+ }
+ }
+ str = aStr.slice(index, end);
+
+ segment = cachedSegments[str];
+ if (segment) {
+ index += str.length;
+ } else {
+ segment = [];
+ while (index < end) {
+ base64VLQ.decode(aStr, index, temp);
+ value = temp.value;
+ index = temp.rest;
+ segment.push(value);
+ }
+
+ if (segment.length === 2) {
+ throw new Error('Found a source, but no line and column');
+ }
+
+ if (segment.length === 3) {
+ throw new Error('Found a source and line, but no column');
+ }
+
+ cachedSegments[str] = segment;
+ }
+
+ // Generated column.
+ mapping.generatedColumn = previousGeneratedColumn + segment[0];
+ previousGeneratedColumn = mapping.generatedColumn;
+
+ if (segment.length > 1) {
+ // Original source.
+ mapping.source = previousSource + segment[1];
+ previousSource += segment[1];
+
+ // Original line.
+ mapping.originalLine = previousOriginalLine + segment[2];
+ previousOriginalLine = mapping.originalLine;
+ // Lines are stored 0-based
+ mapping.originalLine += 1;
+
+ // Original column.
+ mapping.originalColumn = previousOriginalColumn + segment[3];
+ previousOriginalColumn = mapping.originalColumn;
+
+ if (segment.length > 4) {
+ // Original name.
+ mapping.name = previousName + segment[4];
+ previousName += segment[4];
+ }
+ }
+
+ generatedMappings.push(mapping);
+ if (typeof mapping.originalLine === 'number') {
+ originalMappings.push(mapping);
+ }
+ }
+ }
+
+ quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated);
+ this.__generatedMappings = generatedMappings;
+
+ quickSort(originalMappings, util.compareByOriginalPositions);
+ this.__originalMappings = originalMappings;
+ };
+
+ /**
+ * Find the mapping that best matches the hypothetical "needle" mapping that
+ * we are searching for in the given "haystack" of mappings.
+ */
+ BasicSourceMapConsumer.prototype._findMapping =
+ function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName,
+ aColumnName, aComparator, aBias) {
+ // To return the position we are searching for, we must first find the
+ // mapping for the given position and then return the opposite position it
+ // points to. Because the mappings are sorted, we can use binary search to
+ // find the best mapping.
+
+ if (aNeedle[aLineName] <= 0) {
+ throw new TypeError('Line must be greater than or equal to 1, got '
+ + aNeedle[aLineName]);
+ }
+ if (aNeedle[aColumnName] < 0) {
+ throw new TypeError('Column must be greater than or equal to 0, got '
+ + aNeedle[aColumnName]);
+ }
+
+ return binarySearch.search(aNeedle, aMappings, aComparator, aBias);
+ };
+
+ /**
+ * Compute the last column for each generated mapping. The last column is
+ * inclusive.
+ */
+ BasicSourceMapConsumer.prototype.computeColumnSpans =
+ function SourceMapConsumer_computeColumnSpans() {
+ for (var index = 0; index < this._generatedMappings.length; ++index) {
+ var mapping = this._generatedMappings[index];
+
+ // Mappings do not contain a field for the last generated columnt. We
+ // can come up with an optimistic estimate, however, by assuming that
+ // mappings are contiguous (i.e. given two consecutive mappings, the
+ // first mapping ends where the second one starts).
+ if (index + 1 < this._generatedMappings.length) {
+ var nextMapping = this._generatedMappings[index + 1];
+
+ if (mapping.generatedLine === nextMapping.generatedLine) {
+ mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1;
+ continue;
+ }
+ }
+
+ // The last mapping for each line spans the entire line.
+ mapping.lastGeneratedColumn = Infinity;
+ }
+ };
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ BasicSourceMapConsumer.prototype.originalPositionFor =
+ function SourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._generatedMappings,
+ "generatedLine",
+ "generatedColumn",
+ util.compareByGeneratedPositionsDeflated,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._generatedMappings[index];
+
+ if (mapping.generatedLine === needle.generatedLine) {
+ var source = util.getArg(mapping, 'source', null);
+ if (source !== null) {
+ source = this._sources.at(source);
+ if (this.sourceRoot != null) {
+ source = util.join(this.sourceRoot, source);
+ }
+ }
+ var name = util.getArg(mapping, 'name', null);
+ if (name !== null) {
+ name = this._names.at(name);
+ }
+ return {
+ source: source,
+ line: util.getArg(mapping, 'originalLine', null),
+ column: util.getArg(mapping, 'originalColumn', null),
+ name: name
+ };
+ }
+ }
+
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ BasicSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function BasicSourceMapConsumer_hasContentsOfAllSources() {
+ if (!this.sourcesContent) {
+ return false;
+ }
+ return this.sourcesContent.length >= this._sources.size() &&
+ !this.sourcesContent.some(function (sc) { return sc == null; });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * availible.
+ */
+ BasicSourceMapConsumer.prototype.sourceContentFor =
+ function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ if (!this.sourcesContent) {
+ return null;
+ }
+
+ if (this.sourceRoot != null) {
+ aSource = util.relative(this.sourceRoot, aSource);
+ }
+
+ if (this._sources.has(aSource)) {
+ return this.sourcesContent[this._sources.indexOf(aSource)];
+ }
+
+ var url;
+ if (this.sourceRoot != null
+ && (url = util.urlParse(this.sourceRoot))) {
+ // XXX: file:// URIs and absolute paths lead to unexpected behavior for
+ // many users. We can help them out when they expect file:// URIs to
+ // behave like it would if they were running a local HTTP server. See
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=885597.
+ var fileUriAbsPath = aSource.replace(/^file:\/\//, "");
+ if (url.scheme == "file"
+ && this._sources.has(fileUriAbsPath)) {
+ return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)]
+ }
+
+ if ((!url.path || url.path == "/")
+ && this._sources.has("/" + aSource)) {
+ return this.sourcesContent[this._sources.indexOf("/" + aSource)];
+ }
+ }
+
+ // This function is used recursively from
+ // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we
+ // don't want to throw if we can't find the source - we just want to
+ // return null, so we provide a flag to exit gracefully.
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or
+ * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ BasicSourceMapConsumer.prototype.generatedPositionFor =
+ function SourceMapConsumer_generatedPositionFor(aArgs) {
+ var source = util.getArg(aArgs, 'source');
+ if (this.sourceRoot != null) {
+ source = util.relative(this.sourceRoot, source);
+ }
+ if (!this._sources.has(source)) {
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ }
+ source = this._sources.indexOf(source);
+
+ var needle = {
+ source: source,
+ originalLine: util.getArg(aArgs, 'line'),
+ originalColumn: util.getArg(aArgs, 'column')
+ };
+
+ var index = this._findMapping(
+ needle,
+ this._originalMappings,
+ "originalLine",
+ "originalColumn",
+ util.compareByOriginalPositions,
+ util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND)
+ );
+
+ if (index >= 0) {
+ var mapping = this._originalMappings[index];
+
+ if (mapping.source === needle.source) {
+ return {
+ line: util.getArg(mapping, 'generatedLine', null),
+ column: util.getArg(mapping, 'generatedColumn', null),
+ lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null)
+ };
+ }
+ }
+
+ return {
+ line: null,
+ column: null,
+ lastColumn: null
+ };
+ };
+
+ exports.BasicSourceMapConsumer = BasicSourceMapConsumer;
+
+ /**
+ * An IndexedSourceMapConsumer instance represents a parsed source map which
+ * we can query for information. It differs from BasicSourceMapConsumer in
+ * that it takes "indexed" source maps (i.e. ones with a "sections" field) as
+ * input.
+ *
+ * The only parameter is a raw source map (either as a JSON string, or already
+ * parsed to an object). According to the spec for indexed source maps, they
+ * have the following attributes:
+ *
+ * - version: Which version of the source map spec this map is following.
+ * - file: Optional. The generated file this source map is associated with.
+ * - sections: A list of section definitions.
+ *
+ * Each value under the "sections" field has two fields:
+ * - offset: The offset into the original specified at which this section
+ * begins to apply, defined as an object with a "line" and "column"
+ * field.
+ * - map: A source map definition. This source map could also be indexed,
+ * but doesn't have to be.
+ *
+ * Instead of the "map" field, it's also possible to have a "url" field
+ * specifying a URL to retrieve a source map from, but that's currently
+ * unsupported.
+ *
+ * Here's an example source map, taken from the source map spec[0], but
+ * modified to omit a section which uses the "url" field.
+ *
+ * {
+ * version : 3,
+ * file: "app.js",
+ * sections: [{
+ * offset: {line:100, column:10},
+ * map: {
+ * version : 3,
+ * file: "section.js",
+ * sources: ["foo.js", "bar.js"],
+ * names: ["src", "maps", "are", "fun"],
+ * mappings: "AAAA,E;;ABCDE;"
+ * }
+ * }],
+ * }
+ *
+ * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt
+ */
+ function IndexedSourceMapConsumer(aSourceMap) {
+ var sourceMap = aSourceMap;
+ if (typeof aSourceMap === 'string') {
+ sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, ''));
+ }
+
+ var version = util.getArg(sourceMap, 'version');
+ var sections = util.getArg(sourceMap, 'sections');
+
+ if (version != this._version) {
+ throw new Error('Unsupported version: ' + version);
+ }
+
+ this._sources = new ArraySet();
+ this._names = new ArraySet();
+
+ var lastOffset = {
+ line: -1,
+ column: 0
+ };
+ this._sections = sections.map(function (s) {
+ if (s.url) {
+ // The url field will require support for asynchronicity.
+ // See https://github.com/mozilla/source-map/issues/16
+ throw new Error('Support for url field in sections not implemented.');
+ }
+ var offset = util.getArg(s, 'offset');
+ var offsetLine = util.getArg(offset, 'line');
+ var offsetColumn = util.getArg(offset, 'column');
+
+ if (offsetLine < lastOffset.line ||
+ (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) {
+ throw new Error('Section offsets must be ordered and non-overlapping.');
+ }
+ lastOffset = offset;
+
+ return {
+ generatedOffset: {
+ // The offset fields are 0-based, but we use 1-based indices when
+ // encoding/decoding from VLQ.
+ generatedLine: offsetLine + 1,
+ generatedColumn: offsetColumn + 1
+ },
+ consumer: new SourceMapConsumer(util.getArg(s, 'map'))
+ }
+ });
+ }
+
+ IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype);
+ IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer;
+
+ /**
+ * The version of the source mapping spec that we are consuming.
+ */
+ IndexedSourceMapConsumer.prototype._version = 3;
+
+ /**
+ * The list of original sources.
+ */
+ Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', {
+ get: function () {
+ var sources = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ for (var j = 0; j < this._sections[i].consumer.sources.length; j++) {
+ sources.push(this._sections[i].consumer.sources[j]);
+ }
+ }
+ return sources;
+ }
+ });
+
+ /**
+ * Returns the original source, line, and column information for the generated
+ * source's line and column positions provided. The only argument is an object
+ * with the following properties:
+ *
+ * - line: The line number in the generated source.
+ * - column: The column number in the generated source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - source: The original source file, or null.
+ * - line: The line number in the original source, or null.
+ * - column: The column number in the original source, or null.
+ * - name: The original identifier, or null.
+ */
+ IndexedSourceMapConsumer.prototype.originalPositionFor =
+ function IndexedSourceMapConsumer_originalPositionFor(aArgs) {
+ var needle = {
+ generatedLine: util.getArg(aArgs, 'line'),
+ generatedColumn: util.getArg(aArgs, 'column')
+ };
+
+ // Find the section containing the generated position we're trying to map
+ // to an original position.
+ var sectionIndex = binarySearch.search(needle, this._sections,
+ function(needle, section) {
+ var cmp = needle.generatedLine - section.generatedOffset.generatedLine;
+ if (cmp) {
+ return cmp;
+ }
+
+ return (needle.generatedColumn -
+ section.generatedOffset.generatedColumn);
+ });
+ var section = this._sections[sectionIndex];
+
+ if (!section) {
+ return {
+ source: null,
+ line: null,
+ column: null,
+ name: null
+ };
+ }
+
+ return section.consumer.originalPositionFor({
+ line: needle.generatedLine -
+ (section.generatedOffset.generatedLine - 1),
+ column: needle.generatedColumn -
+ (section.generatedOffset.generatedLine === needle.generatedLine
+ ? section.generatedOffset.generatedColumn - 1
+ : 0),
+ bias: aArgs.bias
+ });
+ };
+
+ /**
+ * Return true if we have the source content for every source in the source
+ * map, false otherwise.
+ */
+ IndexedSourceMapConsumer.prototype.hasContentsOfAllSources =
+ function IndexedSourceMapConsumer_hasContentsOfAllSources() {
+ return this._sections.every(function (s) {
+ return s.consumer.hasContentsOfAllSources();
+ });
+ };
+
+ /**
+ * Returns the original source content. The only argument is the url of the
+ * original source file. Returns null if no original source content is
+ * available.
+ */
+ IndexedSourceMapConsumer.prototype.sourceContentFor =
+ function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ var content = section.consumer.sourceContentFor(aSource, true);
+ if (content) {
+ return content;
+ }
+ }
+ if (nullOnMissing) {
+ return null;
+ }
+ else {
+ throw new Error('"' + aSource + '" is not in the SourceMap.');
+ }
+ };
+
+ /**
+ * Returns the generated line and column information for the original source,
+ * line, and column positions provided. The only argument is an object with
+ * the following properties:
+ *
+ * - source: The filename of the original source.
+ * - line: The line number in the original source.
+ * - column: The column number in the original source.
+ *
+ * and an object is returned with the following properties:
+ *
+ * - line: The line number in the generated source, or null.
+ * - column: The column number in the generated source, or null.
+ */
+ IndexedSourceMapConsumer.prototype.generatedPositionFor =
+ function IndexedSourceMapConsumer_generatedPositionFor(aArgs) {
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+
+ // Only consider this section if the requested source is in the list of
+ // sources of the consumer.
+ if (section.consumer.sources.indexOf(util.getArg(aArgs, 'source')) === -1) {
+ continue;
+ }
+ var generatedPosition = section.consumer.generatedPositionFor(aArgs);
+ if (generatedPosition) {
+ var ret = {
+ line: generatedPosition.line +
+ (section.generatedOffset.generatedLine - 1),
+ column: generatedPosition.column +
+ (section.generatedOffset.generatedLine === generatedPosition.line
+ ? section.generatedOffset.generatedColumn - 1
+ : 0)
+ };
+ return ret;
+ }
+ }
+
+ return {
+ line: null,
+ column: null
+ };
+ };
+
+ /**
+ * Parse the mappings in a string in to a data structure which we can easily
+ * query (the ordered arrays in the `this.__generatedMappings` and
+ * `this.__originalMappings` properties).
+ */
+ IndexedSourceMapConsumer.prototype._parseMappings =
+ function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) {
+ this.__generatedMappings = [];
+ this.__originalMappings = [];
+ for (var i = 0; i < this._sections.length; i++) {
+ var section = this._sections[i];
+ var sectionMappings = section.consumer._generatedMappings;
+ for (var j = 0; j < sectionMappings.length; j++) {
+ var mapping = sectionMappings[i];
+
+ var source = section.consumer._sources.at(mapping.source);
+ if (section.consumer.sourceRoot !== null) {
+ source = util.join(section.consumer.sourceRoot, source);
+ }
+ this._sources.add(source);
+ source = this._sources.indexOf(source);
+
+ var name = section.consumer._names.at(mapping.name);
+ this._names.add(name);
+ name = this._names.indexOf(name);
+
+ // The mappings coming from the consumer for the section have
+ // generated positions relative to the start of the section, so we
+ // need to offset them to be relative to the start of the concatenated
+ // generated file.
+ var adjustedMapping = {
+ source: source,
+ generatedLine: mapping.generatedLine +
+ (section.generatedOffset.generatedLine - 1),
+ generatedColumn: mapping.column +
+ (section.generatedOffset.generatedLine === mapping.generatedLine)
+ ? section.generatedOffset.generatedColumn - 1
+ : 0,
+ originalLine: mapping.originalLine,
+ originalColumn: mapping.originalColumn,
+ name: name
+ };
+
+ this.__generatedMappings.push(adjustedMapping);
+ if (typeof adjustedMapping.originalLine === 'number') {
+ this.__originalMappings.push(adjustedMapping);
+ }
+ }
+ }
+
+ quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated);
+ quickSort(this.__originalMappings, util.compareByOriginalPositions);
+ };
+
+ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer;
+ }
+
+
+/***/ },
+/* 9 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ exports.GREATEST_LOWER_BOUND = 1;
+ exports.LEAST_UPPER_BOUND = 2;
+
+ /**
+ * Recursive implementation of binary search.
+ *
+ * @param aLow Indices here and lower do not contain the needle.
+ * @param aHigh Indices here and higher do not contain the needle.
+ * @param aNeedle The element being searched for.
+ * @param aHaystack The non-empty array being searched.
+ * @param aCompare Function which takes two elements and returns -1, 0, or 1.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ */
+ function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) {
+ // This function terminates when one of the following is true:
+ //
+ // 1. We find the exact element we are looking for.
+ //
+ // 2. We did not find the exact element, but we can return the index of
+ // the next-closest element.
+ //
+ // 3. We did not find the exact element, and there is no next-closest
+ // element than the one we are searching for, so we return -1.
+ var mid = Math.floor((aHigh - aLow) / 2) + aLow;
+ var cmp = aCompare(aNeedle, aHaystack[mid], true);
+ if (cmp === 0) {
+ // Found the element we are looking for.
+ return mid;
+ }
+ else if (cmp > 0) {
+ // Our needle is greater than aHaystack[mid].
+ if (aHigh - mid > 1) {
+ // The element is in the upper half.
+ return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // The exact needle element was not found in this haystack. Determine if
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return aHigh < aHaystack.length ? aHigh : -1;
+ } else {
+ return mid;
+ }
+ }
+ else {
+ // Our needle is less than aHaystack[mid].
+ if (mid - aLow > 1) {
+ // The element is in the lower half.
+ return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias);
+ }
+
+ // we are in termination case (3) or (2) and return the appropriate thing.
+ if (aBias == exports.LEAST_UPPER_BOUND) {
+ return mid;
+ } else {
+ return aLow < 0 ? -1 : aLow;
+ }
+ }
+ }
+
+ /**
+ * This is an implementation of binary search which will always try and return
+ * the index of the closest element if there is no exact hit. This is because
+ * mappings between original and generated line/col pairs are single points,
+ * and there is an implicit region between each of them, so a miss just means
+ * that you aren't on the very start of a region.
+ *
+ * @param aNeedle The element you are looking for.
+ * @param aHaystack The array that is being searched.
+ * @param aCompare A function which takes the needle and an element in the
+ * array and returns -1, 0, or 1 depending on whether the needle is less
+ * than, equal to, or greater than the element, respectively.
+ * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or
+ * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the
+ * closest element that is smaller than or greater than the one we are
+ * searching for, respectively, if the exact element cannot be found.
+ * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'.
+ */
+ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) {
+ if (aHaystack.length === 0) {
+ return -1;
+ }
+
+ var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack,
+ aCompare, aBias || exports.GREATEST_LOWER_BOUND);
+ if (index < 0) {
+ return -1;
+ }
+
+ // We have found either the exact element, or the next-closest element than
+ // the one we are searching for. However, there may be more than one such
+ // element. Make sure we always return the smallest of these.
+ while (index - 1 >= 0) {
+ if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) {
+ break;
+ }
+ --index;
+ }
+
+ return index;
+ };
+ }
+
+
+/***/ },
+/* 10 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ // It turns out that some (most?) JavaScript engines don't self-host
+ // `Array.prototype.sort`. This makes sense because C++ will likely remain
+ // faster than JS when doing raw CPU-intensive sorting. However, when using a
+ // custom comparator function, calling back and forth between the VM's C++ and
+ // JIT'd JS is rather slow *and* loses JIT type information, resulting in
+ // worse generated code for the comparator function than would be optimal. In
+ // fact, when sorting with a comparator, these costs outweigh the benefits of
+ // sorting in C++. By using our own JS-implemented Quick Sort (below), we get
+ // a ~3500ms mean speed-up in `bench/bench.html`.
+
+ /**
+ * Swap the elements indexed by `x` and `y` in the array `ary`.
+ *
+ * @param {Array} ary
+ * The array.
+ * @param {Number} x
+ * The index of the first item.
+ * @param {Number} y
+ * The index of the second item.
+ */
+ function swap(ary, x, y) {
+ var temp = ary[x];
+ ary[x] = ary[y];
+ ary[y] = temp;
+ }
+
+ /**
+ * Returns a random integer within the range `low .. high` inclusive.
+ *
+ * @param {Number} low
+ * The lower bound on the range.
+ * @param {Number} high
+ * The upper bound on the range.
+ */
+ function randomIntInRange(low, high) {
+ return Math.round(low + (Math.random() * (high - low)));
+ }
+
+ /**
+ * The Quick Sort algorithm.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ * @param {Number} p
+ * Start index of the array
+ * @param {Number} r
+ * End index of the array
+ */
+ function doQuickSort(ary, comparator, p, r) {
+ // If our lower bound is less than our upper bound, we (1) partition the
+ // array into two pieces and (2) recurse on each half. If it is not, this is
+ // the empty array and our base case.
+
+ if (p < r) {
+ // (1) Partitioning.
+ //
+ // The partitioning chooses a pivot between `p` and `r` and moves all
+ // elements that are less than or equal to the pivot to the before it, and
+ // all the elements that are greater than it after it. The effect is that
+ // once partition is done, the pivot is in the exact place it will be when
+ // the array is put in sorted order, and it will not need to be moved
+ // again. This runs in O(n) time.
+
+ // Always choose a random pivot so that an input array which is reverse
+ // sorted does not cause O(n^2) running time.
+ var pivotIndex = randomIntInRange(p, r);
+ var i = p - 1;
+
+ swap(ary, pivotIndex, r);
+ var pivot = ary[r];
+
+ // Immediately after `j` is incremented in this loop, the following hold
+ // true:
+ //
+ // * Every element in `ary[p .. i]` is less than or equal to the pivot.
+ //
+ // * Every element in `ary[i+1 .. j-1]` is greater than the pivot.
+ for (var j = p; j < r; j++) {
+ if (comparator(ary[j], pivot) <= 0) {
+ i += 1;
+ swap(ary, i, j);
+ }
+ }
+
+ swap(ary, i + 1, j);
+ var q = i + 1;
+
+ // (2) Recurse on each half.
+
+ doQuickSort(ary, comparator, p, q - 1);
+ doQuickSort(ary, comparator, q + 1, r);
+ }
+ }
+
+ /**
+ * Sort the given array in-place with the given comparator function.
+ *
+ * @param {Array} ary
+ * An array to sort.
+ * @param {function} comparator
+ * Function to use to compare two items.
+ */
+ exports.quickSort = function (ary, comparator) {
+ doQuickSort(ary, comparator, 0, ary.length - 1);
+ };
+ }
+
+
+/***/ },
+/* 11 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var SourceMapGenerator = __webpack_require__(3).SourceMapGenerator;
+ var util = __webpack_require__(2);
+
+ // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other
+ // operating systems these days (capturing the result).
+ var REGEX_NEWLINE = /(\r?\n)/;
+
+ // Newline character code for charCodeAt() comparisons
+ var NEWLINE_CODE = 10;
+
+ // Private symbol for identifying `SourceNode`s when multiple versions of
+ // the source-map library are loaded. This MUST NOT CHANGE across
+ // versions!
+ var isSourceNode = "$$$isSourceNode$$$";
+
+ /**
+ * SourceNodes provide a way to abstract over interpolating/concatenating
+ * snippets of generated JavaScript source code while maintaining the line and
+ * column information associated with the original source code.
+ *
+ * @param aLine The original line number.
+ * @param aColumn The original column number.
+ * @param aSource The original source's filename.
+ * @param aChunks Optional. An array of strings which are snippets of
+ * generated JS, or other SourceNodes.
+ * @param aName The original identifier.
+ */
+ function SourceNode(aLine, aColumn, aSource, aChunks, aName) {
+ this.children = [];
+ this.sourceContents = {};
+ this.line = aLine == null ? null : aLine;
+ this.column = aColumn == null ? null : aColumn;
+ this.source = aSource == null ? null : aSource;
+ this.name = aName == null ? null : aName;
+ this[isSourceNode] = true;
+ if (aChunks != null) this.add(aChunks);
+ }
+
+ /**
+ * Creates a SourceNode from generated code and a SourceMapConsumer.
+ *
+ * @param aGeneratedCode The generated code
+ * @param aSourceMapConsumer The SourceMap for the generated code
+ * @param aRelativePath Optional. The path that relative sources in the
+ * SourceMapConsumer should be relative to.
+ */
+ SourceNode.fromStringWithSourceMap =
+ function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) {
+ // The SourceNode we want to fill with the generated code
+ // and the SourceMap
+ var node = new SourceNode();
+
+ // All even indices of this array are one line of the generated code,
+ // while all odd indices are the newlines between two adjacent lines
+ // (since `REGEX_NEWLINE` captures its match).
+ // Processed fragments are removed from this array, by calling `shiftNextLine`.
+ var remainingLines = aGeneratedCode.split(REGEX_NEWLINE);
+ var shiftNextLine = function() {
+ var lineContents = remainingLines.shift();
+ // The last line of a file might not have a newline.
+ var newLine = remainingLines.shift() || "";
+ return lineContents + newLine;
+ };
+
+ // We need to remember the position of "remainingLines"
+ var lastGeneratedLine = 1, lastGeneratedColumn = 0;
+
+ // The generate SourceNodes we need a code range.
+ // To extract it current and last mapping is used.
+ // Here we store the last mapping.
+ var lastMapping = null;
+
+ aSourceMapConsumer.eachMapping(function (mapping) {
+ if (lastMapping !== null) {
+ // We add the code from "lastMapping" to "mapping":
+ // First check if there is a new line in between.
+ if (lastGeneratedLine < mapping.generatedLine) {
+ var code = "";
+ // Associate first line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ lastGeneratedLine++;
+ lastGeneratedColumn = 0;
+ // The remaining code is added without mapping
+ } else {
+ // There is no new line in between.
+ // Associate the code between "lastGeneratedColumn" and
+ // "mapping.generatedColumn" with "lastMapping"
+ var nextLine = remainingLines[0];
+ var code = nextLine.substr(0, mapping.generatedColumn -
+ lastGeneratedColumn);
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn -
+ lastGeneratedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ addMappingWithCode(lastMapping, code);
+ // No more remaining code, continue
+ lastMapping = mapping;
+ return;
+ }
+ }
+ // We add the generated code until the first mapping
+ // to the SourceNode without any mapping.
+ // Each line is added as separate string.
+ while (lastGeneratedLine < mapping.generatedLine) {
+ node.add(shiftNextLine());
+ lastGeneratedLine++;
+ }
+ if (lastGeneratedColumn < mapping.generatedColumn) {
+ var nextLine = remainingLines[0];
+ node.add(nextLine.substr(0, mapping.generatedColumn));
+ remainingLines[0] = nextLine.substr(mapping.generatedColumn);
+ lastGeneratedColumn = mapping.generatedColumn;
+ }
+ lastMapping = mapping;
+ }, this);
+ // We have processed all mappings.
+ if (remainingLines.length > 0) {
+ if (lastMapping) {
+ // Associate the remaining code in the current line with "lastMapping"
+ addMappingWithCode(lastMapping, shiftNextLine());
+ }
+ // and add the remaining lines without any mapping
+ node.add(remainingLines.join(""));
+ }
+
+ // Copy sourcesContent into SourceNode
+ aSourceMapConsumer.sources.forEach(function (sourceFile) {
+ var content = aSourceMapConsumer.sourceContentFor(sourceFile);
+ if (content != null) {
+ if (aRelativePath != null) {
+ sourceFile = util.join(aRelativePath, sourceFile);
+ }
+ node.setSourceContent(sourceFile, content);
+ }
+ });
+
+ return node;
+
+ function addMappingWithCode(mapping, code) {
+ if (mapping === null || mapping.source === undefined) {
+ node.add(code);
+ } else {
+ var source = aRelativePath
+ ? util.join(aRelativePath, mapping.source)
+ : mapping.source;
+ node.add(new SourceNode(mapping.originalLine,
+ mapping.originalColumn,
+ source,
+ code,
+ mapping.name));
+ }
+ }
+ };
+
+ /**
+ * Add a chunk of generated JS to this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.add = function SourceNode_add(aChunk) {
+ if (Array.isArray(aChunk)) {
+ aChunk.forEach(function (chunk) {
+ this.add(chunk);
+ }, this);
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ if (aChunk) {
+ this.children.push(aChunk);
+ }
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Add a chunk of generated JS to the beginning of this source node.
+ *
+ * @param aChunk A string snippet of generated JS code, another instance of
+ * SourceNode, or an array where each member is one of those things.
+ */
+ SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) {
+ if (Array.isArray(aChunk)) {
+ for (var i = aChunk.length-1; i >= 0; i--) {
+ this.prepend(aChunk[i]);
+ }
+ }
+ else if (aChunk[isSourceNode] || typeof aChunk === "string") {
+ this.children.unshift(aChunk);
+ }
+ else {
+ throw new TypeError(
+ "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk
+ );
+ }
+ return this;
+ };
+
+ /**
+ * Walk over the tree of JS snippets in this node and its children. The
+ * walking function is called once for each snippet of JS and is passed that
+ * snippet and the its original associated source's line/column location.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walk = function SourceNode_walk(aFn) {
+ var chunk;
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ chunk = this.children[i];
+ if (chunk[isSourceNode]) {
+ chunk.walk(aFn);
+ }
+ else {
+ if (chunk !== '') {
+ aFn(chunk, { source: this.source,
+ line: this.line,
+ column: this.column,
+ name: this.name });
+ }
+ }
+ }
+ };
+
+ /**
+ * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between
+ * each of `this.children`.
+ *
+ * @param aSep The separator.
+ */
+ SourceNode.prototype.join = function SourceNode_join(aSep) {
+ var newChildren;
+ var i;
+ var len = this.children.length;
+ if (len > 0) {
+ newChildren = [];
+ for (i = 0; i < len-1; i++) {
+ newChildren.push(this.children[i]);
+ newChildren.push(aSep);
+ }
+ newChildren.push(this.children[i]);
+ this.children = newChildren;
+ }
+ return this;
+ };
+
+ /**
+ * Call String.prototype.replace on the very right-most source snippet. Useful
+ * for trimming whitespace from the end of a source node, etc.
+ *
+ * @param aPattern The pattern to replace.
+ * @param aReplacement The thing to replace the pattern with.
+ */
+ SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) {
+ var lastChild = this.children[this.children.length - 1];
+ if (lastChild[isSourceNode]) {
+ lastChild.replaceRight(aPattern, aReplacement);
+ }
+ else if (typeof lastChild === 'string') {
+ this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement);
+ }
+ else {
+ this.children.push(''.replace(aPattern, aReplacement));
+ }
+ return this;
+ };
+
+ /**
+ * Set the source content for a source file. This will be added to the SourceMapGenerator
+ * in the sourcesContent field.
+ *
+ * @param aSourceFile The filename of the source file
+ * @param aSourceContent The content of the source file
+ */
+ SourceNode.prototype.setSourceContent =
+ function SourceNode_setSourceContent(aSourceFile, aSourceContent) {
+ this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent;
+ };
+
+ /**
+ * Walk over the tree of SourceNodes. The walking function is called for each
+ * source file content and is passed the filename and source content.
+ *
+ * @param aFn The traversal function.
+ */
+ SourceNode.prototype.walkSourceContents =
+ function SourceNode_walkSourceContents(aFn) {
+ for (var i = 0, len = this.children.length; i < len; i++) {
+ if (this.children[i][isSourceNode]) {
+ this.children[i].walkSourceContents(aFn);
+ }
+ }
+
+ var sources = Object.keys(this.sourceContents);
+ for (var i = 0, len = sources.length; i < len; i++) {
+ aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]);
+ }
+ };
+
+ /**
+ * Return the string representation of this source node. Walks over the tree
+ * and concatenates all the various snippets together to one string.
+ */
+ SourceNode.prototype.toString = function SourceNode_toString() {
+ var str = "";
+ this.walk(function (chunk) {
+ str += chunk;
+ });
+ return str;
+ };
+
+ /**
+ * Returns the string representation of this source node along with a source
+ * map.
+ */
+ SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) {
+ var generated = {
+ code: "",
+ line: 1,
+ column: 0
+ };
+ var map = new SourceMapGenerator(aArgs);
+ var sourceMappingActive = false;
+ var lastOriginalSource = null;
+ var lastOriginalLine = null;
+ var lastOriginalColumn = null;
+ var lastOriginalName = null;
+ this.walk(function (chunk, original) {
+ generated.code += chunk;
+ if (original.source !== null
+ && original.line !== null
+ && original.column !== null) {
+ if(lastOriginalSource !== original.source
+ || lastOriginalLine !== original.line
+ || lastOriginalColumn !== original.column
+ || lastOriginalName !== original.name) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ lastOriginalSource = original.source;
+ lastOriginalLine = original.line;
+ lastOriginalColumn = original.column;
+ lastOriginalName = original.name;
+ sourceMappingActive = true;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ generated: {
+ line: generated.line,
+ column: generated.column
+ }
+ });
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ }
+ for (var idx = 0, length = chunk.length; idx < length; idx++) {
+ if (chunk.charCodeAt(idx) === NEWLINE_CODE) {
+ generated.line++;
+ generated.column = 0;
+ // Mappings end at eol
+ if (idx + 1 === length) {
+ lastOriginalSource = null;
+ sourceMappingActive = false;
+ } else if (sourceMappingActive) {
+ map.addMapping({
+ source: original.source,
+ original: {
+ line: original.line,
+ column: original.column
+ },
+ generated: {
+ line: generated.line,
+ column: generated.column
+ },
+ name: original.name
+ });
+ }
+ } else {
+ generated.column++;
+ }
+ }
+ });
+ this.walkSourceContents(function (sourceFile, sourceContent) {
+ map.setSourceContent(sourceFile, sourceContent);
+ });
+
+ return { code: generated.code, map: map };
+ };
+
+ exports.SourceNode = SourceNode;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgNWY2ODNjMjEwMTc4ZjE5OGUxOWMiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LXNvdXJjZS1ub2RlLmpzIiwid2VicGFjazovLy8uL3Rlc3QvdXRpbC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvdXRpbC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvc291cmNlLW1hcC1nZW5lcmF0b3IuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC12bHEuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2Jhc2U2NC5qcyIsIndlYnBhY2s6Ly8vLi9saWIvYXJyYXktc2V0LmpzIiwid2VicGFjazovLy8uL2xpYi9tYXBwaW5nLWxpc3QuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3NvdXJjZS1tYXAtY29uc3VtZXIuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL2JpbmFyeS1zZWFyY2guanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3F1aWNrLXNvcnQuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3NvdXJjZS1ub2RlLmpzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7Ozs7Ozs7O0FBQUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsdUJBQWU7QUFDZjtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7O0FBR0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7Ozs7OztBQ3RDQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBLGlDQUFnQzs7QUFFaEM7QUFDQTs7QUFFQTtBQUNBLGdDQUErQjtBQUMvQjtBQUNBLHlDQUF3QztBQUN4QyxpQkFBZ0I7O0FBRWhCO0FBQ0E7QUFDQSxrQkFBaUI7QUFDakIsTUFBSztBQUNMO0FBQ0EsOEJBQTZCO0FBQzdCLE1BQUs7QUFDTDs7QUFFQTtBQUNBOztBQUVBO0FBQ0EscUNBQW9DO0FBQ3BDLHVEQUFzRDtBQUN0RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx1REFBc0Q7QUFDdEQ7O0FBRUE7QUFDQSxvQ0FBbUM7QUFDbkM7QUFDQSx5Q0FBd0M7QUFDeEMsaUJBQWdCO0FBQ2hCLHFEQUFvRDtBQUNwRCwrQ0FBOEM7QUFDOUMsc0NBQXFDO0FBQ3JDO0FBQ0EsdURBQXNEO0FBQ3REOztBQUVBO0FBQ0E7QUFDQSxzQkFBcUI7QUFDckIsTUFBSztBQUNMO0FBQ0Esa0NBQWlDO0FBQ2pDLE1BQUs7QUFDTDs7QUFFQTtBQUNBO0FBQ0Esb0RBQW1EO0FBQ25ELCtFQUE4RTtBQUM5RSxxQ0FBb0M7QUFDcEMsbUNBQWtDLFdBQVc7QUFDN0M7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsK0NBQThDO0FBQzlDLHNGQUFxRjtBQUNyRiwwRkFBeUY7QUFDekYsa0NBQWlDLElBQUk7QUFDckM7QUFDQSxRQUFPLHFCQUFxQiwrQ0FBK0M7QUFDM0UsUUFBTyxvRUFBb0U7QUFDM0UsUUFBTyxvRUFBb0U7QUFDM0UsUUFBTyxRQUFRLDREQUE0RDtBQUMzRSxRQUFPLG9FQUFvRTtBQUMzRSxRQUFPLG9FQUFvRTtBQUMzRSxRQUFPLFFBQVEsNERBQTREO0FBQzNFLFFBQU8sUUFBUSxJQUFJLHdEQUF3RDtBQUMzRTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLCtDQUE4QztBQUM5QztBQUNBO0FBQ0E7QUFDQSxvQ0FBbUM7QUFDbkMsMEZBQXlGO0FBQ3pGLGtDQUFpQyxJQUFJO0FBQ3JDO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0Esc0JBQXFCO0FBQ3JCLHFCQUFvQjtBQUNwQix5QkFBd0I7QUFDeEIsU0FBUSxJQUFJO0FBQ1o7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0EscUJBQW9CO0FBQ3BCLDBCQUF5QjtBQUN6Qix1REFBc0QsZUFBZTtBQUNyRSw4REFBNkQ7QUFDN0QsV0FBVSxJQUFJO0FBQ2Q7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSwyQ0FBMEMsWUFBWTtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0NBQWlDLGlCQUFpQjtBQUNsRDtBQUNBO0FBQ0E7QUFDQTs7QUFFQSx3RUFBdUU7QUFDdkU7QUFDQSx1RUFBc0U7QUFDdEUsNkZBQTRGO0FBQzVGO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUwsdURBQXNEOztBQUV0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBd0M7QUFDeEM7QUFDQTtBQUNBLHdDQUF1QztBQUN2QztBQUNBO0FBQ0EsMENBQXlDO0FBQ3pDO0FBQ0EseUNBQXdDO0FBQ3hDLFNBQVEsSUFBSTtBQUNaO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBLHFCQUFvQjtBQUNwQix3QkFBdUI7QUFDdkIsa0JBQWlCLGVBQWU7QUFDaEMsU0FBUSxJQUFJO0FBQ1o7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxrQkFBaUI7QUFDakIsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQTtBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0EsbUJBQWtCO0FBQ2xCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixzQkFBc0I7QUFDeEM7QUFDQTtBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQSxrREFBaUQsMkJBQTJCLHdCQUF3QjtBQUNwRywwREFBeUQ7QUFDekQseURBQXdEO0FBQ3hELG1EQUFrRDtBQUNsRDtBQUNBLG1EQUFrRDtBQUNsRDtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQSxxQkFBb0I7QUFDcEIseUJBQXdCO0FBQ3hCLHNCQUFxQjtBQUNyQiw2QkFBNEI7QUFDNUIsNEJBQTJCO0FBQzNCLHNCQUFxQjtBQUNyQjtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQjtBQUNyQjtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxrQkFBaUI7QUFDakIsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxrQkFBaUI7QUFDakIsTUFBSztBQUNMO0FBQ0EsbUJBQWtCLHFCQUFxQjtBQUN2QztBQUNBLGtCQUFpQjtBQUNqQixNQUFLO0FBQ0w7QUFDQSxtQkFBa0Isc0JBQXNCO0FBQ3hDO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGtEQUFpRDtBQUNqRCx5Q0FBd0M7QUFDeEM7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQSxxQkFBb0I7QUFDcEIsWUFBVztBQUNYOztBQUVBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxtQkFBa0IscUJBQXFCO0FBQ3ZDO0FBQ0Esa0JBQWlCO0FBQ2pCLE1BQUs7QUFDTDtBQUNBLG1CQUFrQixxQkFBcUI7QUFDdkM7QUFDQSxrQkFBaUI7QUFDakIsTUFBSzs7QUFFTDtBQUNBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsK0NBQThDO0FBQzlDO0FBQ0E7QUFDQSxrQ0FBaUMsSUFBSTtBQUNyQztBQUNBO0FBQ0E7QUFDQSxNQUFLOztBQUVMO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQSwrQ0FBOEM7QUFDOUM7QUFDQTtBQUNBLGtDQUFpQyxJQUFJO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDaG1CQSxpQkFBZ0Isb0JBQW9CO0FBQ3BDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBMkI7QUFDM0IsNEJBQTJCO0FBQzNCLHFEQUFvRCxnQkFBZ0I7QUFDcEUscURBQW9ELGFBQWE7QUFDakU7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx5Q0FBd0M7QUFDeEMsaUNBQWdDO0FBQ2hDLGlCQUFnQjtBQUNoQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsdUNBQXNDO0FBQ3RDLDhCQUE2QjtBQUM3QixpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDO0FBQ3hDLGlDQUFnQztBQUNoQyxpQkFBZ0I7QUFDaEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHVDQUFzQztBQUN0Qyw4QkFBNkI7QUFDN0IsaUJBQWdCO0FBQ2hCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQ0FBa0M7QUFDbEMsMkJBQTBCO0FBQzFCLFdBQVU7QUFDVixpQ0FBZ0M7QUFDaEMsd0JBQXVCO0FBQ3ZCLFdBQVU7QUFDVjtBQUNBO0FBQ0EsdURBQXNEO0FBQ3REO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsbUNBQWtDO0FBQ2xDLDJCQUEwQjtBQUMxQixXQUFVO0FBQ1YsaUNBQWdDO0FBQ2hDLHdCQUF1QjtBQUN2QixXQUFVO0FBQ1Y7QUFDQTtBQUNBLHVEQUFzRDtBQUN0RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQkFBbUIsNEJBQTRCO0FBQy9DO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQiw4QkFBOEI7QUFDakQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLHFDQUFxQztBQUMxRDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDdlNBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSxpREFBZ0QsUUFBUTtBQUN4RDtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNoWEEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQSxRQUFPO0FBQ1A7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLDZDQUE0QyxTQUFTO0FBQ3JEOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHlCQUF3QjtBQUN4QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDM1lBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLDREQUEyRDtBQUMzRCxxQkFBb0I7QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSzs7QUFFTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7O0FBRUw7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUM1SUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG1CQUFrQjtBQUNsQixtQkFBa0I7O0FBRWxCLHNCQUFxQjtBQUNyQix1QkFBc0I7O0FBRXRCLG1CQUFrQjtBQUNsQixtQkFBa0I7O0FBRWxCLG1CQUFrQjtBQUNsQixvQkFBbUI7O0FBRW5CO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUNuRUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseUNBQXdDLFNBQVM7QUFDakQ7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN2R0EsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxtQkFBa0I7QUFDbEI7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsTUFBSztBQUNMO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUMvRUEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSxJQUFHOztBQUVIO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQjtBQUNyQjs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsY0FBYTs7QUFFYjtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxjQUFhOztBQUViO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw4QkFBNkIsTUFBTTtBQUNuQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHlEQUF3RDtBQUN4RDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87O0FBRVA7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBLHlEQUF3RCxZQUFZO0FBQ3BFO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLElBQUc7O0FBRUg7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0Esc0NBQXFDO0FBQ3JDO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSw0QkFBMkIsY0FBYztBQUN6QztBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsMEJBQXlCLHdDQUF3QztBQUNqRTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWlELG1CQUFtQixFQUFFO0FBQ3RFOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLG9CQUFtQixvQkFBb0I7QUFDdkM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdDQUErQixNQUFNO0FBQ3JDO0FBQ0EsVUFBUztBQUNUO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EseURBQXdEO0FBQ3hEOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBLE1BQUs7QUFDTDs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQsd0JBQXVCLCtDQUErQztBQUN0RTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsSUFBRzs7QUFFSDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0EsVUFBUztBQUNUOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esc0JBQXFCLDJCQUEyQjtBQUNoRDs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxzQkFBcUIsMkJBQTJCO0FBQ2hEOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQiwyQkFBMkI7QUFDaEQ7QUFDQTtBQUNBLHdCQUF1Qiw0QkFBNEI7QUFDbkQ7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7Ozs7Ozs7QUN6akNBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxRQUFPO0FBQ1A7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7Ozs7O0FDL0dBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBLGNBQWEsTUFBTTtBQUNuQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBLGNBQWEsT0FBTztBQUNwQjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQSxjQUFhLE9BQU87QUFDcEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLHNCQUFxQixPQUFPO0FBQzVCO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSxjQUFhLE1BQU07QUFDbkI7QUFDQSxjQUFhLFNBQVM7QUFDdEI7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7Ozs7O0FDbEhBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLFlBQVc7QUFDWDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxRQUFPOztBQUVQOztBQUVBO0FBQ0E7QUFDQTtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxvQ0FBbUMsUUFBUTtBQUMzQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxnREFBK0MsU0FBUztBQUN4RDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSx1QkFBc0I7QUFDdEI7QUFDQTtBQUNBLHlDQUF3QztBQUN4QztBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxrQkFBaUIsV0FBVztBQUM1QjtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0Esa0RBQWlELFNBQVM7QUFDMUQ7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQSw0Q0FBMkMsU0FBUztBQUNwRDtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGNBQWE7QUFDYjtBQUNBO0FBQ0E7QUFDQSxjQUFhO0FBQ2I7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0EsUUFBTztBQUNQO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0EsK0NBQThDLGNBQWM7QUFDNUQ7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxZQUFXO0FBQ1g7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLGdCQUFlO0FBQ2Y7QUFDQTtBQUNBO0FBQ0EsZ0JBQWU7QUFDZjtBQUNBLGNBQWE7QUFDYjtBQUNBLFVBQVM7QUFDVDtBQUNBO0FBQ0E7QUFDQSxNQUFLO0FBQ0w7QUFDQTtBQUNBLE1BQUs7O0FBRUwsYUFBWTtBQUNaOztBQUVBO0FBQ0EiLCJmaWxlIjoidGVzdF9zb3VyY2Vfbm9kZS5qcyIsInNvdXJjZXNDb250ZW50IjpbIiBcdC8vIFRoZSBtb2R1bGUgY2FjaGVcbiBcdHZhciBpbnN0YWxsZWRNb2R1bGVzID0ge307XG5cbiBcdC8vIFRoZSByZXF1aXJlIGZ1bmN0aW9uXG4gXHRmdW5jdGlvbiBfX3dlYnBhY2tfcmVxdWlyZV9fKG1vZHVsZUlkKSB7XG5cbiBcdFx0Ly8gQ2hlY2sgaWYgbW9kdWxlIGlzIGluIGNhY2hlXG4gXHRcdGlmKGluc3RhbGxlZE1vZHVsZXNbbW9kdWxlSWRdKVxuIFx0XHRcdHJldHVybiBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXS5leHBvcnRzO1xuXG4gXHRcdC8vIENyZWF0ZSBhIG5ldyBtb2R1bGUgKGFuZCBwdXQgaXQgaW50byB0aGUgY2FjaGUpXG4gXHRcdHZhciBtb2R1bGUgPSBpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSA9IHtcbiBcdFx0XHRleHBvcnRzOiB7fSxcbiBcdFx0XHRpZDogbW9kdWxlSWQsXG4gXHRcdFx0bG9hZGVkOiBmYWxzZVxuIFx0XHR9O1xuXG4gXHRcdC8vIEV4ZWN1dGUgdGhlIG1vZHVsZSBmdW5jdGlvblxuIFx0XHRtb2R1bGVzW21vZHVsZUlkXS5jYWxsKG1vZHVsZS5leHBvcnRzLCBtb2R1bGUsIG1vZHVsZS5leHBvcnRzLCBfX3dlYnBhY2tfcmVxdWlyZV9fKTtcblxuIFx0XHQvLyBGbGFnIHRoZSBtb2R1bGUgYXMgbG9hZGVkXG4gXHRcdG1vZHVsZS5sb2FkZWQgPSB0cnVlO1xuXG4gXHRcdC8vIFJldHVybiB0aGUgZXhwb3J0cyBvZiB0aGUgbW9kdWxlXG4gXHRcdHJldHVybiBtb2R1bGUuZXhwb3J0cztcbiBcdH1cblxuXG4gXHQvLyBleHBvc2UgdGhlIG1vZHVsZXMgb2JqZWN0IChfX3dlYnBhY2tfbW9kdWxlc19fKVxuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5tID0gbW9kdWxlcztcblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGUgY2FjaGVcbiBcdF9fd2VicGFja19yZXF1aXJlX18uYyA9IGluc3RhbGxlZE1vZHVsZXM7XG5cbiBcdC8vIF9fd2VicGFja19wdWJsaWNfcGF0aF9fXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLnAgPSBcIlwiO1xuXG4gXHQvLyBMb2FkIGVudHJ5IG1vZHVsZSBhbmQgcmV0dXJuIGV4cG9ydHNcbiBcdHJldHVybiBfX3dlYnBhY2tfcmVxdWlyZV9fKDApO1xuXG5cblxuLyoqIFdFQlBBQ0sgRk9PVEVSICoqXG4gKiogd2VicGFjay9ib290c3RyYXAgNWY2ODNjMjEwMTc4ZjE5OGUxOWNcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciB1dGlsID0gcmVxdWlyZShcIi4vdXRpbFwiKTtcbiAgdmFyIFNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4uL2xpYi9zb3VyY2UtbWFwLWdlbmVyYXRvcicpLlNvdXJjZU1hcEdlbmVyYXRvcjtcbiAgdmFyIFNvdXJjZU1hcENvbnN1bWVyID0gcmVxdWlyZSgnLi4vbGliL3NvdXJjZS1tYXAtY29uc3VtZXInKS5Tb3VyY2VNYXBDb25zdW1lcjtcbiAgdmFyIFNvdXJjZU5vZGUgPSByZXF1aXJlKCcuLi9saWIvc291cmNlLW5vZGUnKS5Tb3VyY2VOb2RlO1xuXG4gIGZ1bmN0aW9uIGZvckVhY2hOZXdsaW5lKGZuKSB7XG4gICAgcmV0dXJuIGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICAgIFsnXFxuJywgJ1xcclxcbiddLmZvckVhY2goZm4uYmluZChudWxsLCBhc3NlcnQpKTtcbiAgICB9XG4gIH1cblxuICBleHBvcnRzWyd0ZXN0IC5hZGQoKSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCk7XG5cbiAgICAvLyBBZGRpbmcgYSBzdHJpbmcgd29ya3MuXG4gICAgbm9kZS5hZGQoJ2Z1bmN0aW9uIG5vb3AoKSB7fScpO1xuXG4gICAgLy8gQWRkaW5nIGFub3RoZXIgc291cmNlIG5vZGUgd29ya3MuXG4gICAgbm9kZS5hZGQobmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCkpO1xuXG4gICAgLy8gQWRkaW5nIGFuIGFycmF5IHdvcmtzLlxuICAgIG5vZGUuYWRkKFsnZnVuY3Rpb24gZm9vKCkgeycsXG4gICAgICAgICAgICAgIG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyZXR1cm4gMTA7JyksXG4gICAgICAgICAgICAgICd9J10pO1xuXG4gICAgLy8gQWRkaW5nIG90aGVyIHN0dWZmIGRvZXNuJ3QuXG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBub2RlLmFkZCh7fSk7XG4gICAgfSk7XG4gICAgYXNzZXJ0LnRocm93cyhmdW5jdGlvbiAoKSB7XG4gICAgICBub2RlLmFkZChmdW5jdGlvbiAoKSB7fSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAucHJlcGVuZCgpJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5vZGUgPSBuZXcgU291cmNlTm9kZShudWxsLCBudWxsLCBudWxsKTtcblxuICAgIC8vIFByZXBlbmRpbmcgYSBzdHJpbmcgd29ya3MuXG4gICAgbm9kZS5wcmVwZW5kKCdmdW5jdGlvbiBub29wKCkge30nKTtcbiAgICBhc3NlcnQuZXF1YWwobm9kZS5jaGlsZHJlblswXSwgJ2Z1bmN0aW9uIG5vb3AoKSB7fScpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuLmxlbmd0aCwgMSk7XG5cbiAgICAvLyBQcmVwZW5kaW5nIGFub3RoZXIgc291cmNlIG5vZGUgd29ya3MuXG4gICAgbm9kZS5wcmVwZW5kKG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwpKTtcbiAgICBhc3NlcnQuZXF1YWwobm9kZS5jaGlsZHJlblswXSwgJycpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuWzFdLCAnZnVuY3Rpb24gbm9vcCgpIHt9Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKG5vZGUuY2hpbGRyZW4ubGVuZ3RoLCAyKTtcblxuICAgIC8vIFByZXBlbmRpbmcgYW4gYXJyYXkgd29ya3MuXG4gICAgbm9kZS5wcmVwZW5kKFsnZnVuY3Rpb24gZm9vKCkgeycsXG4gICAgICAgICAgICAgIG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICdyZXR1cm4gMTA7JyksXG4gICAgICAgICAgICAgICd9J10pO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuWzBdLCAnZnVuY3Rpb24gZm9vKCkgeycpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuWzFdLCAncmV0dXJuIDEwOycpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuWzJdLCAnfScpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLmNoaWxkcmVuWzNdLCAnJyk7XG4gICAgYXNzZXJ0LmVxdWFsKG5vZGUuY2hpbGRyZW5bNF0sICdmdW5jdGlvbiBub29wKCkge30nKTtcbiAgICBhc3NlcnQuZXF1YWwobm9kZS5jaGlsZHJlbi5sZW5ndGgsIDUpO1xuXG4gICAgLy8gUHJlcGVuZGluZyBvdGhlciBzdHVmZiBkb2Vzbid0LlxuICAgIGFzc2VydC50aHJvd3MoZnVuY3Rpb24gKCkge1xuICAgICAgbm9kZS5wcmVwZW5kKHt9KTtcbiAgICB9KTtcbiAgICBhc3NlcnQudGhyb3dzKGZ1bmN0aW9uICgpIHtcbiAgICAgIG5vZGUucHJlcGVuZChmdW5jdGlvbiAoKSB7fSk7XG4gICAgfSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAudG9TdHJpbmcoKSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGFzc2VydC5lcXVhbCgobmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsnZnVuY3Rpb24gZm9vKCkgeycsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgJ3JldHVybiAxMDsnKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnfSddKSkudG9TdHJpbmcoKSxcbiAgICAgICAgICAgICAgICAgJ2Z1bmN0aW9uIGZvbygpIHtyZXR1cm4gMTA7fScpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLmpvaW4oKSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIGFzc2VydC5lcXVhbCgobmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsnYScsICdiJywgJ2MnLCAnZCddKSkuam9pbignLCAnKS50b1N0cmluZygpLFxuICAgICAgICAgICAgICAgICAnYSwgYiwgYywgZCcpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLndhbGsoKSddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsnKGZ1bmN0aW9uICgpIHtcXG4nLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcgICcsIG5ldyBTb3VyY2VOb2RlKDEsIDAsICdhLmpzJywgWydzb21lQ2FsbCgpJ10pLCAnO1xcbicsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJyAgJywgbmV3IFNvdXJjZU5vZGUoMiwgMCwgJ2IuanMnLCBbJ2lmIChmb28pIGJhcigpJ10pLCAnO1xcbicsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ30oKSk7J10pO1xuICAgIHZhciBleHBlY3RlZCA9IFtcbiAgICAgIHsgc3RyOiAnKGZ1bmN0aW9uICgpIHtcXG4nLCBzb3VyY2U6IG51bGwsICAgbGluZTogbnVsbCwgY29sdW1uOiBudWxsIH0sXG4gICAgICB7IHN0cjogJyAgJywgICAgICAgICAgICAgICBzb3VyY2U6IG51bGwsICAgbGluZTogbnVsbCwgY29sdW1uOiBudWxsIH0sXG4gICAgICB7IHN0cjogJ3NvbWVDYWxsKCknLCAgICAgICBzb3VyY2U6ICdhLmpzJywgbGluZTogMSwgICAgY29sdW1uOiAwICAgIH0sXG4gICAgICB7IHN0cjogJztcXG4nLCAgICAgICAgICAgICAgc291cmNlOiBudWxsLCAgIGxpbmU6IG51bGwsIGNvbHVtbjogbnVsbCB9LFxuICAgICAgeyBzdHI6ICcgICcsICAgICAgICAgICAgICAgc291cmNlOiBudWxsLCAgIGxpbmU6IG51bGwsIGNvbHVtbjogbnVsbCB9LFxuICAgICAgeyBzdHI6ICdpZiAoZm9vKSBiYXIoKScsICAgc291cmNlOiAnYi5qcycsIGxpbmU6IDIsICAgIGNvbHVtbjogMCAgICB9LFxuICAgICAgeyBzdHI6ICc7XFxuJywgICAgICAgICAgICAgIHNvdXJjZTogbnVsbCwgICBsaW5lOiBudWxsLCBjb2x1bW46IG51bGwgfSxcbiAgICAgIHsgc3RyOiAnfSgpKTsnLCAgICAgICAgICAgIHNvdXJjZTogbnVsbCwgICBsaW5lOiBudWxsLCBjb2x1bW46IG51bGwgfSxcbiAgICBdO1xuICAgIHZhciBpID0gMDtcbiAgICBub2RlLndhbGsoZnVuY3Rpb24gKGNodW5rLCBsb2MpIHtcbiAgICAgIGFzc2VydC5lcXVhbChleHBlY3RlZFtpXS5zdHIsIGNodW5rKTtcbiAgICAgIGFzc2VydC5lcXVhbChleHBlY3RlZFtpXS5zb3VyY2UsIGxvYy5zb3VyY2UpO1xuICAgICAgYXNzZXJ0LmVxdWFsKGV4cGVjdGVkW2ldLmxpbmUsIGxvYy5saW5lKTtcbiAgICAgIGFzc2VydC5lcXVhbChleHBlY3RlZFtpXS5jb2x1bW4sIGxvYy5jb2x1bW4pO1xuICAgICAgaSsrO1xuICAgIH0pO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLnJlcGxhY2VSaWdodCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIHZhciBub2RlO1xuXG4gICAgLy8gTm90IG5lc3RlZFxuICAgIG5vZGUgPSBuZXcgU291cmNlTm9kZShudWxsLCBudWxsLCBudWxsLCAnaGVsbG8gd29ybGQnKTtcbiAgICBub2RlLnJlcGxhY2VSaWdodCgvd29ybGQvLCAndW5pdmVyc2UnKTtcbiAgICBhc3NlcnQuZXF1YWwobm9kZS50b1N0cmluZygpLCAnaGVsbG8gdW5pdmVyc2UnKTtcblxuICAgIC8vIE5lc3RlZFxuICAgIG5vZGUgPSBuZXcgU291cmNlTm9kZShudWxsLCBudWxsLCBudWxsLFxuICAgICAgICAgICAgICAgICAgICAgICAgICBbbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgJ2hleSBzZXh5IG1hbWEsICcpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgJ3dhbnQgdG8ga2lsbCBhbGwgaHVtYW5zPycpXSk7XG4gICAgbm9kZS5yZXBsYWNlUmlnaHQoL2tpbGwgYWxsIGh1bWFucy8sICd3YXRjaCBGdXR1cmFtYScpO1xuICAgIGFzc2VydC5lcXVhbChub2RlLnRvU3RyaW5nKCksICdoZXkgc2V4eSBtYW1hLCB3YW50IHRvIHdhdGNoIEZ1dHVyYW1hPycpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCgpJ10gPSBmb3JFYWNoTmV3bGluZShmdW5jdGlvbiAoYXNzZXJ0LCBubCkge1xuICAgIHZhciBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsnKGZ1bmN0aW9uICgpIHsnICsgbmwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJyAgJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldyBTb3VyY2VOb2RlKDEsIDAsICdhLmpzJywgJ3NvbWVDYWxsJywgJ29yaWdpbmFsQ2FsbCcpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgOCwgJ2EuanMnLCAnKCknKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICc7JyArIG5sLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcgICcsIG5ldyBTb3VyY2VOb2RlKDIsIDAsICdiLmpzJywgWydpZiAoZm9vKSBiYXIoKSddKSwgJzsnICsgbmwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ30oKSk7J10pO1xuICAgIHZhciByZXN1bHQgPSBub2RlLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKHJlc3VsdC5jb2RlLCBbXG4gICAgICAnKGZ1bmN0aW9uICgpIHsnLFxuICAgICAgJyAgc29tZUNhbGwoKTsnLFxuICAgICAgJyAgaWYgKGZvbykgYmFyKCk7JyxcbiAgICAgICd9KCkpOydcbiAgICBdLmpvaW4obmwpKTtcblxuICAgIHZhciBtYXAgPSByZXN1bHQubWFwO1xuICAgIHZhciBtYXBXaXRob3V0T3B0aW9ucyA9IG5vZGUudG9TdHJpbmdXaXRoU291cmNlTWFwKCkubWFwO1xuXG4gICAgYXNzZXJ0Lm9rKG1hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvciwgJ21hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvcicpO1xuICAgIGFzc2VydC5vayhtYXBXaXRob3V0T3B0aW9ucyBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvciwgJ21hcFdpdGhvdXRPcHRpb25zIGluc3RhbmNlb2YgU291cmNlTWFwR2VuZXJhdG9yJyk7XG4gICAgYXNzZXJ0Lm9rKCEoJ2ZpbGUnIGluIG1hcFdpdGhvdXRPcHRpb25zKSk7XG4gICAgbWFwV2l0aG91dE9wdGlvbnMuX2ZpbGUgPSAnZm9vLmpzJztcbiAgICB1dGlsLmFzc2VydEVxdWFsTWFwcyhhc3NlcnQsIG1hcC50b0pTT04oKSwgbWFwV2l0aG91dE9wdGlvbnMudG9KU09OKCkpO1xuXG4gICAgbWFwID0gbmV3IFNvdXJjZU1hcENvbnN1bWVyKG1hcC50b1N0cmluZygpKTtcblxuICAgIHZhciBhY3R1YWw7XG5cbiAgICBhY3R1YWwgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiAxLFxuICAgICAgY29sdW1uOiA0XG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5zb3VyY2UsIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwubGluZSwgbnVsbCk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5jb2x1bW4sIG51bGwpO1xuXG4gICAgYWN0dWFsID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMixcbiAgICAgIGNvbHVtbjogMlxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwuc291cmNlLCAnYS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwubGluZSwgMSk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5jb2x1bW4sIDApO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwubmFtZSwgJ29yaWdpbmFsQ2FsbCcpO1xuXG4gICAgYWN0dWFsID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMyxcbiAgICAgIGNvbHVtbjogMlxuICAgIH0pO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwuc291cmNlLCAnYi5qcycpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwubGluZSwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5jb2x1bW4sIDApO1xuXG4gICAgYWN0dWFsID0gbWFwLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgbGluZTogMyxcbiAgICAgIGNvbHVtbjogMTZcbiAgICB9KTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsLnNvdXJjZSwgbnVsbCk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5saW5lLCBudWxsKTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsLmNvbHVtbiwgbnVsbCk7XG5cbiAgICBhY3R1YWwgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICBsaW5lOiA0LFxuICAgICAgY29sdW1uOiAyXG4gICAgfSk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5zb3VyY2UsIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWwubGluZSwgbnVsbCk7XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbC5jb2x1bW4sIG51bGwpO1xuICB9KTtcblxuICBleHBvcnRzWyd0ZXN0IC5mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcCgpJ10gPSBmb3JFYWNoTmV3bGluZShmdW5jdGlvbiAoYXNzZXJ0LCBubCkge1xuICAgIHZhciB0ZXN0Q29kZSA9IHV0aWwudGVzdEdlbmVyYXRlZENvZGUucmVwbGFjZSgvXFxuL2csIG5sKTtcbiAgICB2YXIgbm9kZSA9IFNvdXJjZU5vZGUuZnJvbVN0cmluZ1dpdGhTb3VyY2VNYXAoXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0ZXN0Q29kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG5ldyBTb3VyY2VNYXBDb25zdW1lcih1dGlsLnRlc3RNYXApKTtcblxuICAgIHZhciByZXN1bHQgPSBub2RlLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnbWluLmpzJ1xuICAgIH0pO1xuICAgIHZhciBtYXAgPSByZXN1bHQubWFwO1xuICAgIHZhciBjb2RlID0gcmVzdWx0LmNvZGU7XG5cbiAgICBhc3NlcnQuZXF1YWwoY29kZSwgdGVzdENvZGUpO1xuICAgIGFzc2VydC5vayhtYXAgaW5zdGFuY2VvZiBTb3VyY2VNYXBHZW5lcmF0b3IsICdtYXAgaW5zdGFuY2VvZiBTb3VyY2VNYXBHZW5lcmF0b3InKTtcbiAgICBtYXAgPSBtYXAudG9KU09OKCk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC52ZXJzaW9uLCB1dGlsLnRlc3RNYXAudmVyc2lvbik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5maWxlLCB1dGlsLnRlc3RNYXAuZmlsZSk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5tYXBwaW5ncywgdXRpbC50ZXN0TWFwLm1hcHBpbmdzKTtcbiAgfSk7XG5cbiAgZXhwb3J0c1sndGVzdCAuZnJvbVN0cmluZ1dpdGhTb3VyY2VNYXAoKSBlbXB0eSBtYXAnXSA9IGZvckVhY2hOZXdsaW5lKGZ1bmN0aW9uIChhc3NlcnQsIG5sKSB7XG4gICAgdmFyIG5vZGUgPSBTb3VyY2VOb2RlLmZyb21TdHJpbmdXaXRoU291cmNlTWFwKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdXRpbC50ZXN0R2VuZXJhdGVkQ29kZS5yZXBsYWNlKC9cXG4vZywgbmwpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuZW1wdHlNYXApKTtcbiAgICB2YXIgcmVzdWx0ID0gbm9kZS50b1N0cmluZ1dpdGhTb3VyY2VNYXAoe1xuICAgICAgZmlsZTogJ21pbi5qcydcbiAgICB9KTtcbiAgICB2YXIgbWFwID0gcmVzdWx0Lm1hcDtcbiAgICB2YXIgY29kZSA9IHJlc3VsdC5jb2RlO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGNvZGUsIHV0aWwudGVzdEdlbmVyYXRlZENvZGUucmVwbGFjZSgvXFxuL2csIG5sKSk7XG4gICAgYXNzZXJ0Lm9rKG1hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvciwgJ21hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvcicpO1xuICAgIG1hcCA9IG1hcC50b0pTT04oKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnZlcnNpb24sIHV0aWwuZW1wdHlNYXAudmVyc2lvbik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5maWxlLCB1dGlsLmVtcHR5TWFwLmZpbGUpO1xuICAgIGFzc2VydC5lcXVhbChtYXAubWFwcGluZ3MubGVuZ3RoLCB1dGlsLmVtcHR5TWFwLm1hcHBpbmdzLmxlbmd0aCk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5tYXBwaW5ncywgdXRpbC5lbXB0eU1hcC5tYXBwaW5ncyk7XG4gIH0pO1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLmZyb21TdHJpbmdXaXRoU291cmNlTWFwKCkgY29tcGxleCB2ZXJzaW9uJ10gPSBmb3JFYWNoTmV3bGluZShmdW5jdGlvbiAoYXNzZXJ0LCBubCkge1xuICAgIHZhciBpbnB1dCA9IG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwsIFtcbiAgICAgIFwiKGZ1bmN0aW9uKCkge1wiICsgbmwsXG4gICAgICAgIFwiICB2YXIgVGVzdCA9IHt9O1wiICsgbmwsXG4gICAgICAgIFwiICBcIiwgbmV3IFNvdXJjZU5vZGUoMSwgMCwgXCJhLmpzXCIsIFwiVGVzdC5BID0geyB2YWx1ZTogMTIzNCB9O1wiICsgbmwpLFxuICAgICAgICBcIiAgXCIsIG5ldyBTb3VyY2VOb2RlKDIsIDAsIFwiYS5qc1wiLCBcIlRlc3QuQS54ID0gJ3h5eic7XCIpLCBubCxcbiAgICAgICAgXCJ9KCkpO1wiICsgbmwsXG4gICAgICAgIFwiLyogR2VuZXJhdGVkIFNvdXJjZSAqL1wiXSk7XG4gICAgaW5wdXQgPSBpbnB1dC50b1N0cmluZ1dpdGhTb3VyY2VNYXAoe1xuICAgICAgZmlsZTogJ2Zvby5qcydcbiAgICB9KTtcblxuICAgIHZhciBub2RlID0gU291cmNlTm9kZS5mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcChcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlucHV0LmNvZGUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuZXcgU291cmNlTWFwQ29uc3VtZXIoaW5wdXQubWFwLnRvU3RyaW5nKCkpKTtcblxuICAgIHZhciByZXN1bHQgPSBub2RlLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuICAgIHZhciBtYXAgPSByZXN1bHQubWFwO1xuICAgIHZhciBjb2RlID0gcmVzdWx0LmNvZGU7XG5cbiAgICBhc3NlcnQuZXF1YWwoY29kZSwgaW5wdXQuY29kZSk7XG4gICAgYXNzZXJ0Lm9rKG1hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvciwgJ21hcCBpbnN0YW5jZW9mIFNvdXJjZU1hcEdlbmVyYXRvcicpO1xuICAgIG1hcCA9IG1hcC50b0pTT04oKTtcbiAgICB2YXIgaW5wdXRNYXAgPSBpbnB1dC5tYXAudG9KU09OKCk7XG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBtYXAsIGlucHV0TWFwKTtcbiAgfSk7XG5cbiAgZXhwb3J0c1sndGVzdCAuZnJvbVN0cmluZ1dpdGhTb3VyY2VNYXAoKSB0aGlyZCBhcmd1bWVudCddID0gZnVuY3Rpb24gKGFzc2VydCkge1xuICAgIC8vIEFzc3VtZSB0aGUgZm9sbG93aW5nIGRpcmVjdG9yeSBzdHJ1Y3R1cmU6XG4gICAgLy9cbiAgICAvLyBodHRwOi8vZm9vLm9yZy9cbiAgICAvLyAgIGJhci5jb2ZmZWVcbiAgICAvLyAgIGFwcC9cbiAgICAvLyAgICAgY29mZmVlL1xuICAgIC8vICAgICAgIGZvby5jb2ZmZWVcbiAgICAvLyAgICAgICBjb2ZmZWVCdW5kbGUuanMgIyBNYWRlIGZyb20ge2ZvbyxiYXIsYmF6fS5jb2ZmZWVcbiAgICAvLyAgICAgICBtYXBzL1xuICAgIC8vICAgICAgICAgY29mZmVlQnVuZGxlLmpzLm1hcFxuICAgIC8vICAgICBqcy9cbiAgICAvLyAgICAgICBmb28uanNcbiAgICAvLyAgICAgcHVibGljL1xuICAgIC8vICAgICAgIGFwcC5qcyAjIE1hZGUgZnJvbSB7Zm9vLGNvZmZlZUJ1bmRsZX0uanNcbiAgICAvLyAgICAgICBhcHAuanMubWFwXG4gICAgLy9cbiAgICAvLyBodHRwOi8vd3d3LmV4YW1wbGUuY29tL1xuICAgIC8vICAgYmF6LmNvZmZlZVxuXG4gICAgdmFyIGNvZmZlZUJ1bmRsZSA9IG5ldyBTb3VyY2VOb2RlKDEsIDAsICdmb28uY29mZmVlJywgJ2Zvbyhjb2ZmZWUpO1xcbicpO1xuICAgIGNvZmZlZUJ1bmRsZS5zZXRTb3VyY2VDb250ZW50KCdmb28uY29mZmVlJywgJ2ZvbyBjb2ZmZWUnKTtcbiAgICBjb2ZmZWVCdW5kbGUuYWRkKG5ldyBTb3VyY2VOb2RlKDIsIDAsICcvYmFyLmNvZmZlZScsICdiYXIoY29mZmVlKTtcXG4nKSk7XG4gICAgY29mZmVlQnVuZGxlLmFkZChuZXcgU291cmNlTm9kZSgzLCAwLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJywgJ2Jheihjb2ZmZWUpOycpKTtcbiAgICBjb2ZmZWVCdW5kbGUgPSBjb2ZmZWVCdW5kbGUudG9TdHJpbmdXaXRoU291cmNlTWFwKHtcbiAgICAgIGZpbGU6ICdmb28uanMnLFxuICAgICAgc291cmNlUm9vdDogJy4uJ1xuICAgIH0pO1xuXG4gICAgdmFyIGZvbyA9IG5ldyBTb3VyY2VOb2RlKDEsIDAsICdmb28uanMnLCAnZm9vKGpzKTsnKTtcblxuICAgIHZhciB0ZXN0ID0gZnVuY3Rpb24ocmVsYXRpdmVQYXRoLCBleHBlY3RlZFNvdXJjZXMpIHtcbiAgICAgIHZhciBhcHAgPSBuZXcgU291cmNlTm9kZSgpO1xuICAgICAgYXBwLmFkZChTb3VyY2VOb2RlLmZyb21TdHJpbmdXaXRoU291cmNlTWFwKFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2ZmZWVCdW5kbGUuY29kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbmV3IFNvdXJjZU1hcENvbnN1bWVyKGNvZmZlZUJ1bmRsZS5tYXAudG9TdHJpbmcoKSksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHJlbGF0aXZlUGF0aCkpO1xuICAgICAgYXBwLmFkZChmb28pO1xuICAgICAgdmFyIGkgPSAwO1xuICAgICAgYXBwLndhbGsoZnVuY3Rpb24gKGNodW5rLCBsb2MpIHtcbiAgICAgICAgYXNzZXJ0LmVxdWFsKGxvYy5zb3VyY2UsIGV4cGVjdGVkU291cmNlc1tpXSk7XG4gICAgICAgIGkrKztcbiAgICAgIH0pO1xuICAgICAgYXBwLndhbGtTb3VyY2VDb250ZW50cyhmdW5jdGlvbiAoc291cmNlRmlsZSwgc291cmNlQ29udGVudCkge1xuICAgICAgICBhc3NlcnQuZXF1YWwoc291cmNlRmlsZSwgZXhwZWN0ZWRTb3VyY2VzWzBdKTtcbiAgICAgICAgYXNzZXJ0LmVxdWFsKHNvdXJjZUNvbnRlbnQsICdmb28gY29mZmVlJyk7XG4gICAgICB9KVxuICAgIH07XG5cbiAgICB0ZXN0KCcuLi9jb2ZmZWUvbWFwcycsIFtcbiAgICAgICcuLi9jb2ZmZWUvZm9vLmNvZmZlZScsXG4gICAgICAnL2Jhci5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYmF6LmNvZmZlZScsXG4gICAgICAnZm9vLmpzJ1xuICAgIF0pO1xuXG4gICAgLy8gSWYgdGhlIHRoaXJkIHBhcmFtZXRlciBpcyBvbWl0dGVkIG9yIHNldCB0byB0aGUgY3VycmVudCB3b3JraW5nXG4gICAgLy8gZGlyZWN0b3J5IHdlIGdldCBpbmNvcnJlY3Qgc291cmNlIHBhdGhzOlxuXG4gICAgdGVzdCh1bmRlZmluZWQsIFtcbiAgICAgICcuLi9mb28uY29mZmVlJyxcbiAgICAgICcvYmFyLmNvZmZlZScsXG4gICAgICAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJyxcbiAgICAgICdmb28uanMnXG4gICAgXSk7XG5cbiAgICB0ZXN0KCcnLCBbXG4gICAgICAnLi4vZm9vLmNvZmZlZScsXG4gICAgICAnL2Jhci5jb2ZmZWUnLFxuICAgICAgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYmF6LmNvZmZlZScsXG4gICAgICAnZm9vLmpzJ1xuICAgIF0pO1xuXG4gICAgdGVzdCgnLicsIFtcbiAgICAgICcuLi9mb28uY29mZmVlJyxcbiAgICAgICcvYmFyLmNvZmZlZScsXG4gICAgICAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJyxcbiAgICAgICdmb28uanMnXG4gICAgXSk7XG5cbiAgICB0ZXN0KCcuLycsIFtcbiAgICAgICcuLi9mb28uY29mZmVlJyxcbiAgICAgICcvYmFyLmNvZmZlZScsXG4gICAgICAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9iYXouY29mZmVlJyxcbiAgICAgICdmb28uanMnXG4gICAgXSk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAudG9TdHJpbmdXaXRoU291cmNlTWFwKCkgbWVyZ2luZyBkdXBsaWNhdGUgbWFwcGluZ3MnXSA9IGZvckVhY2hOZXdsaW5lKGZ1bmN0aW9uIChhc3NlcnQsIG5sKSB7XG4gICAgdmFyIGlucHV0ID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCwgW1xuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgXCJhLmpzXCIsIFwiKGZ1bmN0aW9uXCIpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgXCJhLmpzXCIsIFwiKCkge1wiICsgbmwpLFxuICAgICAgXCIgIFwiLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgXCJhLmpzXCIsIFwidmFyIFRlc3QgPSBcIiksXG4gICAgICBuZXcgU291cmNlTm9kZSgxLCAwLCBcImIuanNcIiwgXCJ7fTtcIiArIG5sKSxcbiAgICAgIG5ldyBTb3VyY2VOb2RlKDIsIDAsIFwiYi5qc1wiLCBcIlRlc3RcIiksXG4gICAgICBuZXcgU291cmNlTm9kZSgyLCAwLCBcImIuanNcIiwgXCIuQVwiLCBcIkFcIiksXG4gICAgICBuZXcgU291cmNlTm9kZSgyLCAyMCwgXCJiLmpzXCIsIFwiID0geyB2YWx1ZTogXCIsIFwiQVwiKSxcbiAgICAgIFwiMTIzNFwiLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgNDAsIFwiYi5qc1wiLCBcIiB9O1wiICsgbmwsIFwiQVwiKSxcbiAgICAgIFwifSgpKTtcIiArIG5sLFxuICAgICAgXCIvKiBHZW5lcmF0ZWQgU291cmNlICovXCJcbiAgICBdKTtcbiAgICBpbnB1dCA9IGlucHV0LnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGlucHV0LmNvZGUsIFtcbiAgICAgIFwiKGZ1bmN0aW9uKCkge1wiLFxuICAgICAgXCIgIHZhciBUZXN0ID0ge307XCIsXG4gICAgICBcIlRlc3QuQSA9IHsgdmFsdWU6IDEyMzQgfTtcIixcbiAgICAgIFwifSgpKTtcIixcbiAgICAgIFwiLyogR2VuZXJhdGVkIFNvdXJjZSAqL1wiXG4gICAgXS5qb2luKG5sKSlcblxuICAgIHZhciBjb3JyZWN0TWFwID0gbmV3IFNvdXJjZU1hcEdlbmVyYXRvcih7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuICAgIGNvcnJlY3RNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgLy8gSGVyZSBpcyBubyBuZWVkIGZvciBhIGVtcHR5IG1hcHBpbmcsXG4gICAgLy8gYmVjYXVzZSBtYXBwaW5ncyBlbmRzIGF0IGVvbFxuICAgIGNvcnJlY3RNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMiwgY29sdW1uOiAyIH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAyLCBjb2x1bW46IDEzIH0sXG4gICAgICBzb3VyY2U6ICdiLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAzLCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ2IuanMnLFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAwIH1cbiAgICB9KTtcbiAgICBjb3JyZWN0TWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDMsIGNvbHVtbjogNCB9LFxuICAgICAgc291cmNlOiAnYi5qcycsXG4gICAgICBuYW1lOiAnQScsXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDAgfVxuICAgIH0pO1xuICAgIGNvcnJlY3RNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMywgY29sdW1uOiA2IH0sXG4gICAgICBzb3VyY2U6ICdiLmpzJyxcbiAgICAgIG5hbWU6ICdBJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMjAgfVxuICAgIH0pO1xuICAgIC8vIFRoaXMgZW1wdHkgbWFwcGluZyBpcyByZXF1aXJlZCxcbiAgICAvLyBiZWNhdXNlIHRoZXJlIGlzIGEgaG9sZSBpbiB0aGUgbWlkZGxlIG9mIHRoZSBsaW5lXG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAzLCBjb2x1bW46IDE4IH1cbiAgICB9KTtcbiAgICBjb3JyZWN0TWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDMsIGNvbHVtbjogMjIgfSxcbiAgICAgIHNvdXJjZTogJ2IuanMnLFxuICAgICAgbmFtZTogJ0EnLFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiA0MCB9XG4gICAgfSk7XG4gICAgLy8gSGVyZSBpcyBubyBuZWVkIGZvciBhIGVtcHR5IG1hcHBpbmcsXG4gICAgLy8gYmVjYXVzZSBtYXBwaW5ncyBlbmRzIGF0IGVvbFxuXG4gICAgdmFyIGlucHV0TWFwID0gaW5wdXQubWFwLnRvSlNPTigpO1xuICAgIGNvcnJlY3RNYXAgPSBjb3JyZWN0TWFwLnRvSlNPTigpO1xuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgaW5wdXRNYXAsIGNvcnJlY3RNYXApO1xuICB9KTtcblxuICBleHBvcnRzWyd0ZXN0IC50b1N0cmluZ1dpdGhTb3VyY2VNYXAoKSBtdWx0aS1saW5lIFNvdXJjZU5vZGVzJ10gPSBmb3JFYWNoTmV3bGluZShmdW5jdGlvbiAoYXNzZXJ0LCBubCkge1xuICAgIHZhciBpbnB1dCA9IG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwsIFtcbiAgICAgIG5ldyBTb3VyY2VOb2RlKDEsIDAsIFwiYS5qc1wiLCBcIihmdW5jdGlvbigpIHtcIiArIG5sICsgXCJ2YXIgbmV4dExpbmUgPSAxO1wiICsgbmwgKyBcImFub3RoZXJMaW5lKCk7XCIgKyBubCksXG4gICAgICBuZXcgU291cmNlTm9kZSgyLCAyLCBcImIuanNcIiwgXCJUZXN0LmNhbGwodGhpcywgMTIzKTtcIiArIG5sKSxcbiAgICAgIG5ldyBTb3VyY2VOb2RlKDIsIDIsIFwiYi5qc1wiLCBcInRoaXNbJ3N0dWZmJ10gPSAndic7XCIgKyBubCksXG4gICAgICBuZXcgU291cmNlTm9kZSgyLCAyLCBcImIuanNcIiwgXCJhbm90aGVyTGluZSgpO1wiICsgbmwpLFxuICAgICAgXCIvKlwiICsgbmwgKyBcIkdlbmVyYXRlZFwiICsgbmwgKyBcIlNvdXJjZVwiICsgbmwgKyBcIiovXCIgKyBubCxcbiAgICAgIG5ldyBTb3VyY2VOb2RlKDMsIDQsIFwiYy5qc1wiLCBcImFub3RoZXJMaW5lKCk7XCIgKyBubCksXG4gICAgICBcIi8qXCIgKyBubCArIFwiR2VuZXJhdGVkXCIgKyBubCArIFwiU291cmNlXCIgKyBubCArIFwiKi9cIlxuICAgIF0pO1xuICAgIGlucHV0ID0gaW5wdXQudG9TdHJpbmdXaXRoU291cmNlTWFwKHtcbiAgICAgIGZpbGU6ICdmb28uanMnXG4gICAgfSk7XG5cbiAgICBhc3NlcnQuZXF1YWwoaW5wdXQuY29kZSwgW1xuICAgICAgXCIoZnVuY3Rpb24oKSB7XCIsXG4gICAgICBcInZhciBuZXh0TGluZSA9IDE7XCIsXG4gICAgICBcImFub3RoZXJMaW5lKCk7XCIsXG4gICAgICBcIlRlc3QuY2FsbCh0aGlzLCAxMjMpO1wiLFxuICAgICAgXCJ0aGlzWydzdHVmZiddID0gJ3YnO1wiLFxuICAgICAgXCJhbm90aGVyTGluZSgpO1wiLFxuICAgICAgXCIvKlwiLFxuICAgICAgXCJHZW5lcmF0ZWRcIixcbiAgICAgIFwiU291cmNlXCIsXG4gICAgICBcIiovXCIsXG4gICAgICBcImFub3RoZXJMaW5lKCk7XCIsXG4gICAgICBcIi8qXCIsXG4gICAgICBcIkdlbmVyYXRlZFwiLFxuICAgICAgXCJTb3VyY2VcIixcbiAgICAgIFwiKi9cIlxuICAgIF0uam9pbihubCkpO1xuXG4gICAgdmFyIGNvcnJlY3RNYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdmb28uanMnXG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxLCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ2EuanMnLFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAwIH1cbiAgICB9KTtcbiAgICBjb3JyZWN0TWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDIsIGNvbHVtbjogMCB9LFxuICAgICAgc291cmNlOiAnYS5qcycsXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAxLCBjb2x1bW46IDAgfVxuICAgIH0pO1xuICAgIGNvcnJlY3RNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogMywgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdhLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDEsIGNvbHVtbjogMCB9XG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiA0LCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ2IuanMnLFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMiwgY29sdW1uOiAyIH1cbiAgICB9KTtcbiAgICBjb3JyZWN0TWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDUsIGNvbHVtbjogMCB9LFxuICAgICAgc291cmNlOiAnYi5qcycsXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDIgfVxuICAgIH0pO1xuICAgIGNvcnJlY3RNYXAuYWRkTWFwcGluZyh7XG4gICAgICBnZW5lcmF0ZWQ6IHsgbGluZTogNiwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdiLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDIsIGNvbHVtbjogMiB9XG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAxMSwgY29sdW1uOiAwIH0sXG4gICAgICBzb3VyY2U6ICdjLmpzJyxcbiAgICAgIG9yaWdpbmFsOiB7IGxpbmU6IDMsIGNvbHVtbjogNCB9XG4gICAgfSk7XG5cbiAgICB2YXIgaW5wdXRNYXAgPSBpbnB1dC5tYXAudG9KU09OKCk7XG4gICAgY29ycmVjdE1hcCA9IGNvcnJlY3RNYXAudG9KU09OKCk7XG4gICAgdXRpbC5hc3NlcnRFcXVhbE1hcHMoYXNzZXJ0LCBpbnB1dE1hcCwgY29ycmVjdE1hcCk7XG4gIH0pO1xuXG4gIGV4cG9ydHNbJ3Rlc3QgLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCgpIHdpdGggZW1wdHkgc3RyaW5nJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIG5vZGUgPSBuZXcgU291cmNlTm9kZSgxLCAwLCAnZW1wdHkuanMnLCAnJyk7XG4gICAgdmFyIHJlc3VsdCA9IG5vZGUudG9TdHJpbmdXaXRoU291cmNlTWFwKCk7XG4gICAgYXNzZXJ0LmVxdWFsKHJlc3VsdC5jb2RlLCAnJyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCAudG9TdHJpbmdXaXRoU291cmNlTWFwKCkgd2l0aCBjb25zZWN1dGl2ZSBuZXdsaW5lcyddID0gZm9yRWFjaE5ld2xpbmUoZnVuY3Rpb24gKGFzc2VydCwgbmwpIHtcbiAgICB2YXIgaW5wdXQgPSBuZXcgU291cmNlTm9kZShudWxsLCBudWxsLCBudWxsLCBbXG4gICAgICBcIi8qKiovXCIgKyBubCArIG5sLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMSwgMCwgXCJhLmpzXCIsIFwiJ3VzZSBzdHJpY3QnO1wiICsgbmwpLFxuICAgICAgbmV3IFNvdXJjZU5vZGUoMiwgMCwgXCJhLmpzXCIsIFwiYSgpO1wiKSxcbiAgICBdKTtcbiAgICBpbnB1dCA9IGlucHV0LnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGlucHV0LmNvZGUsIFtcbiAgICAgIFwiLyoqKi9cIixcbiAgICAgIFwiXCIsXG4gICAgICBcIid1c2Ugc3RyaWN0JztcIixcbiAgICAgIFwiYSgpO1wiLFxuICAgIF0uam9pbihubCkpO1xuXG4gICAgdmFyIGNvcnJlY3RNYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKHtcbiAgICAgIGZpbGU6ICdmb28uanMnXG4gICAgfSk7XG4gICAgY29ycmVjdE1hcC5hZGRNYXBwaW5nKHtcbiAgICAgIGdlbmVyYXRlZDogeyBsaW5lOiAzLCBjb2x1bW46IDAgfSxcbiAgICAgIHNvdXJjZTogJ2EuanMnLFxuICAgICAgb3JpZ2luYWw6IHsgbGluZTogMSwgY29sdW1uOiAwIH1cbiAgICB9KTtcbiAgICBjb3JyZWN0TWFwLmFkZE1hcHBpbmcoe1xuICAgICAgZ2VuZXJhdGVkOiB7IGxpbmU6IDQsIGNvbHVtbjogMCB9LFxuICAgICAgc291cmNlOiAnYS5qcycsXG4gICAgICBvcmlnaW5hbDogeyBsaW5lOiAyLCBjb2x1bW46IDAgfVxuICAgIH0pO1xuXG4gICAgdmFyIGlucHV0TWFwID0gaW5wdXQubWFwLnRvSlNPTigpO1xuICAgIGNvcnJlY3RNYXAgPSBjb3JyZWN0TWFwLnRvSlNPTigpO1xuICAgIHV0aWwuYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgaW5wdXRNYXAsIGNvcnJlY3RNYXApO1xuICB9KTtcblxuICBleHBvcnRzWyd0ZXN0IHNldFNvdXJjZUNvbnRlbnQgd2l0aCB0b1N0cmluZ1dpdGhTb3VyY2VNYXAnXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICB2YXIgYU5vZGUgPSBuZXcgU291cmNlTm9kZSgxLCAxLCAnYS5qcycsICdhJyk7XG4gICAgYU5vZGUuc2V0U291cmNlQ29udGVudCgnYS5qcycsICdzb21lQ29udGVudCcpO1xuICAgIHZhciBub2RlID0gbmV3IFNvdXJjZU5vZGUobnVsbCwgbnVsbCwgbnVsbCxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFsnKGZ1bmN0aW9uICgpIHtcXG4nLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcgICcsIGFOb2RlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICcgICcsIG5ldyBTb3VyY2VOb2RlKDEsIDEsICdiLmpzJywgJ2InKSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnfSgpKTsnXSk7XG4gICAgbm9kZS5zZXRTb3VyY2VDb250ZW50KCdiLmpzJywgJ290aGVyQ29udGVudCcpO1xuICAgIHZhciBtYXAgPSBub2RlLnRvU3RyaW5nV2l0aFNvdXJjZU1hcCh7XG4gICAgICBmaWxlOiAnZm9vLmpzJ1xuICAgIH0pLm1hcDtcblxuICAgIGFzc2VydC5vayhtYXAgaW5zdGFuY2VvZiBTb3VyY2VNYXBHZW5lcmF0b3IsICdtYXAgaW5zdGFuY2VvZiBTb3VyY2VNYXBHZW5lcmF0b3InKTtcbiAgICBtYXAgPSBuZXcgU291cmNlTWFwQ29uc3VtZXIobWFwLnRvU3RyaW5nKCkpO1xuXG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VzLmxlbmd0aCwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VzWzBdLCAnYS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChtYXAuc291cmNlc1sxXSwgJ2IuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwobWFwLnNvdXJjZXNDb250ZW50Lmxlbmd0aCwgMik7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VzQ29udGVudFswXSwgJ3NvbWVDb250ZW50Jyk7XG4gICAgYXNzZXJ0LmVxdWFsKG1hcC5zb3VyY2VzQ29udGVudFsxXSwgJ290aGVyQ29udGVudCcpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgd2Fsa1NvdXJjZUNvbnRlbnRzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIGFOb2RlID0gbmV3IFNvdXJjZU5vZGUoMSwgMSwgJ2EuanMnLCAnYScpO1xuICAgIGFOb2RlLnNldFNvdXJjZUNvbnRlbnQoJ2EuanMnLCAnc29tZUNvbnRlbnQnKTtcbiAgICB2YXIgbm9kZSA9IG5ldyBTb3VyY2VOb2RlKG51bGwsIG51bGwsIG51bGwsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICBbJyhmdW5jdGlvbiAoKSB7XFxuJyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnICAnLCBhTm9kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnICAnLCBuZXcgU291cmNlTm9kZSgxLCAxLCAnYi5qcycsICdiJyksXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgJ30oKSk7J10pO1xuICAgIG5vZGUuc2V0U291cmNlQ29udGVudCgnYi5qcycsICdvdGhlckNvbnRlbnQnKTtcbiAgICB2YXIgcmVzdWx0cyA9IFtdO1xuICAgIG5vZGUud2Fsa1NvdXJjZUNvbnRlbnRzKGZ1bmN0aW9uIChzb3VyY2VGaWxlLCBzb3VyY2VDb250ZW50KSB7XG4gICAgICByZXN1bHRzLnB1c2goW3NvdXJjZUZpbGUsIHNvdXJjZUNvbnRlbnRdKTtcbiAgICB9KTtcbiAgICBhc3NlcnQuZXF1YWwocmVzdWx0cy5sZW5ndGgsIDIpO1xuICAgIGFzc2VydC5lcXVhbChyZXN1bHRzWzBdWzBdLCAnYS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChyZXN1bHRzWzBdWzFdLCAnc29tZUNvbnRlbnQnKTtcbiAgICBhc3NlcnQuZXF1YWwocmVzdWx0c1sxXVswXSwgJ2IuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwocmVzdWx0c1sxXVsxXSwgJ290aGVyQ29udGVudCcpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL3Rlc3QvdGVzdC1zb3VyY2Utbm9kZS5qc1xuICoqIG1vZHVsZSBpZCA9IDBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuLi9saWIvdXRpbCcpO1xuXG4gIC8vIFRoaXMgaXMgYSB0ZXN0IG1hcHBpbmcgd2hpY2ggbWFwcyBmdW5jdGlvbnMgZnJvbSB0d28gZGlmZmVyZW50IGZpbGVzXG4gIC8vIChvbmUuanMgYW5kIHR3by5qcykgdG8gYSBtaW5pZmllZCBnZW5lcmF0ZWQgc291cmNlLlxuICAvL1xuICAvLyBIZXJlIGlzIG9uZS5qczpcbiAgLy9cbiAgLy8gICBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xuICAvLyAgICAgcmV0dXJuIGJheihiYXIpO1xuICAvLyAgIH07XG4gIC8vXG4gIC8vIEhlcmUgaXMgdHdvLmpzOlxuICAvL1xuICAvLyAgIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xuICAvLyAgICAgcmV0dXJuIG4gKyAxO1xuICAvLyAgIH07XG4gIC8vXG4gIC8vIEFuZCBoZXJlIGlzIHRoZSBnZW5lcmF0ZWQgY29kZSAobWluLmpzKTpcbiAgLy9cbiAgLy8gICBPTkUuZm9vPWZ1bmN0aW9uKGEpe3JldHVybiBiYXooYSk7fTtcbiAgLy8gICBUV08uaW5jPWZ1bmN0aW9uKGEpe3JldHVybiBhKzE7fTtcbiAgZXhwb3J0cy50ZXN0R2VuZXJhdGVkQ29kZSA9IFwiIE9ORS5mb289ZnVuY3Rpb24oYSl7cmV0dXJuIGJheihhKTt9O1xcblwiK1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgXCIgVFdPLmluYz1mdW5jdGlvbihhKXtyZXR1cm4gYSsxO307XCI7XG4gIGV4cG9ydHMudGVzdE1hcCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIHNvdXJjZVJvb3Q6ICcvdGhlL3Jvb3QnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcE5vU291cmNlUm9vdCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIGV4cG9ydHMudGVzdE1hcEVtcHR5U291cmNlUm9vdCA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIG5hbWVzOiBbJ2JhcicsICdiYXonLCAnbiddLFxuICAgIHNvdXJjZXM6IFsnb25lLmpzJywgJ3R3by5qcyddLFxuICAgIHNvdXJjZVJvb3Q6ICcnLFxuICAgIG1hcHBpbmdzOiAnQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSUQ7Q0NEYixJQUFJLElBQU0sU0FBVUUsR0FDbEIsT0FBT0EnXG4gIH07XG4gIC8vIFRoaXMgbWFwcGluZyBpcyBpZGVudGljYWwgdG8gYWJvdmUsIGJ1dCB1c2VzIHRoZSBpbmRleGVkIGZvcm1hdCBpbnN0ZWFkLlxuICBleHBvcnRzLmluZGV4ZWRUZXN0TWFwID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgc2VjdGlvbnM6IFtcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMCxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcIm9uZS5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICAgICAgICcgfTsnLFxuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiYmFyXCIsXG4gICAgICAgICAgICBcImJhelwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRFwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvdGhlL3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9LFxuICAgICAge1xuICAgICAgICBvZmZzZXQ6IHtcbiAgICAgICAgICBsaW5lOiAxLFxuICAgICAgICAgIGNvbHVtbjogMFxuICAgICAgICB9LFxuICAgICAgICBtYXA6IHtcbiAgICAgICAgICB2ZXJzaW9uOiAzLFxuICAgICAgICAgIHNvdXJjZXM6IFtcbiAgICAgICAgICAgIFwidHdvLmpzXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAgICAgICAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbicgK1xuICAgICAgICAgICAgJyAgIHJldHVybiBuICsgMTtcXG4nICtcbiAgICAgICAgICAgICcgfTsnXG4gICAgICAgICAgXSxcbiAgICAgICAgICBuYW1lczogW1xuICAgICAgICAgICAgXCJuXCJcbiAgICAgICAgICBdLFxuICAgICAgICAgIG1hcHBpbmdzOiBcIkNBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9BXCIsXG4gICAgICAgICAgZmlsZTogXCJtaW4uanNcIixcbiAgICAgICAgICBzb3VyY2VSb290OiBcIi90aGUvcm9vdFwiXG4gICAgICAgIH1cbiAgICAgIH1cbiAgICBdXG4gIH07XG4gIGV4cG9ydHMuaW5kZXhlZFRlc3RNYXBEaWZmZXJlbnRTb3VyY2VSb290cyA9IHtcbiAgICB2ZXJzaW9uOiAzLFxuICAgIGZpbGU6ICdtaW4uanMnLFxuICAgIHNlY3Rpb25zOiBbXG4gICAgICB7XG4gICAgICAgIG9mZnNldDoge1xuICAgICAgICAgIGxpbmU6IDAsXG4gICAgICAgICAgY29sdW1uOiAwXG4gICAgICAgIH0sXG4gICAgICAgIG1hcDoge1xuICAgICAgICAgIHZlcnNpb246IDMsXG4gICAgICAgICAgc291cmNlczogW1xuICAgICAgICAgICAgXCJvbmUuanNcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgc291cmNlc0NvbnRlbnQ6IFtcbiAgICAgICAgICAgICcgT05FLmZvbyA9IGZ1bmN0aW9uIChiYXIpIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gYmF6KGJhcik7XFxuJyArXG4gICAgICAgICAgICAnIH07JyxcbiAgICAgICAgICBdLFxuICAgICAgICAgIG5hbWVzOiBbXG4gICAgICAgICAgICBcImJhclwiLFxuICAgICAgICAgICAgXCJiYXpcIlxuICAgICAgICAgIF0sXG4gICAgICAgICAgbWFwcGluZ3M6IFwiQ0FBQyxJQUFJLElBQU0sU0FBVUEsR0FDbEIsT0FBT0MsSUFBSURcIixcbiAgICAgICAgICBmaWxlOiBcIm1pbi5qc1wiLFxuICAgICAgICAgIHNvdXJjZVJvb3Q6IFwiL3RoZS9yb290XCJcbiAgICAgICAgfVxuICAgICAgfSxcbiAgICAgIHtcbiAgICAgICAgb2Zmc2V0OiB7XG4gICAgICAgICAgbGluZTogMSxcbiAgICAgICAgICBjb2x1bW46IDBcbiAgICAgICAgfSxcbiAgICAgICAgbWFwOiB7XG4gICAgICAgICAgdmVyc2lvbjogMyxcbiAgICAgICAgICBzb3VyY2VzOiBbXG4gICAgICAgICAgICBcInR3by5qc1wiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAgICAgICAnIH07J1xuICAgICAgICAgIF0sXG4gICAgICAgICAgbmFtZXM6IFtcbiAgICAgICAgICAgIFwiblwiXG4gICAgICAgICAgXSxcbiAgICAgICAgICBtYXBwaW5nczogXCJDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQVwiLFxuICAgICAgICAgIGZpbGU6IFwibWluLmpzXCIsXG4gICAgICAgICAgc291cmNlUm9vdDogXCIvZGlmZmVyZW50L3Jvb3RcIlxuICAgICAgICB9XG4gICAgICB9XG4gICAgXVxuICB9O1xuICBleHBvcnRzLnRlc3RNYXBXaXRoU291cmNlc0NvbnRlbnQgPSB7XG4gICAgdmVyc2lvbjogMyxcbiAgICBmaWxlOiAnbWluLmpzJyxcbiAgICBuYW1lczogWydiYXInLCAnYmF6JywgJ24nXSxcbiAgICBzb3VyY2VzOiBbJ29uZS5qcycsICd0d28uanMnXSxcbiAgICBzb3VyY2VzQ29udGVudDogW1xuICAgICAgJyBPTkUuZm9vID0gZnVuY3Rpb24gKGJhcikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBiYXooYmFyKTtcXG4nICtcbiAgICAgICcgfTsnLFxuICAgICAgJyBUV08uaW5jID0gZnVuY3Rpb24gKG4pIHtcXG4nICtcbiAgICAgICcgICByZXR1cm4gbiArIDE7XFxuJyArXG4gICAgICAnIH07J1xuICAgIF0sXG4gICAgc291cmNlUm9vdDogJy90aGUvcm9vdCcsXG4gICAgbWFwcGluZ3M6ICdDQUFDLElBQUksSUFBTSxTQUFVQSxHQUNsQixPQUFPQyxJQUFJRDtDQ0RiLElBQUksSUFBTSxTQUFVRSxHQUNsQixPQUFPQSdcbiAgfTtcbiAgZXhwb3J0cy50ZXN0TWFwUmVsYXRpdmVTb3VyY2VzID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFsnYmFyJywgJ2JheicsICduJ10sXG4gICAgc291cmNlczogWycuL29uZS5qcycsICcuL3R3by5qcyddLFxuICAgIHNvdXJjZXNDb250ZW50OiBbXG4gICAgICAnIE9ORS5mb28gPSBmdW5jdGlvbiAoYmFyKSB7XFxuJyArXG4gICAgICAnICAgcmV0dXJuIGJheihiYXIpO1xcbicgK1xuICAgICAgJyB9OycsXG4gICAgICAnIFRXTy5pbmMgPSBmdW5jdGlvbiAobikge1xcbicgK1xuICAgICAgJyAgIHJldHVybiBuICsgMTtcXG4nICtcbiAgICAgICcgfTsnXG4gICAgXSxcbiAgICBzb3VyY2VSb290OiAnL3RoZS9yb290JyxcbiAgICBtYXBwaW5nczogJ0NBQUMsSUFBSSxJQUFNLFNBQVVBLEdBQ2xCLE9BQU9DLElBQUlEO0NDRGIsSUFBSSxJQUFNLFNBQVVFLEdBQ2xCLE9BQU9BJ1xuICB9O1xuICBleHBvcnRzLmVtcHR5TWFwID0ge1xuICAgIHZlcnNpb246IDMsXG4gICAgZmlsZTogJ21pbi5qcycsXG4gICAgbmFtZXM6IFtdLFxuICAgIHNvdXJjZXM6IFtdLFxuICAgIG1hcHBpbmdzOiAnJ1xuICB9O1xuXG5cbiAgZnVuY3Rpb24gYXNzZXJ0TWFwcGluZyhnZW5lcmF0ZWRMaW5lLCBnZW5lcmF0ZWRDb2x1bW4sIG9yaWdpbmFsU291cmNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgIG9yaWdpbmFsTGluZSwgb3JpZ2luYWxDb2x1bW4sIG5hbWUsIGJpYXMsIG1hcCwgYXNzZXJ0LFxuICAgICAgICAgICAgICAgICAgICAgICAgIGRvbnRUZXN0R2VuZXJhdGVkLCBkb250VGVzdE9yaWdpbmFsKSB7XG4gICAgaWYgKCFkb250VGVzdE9yaWdpbmFsKSB7XG4gICAgICB2YXIgb3JpZ01hcHBpbmcgPSBtYXAub3JpZ2luYWxQb3NpdGlvbkZvcih7XG4gICAgICAgIGxpbmU6IGdlbmVyYXRlZExpbmUsXG4gICAgICAgIGNvbHVtbjogZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICBiaWFzOiBiaWFzXG4gICAgICB9KTtcbiAgICAgIGFzc2VydC5lcXVhbChvcmlnTWFwcGluZy5uYW1lLCBuYW1lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbmFtZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG5hbWUpXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdNYXBwaW5nLm5hbWUpKTtcbiAgICAgIGFzc2VydC5lcXVhbChvcmlnTWFwcGluZy5saW5lLCBvcmlnaW5hbExpbmUsXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBsaW5lLCBleHBlY3RlZCAnICsgSlNPTi5zdHJpbmdpZnkob3JpZ2luYWxMaW5lKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5saW5lKSk7XG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcuY29sdW1uLCBvcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IGNvbHVtbiwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KG9yaWdpbmFsQ29sdW1uKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5jb2x1bW4pKTtcblxuICAgICAgdmFyIGV4cGVjdGVkU291cmNlO1xuXG4gICAgICBpZiAob3JpZ2luYWxTb3VyY2UgJiYgbWFwLnNvdXJjZVJvb3QgJiYgb3JpZ2luYWxTb3VyY2UuaW5kZXhPZihtYXAuc291cmNlUm9vdCkgPT09IDApIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBvcmlnaW5hbFNvdXJjZTtcbiAgICAgIH0gZWxzZSBpZiAob3JpZ2luYWxTb3VyY2UpIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBtYXAuc291cmNlUm9vdFxuICAgICAgICAgID8gdXRpbC5qb2luKG1hcC5zb3VyY2VSb290LCBvcmlnaW5hbFNvdXJjZSlcbiAgICAgICAgICA6IG9yaWdpbmFsU291cmNlO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgZXhwZWN0ZWRTb3VyY2UgPSBudWxsO1xuICAgICAgfVxuXG4gICAgICBhc3NlcnQuZXF1YWwob3JpZ01hcHBpbmcuc291cmNlLCBleHBlY3RlZFNvdXJjZSxcbiAgICAgICAgICAgICAgICAgICAnSW5jb3JyZWN0IHNvdXJjZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KGV4cGVjdGVkU291cmNlKVxuICAgICAgICAgICAgICAgICAgICsgJywgZ290ICcgKyBKU09OLnN0cmluZ2lmeShvcmlnTWFwcGluZy5zb3VyY2UpKTtcbiAgICB9XG5cbiAgICBpZiAoIWRvbnRUZXN0R2VuZXJhdGVkKSB7XG4gICAgICB2YXIgZ2VuTWFwcGluZyA9IG1hcC5nZW5lcmF0ZWRQb3NpdGlvbkZvcih7XG4gICAgICAgIHNvdXJjZTogb3JpZ2luYWxTb3VyY2UsXG4gICAgICAgIGxpbmU6IG9yaWdpbmFsTGluZSxcbiAgICAgICAgY29sdW1uOiBvcmlnaW5hbENvbHVtbixcbiAgICAgICAgYmlhczogYmlhc1xuICAgICAgfSk7XG4gICAgICBhc3NlcnQuZXF1YWwoZ2VuTWFwcGluZy5saW5lLCBnZW5lcmF0ZWRMaW5lLFxuICAgICAgICAgICAgICAgICAgICdJbmNvcnJlY3QgbGluZSwgZXhwZWN0ZWQgJyArIEpTT04uc3RyaW5naWZ5KGdlbmVyYXRlZExpbmUpXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KGdlbk1hcHBpbmcubGluZSkpO1xuICAgICAgYXNzZXJ0LmVxdWFsKGdlbk1hcHBpbmcuY29sdW1uLCBnZW5lcmF0ZWRDb2x1bW4sXG4gICAgICAgICAgICAgICAgICAgJ0luY29ycmVjdCBjb2x1bW4sIGV4cGVjdGVkICcgKyBKU09OLnN0cmluZ2lmeShnZW5lcmF0ZWRDb2x1bW4pXG4gICAgICAgICAgICAgICAgICAgKyAnLCBnb3QgJyArIEpTT04uc3RyaW5naWZ5KGdlbk1hcHBpbmcuY29sdW1uKSk7XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuYXNzZXJ0TWFwcGluZyA9IGFzc2VydE1hcHBpbmc7XG5cbiAgZnVuY3Rpb24gYXNzZXJ0RXF1YWxNYXBzKGFzc2VydCwgYWN0dWFsTWFwLCBleHBlY3RlZE1hcCkge1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAudmVyc2lvbiwgZXhwZWN0ZWRNYXAudmVyc2lvbiwgXCJ2ZXJzaW9uIG1pc21hdGNoXCIpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAuZmlsZSwgZXhwZWN0ZWRNYXAuZmlsZSwgXCJmaWxlIG1pc21hdGNoXCIpO1xuICAgIGFzc2VydC5lcXVhbChhY3R1YWxNYXAubmFtZXMubGVuZ3RoLFxuICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5uYW1lcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIFwibmFtZXMgbGVuZ3RoIG1pc21hdGNoOiBcIiArXG4gICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAubmFtZXMuam9pbihcIiwgXCIpKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5uYW1lcy5sZW5ndGg7IGkrKykge1xuICAgICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5uYW1lc1tpXSxcbiAgICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5uYW1lc1tpXSxcbiAgICAgICAgICAgICAgICAgICBcIm5hbWVzW1wiICsgaSArIFwiXSBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgICAgYWN0dWFsTWFwLm5hbWVzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAubmFtZXMuam9pbihcIiwgXCIpKTtcbiAgICB9XG4gICAgYXNzZXJ0LmVxdWFsKGFjdHVhbE1hcC5zb3VyY2VzLmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlcy5sZW5ndGgsXG4gICAgICAgICAgICAgICAgIFwic291cmNlcyBsZW5ndGggbWlzbWF0Y2g6IFwiICtcbiAgICAgICAgICAgICAgICAgICBhY3R1YWxNYXAuc291cmNlcy5qb2luKFwiLCBcIikgKyBcIiAhPSBcIiArIGV4cGVjdGVkTWFwLnNvdXJjZXMuam9pbihcIiwgXCIpKTtcbiAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5zb3VyY2VzLmxlbmd0aDsgaSsrKSB7XG4gICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNbaV0sXG4gICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc1tpXSxcbiAgICAgICAgICAgICAgICAgICBcInNvdXJjZXNbXCIgKyBpICsgXCJdIGxlbmd0aCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5zb3VyY2VzLmpvaW4oXCIsIFwiKSArIFwiICE9IFwiICsgZXhwZWN0ZWRNYXAuc291cmNlcy5qb2luKFwiLCBcIikpO1xuICAgIH1cbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZVJvb3QsXG4gICAgICAgICAgICAgICAgIGV4cGVjdGVkTWFwLnNvdXJjZVJvb3QsXG4gICAgICAgICAgICAgICAgIFwic291cmNlUm9vdCBtaXNtYXRjaDogXCIgK1xuICAgICAgICAgICAgICAgICAgIGFjdHVhbE1hcC5zb3VyY2VSb290ICsgXCIgIT0gXCIgKyBleHBlY3RlZE1hcC5zb3VyY2VSb290KTtcbiAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLm1hcHBpbmdzLCBleHBlY3RlZE1hcC5tYXBwaW5ncyxcbiAgICAgICAgICAgICAgICAgXCJtYXBwaW5ncyBtaXNtYXRjaDpcXG5BY3R1YWw6ICAgXCIgKyBhY3R1YWxNYXAubWFwcGluZ3MgKyBcIlxcbkV4cGVjdGVkOiBcIiArIGV4cGVjdGVkTWFwLm1hcHBpbmdzKTtcbiAgICBpZiAoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50KSB7XG4gICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50Lmxlbmd0aCxcbiAgICAgICAgICAgICAgICAgICBleHBlY3RlZE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGgsXG4gICAgICAgICAgICAgICAgICAgXCJzb3VyY2VzQ29udGVudCBsZW5ndGggbWlzbWF0Y2hcIik7XG4gICAgICBmb3IgKHZhciBpID0gMDsgaSA8IGFjdHVhbE1hcC5zb3VyY2VzQ29udGVudC5sZW5ndGg7IGkrKykge1xuICAgICAgICBhc3NlcnQuZXF1YWwoYWN0dWFsTWFwLnNvdXJjZXNDb250ZW50W2ldLFxuICAgICAgICAgICAgICAgICAgICAgZXhwZWN0ZWRNYXAuc291cmNlc0NvbnRlbnRbaV0sXG4gICAgICAgICAgICAgICAgICAgICBcInNvdXJjZXNDb250ZW50W1wiICsgaSArIFwiXSBtaXNtYXRjaFwiKTtcbiAgICAgIH1cbiAgICB9XG4gIH1cbiAgZXhwb3J0cy5hc3NlcnRFcXVhbE1hcHMgPSBhc3NlcnRFcXVhbE1hcHM7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC91dGlsLmpzXG4gKiogbW9kdWxlIGlkID0gMVxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICAvKipcbiAgICogVGhpcyBpcyBhIGhlbHBlciBmdW5jdGlvbiBmb3IgZ2V0dGluZyB2YWx1ZXMgZnJvbSBwYXJhbWV0ZXIvb3B0aW9uc1xuICAgKiBvYmplY3RzLlxuICAgKlxuICAgKiBAcGFyYW0gYXJncyBUaGUgb2JqZWN0IHdlIGFyZSBleHRyYWN0aW5nIHZhbHVlcyBmcm9tXG4gICAqIEBwYXJhbSBuYW1lIFRoZSBuYW1lIG9mIHRoZSBwcm9wZXJ0eSB3ZSBhcmUgZ2V0dGluZy5cbiAgICogQHBhcmFtIGRlZmF1bHRWYWx1ZSBBbiBvcHRpb25hbCB2YWx1ZSB0byByZXR1cm4gaWYgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmdcbiAgICogZnJvbSB0aGUgb2JqZWN0LiBJZiB0aGlzIGlzIG5vdCBzcGVjaWZpZWQgYW5kIHRoZSBwcm9wZXJ0eSBpcyBtaXNzaW5nLCBhblxuICAgKiBlcnJvciB3aWxsIGJlIHRocm93bi5cbiAgICovXG4gIGZ1bmN0aW9uIGdldEFyZyhhQXJncywgYU5hbWUsIGFEZWZhdWx0VmFsdWUpIHtcbiAgICBpZiAoYU5hbWUgaW4gYUFyZ3MpIHtcbiAgICAgIHJldHVybiBhQXJnc1thTmFtZV07XG4gICAgfSBlbHNlIGlmIChhcmd1bWVudHMubGVuZ3RoID09PSAzKSB7XG4gICAgICByZXR1cm4gYURlZmF1bHRWYWx1ZTtcbiAgICB9IGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhTmFtZSArICdcIiBpcyBhIHJlcXVpcmVkIGFyZ3VtZW50LicpO1xuICAgIH1cbiAgfVxuICBleHBvcnRzLmdldEFyZyA9IGdldEFyZztcblxuICB2YXIgdXJsUmVnZXhwID0gL14oPzooW1xcdytcXC0uXSspOik/XFwvXFwvKD86KFxcdys6XFx3KylAKT8oW1xcdy5dKikoPzo6KFxcZCspKT8oXFxTKikkLztcbiAgdmFyIGRhdGFVcmxSZWdleHAgPSAvXmRhdGE6LitcXCwuKyQvO1xuXG4gIGZ1bmN0aW9uIHVybFBhcnNlKGFVcmwpIHtcbiAgICB2YXIgbWF0Y2ggPSBhVXJsLm1hdGNoKHVybFJlZ2V4cCk7XG4gICAgaWYgKCFtYXRjaCkge1xuICAgICAgcmV0dXJuIG51bGw7XG4gICAgfVxuICAgIHJldHVybiB7XG4gICAgICBzY2hlbWU6IG1hdGNoWzFdLFxuICAgICAgYXV0aDogbWF0Y2hbMl0sXG4gICAgICBob3N0OiBtYXRjaFszXSxcbiAgICAgIHBvcnQ6IG1hdGNoWzRdLFxuICAgICAgcGF0aDogbWF0Y2hbNV1cbiAgICB9O1xuICB9XG4gIGV4cG9ydHMudXJsUGFyc2UgPSB1cmxQYXJzZTtcblxuICBmdW5jdGlvbiB1cmxHZW5lcmF0ZShhUGFyc2VkVXJsKSB7XG4gICAgdmFyIHVybCA9ICcnO1xuICAgIGlmIChhUGFyc2VkVXJsLnNjaGVtZSkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwuc2NoZW1lICsgJzonO1xuICAgIH1cbiAgICB1cmwgKz0gJy8vJztcbiAgICBpZiAoYVBhcnNlZFVybC5hdXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5hdXRoICsgJ0AnO1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5ob3N0KSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5ob3N0O1xuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wb3J0KSB7XG4gICAgICB1cmwgKz0gXCI6XCIgKyBhUGFyc2VkVXJsLnBvcnRcbiAgICB9XG4gICAgaWYgKGFQYXJzZWRVcmwucGF0aCkge1xuICAgICAgdXJsICs9IGFQYXJzZWRVcmwucGF0aDtcbiAgICB9XG4gICAgcmV0dXJuIHVybDtcbiAgfVxuICBleHBvcnRzLnVybEdlbmVyYXRlID0gdXJsR2VuZXJhdGU7XG5cbiAgLyoqXG4gICAqIE5vcm1hbGl6ZXMgYSBwYXRoLCBvciB0aGUgcGF0aCBwb3J0aW9uIG9mIGEgVVJMOlxuICAgKlxuICAgKiAtIFJlcGxhY2VzIGNvbnNlcXV0aXZlIHNsYXNoZXMgd2l0aCBvbmUgc2xhc2guXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnLicgcGFydHMuXG4gICAqIC0gUmVtb3ZlcyB1bm5lY2Vzc2FyeSAnPGRpcj4vLi4nIHBhcnRzLlxuICAgKlxuICAgKiBCYXNlZCBvbiBjb2RlIGluIHRoZSBOb2RlLmpzICdwYXRoJyBjb3JlIG1vZHVsZS5cbiAgICpcbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIHVybCB0byBub3JtYWxpemUuXG4gICAqL1xuICBmdW5jdGlvbiBub3JtYWxpemUoYVBhdGgpIHtcbiAgICB2YXIgcGF0aCA9IGFQYXRoO1xuICAgIHZhciB1cmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgaWYgKHVybCkge1xuICAgICAgaWYgKCF1cmwucGF0aCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG4gICAgICBwYXRoID0gdXJsLnBhdGg7XG4gICAgfVxuICAgIHZhciBpc0Fic29sdXRlID0gZXhwb3J0cy5pc0Fic29sdXRlKHBhdGgpO1xuXG4gICAgdmFyIHBhcnRzID0gcGF0aC5zcGxpdCgvXFwvKy8pO1xuICAgIGZvciAodmFyIHBhcnQsIHVwID0gMCwgaSA9IHBhcnRzLmxlbmd0aCAtIDE7IGkgPj0gMDsgaS0tKSB7XG4gICAgICBwYXJ0ID0gcGFydHNbaV07XG4gICAgICBpZiAocGFydCA9PT0gJy4nKSB7XG4gICAgICAgIHBhcnRzLnNwbGljZShpLCAxKTtcbiAgICAgIH0gZWxzZSBpZiAocGFydCA9PT0gJy4uJykge1xuICAgICAgICB1cCsrO1xuICAgICAgfSBlbHNlIGlmICh1cCA+IDApIHtcbiAgICAgICAgaWYgKHBhcnQgPT09ICcnKSB7XG4gICAgICAgICAgLy8gVGhlIGZpcnN0IHBhcnQgaXMgYmxhbmsgaWYgdGhlIHBhdGggaXMgYWJzb2x1dGUuIFRyeWluZyB0byBnb1xuICAgICAgICAgIC8vIGFib3ZlIHRoZSByb290IGlzIGEgbm8tb3AuIFRoZXJlZm9yZSB3ZSBjYW4gcmVtb3ZlIGFsbCAnLi4nIHBhcnRzXG4gICAgICAgICAgLy8gZGlyZWN0bHkgYWZ0ZXIgdGhlIHJvb3QuXG4gICAgICAgICAgcGFydHMuc3BsaWNlKGkgKyAxLCB1cCk7XG4gICAgICAgICAgdXAgPSAwO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHBhcnRzLnNwbGljZShpLCAyKTtcbiAgICAgICAgICB1cC0tO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICAgIHBhdGggPSBwYXJ0cy5qb2luKCcvJyk7XG5cbiAgICBpZiAocGF0aCA9PT0gJycpIHtcbiAgICAgIHBhdGggPSBpc0Fic29sdXRlID8gJy8nIDogJy4nO1xuICAgIH1cblxuICAgIGlmICh1cmwpIHtcbiAgICAgIHVybC5wYXRoID0gcGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZSh1cmwpO1xuICAgIH1cbiAgICByZXR1cm4gcGF0aDtcbiAgfVxuICBleHBvcnRzLm5vcm1hbGl6ZSA9IG5vcm1hbGl6ZTtcblxuICAvKipcbiAgICogSm9pbnMgdHdvIHBhdGhzL1VSTHMuXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBqb2luZWQgd2l0aCB0aGUgcm9vdC5cbiAgICpcbiAgICogLSBJZiBhUGF0aCBpcyBhIFVSTCBvciBhIGRhdGEgVVJJLCBhUGF0aCBpcyByZXR1cm5lZCwgdW5sZXNzIGFQYXRoIGlzIGFcbiAgICogICBzY2hlbWUtcmVsYXRpdmUgVVJMOiBUaGVuIHRoZSBzY2hlbWUgb2YgYVJvb3QsIGlmIGFueSwgaXMgcHJlcGVuZGVkXG4gICAqICAgZmlyc3QuXG4gICAqIC0gT3RoZXJ3aXNlIGFQYXRoIGlzIGEgcGF0aC4gSWYgYVJvb3QgaXMgYSBVUkwsIHRoZW4gaXRzIHBhdGggcG9ydGlvblxuICAgKiAgIGlzIHVwZGF0ZWQgd2l0aCB0aGUgcmVzdWx0IGFuZCBhUm9vdCBpcyByZXR1cm5lZC4gT3RoZXJ3aXNlIHRoZSByZXN1bHRcbiAgICogICBpcyByZXR1cm5lZC5cbiAgICogICAtIElmIGFQYXRoIGlzIGFic29sdXRlLCB0aGUgcmVzdWx0IGlzIGFQYXRoLlxuICAgKiAgIC0gT3RoZXJ3aXNlIHRoZSB0d28gcGF0aHMgYXJlIGpvaW5lZCB3aXRoIGEgc2xhc2guXG4gICAqIC0gSm9pbmluZyBmb3IgZXhhbXBsZSAnaHR0cDovLycgYW5kICd3d3cuZXhhbXBsZS5jb20nIGlzIGFsc28gc3VwcG9ydGVkLlxuICAgKi9cbiAgZnVuY3Rpb24gam9pbihhUm9vdCwgYVBhdGgpIHtcbiAgICBpZiAoYVJvb3QgPT09IFwiXCIpIHtcbiAgICAgIGFSb290ID0gXCIuXCI7XG4gICAgfVxuICAgIGlmIChhUGF0aCA9PT0gXCJcIikge1xuICAgICAgYVBhdGggPSBcIi5cIjtcbiAgICB9XG4gICAgdmFyIGFQYXRoVXJsID0gdXJsUGFyc2UoYVBhdGgpO1xuICAgIHZhciBhUm9vdFVybCA9IHVybFBhcnNlKGFSb290KTtcbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290ID0gYVJvb3RVcmwucGF0aCB8fCAnLyc7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oZm9vLCAnLy93d3cuZXhhbXBsZS5vcmcnKWBcbiAgICBpZiAoYVBhdGhVcmwgJiYgIWFQYXRoVXJsLnNjaGVtZSkge1xuICAgICAgaWYgKGFSb290VXJsKSB7XG4gICAgICAgIGFQYXRoVXJsLnNjaGVtZSA9IGFSb290VXJsLnNjaGVtZTtcbiAgICAgIH1cbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUGF0aFVybCk7XG4gICAgfVxuXG4gICAgaWYgKGFQYXRoVXJsIHx8IGFQYXRoLm1hdGNoKGRhdGFVcmxSZWdleHApKSB7XG4gICAgICByZXR1cm4gYVBhdGg7XG4gICAgfVxuXG4gICAgLy8gYGpvaW4oJ2h0dHA6Ly8nLCAnd3d3LmV4YW1wbGUuY29tJylgXG4gICAgaWYgKGFSb290VXJsICYmICFhUm9vdFVybC5ob3N0ICYmICFhUm9vdFVybC5wYXRoKSB7XG4gICAgICBhUm9vdFVybC5ob3N0ID0gYVBhdGg7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cblxuICAgIHZhciBqb2luZWQgPSBhUGF0aC5jaGFyQXQoMCkgPT09ICcvJ1xuICAgICAgPyBhUGF0aFxuICAgICAgOiBub3JtYWxpemUoYVJvb3QucmVwbGFjZSgvXFwvKyQvLCAnJykgKyAnLycgKyBhUGF0aCk7XG5cbiAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgIGFSb290VXJsLnBhdGggPSBqb2luZWQ7XG4gICAgICByZXR1cm4gdXJsR2VuZXJhdGUoYVJvb3RVcmwpO1xuICAgIH1cbiAgICByZXR1cm4gam9pbmVkO1xuICB9XG4gIGV4cG9ydHMuam9pbiA9IGpvaW47XG5cbiAgZXhwb3J0cy5pc0Fic29sdXRlID0gZnVuY3Rpb24gKGFQYXRoKSB7XG4gICAgcmV0dXJuIGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nIHx8ICEhYVBhdGgubWF0Y2godXJsUmVnZXhwKTtcbiAgfTtcblxuICAvKipcbiAgICogTWFrZSBhIHBhdGggcmVsYXRpdmUgdG8gYSBVUkwgb3IgYW5vdGhlciBwYXRoLlxuICAgKlxuICAgKiBAcGFyYW0gYVJvb3QgVGhlIHJvb3QgcGF0aCBvciBVUkwuXG4gICAqIEBwYXJhbSBhUGF0aCBUaGUgcGF0aCBvciBVUkwgdG8gYmUgbWFkZSByZWxhdGl2ZSB0byBhUm9vdC5cbiAgICovXG4gIGZ1bmN0aW9uIHJlbGF0aXZlKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG5cbiAgICBhUm9vdCA9IGFSb290LnJlcGxhY2UoL1xcLyQvLCAnJyk7XG5cbiAgICAvLyBJdCBpcyBwb3NzaWJsZSBmb3IgdGhlIHBhdGggdG8gYmUgYWJvdmUgdGhlIHJvb3QuIEluIHRoaXMgY2FzZSwgc2ltcGx5XG4gICAgLy8gY2hlY2tpbmcgd2hldGhlciB0aGUgcm9vdCBpcyBhIHByZWZpeCBvZiB0aGUgcGF0aCB3b24ndCB3b3JrLiBJbnN0ZWFkLCB3ZVxuICAgIC8vIG5lZWQgdG8gcmVtb3ZlIGNvbXBvbmVudHMgZnJvbSB0aGUgcm9vdCBvbmUgYnkgb25lLCB1bnRpbCBlaXRoZXIgd2UgZmluZFxuICAgIC8vIGEgcHJlZml4IHRoYXQgZml0cywgb3Igd2UgcnVuIG91dCBvZiBjb21wb25lbnRzIHRvIHJlbW92ZS5cbiAgICB2YXIgbGV2ZWwgPSAwO1xuICAgIHdoaWxlIChhUGF0aC5pbmRleE9mKGFSb290ICsgJy8nKSAhPT0gMCkge1xuICAgICAgdmFyIGluZGV4ID0gYVJvb3QubGFzdEluZGV4T2YoXCIvXCIpO1xuICAgICAgaWYgKGluZGV4IDwgMCkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgIC8vIElmIHRoZSBvbmx5IHBhcnQgb2YgdGhlIHJvb3QgdGhhdCBpcyBsZWZ0IGlzIHRoZSBzY2hlbWUgKGkuZS4gaHR0cDovLyxcbiAgICAgIC8vIGZpbGU6Ly8vLCBldGMuKSwgb25lIG9yIG1vcmUgc2xhc2hlcyAoLyksIG9yIHNpbXBseSBub3RoaW5nIGF0IGFsbCwgd2VcbiAgICAgIC8vIGhhdmUgZXhoYXVzdGVkIGFsbCBjb21wb25lbnRzLCBzbyB0aGUgcGF0aCBpcyBub3QgcmVsYXRpdmUgdG8gdGhlIHJvb3QuXG4gICAgICBhUm9vdCA9IGFSb290LnNsaWNlKDAsIGluZGV4KTtcbiAgICAgIGlmIChhUm9vdC5tYXRjaCgvXihbXlxcL10rOlxcLyk/XFwvKiQvKSkge1xuICAgICAgICByZXR1cm4gYVBhdGg7XG4gICAgICB9XG5cbiAgICAgICsrbGV2ZWw7XG4gICAgfVxuXG4gICAgLy8gTWFrZSBzdXJlIHdlIGFkZCBhIFwiLi4vXCIgZm9yIGVhY2ggY29tcG9uZW50IHdlIHJlbW92ZWQgZnJvbSB0aGUgcm9vdC5cbiAgICByZXR1cm4gQXJyYXkobGV2ZWwgKyAxKS5qb2luKFwiLi4vXCIpICsgYVBhdGguc3Vic3RyKGFSb290Lmxlbmd0aCArIDEpO1xuICB9XG4gIGV4cG9ydHMucmVsYXRpdmUgPSByZWxhdGl2ZTtcblxuICAvKipcbiAgICogQmVjYXVzZSBiZWhhdmlvciBnb2VzIHdhY2t5IHdoZW4geW91IHNldCBgX19wcm90b19fYCBvbiBvYmplY3RzLCB3ZVxuICAgKiBoYXZlIHRvIHByZWZpeCBhbGwgdGhlIHN0cmluZ3MgaW4gb3VyIHNldCB3aXRoIGFuIGFyYml0cmFyeSBjaGFyYWN0ZXIuXG4gICAqXG4gICAqIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL3B1bGwvMzEgYW5kXG4gICAqIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvaXNzdWVzLzMwXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgZnVuY3Rpb24gdG9TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiAnJCcgKyBhU3RyO1xuICB9XG4gIGV4cG9ydHMudG9TZXRTdHJpbmcgPSB0b1NldFN0cmluZztcblxuICBmdW5jdGlvbiBmcm9tU2V0U3RyaW5nKGFTdHIpIHtcbiAgICByZXR1cm4gYVN0ci5zdWJzdHIoMSk7XG4gIH1cbiAgZXhwb3J0cy5mcm9tU2V0U3RyaW5nID0gZnJvbVNldFN0cmluZztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aGVyZSB0aGUgb3JpZ2luYWwgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIG9yaWdpbmFsIHNvdXJjZS9saW5lL2NvbHVtbiwgYnV0IGRpZmZlcmVudCBnZW5lcmF0ZWRcbiAgICogbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGEgbWFwcGluZyB3aXRoIGFcbiAgICogc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVPcmlnaW5hbCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zID0gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnM7XG5cbiAgLyoqXG4gICAqIENvbXBhcmF0b3IgYmV0d2VlbiB0d28gbWFwcGluZ3Mgd2l0aCBkZWZsYXRlZCBzb3VyY2UgYW5kIG5hbWUgaW5kaWNlcyB3aGVyZVxuICAgKiB0aGUgZ2VuZXJhdGVkIHBvc2l0aW9ucyBhcmUgY29tcGFyZWQuXG4gICAqXG4gICAqIE9wdGlvbmFsbHkgcGFzcyBpbiBgdHJ1ZWAgYXMgYG9ubHlDb21wYXJlR2VuZXJhdGVkYCB0byBjb25zaWRlciB0d29cbiAgICogbWFwcGluZ3Mgd2l0aCB0aGUgc2FtZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uLCBidXQgZGlmZmVyZW50XG4gICAqIHNvdXJjZS9uYW1lL29yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiB0aGUgc2FtZS4gVXNlZnVsIHdoZW4gc2VhcmNoaW5nIGZvciBhXG4gICAqIG1hcHBpbmcgd2l0aCBhIHN0dWJiZWQgb3V0IG1hcHBpbmcuXG4gICAqL1xuICBmdW5jdGlvbiBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZChtYXBwaW5nQSwgbWFwcGluZ0IsIG9ubHlDb21wYXJlR2VuZXJhdGVkKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDAgfHwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Euc291cmNlIC0gbWFwcGluZ0Iuc291cmNlO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxMaW5lIC0gbWFwcGluZ0Iub3JpZ2luYWxMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0Eub3JpZ2luYWxDb2x1bW4gLSBtYXBwaW5nQi5vcmlnaW5hbENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIHJldHVybiBtYXBwaW5nQS5uYW1lIC0gbWFwcGluZ0IubmFtZTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQ7XG5cbiAgZnVuY3Rpb24gc3RyY21wKGFTdHIxLCBhU3RyMikge1xuICAgIGlmIChhU3RyMSA9PT0gYVN0cjIpIHtcbiAgICAgIHJldHVybiAwO1xuICAgIH1cblxuICAgIGlmIChhU3RyMSA+IGFTdHIyKSB7XG4gICAgICByZXR1cm4gMTtcbiAgICB9XG5cbiAgICByZXR1cm4gLTE7XG4gIH1cblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGluZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBzdHJpbmdzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikge1xuICAgIHZhciBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRMaW5lIC0gbWFwcGluZ0IuZ2VuZXJhdGVkTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IHN0cmNtcChtYXBwaW5nQS5zb3VyY2UsIG1hcHBpbmdCLnNvdXJjZSk7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIHN0cmNtcChtYXBwaW5nQS5uYW1lLCBtYXBwaW5nQi5uYW1lKTtcbiAgfVxuICBleHBvcnRzLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkID0gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3V0aWwuanNcbiAqKiBtb2R1bGUgaWQgPSAyXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIHZhciBiYXNlNjRWTFEgPSByZXF1aXJlKCcuL2Jhc2U2NC12bHEnKTtcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcbiAgdmFyIEFycmF5U2V0ID0gcmVxdWlyZSgnLi9hcnJheS1zZXQnKS5BcnJheVNldDtcbiAgdmFyIE1hcHBpbmdMaXN0ID0gcmVxdWlyZSgnLi9tYXBwaW5nLWxpc3QnKS5NYXBwaW5nTGlzdDtcblxuICAvKipcbiAgICogQW4gaW5zdGFuY2Ugb2YgdGhlIFNvdXJjZU1hcEdlbmVyYXRvciByZXByZXNlbnRzIGEgc291cmNlIG1hcCB3aGljaCBpc1xuICAgKiBiZWluZyBidWlsdCBpbmNyZW1lbnRhbGx5LiBZb3UgbWF5IHBhc3MgYW4gb2JqZWN0IHdpdGggdGhlIGZvbGxvd2luZ1xuICAgKiBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gZmlsZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKiAgIC0gc291cmNlUm9vdDogQSByb290IGZvciBhbGwgcmVsYXRpdmUgVVJMcyBpbiB0aGlzIHNvdXJjZSBtYXAuXG4gICAqL1xuICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3IoYUFyZ3MpIHtcbiAgICBpZiAoIWFBcmdzKSB7XG4gICAgICBhQXJncyA9IHt9O1xuICAgIH1cbiAgICB0aGlzLl9maWxlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdmaWxlJywgbnVsbCk7XG4gICAgdGhpcy5fc291cmNlUm9vdCA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlUm9vdCcsIG51bGwpO1xuICAgIHRoaXMuX3NraXBWYWxpZGF0aW9uID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdza2lwVmFsaWRhdGlvbicsIGZhbHNlKTtcbiAgICB0aGlzLl9zb3VyY2VzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbmFtZXMgPSBuZXcgQXJyYXlTZXQoKTtcbiAgICB0aGlzLl9tYXBwaW5ncyA9IG5ldyBNYXBwaW5nTGlzdCgpO1xuICAgIHRoaXMuX3NvdXJjZXNDb250ZW50cyA9IG51bGw7XG4gIH1cblxuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl92ZXJzaW9uID0gMztcblxuICAvKipcbiAgICogQ3JlYXRlcyBhIG5ldyBTb3VyY2VNYXBHZW5lcmF0b3IgYmFzZWQgb24gYSBTb3VyY2VNYXBDb25zdW1lclxuICAgKlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBTb3VyY2VNYXAuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IuZnJvbVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2Zyb21Tb3VyY2VNYXAoYVNvdXJjZU1hcENvbnN1bWVyKSB7XG4gICAgICB2YXIgc291cmNlUm9vdCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VSb290O1xuICAgICAgdmFyIGdlbmVyYXRvciA9IG5ldyBTb3VyY2VNYXBHZW5lcmF0b3Ioe1xuICAgICAgICBmaWxlOiBhU291cmNlTWFwQ29uc3VtZXIuZmlsZSxcbiAgICAgICAgc291cmNlUm9vdDogc291cmNlUm9vdFxuICAgICAgfSk7XG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuZWFjaE1hcHBpbmcoZnVuY3Rpb24gKG1hcHBpbmcpIHtcbiAgICAgICAgdmFyIG5ld01hcHBpbmcgPSB7XG4gICAgICAgICAgZ2VuZXJhdGVkOiB7XG4gICAgICAgICAgICBsaW5lOiBtYXBwaW5nLmdlbmVyYXRlZExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uXG4gICAgICAgICAgfVxuICAgICAgICB9O1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgbmV3TWFwcGluZy5zb3VyY2UgPSBtYXBwaW5nLnNvdXJjZTtcbiAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICBuZXdNYXBwaW5nLnNvdXJjZSA9IHV0aWwucmVsYXRpdmUoc291cmNlUm9vdCwgbmV3TWFwcGluZy5zb3VyY2UpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIG5ld01hcHBpbmcub3JpZ2luYWwgPSB7XG4gICAgICAgICAgICBsaW5lOiBtYXBwaW5nLm9yaWdpbmFsTGluZSxcbiAgICAgICAgICAgIGNvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtblxuICAgICAgICAgIH07XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5uYW1lICE9IG51bGwpIHtcbiAgICAgICAgICAgIG5ld01hcHBpbmcubmFtZSA9IG1hcHBpbmcubmFtZTtcbiAgICAgICAgICB9XG4gICAgICAgIH1cblxuICAgICAgICBnZW5lcmF0b3IuYWRkTWFwcGluZyhuZXdNYXBwaW5nKTtcbiAgICAgIH0pO1xuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZXMuZm9yRWFjaChmdW5jdGlvbiAoc291cmNlRmlsZSkge1xuICAgICAgICB2YXIgY29udGVudCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZUZpbGUpO1xuICAgICAgICBpZiAoY29udGVudCAhPSBudWxsKSB7XG4gICAgICAgICAgZ2VuZXJhdG9yLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuICAgICAgcmV0dXJuIGdlbmVyYXRvcjtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBZGQgYSBzaW5nbGUgbWFwcGluZyBmcm9tIG9yaWdpbmFsIHNvdXJjZSBsaW5lIGFuZCBjb2x1bW4gdG8gdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gZm9yIHRoaXMgc291cmNlIG1hcCBiZWluZyBjcmVhdGVkLiBUaGUgbWFwcGluZ1xuICAgKiBvYmplY3Qgc2hvdWxkIGhhdmUgdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gZ2VuZXJhdGVkOiBBbiBvYmplY3Qgd2l0aCB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMuXG4gICAqICAgLSBvcmlnaW5hbDogQW4gb2JqZWN0IHdpdGggdGhlIG9yaWdpbmFsIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMuXG4gICAqICAgLSBzb3VyY2U6IFRoZSBvcmlnaW5hbCBzb3VyY2UgZmlsZSAocmVsYXRpdmUgdG8gdGhlIHNvdXJjZVJvb3QpLlxuICAgKiAgIC0gbmFtZTogQW4gb3B0aW9uYWwgb3JpZ2luYWwgdG9rZW4gbmFtZSBmb3IgdGhpcyBtYXBwaW5nLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hZGRNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBHZW5lcmF0b3JfYWRkTWFwcGluZyhhQXJncykge1xuICAgICAgdmFyIGdlbmVyYXRlZCA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnZ2VuZXJhdGVkJyk7XG4gICAgICB2YXIgb3JpZ2luYWwgPSB1dGlsLmdldEFyZyhhQXJncywgJ29yaWdpbmFsJywgbnVsbCk7XG4gICAgICB2YXIgc291cmNlID0gdXRpbC5nZXRBcmcoYUFyZ3MsICdzb3VyY2UnLCBudWxsKTtcbiAgICAgIHZhciBuYW1lID0gdXRpbC5nZXRBcmcoYUFyZ3MsICduYW1lJywgbnVsbCk7XG5cbiAgICAgIGlmICghdGhpcy5fc2tpcFZhbGlkYXRpb24pIHtcbiAgICAgICAgdGhpcy5fdmFsaWRhdGVNYXBwaW5nKGdlbmVyYXRlZCwgb3JpZ2luYWwsIHNvdXJjZSwgbmFtZSk7XG4gICAgICB9XG5cbiAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiAhdGhpcy5fc291cmNlcy5oYXMoc291cmNlKSkge1xuICAgICAgICB0aGlzLl9zb3VyY2VzLmFkZChzb3VyY2UpO1xuICAgICAgfVxuXG4gICAgICBpZiAobmFtZSAhPSBudWxsICYmICF0aGlzLl9uYW1lcy5oYXMobmFtZSkpIHtcbiAgICAgICAgdGhpcy5fbmFtZXMuYWRkKG5hbWUpO1xuICAgICAgfVxuXG4gICAgICB0aGlzLl9tYXBwaW5ncy5hZGQoe1xuICAgICAgICBnZW5lcmF0ZWRMaW5lOiBnZW5lcmF0ZWQubGluZSxcbiAgICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uLFxuICAgICAgICBvcmlnaW5hbExpbmU6IG9yaWdpbmFsICE9IG51bGwgJiYgb3JpZ2luYWwubGluZSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IG9yaWdpbmFsICE9IG51bGwgJiYgb3JpZ2luYWwuY29sdW1uLFxuICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgbmFtZTogbmFtZVxuICAgICAgfSk7XG4gICAgfTtcblxuICAvKipcbiAgICogU2V0IHRoZSBzb3VyY2UgY29udGVudCBmb3IgYSBzb3VyY2UgZmlsZS5cbiAgICovXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuc2V0U291cmNlQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3NldFNvdXJjZUNvbnRlbnQoYVNvdXJjZUZpbGUsIGFTb3VyY2VDb250ZW50KSB7XG4gICAgICB2YXIgc291cmNlID0gYVNvdXJjZUZpbGU7XG4gICAgICBpZiAodGhpcy5fc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIHNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5fc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKGFTb3VyY2VDb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgLy8gQWRkIHRoZSBzb3VyY2UgY29udGVudCB0byB0aGUgX3NvdXJjZXNDb250ZW50cyBtYXAuXG4gICAgICAgIC8vIENyZWF0ZSBhIG5ldyBfc291cmNlc0NvbnRlbnRzIG1hcCBpZiB0aGUgcHJvcGVydHkgaXMgbnVsbC5cbiAgICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgICB0aGlzLl9zb3VyY2VzQ29udGVudHMgPSB7fTtcbiAgICAgICAgfVxuICAgICAgICB0aGlzLl9zb3VyY2VzQ29udGVudHNbdXRpbC50b1NldFN0cmluZyhzb3VyY2UpXSA9IGFTb3VyY2VDb250ZW50O1xuICAgICAgfSBlbHNlIGlmICh0aGlzLl9zb3VyY2VzQ29udGVudHMpIHtcbiAgICAgICAgLy8gUmVtb3ZlIHRoZSBzb3VyY2UgZmlsZSBmcm9tIHRoZSBfc291cmNlc0NvbnRlbnRzIG1hcC5cbiAgICAgICAgLy8gSWYgdGhlIF9zb3VyY2VzQ29udGVudHMgbWFwIGlzIGVtcHR5LCBzZXQgdGhlIHByb3BlcnR5IHRvIG51bGwuXG4gICAgICAgIGRlbGV0ZSB0aGlzLl9zb3VyY2VzQ29udGVudHNbdXRpbC50b1NldFN0cmluZyhzb3VyY2UpXTtcbiAgICAgICAgaWYgKE9iamVjdC5rZXlzKHRoaXMuX3NvdXJjZXNDb250ZW50cykubGVuZ3RoID09PSAwKSB7XG4gICAgICAgICAgdGhpcy5fc291cmNlc0NvbnRlbnRzID0gbnVsbDtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIEFwcGxpZXMgdGhlIG1hcHBpbmdzIG9mIGEgc3ViLXNvdXJjZS1tYXAgZm9yIGEgc3BlY2lmaWMgc291cmNlIGZpbGUgdG8gdGhlXG4gICAqIHNvdXJjZSBtYXAgYmVpbmcgZ2VuZXJhdGVkLiBFYWNoIG1hcHBpbmcgdG8gdGhlIHN1cHBsaWVkIHNvdXJjZSBmaWxlIGlzXG4gICAqIHJld3JpdHRlbiB1c2luZyB0aGUgc3VwcGxpZWQgc291cmNlIG1hcC4gTm90ZTogVGhlIHJlc29sdXRpb24gZm9yIHRoZVxuICAgKiByZXN1bHRpbmcgbWFwcGluZ3MgaXMgdGhlIG1pbmltaXVtIG9mIHRoaXMgbWFwIGFuZCB0aGUgc3VwcGxpZWQgbWFwLlxuICAgKlxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBzb3VyY2UgbWFwIHRvIGJlIGFwcGxpZWQuXG4gICAqIEBwYXJhbSBhU291cmNlRmlsZSBPcHRpb25hbC4gVGhlIGZpbGVuYW1lIG9mIHRoZSBzb3VyY2UgZmlsZS5cbiAgICogICAgICAgIElmIG9taXR0ZWQsIFNvdXJjZU1hcENvbnN1bWVyJ3MgZmlsZSBwcm9wZXJ0eSB3aWxsIGJlIHVzZWQuXG4gICAqIEBwYXJhbSBhU291cmNlTWFwUGF0aCBPcHRpb25hbC4gVGhlIGRpcm5hbWUgb2YgdGhlIHBhdGggdG8gdGhlIHNvdXJjZSBtYXBcbiAgICogICAgICAgIHRvIGJlIGFwcGxpZWQuIElmIHJlbGF0aXZlLCBpdCBpcyByZWxhdGl2ZSB0byB0aGUgU291cmNlTWFwQ29uc3VtZXIuXG4gICAqICAgICAgICBUaGlzIHBhcmFtZXRlciBpcyBuZWVkZWQgd2hlbiB0aGUgdHdvIHNvdXJjZSBtYXBzIGFyZW4ndCBpbiB0aGUgc2FtZVxuICAgKiAgICAgICAgZGlyZWN0b3J5LCBhbmQgdGhlIHNvdXJjZSBtYXAgdG8gYmUgYXBwbGllZCBjb250YWlucyByZWxhdGl2ZSBzb3VyY2VcbiAgICogICAgICAgIHBhdGhzLiBJZiBzbywgdGhvc2UgcmVsYXRpdmUgc291cmNlIHBhdGhzIG5lZWQgdG8gYmUgcmV3cml0dGVuXG4gICAqICAgICAgICByZWxhdGl2ZSB0byB0aGUgU291cmNlTWFwR2VuZXJhdG9yLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hcHBseVNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX2FwcGx5U291cmNlTWFwKGFTb3VyY2VNYXBDb25zdW1lciwgYVNvdXJjZUZpbGUsIGFTb3VyY2VNYXBQYXRoKSB7XG4gICAgICB2YXIgc291cmNlRmlsZSA9IGFTb3VyY2VGaWxlO1xuICAgICAgLy8gSWYgYVNvdXJjZUZpbGUgaXMgb21pdHRlZCwgd2Ugd2lsbCB1c2UgdGhlIGZpbGUgcHJvcGVydHkgb2YgdGhlIFNvdXJjZU1hcFxuICAgICAgaWYgKGFTb3VyY2VGaWxlID09IG51bGwpIHtcbiAgICAgICAgaWYgKGFTb3VyY2VNYXBDb25zdW1lci5maWxlID09IG51bGwpIHtcbiAgICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXG4gICAgICAgICAgICAnU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5hcHBseVNvdXJjZU1hcCByZXF1aXJlcyBlaXRoZXIgYW4gZXhwbGljaXQgc291cmNlIGZpbGUsICcgK1xuICAgICAgICAgICAgJ29yIHRoZSBzb3VyY2UgbWFwXFwncyBcImZpbGVcIiBwcm9wZXJ0eS4gQm90aCB3ZXJlIG9taXR0ZWQuJ1xuICAgICAgICAgICk7XG4gICAgICAgIH1cbiAgICAgICAgc291cmNlRmlsZSA9IGFTb3VyY2VNYXBDb25zdW1lci5maWxlO1xuICAgICAgfVxuICAgICAgdmFyIHNvdXJjZVJvb3QgPSB0aGlzLl9zb3VyY2VSb290O1xuICAgICAgLy8gTWFrZSBcInNvdXJjZUZpbGVcIiByZWxhdGl2ZSBpZiBhbiBhYnNvbHV0ZSBVcmwgaXMgcGFzc2VkLlxuICAgICAgaWYgKHNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2VGaWxlKTtcbiAgICAgIH1cbiAgICAgIC8vIEFwcGx5aW5nIHRoZSBTb3VyY2VNYXAgY2FuIGFkZCBhbmQgcmVtb3ZlIGl0ZW1zIGZyb20gdGhlIHNvdXJjZXMgYW5kXG4gICAgICAvLyB0aGUgbmFtZXMgYXJyYXkuXG4gICAgICB2YXIgbmV3U291cmNlcyA9IG5ldyBBcnJheVNldCgpO1xuICAgICAgdmFyIG5ld05hbWVzID0gbmV3IEFycmF5U2V0KCk7XG5cbiAgICAgIC8vIEZpbmQgbWFwcGluZ3MgZm9yIHRoZSBcInNvdXJjZUZpbGVcIlxuICAgICAgdGhpcy5fbWFwcGluZ3MudW5zb3J0ZWRGb3JFYWNoKGZ1bmN0aW9uIChtYXBwaW5nKSB7XG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSA9PT0gc291cmNlRmlsZSAmJiBtYXBwaW5nLm9yaWdpbmFsTGluZSAhPSBudWxsKSB7XG4gICAgICAgICAgLy8gQ2hlY2sgaWYgaXQgY2FuIGJlIG1hcHBlZCBieSB0aGUgc291cmNlIG1hcCwgdGhlbiB1cGRhdGUgdGhlIG1hcHBpbmcuXG4gICAgICAgICAgdmFyIG9yaWdpbmFsID0gYVNvdXJjZU1hcENvbnN1bWVyLm9yaWdpbmFsUG9zaXRpb25Gb3Ioe1xuICAgICAgICAgICAgbGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBjb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW5cbiAgICAgICAgICB9KTtcbiAgICAgICAgICBpZiAob3JpZ2luYWwuc291cmNlICE9IG51bGwpIHtcbiAgICAgICAgICAgIC8vIENvcHkgbWFwcGluZ1xuICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSBvcmlnaW5hbC5zb3VyY2U7XG4gICAgICAgICAgICBpZiAoYVNvdXJjZU1hcFBhdGggIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLnNvdXJjZSA9IHV0aWwuam9pbihhU291cmNlTWFwUGF0aCwgbWFwcGluZy5zb3VyY2UpXG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICAgIG1hcHBpbmcuc291cmNlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSA9IG9yaWdpbmFsLmxpbmU7XG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID0gb3JpZ2luYWwuY29sdW1uO1xuICAgICAgICAgICAgaWYgKG9yaWdpbmFsLm5hbWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUgPSBvcmlnaW5hbC5uYW1lO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBzb3VyY2UgPSBtYXBwaW5nLnNvdXJjZTtcbiAgICAgICAgaWYgKHNvdXJjZSAhPSBudWxsICYmICFuZXdTb3VyY2VzLmhhcyhzb3VyY2UpKSB7XG4gICAgICAgICAgbmV3U291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgICAgfVxuXG4gICAgICAgIHZhciBuYW1lID0gbWFwcGluZy5uYW1lO1xuICAgICAgICBpZiAobmFtZSAhPSBudWxsICYmICFuZXdOYW1lcy5oYXMobmFtZSkpIHtcbiAgICAgICAgICBuZXdOYW1lcy5hZGQobmFtZSk7XG4gICAgICAgIH1cblxuICAgICAgfSwgdGhpcyk7XG4gICAgICB0aGlzLl9zb3VyY2VzID0gbmV3U291cmNlcztcbiAgICAgIHRoaXMuX25hbWVzID0gbmV3TmFtZXM7XG5cbiAgICAgIC8vIENvcHkgc291cmNlc0NvbnRlbnRzIG9mIGFwcGxpZWQgbWFwLlxuICAgICAgYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZXMuZm9yRWFjaChmdW5jdGlvbiAoc291cmNlRmlsZSkge1xuICAgICAgICB2YXIgY29udGVudCA9IGFTb3VyY2VNYXBDb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKHNvdXJjZUZpbGUpO1xuICAgICAgICBpZiAoY29udGVudCAhPSBudWxsKSB7XG4gICAgICAgICAgaWYgKGFTb3VyY2VNYXBQYXRoICE9IG51bGwpIHtcbiAgICAgICAgICAgIHNvdXJjZUZpbGUgPSB1dGlsLmpvaW4oYVNvdXJjZU1hcFBhdGgsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBpZiAoc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2VGaWxlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5zZXRTb3VyY2VDb250ZW50KHNvdXJjZUZpbGUsIGNvbnRlbnQpO1xuICAgICAgICB9XG4gICAgICB9LCB0aGlzKTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBBIG1hcHBpbmcgY2FuIGhhdmUgb25lIG9mIHRoZSB0aHJlZSBsZXZlbHMgb2YgZGF0YTpcbiAgICpcbiAgICogICAxLiBKdXN0IHRoZSBnZW5lcmF0ZWQgcG9zaXRpb24uXG4gICAqICAgMi4gVGhlIEdlbmVyYXRlZCBwb3NpdGlvbiwgb3JpZ2luYWwgcG9zaXRpb24sIGFuZCBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgMy4gR2VuZXJhdGVkIGFuZCBvcmlnaW5hbCBwb3NpdGlvbiwgb3JpZ2luYWwgc291cmNlLCBhcyB3ZWxsIGFzIGEgbmFtZVxuICAgKiAgICAgIHRva2VuLlxuICAgKlxuICAgKiBUbyBtYWludGFpbiBjb25zaXN0ZW5jeSwgd2UgdmFsaWRhdGUgdGhhdCBhbnkgbmV3IG1hcHBpbmcgYmVpbmcgYWRkZWQgZmFsbHNcbiAgICogaW4gdG8gb25lIG9mIHRoZXNlIGNhdGVnb3JpZXMuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLl92YWxpZGF0ZU1hcHBpbmcgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl92YWxpZGF0ZU1hcHBpbmcoYUdlbmVyYXRlZCwgYU9yaWdpbmFsLCBhU291cmNlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYU5hbWUpIHtcbiAgICAgIGlmIChhR2VuZXJhdGVkICYmICdsaW5lJyBpbiBhR2VuZXJhdGVkICYmICdjb2x1bW4nIGluIGFHZW5lcmF0ZWRcbiAgICAgICAgICAmJiBhR2VuZXJhdGVkLmxpbmUgPiAwICYmIGFHZW5lcmF0ZWQuY29sdW1uID49IDBcbiAgICAgICAgICAmJiAhYU9yaWdpbmFsICYmICFhU291cmNlICYmICFhTmFtZSkge1xuICAgICAgICAvLyBDYXNlIDEuXG4gICAgICAgIHJldHVybjtcbiAgICAgIH1cbiAgICAgIGVsc2UgaWYgKGFHZW5lcmF0ZWQgJiYgJ2xpbmUnIGluIGFHZW5lcmF0ZWQgJiYgJ2NvbHVtbicgaW4gYUdlbmVyYXRlZFxuICAgICAgICAgICAgICAgJiYgYU9yaWdpbmFsICYmICdsaW5lJyBpbiBhT3JpZ2luYWwgJiYgJ2NvbHVtbicgaW4gYU9yaWdpbmFsXG4gICAgICAgICAgICAgICAmJiBhR2VuZXJhdGVkLmxpbmUgPiAwICYmIGFHZW5lcmF0ZWQuY29sdW1uID49IDBcbiAgICAgICAgICAgICAgICYmIGFPcmlnaW5hbC5saW5lID4gMCAmJiBhT3JpZ2luYWwuY29sdW1uID49IDBcbiAgICAgICAgICAgICAgICYmIGFTb3VyY2UpIHtcbiAgICAgICAgLy8gQ2FzZXMgMiBhbmQgMy5cbiAgICAgICAgcmV0dXJuO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignSW52YWxpZCBtYXBwaW5nOiAnICsgSlNPTi5zdHJpbmdpZnkoe1xuICAgICAgICAgIGdlbmVyYXRlZDogYUdlbmVyYXRlZCxcbiAgICAgICAgICBzb3VyY2U6IGFTb3VyY2UsXG4gICAgICAgICAgb3JpZ2luYWw6IGFPcmlnaW5hbCxcbiAgICAgICAgICBuYW1lOiBhTmFtZVxuICAgICAgICB9KSk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogU2VyaWFsaXplIHRoZSBhY2N1bXVsYXRlZCBtYXBwaW5ncyBpbiB0byB0aGUgc3RyZWFtIG9mIGJhc2UgNjQgVkxRc1xuICAgKiBzcGVjaWZpZWQgYnkgdGhlIHNvdXJjZSBtYXAgZm9ybWF0LlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS5fc2VyaWFsaXplTWFwcGluZ3MgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9zZXJpYWxpemVNYXBwaW5ncygpIHtcbiAgICAgIHZhciBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRMaW5lID0gMTtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsTGluZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNOYW1lID0gMDtcbiAgICAgIHZhciBwcmV2aW91c1NvdXJjZSA9IDA7XG4gICAgICB2YXIgcmVzdWx0ID0gJyc7XG4gICAgICB2YXIgbWFwcGluZztcbiAgICAgIHZhciBuYW1lSWR4O1xuICAgICAgdmFyIHNvdXJjZUlkeDtcblxuICAgICAgdmFyIG1hcHBpbmdzID0gdGhpcy5fbWFwcGluZ3MudG9BcnJheSgpO1xuICAgICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IG1hcHBpbmdzLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIG1hcHBpbmcgPSBtYXBwaW5nc1tpXTtcblxuICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lICE9PSBwcmV2aW91c0dlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IDA7XG4gICAgICAgICAgd2hpbGUgKG1hcHBpbmcuZ2VuZXJhdGVkTGluZSAhPT0gcHJldmlvdXNHZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgICByZXN1bHQgKz0gJzsnO1xuICAgICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICAgIGVsc2Uge1xuICAgICAgICAgIGlmIChpID4gMCkge1xuICAgICAgICAgICAgaWYgKCF1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmcsIG1hcHBpbmdzW2kgLSAxXSkpIHtcbiAgICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgICB9XG4gICAgICAgICAgICByZXN1bHQgKz0gJywnO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuXG4gICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC0gcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLnNvdXJjZSAhPSBudWxsKSB7XG4gICAgICAgICAgc291cmNlSWR4ID0gdGhpcy5fc291cmNlcy5pbmRleE9mKG1hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShzb3VyY2VJZHggLSBwcmV2aW91c1NvdXJjZSk7XG4gICAgICAgICAgcHJldmlvdXNTb3VyY2UgPSBzb3VyY2VJZHg7XG5cbiAgICAgICAgICAvLyBsaW5lcyBhcmUgc3RvcmVkIDAtYmFzZWQgaW4gU291cmNlTWFwIHNwZWMgdmVyc2lvbiAzXG4gICAgICAgICAgcmVzdWx0ICs9IGJhc2U2NFZMUS5lbmNvZGUobWFwcGluZy5vcmlnaW5hbExpbmUgLSAxXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBwcmV2aW91c09yaWdpbmFsTGluZSk7XG4gICAgICAgICAgcHJldmlvdXNPcmlnaW5hbExpbmUgPSBtYXBwaW5nLm9yaWdpbmFsTGluZSAtIDE7XG5cbiAgICAgICAgICByZXN1bHQgKz0gYmFzZTY0VkxRLmVuY29kZShtYXBwaW5nLm9yaWdpbmFsQ29sdW1uXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgLSBwcmV2aW91c09yaWdpbmFsQ29sdW1uKTtcbiAgICAgICAgICBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgIGlmIChtYXBwaW5nLm5hbWUgIT0gbnVsbCkge1xuICAgICAgICAgICAgbmFtZUlkeCA9IHRoaXMuX25hbWVzLmluZGV4T2YobWFwcGluZy5uYW1lKTtcbiAgICAgICAgICAgIHJlc3VsdCArPSBiYXNlNjRWTFEuZW5jb2RlKG5hbWVJZHggLSBwcmV2aW91c05hbWUpO1xuICAgICAgICAgICAgcHJldmlvdXNOYW1lID0gbmFtZUlkeDtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHJlc3VsdDtcbiAgICB9O1xuXG4gIFNvdXJjZU1hcEdlbmVyYXRvci5wcm90b3R5cGUuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcEdlbmVyYXRvcl9nZW5lcmF0ZVNvdXJjZXNDb250ZW50KGFTb3VyY2VzLCBhU291cmNlUm9vdCkge1xuICAgICAgcmV0dXJuIGFTb3VyY2VzLm1hcChmdW5jdGlvbiAoc291cmNlKSB7XG4gICAgICAgIGlmICghdGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgICAgcmV0dXJuIG51bGw7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGFTb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICBzb3VyY2UgPSB1dGlsLnJlbGF0aXZlKGFTb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgICB9XG4gICAgICAgIHZhciBrZXkgPSB1dGlsLnRvU2V0U3RyaW5nKHNvdXJjZSk7XG4gICAgICAgIHJldHVybiBPYmplY3QucHJvdG90eXBlLmhhc093blByb3BlcnR5LmNhbGwodGhpcy5fc291cmNlc0NvbnRlbnRzLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGtleSlcbiAgICAgICAgICA/IHRoaXMuX3NvdXJjZXNDb250ZW50c1trZXldXG4gICAgICAgICAgOiBudWxsO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogRXh0ZXJuYWxpemUgdGhlIHNvdXJjZSBtYXAuXG4gICAqL1xuICBTb3VyY2VNYXBHZW5lcmF0b3IucHJvdG90eXBlLnRvSlNPTiA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3RvSlNPTigpIHtcbiAgICAgIHZhciBtYXAgPSB7XG4gICAgICAgIHZlcnNpb246IHRoaXMuX3ZlcnNpb24sXG4gICAgICAgIHNvdXJjZXM6IHRoaXMuX3NvdXJjZXMudG9BcnJheSgpLFxuICAgICAgICBuYW1lczogdGhpcy5fbmFtZXMudG9BcnJheSgpLFxuICAgICAgICBtYXBwaW5nczogdGhpcy5fc2VyaWFsaXplTWFwcGluZ3MoKVxuICAgICAgfTtcbiAgICAgIGlmICh0aGlzLl9maWxlICE9IG51bGwpIHtcbiAgICAgICAgbWFwLmZpbGUgPSB0aGlzLl9maWxlO1xuICAgICAgfVxuICAgICAgaWYgKHRoaXMuX3NvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBtYXAuc291cmNlUm9vdCA9IHRoaXMuX3NvdXJjZVJvb3Q7XG4gICAgICB9XG4gICAgICBpZiAodGhpcy5fc291cmNlc0NvbnRlbnRzKSB7XG4gICAgICAgIG1hcC5zb3VyY2VzQ29udGVudCA9IHRoaXMuX2dlbmVyYXRlU291cmNlc0NvbnRlbnQobWFwLnNvdXJjZXMsIG1hcC5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIG1hcDtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZW5kZXIgdGhlIHNvdXJjZSBtYXAgYmVpbmcgZ2VuZXJhdGVkIHRvIGEgc3RyaW5nLlxuICAgKi9cbiAgU291cmNlTWFwR2VuZXJhdG9yLnByb3RvdHlwZS50b1N0cmluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwR2VuZXJhdG9yX3RvU3RyaW5nKCkge1xuICAgICAgcmV0dXJuIEpTT04uc3RyaW5naWZ5KHRoaXMudG9KU09OKCkpO1xuICAgIH07XG5cbiAgZXhwb3J0cy5Tb3VyY2VNYXBHZW5lcmF0b3IgPSBTb3VyY2VNYXBHZW5lcmF0b3I7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL3NvdXJjZS1tYXAtZ2VuZXJhdG9yLmpzXG4gKiogbW9kdWxlIGlkID0gM1xuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqXG4gKiBCYXNlZCBvbiB0aGUgQmFzZSA2NCBWTFEgaW1wbGVtZW50YXRpb24gaW4gQ2xvc3VyZSBDb21waWxlcjpcbiAqIGh0dHBzOi8vY29kZS5nb29nbGUuY29tL3AvY2xvc3VyZS1jb21waWxlci9zb3VyY2UvYnJvd3NlL3RydW5rL3NyYy9jb20vZ29vZ2xlL2RlYnVnZ2luZy9zb3VyY2VtYXAvQmFzZTY0VkxRLmphdmFcbiAqXG4gKiBDb3B5cmlnaHQgMjAxMSBUaGUgQ2xvc3VyZSBDb21waWxlciBBdXRob3JzLiBBbGwgcmlnaHRzIHJlc2VydmVkLlxuICogUmVkaXN0cmlidXRpb24gYW5kIHVzZSBpbiBzb3VyY2UgYW5kIGJpbmFyeSBmb3Jtcywgd2l0aCBvciB3aXRob3V0XG4gKiBtb2RpZmljYXRpb24sIGFyZSBwZXJtaXR0ZWQgcHJvdmlkZWQgdGhhdCB0aGUgZm9sbG93aW5nIGNvbmRpdGlvbnMgYXJlXG4gKiBtZXQ6XG4gKlxuICogICogUmVkaXN0cmlidXRpb25zIG9mIHNvdXJjZSBjb2RlIG11c3QgcmV0YWluIHRoZSBhYm92ZSBjb3B5cmlnaHRcbiAqICAgIG5vdGljZSwgdGhpcyBsaXN0IG9mIGNvbmRpdGlvbnMgYW5kIHRoZSBmb2xsb3dpbmcgZGlzY2xhaW1lci5cbiAqICAqIFJlZGlzdHJpYnV0aW9ucyBpbiBiaW5hcnkgZm9ybSBtdXN0IHJlcHJvZHVjZSB0aGUgYWJvdmVcbiAqICAgIGNvcHlyaWdodCBub3RpY2UsIHRoaXMgbGlzdCBvZiBjb25kaXRpb25zIGFuZCB0aGUgZm9sbG93aW5nXG4gKiAgICBkaXNjbGFpbWVyIGluIHRoZSBkb2N1bWVudGF0aW9uIGFuZC9vciBvdGhlciBtYXRlcmlhbHMgcHJvdmlkZWRcbiAqICAgIHdpdGggdGhlIGRpc3RyaWJ1dGlvbi5cbiAqICAqIE5laXRoZXIgdGhlIG5hbWUgb2YgR29vZ2xlIEluYy4gbm9yIHRoZSBuYW1lcyBvZiBpdHNcbiAqICAgIGNvbnRyaWJ1dG9ycyBtYXkgYmUgdXNlZCB0byBlbmRvcnNlIG9yIHByb21vdGUgcHJvZHVjdHMgZGVyaXZlZFxuICogICAgZnJvbSB0aGlzIHNvZnR3YXJlIHdpdGhvdXQgc3BlY2lmaWMgcHJpb3Igd3JpdHRlbiBwZXJtaXNzaW9uLlxuICpcbiAqIFRISVMgU09GVFdBUkUgSVMgUFJPVklERUQgQlkgVEhFIENPUFlSSUdIVCBIT0xERVJTIEFORCBDT05UUklCVVRPUlNcbiAqIFwiQVMgSVNcIiBBTkQgQU5ZIEVYUFJFU1MgT1IgSU1QTElFRCBXQVJSQU5USUVTLCBJTkNMVURJTkcsIEJVVCBOT1RcbiAqIExJTUlURUQgVE8sIFRIRSBJTVBMSUVEIFdBUlJBTlRJRVMgT0YgTUVSQ0hBTlRBQklMSVRZIEFORCBGSVRORVNTIEZPUlxuICogQSBQQVJUSUNVTEFSIFBVUlBPU0UgQVJFIERJU0NMQUlNRUQuIElOIE5PIEVWRU5UIFNIQUxMIFRIRSBDT1BZUklHSFRcbiAqIE9XTkVSIE9SIENPTlRSSUJVVE9SUyBCRSBMSUFCTEUgRk9SIEFOWSBESVJFQ1QsIElORElSRUNULCBJTkNJREVOVEFMLFxuICogU1BFQ0lBTCwgRVhFTVBMQVJZLCBPUiBDT05TRVFVRU5USUFMIERBTUFHRVMgKElOQ0xVRElORywgQlVUIE5PVFxuICogTElNSVRFRCBUTywgUFJPQ1VSRU1FTlQgT0YgU1VCU1RJVFVURSBHT09EUyBPUiBTRVJWSUNFUzsgTE9TUyBPRiBVU0UsXG4gKiBEQVRBLCBPUiBQUk9GSVRTOyBPUiBCVVNJTkVTUyBJTlRFUlJVUFRJT04pIEhPV0VWRVIgQ0FVU0VEIEFORCBPTiBBTllcbiAqIFRIRU9SWSBPRiBMSUFCSUxJVFksIFdIRVRIRVIgSU4gQ09OVFJBQ1QsIFNUUklDVCBMSUFCSUxJVFksIE9SIFRPUlRcbiAqIChJTkNMVURJTkcgTkVHTElHRU5DRSBPUiBPVEhFUldJU0UpIEFSSVNJTkcgSU4gQU5ZIFdBWSBPVVQgT0YgVEhFIFVTRVxuICogT0YgVEhJUyBTT0ZUV0FSRSwgRVZFTiBJRiBBRFZJU0VEIE9GIFRIRSBQT1NTSUJJTElUWSBPRiBTVUNIIERBTUFHRS5cbiAqL1xue1xuICB2YXIgYmFzZTY0ID0gcmVxdWlyZSgnLi9iYXNlNjQnKTtcblxuICAvLyBBIHNpbmdsZSBiYXNlIDY0IGRpZ2l0IGNhbiBjb250YWluIDYgYml0cyBvZiBkYXRhLiBGb3IgdGhlIGJhc2UgNjQgdmFyaWFibGVcbiAgLy8gbGVuZ3RoIHF1YW50aXRpZXMgd2UgdXNlIGluIHRoZSBzb3VyY2UgbWFwIHNwZWMsIHRoZSBmaXJzdCBiaXQgaXMgdGhlIHNpZ24sXG4gIC8vIHRoZSBuZXh0IGZvdXIgYml0cyBhcmUgdGhlIGFjdHVhbCB2YWx1ZSwgYW5kIHRoZSA2dGggYml0IGlzIHRoZVxuICAvLyBjb250aW51YXRpb24gYml0LiBUaGUgY29udGludWF0aW9uIGJpdCB0ZWxscyB1cyB3aGV0aGVyIHRoZXJlIGFyZSBtb3JlXG4gIC8vIGRpZ2l0cyBpbiB0aGlzIHZhbHVlIGZvbGxvd2luZyB0aGlzIGRpZ2l0LlxuICAvL1xuICAvLyAgIENvbnRpbnVhdGlvblxuICAvLyAgIHwgICAgU2lnblxuICAvLyAgIHwgICAgfFxuICAvLyAgIFYgICAgVlxuICAvLyAgIDEwMTAxMVxuXG4gIHZhciBWTFFfQkFTRV9TSElGVCA9IDU7XG5cbiAgLy8gYmluYXJ5OiAxMDAwMDBcbiAgdmFyIFZMUV9CQVNFID0gMSA8PCBWTFFfQkFTRV9TSElGVDtcblxuICAvLyBiaW5hcnk6IDAxMTExMVxuICB2YXIgVkxRX0JBU0VfTUFTSyA9IFZMUV9CQVNFIC0gMTtcblxuICAvLyBiaW5hcnk6IDEwMDAwMFxuICB2YXIgVkxRX0NPTlRJTlVBVElPTl9CSVQgPSBWTFFfQkFTRTtcblxuICAvKipcbiAgICogQ29udmVydHMgZnJvbSBhIHR3by1jb21wbGVtZW50IHZhbHVlIHRvIGEgdmFsdWUgd2hlcmUgdGhlIHNpZ24gYml0IGlzXG4gICAqIHBsYWNlZCBpbiB0aGUgbGVhc3Qgc2lnbmlmaWNhbnQgYml0LiAgRm9yIGV4YW1wbGUsIGFzIGRlY2ltYWxzOlxuICAgKiAgIDEgYmVjb21lcyAyICgxMCBiaW5hcnkpLCAtMSBiZWNvbWVzIDMgKDExIGJpbmFyeSlcbiAgICogICAyIGJlY29tZXMgNCAoMTAwIGJpbmFyeSksIC0yIGJlY29tZXMgNSAoMTAxIGJpbmFyeSlcbiAgICovXG4gIGZ1bmN0aW9uIHRvVkxRU2lnbmVkKGFWYWx1ZSkge1xuICAgIHJldHVybiBhVmFsdWUgPCAwXG4gICAgICA/ICgoLWFWYWx1ZSkgPDwgMSkgKyAxXG4gICAgICA6IChhVmFsdWUgPDwgMSkgKyAwO1xuICB9XG5cbiAgLyoqXG4gICAqIENvbnZlcnRzIHRvIGEgdHdvLWNvbXBsZW1lbnQgdmFsdWUgZnJvbSBhIHZhbHVlIHdoZXJlIHRoZSBzaWduIGJpdCBpc1xuICAgKiBwbGFjZWQgaW4gdGhlIGxlYXN0IHNpZ25pZmljYW50IGJpdC4gIEZvciBleGFtcGxlLCBhcyBkZWNpbWFsczpcbiAgICogICAyICgxMCBiaW5hcnkpIGJlY29tZXMgMSwgMyAoMTEgYmluYXJ5KSBiZWNvbWVzIC0xXG4gICAqICAgNCAoMTAwIGJpbmFyeSkgYmVjb21lcyAyLCA1ICgxMDEgYmluYXJ5KSBiZWNvbWVzIC0yXG4gICAqL1xuICBmdW5jdGlvbiBmcm9tVkxRU2lnbmVkKGFWYWx1ZSkge1xuICAgIHZhciBpc05lZ2F0aXZlID0gKGFWYWx1ZSAmIDEpID09PSAxO1xuICAgIHZhciBzaGlmdGVkID0gYVZhbHVlID4+IDE7XG4gICAgcmV0dXJuIGlzTmVnYXRpdmVcbiAgICAgID8gLXNoaWZ0ZWRcbiAgICAgIDogc2hpZnRlZDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBiYXNlIDY0IFZMUSBlbmNvZGVkIHZhbHVlLlxuICAgKi9cbiAgZXhwb3J0cy5lbmNvZGUgPSBmdW5jdGlvbiBiYXNlNjRWTFFfZW5jb2RlKGFWYWx1ZSkge1xuICAgIHZhciBlbmNvZGVkID0gXCJcIjtcbiAgICB2YXIgZGlnaXQ7XG5cbiAgICB2YXIgdmxxID0gdG9WTFFTaWduZWQoYVZhbHVlKTtcblxuICAgIGRvIHtcbiAgICAgIGRpZ2l0ID0gdmxxICYgVkxRX0JBU0VfTUFTSztcbiAgICAgIHZscSA+Pj49IFZMUV9CQVNFX1NISUZUO1xuICAgICAgaWYgKHZscSA+IDApIHtcbiAgICAgICAgLy8gVGhlcmUgYXJlIHN0aWxsIG1vcmUgZGlnaXRzIGluIHRoaXMgdmFsdWUsIHNvIHdlIG11c3QgbWFrZSBzdXJlIHRoZVxuICAgICAgICAvLyBjb250aW51YXRpb24gYml0IGlzIG1hcmtlZC5cbiAgICAgICAgZGlnaXQgfD0gVkxRX0NPTlRJTlVBVElPTl9CSVQ7XG4gICAgICB9XG4gICAgICBlbmNvZGVkICs9IGJhc2U2NC5lbmNvZGUoZGlnaXQpO1xuICAgIH0gd2hpbGUgKHZscSA+IDApO1xuXG4gICAgcmV0dXJuIGVuY29kZWQ7XG4gIH07XG5cbiAgLyoqXG4gICAqIERlY29kZXMgdGhlIG5leHQgYmFzZSA2NCBWTFEgdmFsdWUgZnJvbSB0aGUgZ2l2ZW4gc3RyaW5nIGFuZCByZXR1cm5zIHRoZVxuICAgKiB2YWx1ZSBhbmQgdGhlIHJlc3Qgb2YgdGhlIHN0cmluZyB2aWEgdGhlIG91dCBwYXJhbWV0ZXIuXG4gICAqL1xuICBleHBvcnRzLmRlY29kZSA9IGZ1bmN0aW9uIGJhc2U2NFZMUV9kZWNvZGUoYVN0ciwgYUluZGV4LCBhT3V0UGFyYW0pIHtcbiAgICB2YXIgc3RyTGVuID0gYVN0ci5sZW5ndGg7XG4gICAgdmFyIHJlc3VsdCA9IDA7XG4gICAgdmFyIHNoaWZ0ID0gMDtcbiAgICB2YXIgY29udGludWF0aW9uLCBkaWdpdDtcblxuICAgIGRvIHtcbiAgICAgIGlmIChhSW5kZXggPj0gc3RyTGVuKSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcihcIkV4cGVjdGVkIG1vcmUgZGlnaXRzIGluIGJhc2UgNjQgVkxRIHZhbHVlLlwiKTtcbiAgICAgIH1cblxuICAgICAgZGlnaXQgPSBiYXNlNjQuZGVjb2RlKGFTdHIuY2hhckNvZGVBdChhSW5kZXgrKykpO1xuICAgICAgaWYgKGRpZ2l0ID09PSAtMSkge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoXCJJbnZhbGlkIGJhc2U2NCBkaWdpdDogXCIgKyBhU3RyLmNoYXJBdChhSW5kZXggLSAxKSk7XG4gICAgICB9XG5cbiAgICAgIGNvbnRpbnVhdGlvbiA9ICEhKGRpZ2l0ICYgVkxRX0NPTlRJTlVBVElPTl9CSVQpO1xuICAgICAgZGlnaXQgJj0gVkxRX0JBU0VfTUFTSztcbiAgICAgIHJlc3VsdCA9IHJlc3VsdCArIChkaWdpdCA8PCBzaGlmdCk7XG4gICAgICBzaGlmdCArPSBWTFFfQkFTRV9TSElGVDtcbiAgICB9IHdoaWxlIChjb250aW51YXRpb24pO1xuXG4gICAgYU91dFBhcmFtLnZhbHVlID0gZnJvbVZMUVNpZ25lZChyZXN1bHQpO1xuICAgIGFPdXRQYXJhbS5yZXN0ID0gYUluZGV4O1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9iYXNlNjQtdmxxLmpzXG4gKiogbW9kdWxlIGlkID0gNFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgaW50VG9DaGFyTWFwID0gJ0FCQ0RFRkdISUpLTE1OT1BRUlNUVVZXWFlaYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXowMTIzNDU2Nzg5Ky8nLnNwbGl0KCcnKTtcblxuICAvKipcbiAgICogRW5jb2RlIGFuIGludGVnZXIgaW4gdGhlIHJhbmdlIG9mIDAgdG8gNjMgdG8gYSBzaW5nbGUgYmFzZSA2NCBkaWdpdC5cbiAgICovXG4gIGV4cG9ydHMuZW5jb2RlID0gZnVuY3Rpb24gKG51bWJlcikge1xuICAgIGlmICgwIDw9IG51bWJlciAmJiBudW1iZXIgPCBpbnRUb0NoYXJNYXAubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gaW50VG9DaGFyTWFwW251bWJlcl07XG4gICAgfVxuICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXCJNdXN0IGJlIGJldHdlZW4gMCBhbmQgNjM6IFwiICsgbnVtYmVyKTtcbiAgfTtcblxuICAvKipcbiAgICogRGVjb2RlIGEgc2luZ2xlIGJhc2UgNjQgY2hhcmFjdGVyIGNvZGUgZGlnaXQgdG8gYW4gaW50ZWdlci4gUmV0dXJucyAtMSBvblxuICAgKiBmYWlsdXJlLlxuICAgKi9cbiAgZXhwb3J0cy5kZWNvZGUgPSBmdW5jdGlvbiAoY2hhckNvZGUpIHtcbiAgICB2YXIgYmlnQSA9IDY1OyAgICAgLy8gJ0EnXG4gICAgdmFyIGJpZ1ogPSA5MDsgICAgIC8vICdaJ1xuXG4gICAgdmFyIGxpdHRsZUEgPSA5NzsgIC8vICdhJ1xuICAgIHZhciBsaXR0bGVaID0gMTIyOyAvLyAneidcblxuICAgIHZhciB6ZXJvID0gNDg7ICAgICAvLyAnMCdcbiAgICB2YXIgbmluZSA9IDU3OyAgICAgLy8gJzknXG5cbiAgICB2YXIgcGx1cyA9IDQzOyAgICAgLy8gJysnXG4gICAgdmFyIHNsYXNoID0gNDc7ICAgIC8vICcvJ1xuXG4gICAgdmFyIGxpdHRsZU9mZnNldCA9IDI2O1xuICAgIHZhciBudW1iZXJPZmZzZXQgPSA1MjtcblxuICAgIC8vIDAgLSAyNTogQUJDREVGR0hJSktMTU5PUFFSU1RVVldYWVpcbiAgICBpZiAoYmlnQSA8PSBjaGFyQ29kZSAmJiBjaGFyQ29kZSA8PSBiaWdaKSB7XG4gICAgICByZXR1cm4gKGNoYXJDb2RlIC0gYmlnQSk7XG4gICAgfVxuXG4gICAgLy8gMjYgLSA1MTogYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpcbiAgICBpZiAobGl0dGxlQSA8PSBjaGFyQ29kZSAmJiBjaGFyQ29kZSA8PSBsaXR0bGVaKSB7XG4gICAgICByZXR1cm4gKGNoYXJDb2RlIC0gbGl0dGxlQSArIGxpdHRsZU9mZnNldCk7XG4gICAgfVxuXG4gICAgLy8gNTIgLSA2MTogMDEyMzQ1Njc4OVxuICAgIGlmICh6ZXJvIDw9IGNoYXJDb2RlICYmIGNoYXJDb2RlIDw9IG5pbmUpIHtcbiAgICAgIHJldHVybiAoY2hhckNvZGUgLSB6ZXJvICsgbnVtYmVyT2Zmc2V0KTtcbiAgICB9XG5cbiAgICAvLyA2MjogK1xuICAgIGlmIChjaGFyQ29kZSA9PSBwbHVzKSB7XG4gICAgICByZXR1cm4gNjI7XG4gICAgfVxuXG4gICAgLy8gNjM6IC9cbiAgICBpZiAoY2hhckNvZGUgPT0gc2xhc2gpIHtcbiAgICAgIHJldHVybiA2MztcbiAgICB9XG5cbiAgICAvLyBJbnZhbGlkIGJhc2U2NCBkaWdpdC5cbiAgICByZXR1cm4gLTE7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2Jhc2U2NC5qc1xuICoqIG1vZHVsZSBpZCA9IDVcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICAvKipcbiAgICogQSBkYXRhIHN0cnVjdHVyZSB3aGljaCBpcyBhIGNvbWJpbmF0aW9uIG9mIGFuIGFycmF5IGFuZCBhIHNldC4gQWRkaW5nIGEgbmV3XG4gICAqIG1lbWJlciBpcyBPKDEpLCB0ZXN0aW5nIGZvciBtZW1iZXJzaGlwIGlzIE8oMSksIGFuZCBmaW5kaW5nIHRoZSBpbmRleCBvZiBhblxuICAgKiBlbGVtZW50IGlzIE8oMSkuIFJlbW92aW5nIGVsZW1lbnRzIGZyb20gdGhlIHNldCBpcyBub3Qgc3VwcG9ydGVkLiBPbmx5XG4gICAqIHN0cmluZ3MgYXJlIHN1cHBvcnRlZCBmb3IgbWVtYmVyc2hpcC5cbiAgICovXG4gIGZ1bmN0aW9uIEFycmF5U2V0KCkge1xuICAgIHRoaXMuX2FycmF5ID0gW107XG4gICAgdGhpcy5fc2V0ID0ge307XG4gIH1cblxuICAvKipcbiAgICogU3RhdGljIG1ldGhvZCBmb3IgY3JlYXRpbmcgQXJyYXlTZXQgaW5zdGFuY2VzIGZyb20gYW4gZXhpc3RpbmcgYXJyYXkuXG4gICAqL1xuICBBcnJheVNldC5mcm9tQXJyYXkgPSBmdW5jdGlvbiBBcnJheVNldF9mcm9tQXJyYXkoYUFycmF5LCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgdmFyIHNldCA9IG5ldyBBcnJheVNldCgpO1xuICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSBhQXJyYXkubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIHNldC5hZGQoYUFycmF5W2ldLCBhQWxsb3dEdXBsaWNhdGVzKTtcbiAgICB9XG4gICAgcmV0dXJuIHNldDtcbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJuIGhvdyBtYW55IHVuaXF1ZSBpdGVtcyBhcmUgaW4gdGhpcyBBcnJheVNldC4gSWYgZHVwbGljYXRlcyBoYXZlIGJlZW5cbiAgICogYWRkZWQsIHRoYW4gdGhvc2UgZG8gbm90IGNvdW50IHRvd2FyZHMgdGhlIHNpemUuXG4gICAqXG4gICAqIEByZXR1cm5zIE51bWJlclxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLnNpemUgPSBmdW5jdGlvbiBBcnJheVNldF9zaXplKCkge1xuICAgIHJldHVybiBPYmplY3QuZ2V0T3duUHJvcGVydHlOYW1lcyh0aGlzLl9zZXQpLmxlbmd0aDtcbiAgfTtcblxuICAvKipcbiAgICogQWRkIHRoZSBnaXZlbiBzdHJpbmcgdG8gdGhpcyBzZXQuXG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmFkZCA9IGZ1bmN0aW9uIEFycmF5U2V0X2FkZChhU3RyLCBhQWxsb3dEdXBsaWNhdGVzKSB7XG4gICAgdmFyIHNTdHIgPSB1dGlsLnRvU2V0U3RyaW5nKGFTdHIpO1xuICAgIHZhciBpc0R1cGxpY2F0ZSA9IHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKTtcbiAgICB2YXIgaWR4ID0gdGhpcy5fYXJyYXkubGVuZ3RoO1xuICAgIGlmICghaXNEdXBsaWNhdGUgfHwgYUFsbG93RHVwbGljYXRlcykge1xuICAgICAgdGhpcy5fYXJyYXkucHVzaChhU3RyKTtcbiAgICB9XG4gICAgaWYgKCFpc0R1cGxpY2F0ZSkge1xuICAgICAgdGhpcy5fc2V0W3NTdHJdID0gaWR4O1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogSXMgdGhlIGdpdmVuIHN0cmluZyBhIG1lbWJlciBvZiB0aGlzIHNldD9cbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuaGFzID0gZnVuY3Rpb24gQXJyYXlTZXRfaGFzKGFTdHIpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgcmV0dXJuIHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKTtcbiAgfTtcblxuICAvKipcbiAgICogV2hhdCBpcyB0aGUgaW5kZXggb2YgdGhlIGdpdmVuIHN0cmluZyBpbiB0aGUgYXJyYXk/XG4gICAqXG4gICAqIEBwYXJhbSBTdHJpbmcgYVN0clxuICAgKi9cbiAgQXJyYXlTZXQucHJvdG90eXBlLmluZGV4T2YgPSBmdW5jdGlvbiBBcnJheVNldF9pbmRleE9mKGFTdHIpIHtcbiAgICB2YXIgc1N0ciA9IHV0aWwudG9TZXRTdHJpbmcoYVN0cik7XG4gICAgaWYgKHRoaXMuX3NldC5oYXNPd25Qcm9wZXJ0eShzU3RyKSkge1xuICAgICAgcmV0dXJuIHRoaXMuX3NldFtzU3RyXTtcbiAgICB9XG4gICAgdGhyb3cgbmV3IEVycm9yKCdcIicgKyBhU3RyICsgJ1wiIGlzIG5vdCBpbiB0aGUgc2V0LicpO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXaGF0IGlzIHRoZSBlbGVtZW50IGF0IHRoZSBnaXZlbiBpbmRleD9cbiAgICpcbiAgICogQHBhcmFtIE51bWJlciBhSWR4XG4gICAqL1xuICBBcnJheVNldC5wcm90b3R5cGUuYXQgPSBmdW5jdGlvbiBBcnJheVNldF9hdChhSWR4KSB7XG4gICAgaWYgKGFJZHggPj0gMCAmJiBhSWR4IDwgdGhpcy5fYXJyYXkubGVuZ3RoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fYXJyYXlbYUlkeF07XG4gICAgfVxuICAgIHRocm93IG5ldyBFcnJvcignTm8gZWxlbWVudCBpbmRleGVkIGJ5ICcgKyBhSWR4KTtcbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgYXJyYXkgcmVwcmVzZW50YXRpb24gb2YgdGhpcyBzZXQgKHdoaWNoIGhhcyB0aGUgcHJvcGVyIGluZGljZXNcbiAgICogaW5kaWNhdGVkIGJ5IGluZGV4T2YpLiBOb3RlIHRoYXQgdGhpcyBpcyBhIGNvcHkgb2YgdGhlIGludGVybmFsIGFycmF5IHVzZWRcbiAgICogZm9yIHN0b3JpbmcgdGhlIG1lbWJlcnMgc28gdGhhdCBubyBvbmUgY2FuIG1lc3Mgd2l0aCBpbnRlcm5hbCBzdGF0ZS5cbiAgICovXG4gIEFycmF5U2V0LnByb3RvdHlwZS50b0FycmF5ID0gZnVuY3Rpb24gQXJyYXlTZXRfdG9BcnJheSgpIHtcbiAgICByZXR1cm4gdGhpcy5fYXJyYXkuc2xpY2UoKTtcbiAgfTtcblxuICBleHBvcnRzLkFycmF5U2V0ID0gQXJyYXlTZXQ7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL2FycmF5LXNldC5qc1xuICoqIG1vZHVsZSBpZCA9IDZcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxNCBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcblxuICAvKipcbiAgICogRGV0ZXJtaW5lIHdoZXRoZXIgbWFwcGluZ0IgaXMgYWZ0ZXIgbWFwcGluZ0Egd2l0aCByZXNwZWN0IHRvIGdlbmVyYXRlZFxuICAgKiBwb3NpdGlvbi5cbiAgICovXG4gIGZ1bmN0aW9uIGdlbmVyYXRlZFBvc2l0aW9uQWZ0ZXIobWFwcGluZ0EsIG1hcHBpbmdCKSB7XG4gICAgLy8gT3B0aW1pemVkIGZvciBtb3N0IGNvbW1vbiBjYXNlXG4gICAgdmFyIGxpbmVBID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZTtcbiAgICB2YXIgbGluZUIgPSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIHZhciBjb2x1bW5BID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHZhciBjb2x1bW5CID0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIHJldHVybiBsaW5lQiA+IGxpbmVBIHx8IGxpbmVCID09IGxpbmVBICYmIGNvbHVtbkIgPj0gY29sdW1uQSB8fFxuICAgICAgICAgICB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQikgPD0gMDtcbiAgfVxuXG4gIC8qKlxuICAgKiBBIGRhdGEgc3RydWN0dXJlIHRvIHByb3ZpZGUgYSBzb3J0ZWQgdmlldyBvZiBhY2N1bXVsYXRlZCBtYXBwaW5ncyBpbiBhXG4gICAqIHBlcmZvcm1hbmNlIGNvbnNjaW91cyBtYW5uZXIuIEl0IHRyYWRlcyBhIG5lZ2xpYmFibGUgb3ZlcmhlYWQgaW4gZ2VuZXJhbFxuICAgKiBjYXNlIGZvciBhIGxhcmdlIHNwZWVkdXAgaW4gY2FzZSBvZiBtYXBwaW5ncyBiZWluZyBhZGRlZCBpbiBvcmRlci5cbiAgICovXG4gIGZ1bmN0aW9uIE1hcHBpbmdMaXN0KCkge1xuICAgIHRoaXMuX2FycmF5ID0gW107XG4gICAgdGhpcy5fc29ydGVkID0gdHJ1ZTtcbiAgICAvLyBTZXJ2ZXMgYXMgaW5maW11bVxuICAgIHRoaXMuX2xhc3QgPSB7Z2VuZXJhdGVkTGluZTogLTEsIGdlbmVyYXRlZENvbHVtbjogMH07XG4gIH1cblxuICAvKipcbiAgICogSXRlcmF0ZSB0aHJvdWdoIGludGVybmFsIGl0ZW1zLiBUaGlzIG1ldGhvZCB0YWtlcyB0aGUgc2FtZSBhcmd1bWVudHMgdGhhdFxuICAgKiBgQXJyYXkucHJvdG90eXBlLmZvckVhY2hgIHRha2VzLlxuICAgKlxuICAgKiBOT1RFOiBUaGUgb3JkZXIgb2YgdGhlIG1hcHBpbmdzIGlzIE5PVCBndWFyYW50ZWVkLlxuICAgKi9cbiAgTWFwcGluZ0xpc3QucHJvdG90eXBlLnVuc29ydGVkRm9yRWFjaCA9XG4gICAgZnVuY3Rpb24gTWFwcGluZ0xpc3RfZm9yRWFjaChhQ2FsbGJhY2ssIGFUaGlzQXJnKSB7XG4gICAgICB0aGlzLl9hcnJheS5mb3JFYWNoKGFDYWxsYmFjaywgYVRoaXNBcmcpO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCB0aGUgZ2l2ZW4gc291cmNlIG1hcHBpbmcuXG4gICAqXG4gICAqIEBwYXJhbSBPYmplY3QgYU1hcHBpbmdcbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBNYXBwaW5nTGlzdF9hZGQoYU1hcHBpbmcpIHtcbiAgICBpZiAoZ2VuZXJhdGVkUG9zaXRpb25BZnRlcih0aGlzLl9sYXN0LCBhTWFwcGluZykpIHtcbiAgICAgIHRoaXMuX2xhc3QgPSBhTWFwcGluZztcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aGlzLl9zb3J0ZWQgPSBmYWxzZTtcbiAgICAgIHRoaXMuX2FycmF5LnB1c2goYU1hcHBpbmcpO1xuICAgIH1cbiAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZmxhdCwgc29ydGVkIGFycmF5IG9mIG1hcHBpbmdzLiBUaGUgbWFwcGluZ3MgYXJlIHNvcnRlZCBieVxuICAgKiBnZW5lcmF0ZWQgcG9zaXRpb24uXG4gICAqXG4gICAqIFdBUk5JTkc6IFRoaXMgbWV0aG9kIHJldHVybnMgaW50ZXJuYWwgZGF0YSB3aXRob3V0IGNvcHlpbmcsIGZvclxuICAgKiBwZXJmb3JtYW5jZS4gVGhlIHJldHVybiB2YWx1ZSBtdXN0IE5PVCBiZSBtdXRhdGVkLCBhbmQgc2hvdWxkIGJlIHRyZWF0ZWQgYXNcbiAgICogYW4gaW1tdXRhYmxlIGJvcnJvdy4gSWYgeW91IHdhbnQgdG8gdGFrZSBvd25lcnNoaXAsIHlvdSBtdXN0IG1ha2UgeW91ciBvd25cbiAgICogY29weS5cbiAgICovXG4gIE1hcHBpbmdMaXN0LnByb3RvdHlwZS50b0FycmF5ID0gZnVuY3Rpb24gTWFwcGluZ0xpc3RfdG9BcnJheSgpIHtcbiAgICBpZiAoIXRoaXMuX3NvcnRlZCkge1xuICAgICAgdGhpcy5fYXJyYXkuc29ydCh1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0luZmxhdGVkKTtcbiAgICAgIHRoaXMuX3NvcnRlZCA9IHRydWU7XG4gICAgfVxuICAgIHJldHVybiB0aGlzLl9hcnJheTtcbiAgfTtcblxuICBleHBvcnRzLk1hcHBpbmdMaXN0ID0gTWFwcGluZ0xpc3Q7XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vbGliL21hcHBpbmctbGlzdC5qc1xuICoqIG1vZHVsZSBpZCA9IDdcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIHV0aWwgPSByZXF1aXJlKCcuL3V0aWwnKTtcbiAgdmFyIGJpbmFyeVNlYXJjaCA9IHJlcXVpcmUoJy4vYmluYXJ5LXNlYXJjaCcpO1xuICB2YXIgQXJyYXlTZXQgPSByZXF1aXJlKCcuL2FycmF5LXNldCcpLkFycmF5U2V0O1xuICB2YXIgYmFzZTY0VkxRID0gcmVxdWlyZSgnLi9iYXNlNjQtdmxxJyk7XG4gIHZhciBxdWlja1NvcnQgPSByZXF1aXJlKCcuL3F1aWNrLXNvcnQnKS5xdWlja1NvcnQ7XG5cbiAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXIoYVNvdXJjZU1hcCkge1xuICAgIHZhciBzb3VyY2VNYXAgPSBhU291cmNlTWFwO1xuICAgIGlmICh0eXBlb2YgYVNvdXJjZU1hcCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHNvdXJjZU1hcCA9IEpTT04ucGFyc2UoYVNvdXJjZU1hcC5yZXBsYWNlKC9eXFwpXFxdXFx9Jy8sICcnKSk7XG4gICAgfVxuXG4gICAgcmV0dXJuIHNvdXJjZU1hcC5zZWN0aW9ucyAhPSBudWxsXG4gICAgICA/IG5ldyBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIoc291cmNlTWFwKVxuICAgICAgOiBuZXcgQmFzaWNTb3VyY2VNYXBDb25zdW1lcihzb3VyY2VNYXApO1xuICB9XG5cbiAgU291cmNlTWFwQ29uc3VtZXIuZnJvbVNvdXJjZU1hcCA9IGZ1bmN0aW9uKGFTb3VyY2VNYXApIHtcbiAgICByZXR1cm4gQmFzaWNTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwKGFTb3VyY2VNYXApO1xuICB9XG5cbiAgLyoqXG4gICAqIFRoZSB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwcGluZyBzcGVjIHRoYXQgd2UgYXJlIGNvbnN1bWluZy5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLy8gYF9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZCBgX19vcmlnaW5hbE1hcHBpbmdzYCBhcmUgYXJyYXlzIHRoYXQgaG9sZCB0aGVcbiAgLy8gcGFyc2VkIG1hcHBpbmcgY29vcmRpbmF0ZXMgZnJvbSB0aGUgc291cmNlIG1hcCdzIFwibWFwcGluZ3NcIiBhdHRyaWJ1dGUuIFRoZXlcbiAgLy8gYXJlIGxhemlseSBpbnN0YW50aWF0ZWQsIGFjY2Vzc2VkIHZpYSB0aGUgYF9nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gIC8vIGBfb3JpZ2luYWxNYXBwaW5nc2AgZ2V0dGVycyByZXNwZWN0aXZlbHksIGFuZCB3ZSBvbmx5IHBhcnNlIHRoZSBtYXBwaW5nc1xuICAvLyBhbmQgY3JlYXRlIHRoZXNlIGFycmF5cyBvbmNlIHF1ZXJpZWQgZm9yIGEgc291cmNlIGxvY2F0aW9uLiBXZSBqdW1wIHRocm91Z2hcbiAgLy8gdGhlc2UgaG9vcHMgYmVjYXVzZSB0aGVyZSBjYW4gYmUgbWFueSB0aG91c2FuZHMgb2YgbWFwcGluZ3MsIGFuZCBwYXJzaW5nXG4gIC8vIHRoZW0gaXMgZXhwZW5zaXZlLCBzbyB3ZSBvbmx5IHdhbnQgdG8gZG8gaXQgaWYgd2UgbXVzdC5cbiAgLy9cbiAgLy8gRWFjaCBvYmplY3QgaW4gdGhlIGFycmF5cyBpcyBvZiB0aGUgZm9ybTpcbiAgLy9cbiAgLy8gICAgIHtcbiAgLy8gICAgICAgZ2VuZXJhdGVkTGluZTogVGhlIGxpbmUgbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgY29kZSxcbiAgLy8gICAgICAgZ2VuZXJhdGVkQ29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIGNvZGUsXG4gIC8vICAgICAgIHNvdXJjZTogVGhlIHBhdGggdG8gdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlIHRoYXQgZ2VuZXJhdGVkIHRoaXNcbiAgLy8gICAgICAgICAgICAgICBjaHVuayBvZiBjb2RlLFxuICAvLyAgICAgICBvcmlnaW5hbExpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlIHRoYXRcbiAgLy8gICAgICAgICAgICAgICAgICAgICBjb3JyZXNwb25kcyB0byB0aGlzIGNodW5rIG9mIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBvcmlnaW5hbENvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSB0aGF0XG4gIC8vICAgICAgICAgICAgICAgICAgICAgICBjb3JyZXNwb25kcyB0byB0aGlzIGNodW5rIG9mIGdlbmVyYXRlZCBjb2RlLFxuICAvLyAgICAgICBuYW1lOiBUaGUgbmFtZSBvZiB0aGUgb3JpZ2luYWwgc3ltYm9sIHdoaWNoIGdlbmVyYXRlZCB0aGlzIGNodW5rIG9mXG4gIC8vICAgICAgICAgICAgIGNvZGUuXG4gIC8vICAgICB9XG4gIC8vXG4gIC8vIEFsbCBwcm9wZXJ0aWVzIGV4Y2VwdCBmb3IgYGdlbmVyYXRlZExpbmVgIGFuZCBgZ2VuZXJhdGVkQ29sdW1uYCBjYW4gYmVcbiAgLy8gYG51bGxgLlxuICAvL1xuICAvLyBgX2dlbmVyYXRlZE1hcHBpbmdzYCBpcyBvcmRlcmVkIGJ5IHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zLlxuICAvL1xuICAvLyBgX29yaWdpbmFsTWFwcGluZ3NgIGlzIG9yZGVyZWQgYnkgdGhlIG9yaWdpbmFsIHBvc2l0aW9ucy5cblxuICBTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX19nZW5lcmF0ZWRNYXBwaW5ncyA9IG51bGw7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdfZ2VuZXJhdGVkTWFwcGluZ3MnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICBpZiAoIXRoaXMuX19nZW5lcmF0ZWRNYXBwaW5ncykge1xuICAgICAgICB0aGlzLl9wYXJzZU1hcHBpbmdzKHRoaXMuX21hcHBpbmdzLCB0aGlzLnNvdXJjZVJvb3QpO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzO1xuICAgIH1cbiAgfSk7XG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9fb3JpZ2luYWxNYXBwaW5ncyA9IG51bGw7XG4gIE9iamVjdC5kZWZpbmVQcm9wZXJ0eShTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUsICdfb3JpZ2luYWxNYXBwaW5ncycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIGlmICghdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MpIHtcbiAgICAgICAgdGhpcy5fcGFyc2VNYXBwaW5ncyh0aGlzLl9tYXBwaW5ncywgdGhpcy5zb3VyY2VSb290KTtcbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzO1xuICAgIH1cbiAgfSk7XG5cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9jaGFySXNNYXBwaW5nU2VwYXJhdG9yID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9jaGFySXNNYXBwaW5nU2VwYXJhdG9yKGFTdHIsIGluZGV4KSB7XG4gICAgICB2YXIgYyA9IGFTdHIuY2hhckF0KGluZGV4KTtcbiAgICAgIHJldHVybiBjID09PSBcIjtcIiB8fCBjID09PSBcIixcIjtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBQYXJzZSB0aGUgbWFwcGluZ3MgaW4gYSBzdHJpbmcgaW4gdG8gYSBkYXRhIHN0cnVjdHVyZSB3aGljaCB3ZSBjYW4gZWFzaWx5XG4gICAqIHF1ZXJ5ICh0aGUgb3JkZXJlZCBhcnJheXMgaW4gdGhlIGB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3NgIGFuZFxuICAgKiBgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3NgIHByb3BlcnRpZXMpLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9wYXJzZU1hcHBpbmdzKGFTdHIsIGFTb3VyY2VSb290KSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoXCJTdWJjbGFzc2VzIG11c3QgaW1wbGVtZW50IF9wYXJzZU1hcHBpbmdzXCIpO1xuICAgIH07XG5cbiAgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSID0gMTtcbiAgU291cmNlTWFwQ29uc3VtZXIuT1JJR0lOQUxfT1JERVIgPSAyO1xuXG4gIFNvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EID0gMTtcbiAgU291cmNlTWFwQ29uc3VtZXIuTEVBU1RfVVBQRVJfQk9VTkQgPSAyO1xuXG4gIC8qKlxuICAgKiBJdGVyYXRlIG92ZXIgZWFjaCBtYXBwaW5nIGJldHdlZW4gYW4gb3JpZ2luYWwgc291cmNlL2xpbmUvY29sdW1uIGFuZCBhXG4gICAqIGdlbmVyYXRlZCBsaW5lL2NvbHVtbiBpbiB0aGlzIHNvdXJjZSBtYXAuXG4gICAqXG4gICAqIEBwYXJhbSBGdW5jdGlvbiBhQ2FsbGJhY2tcbiAgICogICAgICAgIFRoZSBmdW5jdGlvbiB0aGF0IGlzIGNhbGxlZCB3aXRoIGVhY2ggbWFwcGluZy5cbiAgICogQHBhcmFtIE9iamVjdCBhQ29udGV4dFxuICAgKiAgICAgICAgT3B0aW9uYWwuIElmIHNwZWNpZmllZCwgdGhpcyBvYmplY3Qgd2lsbCBiZSB0aGUgdmFsdWUgb2YgYHRoaXNgIGV2ZXJ5XG4gICAqICAgICAgICB0aW1lIHRoYXQgYGFDYWxsYmFja2AgaXMgY2FsbGVkLlxuICAgKiBAcGFyYW0gYU9yZGVyXG4gICAqICAgICAgICBFaXRoZXIgYFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUmAgb3JcbiAgICogICAgICAgIGBTb3VyY2VNYXBDb25zdW1lci5PUklHSU5BTF9PUkRFUmAuIFNwZWNpZmllcyB3aGV0aGVyIHlvdSB3YW50IHRvXG4gICAqICAgICAgICBpdGVyYXRlIG92ZXIgdGhlIG1hcHBpbmdzIHNvcnRlZCBieSB0aGUgZ2VuZXJhdGVkIGZpbGUncyBsaW5lL2NvbHVtblxuICAgKiAgICAgICAgb3JkZXIgb3IgdGhlIG9yaWdpbmFsJ3Mgc291cmNlL2xpbmUvY29sdW1uIG9yZGVyLCByZXNwZWN0aXZlbHkuIERlZmF1bHRzIHRvXG4gICAqICAgICAgICBgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSYC5cbiAgICovXG4gIFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5lYWNoTWFwcGluZyA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZWFjaE1hcHBpbmcoYUNhbGxiYWNrLCBhQ29udGV4dCwgYU9yZGVyKSB7XG4gICAgICB2YXIgY29udGV4dCA9IGFDb250ZXh0IHx8IG51bGw7XG4gICAgICB2YXIgb3JkZXIgPSBhT3JkZXIgfHwgU291cmNlTWFwQ29uc3VtZXIuR0VORVJBVEVEX09SREVSO1xuXG4gICAgICB2YXIgbWFwcGluZ3M7XG4gICAgICBzd2l0Y2ggKG9yZGVyKSB7XG4gICAgICBjYXNlIFNvdXJjZU1hcENvbnN1bWVyLkdFTkVSQVRFRF9PUkRFUjpcbiAgICAgICAgbWFwcGluZ3MgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncztcbiAgICAgICAgYnJlYWs7XG4gICAgICBjYXNlIFNvdXJjZU1hcENvbnN1bWVyLk9SSUdJTkFMX09SREVSOlxuICAgICAgICBtYXBwaW5ncyA9IHRoaXMuX29yaWdpbmFsTWFwcGluZ3M7XG4gICAgICAgIGJyZWFrO1xuICAgICAgZGVmYXVsdDpcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKFwiVW5rbm93biBvcmRlciBvZiBpdGVyYXRpb24uXCIpO1xuICAgICAgfVxuXG4gICAgICB2YXIgc291cmNlUm9vdCA9IHRoaXMuc291cmNlUm9vdDtcbiAgICAgIG1hcHBpbmdzLm1hcChmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICB2YXIgc291cmNlID0gbWFwcGluZy5zb3VyY2UgPT09IG51bGwgPyBudWxsIDogdGhpcy5fc291cmNlcy5hdChtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgIGlmIChzb3VyY2UgIT0gbnVsbCAmJiBzb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICBzb3VyY2UgPSB1dGlsLmpvaW4oc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgfVxuICAgICAgICByZXR1cm4ge1xuICAgICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSxcbiAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uLFxuICAgICAgICAgIG9yaWdpbmFsTGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgb3JpZ2luYWxDb2x1bW46IG1hcHBpbmcub3JpZ2luYWxDb2x1bW4sXG4gICAgICAgICAgbmFtZTogbWFwcGluZy5uYW1lID09PSBudWxsID8gbnVsbCA6IHRoaXMuX25hbWVzLmF0KG1hcHBpbmcubmFtZSlcbiAgICAgICAgfTtcbiAgICAgIH0sIHRoaXMpLmZvckVhY2goYUNhbGxiYWNrLCBjb250ZXh0KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGFsbCBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgb3JpZ2luYWwgc291cmNlLFxuICAgKiBsaW5lLCBhbmQgY29sdW1uIHByb3ZpZGVkLiBJZiBubyBjb2x1bW4gaXMgcHJvdmlkZWQsIHJldHVybnMgYWxsIG1hcHBpbmdzXG4gICAqIGNvcnJlc3BvbmRpbmcgdG8gYSBlaXRoZXIgdGhlIGxpbmUgd2UgYXJlIHNlYXJjaGluZyBmb3Igb3IgdGhlIG5leHRcbiAgICogY2xvc2VzdCBsaW5lIHRoYXQgaGFzIGFueSBtYXBwaW5ncy4gT3RoZXJ3aXNlLCByZXR1cm5zIGFsbCBtYXBwaW5nc1xuICAgKiBjb3JyZXNwb25kaW5nIHRvIHRoZSBnaXZlbiBsaW5lIGFuZCBlaXRoZXIgdGhlIGNvbHVtbiB3ZSBhcmUgc2VhcmNoaW5nIGZvclxuICAgKiBvciB0aGUgbmV4dCBjbG9zZXN0IGNvbHVtbiB0aGF0IGhhcyBhbnkgb2Zmc2V0cy5cbiAgICpcbiAgICogVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0IHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBPcHRpb25hbC4gdGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIGFycmF5IG9mIG9iamVjdHMgaXMgcmV0dXJuZWQsIGVhY2ggd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLCBvciBudWxsLlxuICAgKi9cbiAgU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmFsbEdlbmVyYXRlZFBvc2l0aW9uc0ZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfYWxsR2VuZXJhdGVkUG9zaXRpb25zRm9yKGFBcmdzKSB7XG4gICAgICB2YXIgbGluZSA9IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpO1xuXG4gICAgICAvLyBXaGVuIHRoZXJlIGlzIG5vIGV4YWN0IG1hdGNoLCBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fZmluZE1hcHBpbmdcbiAgICAgIC8vIHJldHVybnMgdGhlIGluZGV4IG9mIHRoZSBjbG9zZXN0IG1hcHBpbmcgbGVzcyB0aGFuIHRoZSBuZWVkbGUuIEJ5XG4gICAgICAvLyBzZXR0aW5nIG5lZWRsZS5vcmlnaW5hbENvbHVtbiB0byAwLCB3ZSB0aHVzIGZpbmQgdGhlIGxhc3QgbWFwcGluZyBmb3JcbiAgICAgIC8vIHRoZSBnaXZlbiBsaW5lLCBwcm92aWRlZCBzdWNoIGEgbWFwcGluZyBleGlzdHMuXG4gICAgICB2YXIgbmVlZGxlID0ge1xuICAgICAgICBzb3VyY2U6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJyksXG4gICAgICAgIG9yaWdpbmFsTGluZTogbGluZSxcbiAgICAgICAgb3JpZ2luYWxDb2x1bW46IHV0aWwuZ2V0QXJnKGFBcmdzLCAnY29sdW1uJywgMClcbiAgICAgIH07XG5cbiAgICAgIGlmICh0aGlzLnNvdXJjZVJvb3QgIT0gbnVsbCkge1xuICAgICAgICBuZWVkbGUuc291cmNlID0gdXRpbC5yZWxhdGl2ZSh0aGlzLnNvdXJjZVJvb3QsIG5lZWRsZS5zb3VyY2UpO1xuICAgICAgfVxuICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzLmhhcyhuZWVkbGUuc291cmNlKSkge1xuICAgICAgICByZXR1cm4gW107XG4gICAgICB9XG4gICAgICBuZWVkbGUuc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKG5lZWRsZS5zb3VyY2UpO1xuXG4gICAgICB2YXIgbWFwcGluZ3MgPSBbXTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcobmVlZGxlLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGhpcy5fb3JpZ2luYWxNYXBwaW5ncyxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIFwib3JpZ2luYWxMaW5lXCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBcIm9yaWdpbmFsQ29sdW1uXCIsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYmluYXJ5U2VhcmNoLkxFQVNUX1VQUEVSX0JPVU5EKTtcbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgaWYgKGFBcmdzLmNvbHVtbiA9PT0gdW5kZWZpbmVkKSB7XG4gICAgICAgICAgdmFyIG9yaWdpbmFsTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lO1xuXG4gICAgICAgICAgLy8gSXRlcmF0ZSB1bnRpbCBlaXRoZXIgd2UgcnVuIG91dCBvZiBtYXBwaW5ncywgb3Igd2UgcnVuIGludG9cbiAgICAgICAgICAvLyBhIG1hcHBpbmcgZm9yIGEgZGlmZmVyZW50IGxpbmUgdGhhbiB0aGUgb25lIHdlIGZvdW5kLiBTaW5jZVxuICAgICAgICAgIC8vIG1hcHBpbmdzIGFyZSBzb3J0ZWQsIHRoaXMgaXMgZ3VhcmFudGVlZCB0byBmaW5kIGFsbCBtYXBwaW5ncyBmb3JcbiAgICAgICAgICAvLyB0aGUgbGluZSB3ZSBmb3VuZC5cbiAgICAgICAgICB3aGlsZSAobWFwcGluZyAmJiBtYXBwaW5nLm9yaWdpbmFsTGluZSA9PT0gb3JpZ2luYWxMaW5lKSB7XG4gICAgICAgICAgICBtYXBwaW5ncy5wdXNoKHtcbiAgICAgICAgICAgICAgbGluZTogdXRpbC5nZXRBcmcobWFwcGluZywgJ2dlbmVyYXRlZExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgICAgY29sdW1uOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICAgIH0pO1xuXG4gICAgICAgICAgICBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1srK2luZGV4XTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgdmFyIG9yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgIC8vIEl0ZXJhdGUgdW50aWwgZWl0aGVyIHdlIHJ1biBvdXQgb2YgbWFwcGluZ3MsIG9yIHdlIHJ1biBpbnRvXG4gICAgICAgICAgLy8gYSBtYXBwaW5nIGZvciBhIGRpZmZlcmVudCBsaW5lIHRoYW4gdGhlIG9uZSB3ZSB3ZXJlIHNlYXJjaGluZyBmb3IuXG4gICAgICAgICAgLy8gU2luY2UgbWFwcGluZ3MgYXJlIHNvcnRlZCwgdGhpcyBpcyBndWFyYW50ZWVkIHRvIGZpbmQgYWxsIG1hcHBpbmdzIGZvclxuICAgICAgICAgIC8vIHRoZSBsaW5lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLlxuICAgICAgICAgIHdoaWxlIChtYXBwaW5nICYmXG4gICAgICAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxMaW5lID09PSBsaW5lICYmXG4gICAgICAgICAgICAgICAgIG1hcHBpbmcub3JpZ2luYWxDb2x1bW4gPT0gb3JpZ2luYWxDb2x1bW4pIHtcbiAgICAgICAgICAgIG1hcHBpbmdzLnB1c2goe1xuICAgICAgICAgICAgICBsaW5lOiB1dGlsLmdldEFyZyhtYXBwaW5nLCAnZ2VuZXJhdGVkTGluZScsIG51bGwpLFxuICAgICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgICAgbGFzdENvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ2xhc3RHZW5lcmF0ZWRDb2x1bW4nLCBudWxsKVxuICAgICAgICAgICAgfSk7XG5cbiAgICAgICAgICAgIG1hcHBpbmcgPSB0aGlzLl9vcmlnaW5hbE1hcHBpbmdzWysraW5kZXhdO1xuICAgICAgICAgIH1cbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4gbWFwcGluZ3M7XG4gICAgfTtcblxuICBleHBvcnRzLlNvdXJjZU1hcENvbnN1bWVyID0gU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIEEgQmFzaWNTb3VyY2VNYXBDb25zdW1lciBpbnN0YW5jZSByZXByZXNlbnRzIGEgcGFyc2VkIHNvdXJjZSBtYXAgd2hpY2ggd2UgY2FuXG4gICAqIHF1ZXJ5IGZvciBpbmZvcm1hdGlvbiBhYm91dCB0aGUgb3JpZ2luYWwgZmlsZSBwb3NpdGlvbnMgYnkgZ2l2aW5nIGl0IGEgZmlsZVxuICAgKiBwb3NpdGlvbiBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICpcbiAgICogVGhlIG9ubHkgcGFyYW1ldGVyIGlzIHRoZSByYXcgc291cmNlIG1hcCAoZWl0aGVyIGFzIGEgSlNPTiBzdHJpbmcsIG9yXG4gICAqIGFscmVhZHkgcGFyc2VkIHRvIGFuIG9iamVjdCkuIEFjY29yZGluZyB0byB0aGUgc3BlYywgc291cmNlIG1hcHMgaGF2ZSB0aGVcbiAgICogZm9sbG93aW5nIGF0dHJpYnV0ZXM6XG4gICAqXG4gICAqICAgLSB2ZXJzaW9uOiBXaGljaCB2ZXJzaW9uIG9mIHRoZSBzb3VyY2UgbWFwIHNwZWMgdGhpcyBtYXAgaXMgZm9sbG93aW5nLlxuICAgKiAgIC0gc291cmNlczogQW4gYXJyYXkgb2YgVVJMcyB0byB0aGUgb3JpZ2luYWwgc291cmNlIGZpbGVzLlxuICAgKiAgIC0gbmFtZXM6IEFuIGFycmF5IG9mIGlkZW50aWZpZXJzIHdoaWNoIGNhbiBiZSByZWZlcnJlbmNlZCBieSBpbmRpdmlkdWFsIG1hcHBpbmdzLlxuICAgKiAgIC0gc291cmNlUm9vdDogT3B0aW9uYWwuIFRoZSBVUkwgcm9vdCBmcm9tIHdoaWNoIGFsbCBzb3VyY2VzIGFyZSByZWxhdGl2ZS5cbiAgICogICAtIHNvdXJjZXNDb250ZW50OiBPcHRpb25hbC4gQW4gYXJyYXkgb2YgY29udGVudHMgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZSBmaWxlcy5cbiAgICogICAtIG1hcHBpbmdzOiBBIHN0cmluZyBvZiBiYXNlNjQgVkxRcyB3aGljaCBjb250YWluIHRoZSBhY3R1YWwgbWFwcGluZ3MuXG4gICAqICAgLSBmaWxlOiBPcHRpb25hbC4gVGhlIGdlbmVyYXRlZCBmaWxlIHRoaXMgc291cmNlIG1hcCBpcyBhc3NvY2lhdGVkIHdpdGguXG4gICAqXG4gICAqIEhlcmUgaXMgYW4gZXhhbXBsZSBzb3VyY2UgbWFwLCB0YWtlbiBmcm9tIHRoZSBzb3VyY2UgbWFwIHNwZWNbMF06XG4gICAqXG4gICAqICAgICB7XG4gICAqICAgICAgIHZlcnNpb24gOiAzLFxuICAgKiAgICAgICBmaWxlOiBcIm91dC5qc1wiLFxuICAgKiAgICAgICBzb3VyY2VSb290IDogXCJcIixcbiAgICogICAgICAgc291cmNlczogW1wiZm9vLmpzXCIsIFwiYmFyLmpzXCJdLFxuICAgKiAgICAgICBuYW1lczogW1wic3JjXCIsIFwibWFwc1wiLCBcImFyZVwiLCBcImZ1blwiXSxcbiAgICogICAgICAgbWFwcGluZ3M6IFwiQUEsQUI7O0FCQ0RFO1wiXG4gICAqICAgICB9XG4gICAqXG4gICAqIFswXTogaHR0cHM6Ly9kb2NzLmdvb2dsZS5jb20vZG9jdW1lbnQvZC8xVTFSR0FlaFF3UnlwVVRvdkYxS1JscGlPRnplMGItXzJnYzZmQUgwS1kway9lZGl0P3BsaT0xI1xuICAgKi9cbiAgZnVuY3Rpb24gQmFzaWNTb3VyY2VNYXBDb25zdW1lcihhU291cmNlTWFwKSB7XG4gICAgdmFyIHNvdXJjZU1hcCA9IGFTb3VyY2VNYXA7XG4gICAgaWYgKHR5cGVvZiBhU291cmNlTWFwID09PSAnc3RyaW5nJykge1xuICAgICAgc291cmNlTWFwID0gSlNPTi5wYXJzZShhU291cmNlTWFwLnJlcGxhY2UoL15cXClcXF1cXH0nLywgJycpKTtcbiAgICB9XG5cbiAgICB2YXIgdmVyc2lvbiA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3ZlcnNpb24nKTtcbiAgICB2YXIgc291cmNlcyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NvdXJjZXMnKTtcbiAgICAvLyBTYXNzIDMuMyBsZWF2ZXMgb3V0IHRoZSAnbmFtZXMnIGFycmF5LCBzbyB3ZSBkZXZpYXRlIGZyb20gdGhlIHNwZWMgKHdoaWNoXG4gICAgLy8gcmVxdWlyZXMgdGhlIGFycmF5KSB0byBwbGF5IG5pY2UgaGVyZS5cbiAgICB2YXIgbmFtZXMgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICduYW1lcycsIFtdKTtcbiAgICB2YXIgc291cmNlUm9vdCA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NvdXJjZVJvb3QnLCBudWxsKTtcbiAgICB2YXIgc291cmNlc0NvbnRlbnQgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdzb3VyY2VzQ29udGVudCcsIG51bGwpO1xuICAgIHZhciBtYXBwaW5ncyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ21hcHBpbmdzJyk7XG4gICAgdmFyIGZpbGUgPSB1dGlsLmdldEFyZyhzb3VyY2VNYXAsICdmaWxlJywgbnVsbCk7XG5cbiAgICAvLyBPbmNlIGFnYWluLCBTYXNzIGRldmlhdGVzIGZyb20gdGhlIHNwZWMgYW5kIHN1cHBsaWVzIHRoZSB2ZXJzaW9uIGFzIGFcbiAgICAvLyBzdHJpbmcgcmF0aGVyIHRoYW4gYSBudW1iZXIsIHNvIHdlIHVzZSBsb29zZSBlcXVhbGl0eSBjaGVja2luZyBoZXJlLlxuICAgIGlmICh2ZXJzaW9uICE9IHRoaXMuX3ZlcnNpb24pIHtcbiAgICAgIHRocm93IG5ldyBFcnJvcignVW5zdXBwb3J0ZWQgdmVyc2lvbjogJyArIHZlcnNpb24pO1xuICAgIH1cblxuICAgIHNvdXJjZXMgPSBzb3VyY2VzXG4gICAgICAvLyBTb21lIHNvdXJjZSBtYXBzIHByb2R1Y2UgcmVsYXRpdmUgc291cmNlIHBhdGhzIGxpa2UgXCIuL2Zvby5qc1wiIGluc3RlYWQgb2ZcbiAgICAgIC8vIFwiZm9vLmpzXCIuICBOb3JtYWxpemUgdGhlc2UgZmlyc3Qgc28gdGhhdCBmdXR1cmUgY29tcGFyaXNvbnMgd2lsbCBzdWNjZWVkLlxuICAgICAgLy8gU2VlIGJ1Z3ppbC5sYS8xMDkwNzY4LlxuICAgICAgLm1hcCh1dGlsLm5vcm1hbGl6ZSlcbiAgICAgIC8vIEFsd2F5cyBlbnN1cmUgdGhhdCBhYnNvbHV0ZSBzb3VyY2VzIGFyZSBpbnRlcm5hbGx5IHN0b3JlZCByZWxhdGl2ZSB0b1xuICAgICAgLy8gdGhlIHNvdXJjZSByb290LCBpZiB0aGUgc291cmNlIHJvb3QgaXMgYWJzb2x1dGUuIE5vdCBkb2luZyB0aGlzIHdvdWxkXG4gICAgICAvLyBiZSBwYXJ0aWN1bGFybHkgcHJvYmxlbWF0aWMgd2hlbiB0aGUgc291cmNlIHJvb3QgaXMgYSBwcmVmaXggb2YgdGhlXG4gICAgICAvLyBzb3VyY2UgKHZhbGlkLCBidXQgd2h5Pz8pLiBTZWUgZ2l0aHViIGlzc3VlICMxOTkgYW5kIGJ1Z3ppbC5sYS8xMTg4OTgyLlxuICAgICAgLm1hcChmdW5jdGlvbiAoc291cmNlKSB7XG4gICAgICAgIHJldHVybiBzb3VyY2VSb290ICYmIHV0aWwuaXNBYnNvbHV0ZShzb3VyY2VSb290KSAmJiB1dGlsLmlzQWJzb2x1dGUoc291cmNlKVxuICAgICAgICAgID8gdXRpbC5yZWxhdGl2ZShzb3VyY2VSb290LCBzb3VyY2UpXG4gICAgICAgICAgOiBzb3VyY2U7XG4gICAgICB9KTtcblxuICAgIC8vIFBhc3MgYHRydWVgIGJlbG93IHRvIGFsbG93IGR1cGxpY2F0ZSBuYW1lcyBhbmQgc291cmNlcy4gV2hpbGUgc291cmNlIG1hcHNcbiAgICAvLyBhcmUgaW50ZW5kZWQgdG8gYmUgY29tcHJlc3NlZCBhbmQgZGVkdXBsaWNhdGVkLCB0aGUgVHlwZVNjcmlwdCBjb21waWxlclxuICAgIC8vIHNvbWV0aW1lcyBnZW5lcmF0ZXMgc291cmNlIG1hcHMgd2l0aCBkdXBsaWNhdGVzIGluIHRoZW0uIFNlZSBHaXRodWIgaXNzdWVcbiAgICAvLyAjNzIgYW5kIGJ1Z3ppbC5sYS84ODk0OTIuXG4gICAgdGhpcy5fbmFtZXMgPSBBcnJheVNldC5mcm9tQXJyYXkobmFtZXMsIHRydWUpO1xuICAgIHRoaXMuX3NvdXJjZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoc291cmNlcywgdHJ1ZSk7XG5cbiAgICB0aGlzLnNvdXJjZVJvb3QgPSBzb3VyY2VSb290O1xuICAgIHRoaXMuc291cmNlc0NvbnRlbnQgPSBzb3VyY2VzQ29udGVudDtcbiAgICB0aGlzLl9tYXBwaW5ncyA9IG1hcHBpbmdzO1xuICAgIHRoaXMuZmlsZSA9IGZpbGU7XG4gIH1cblxuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSA9IE9iamVjdC5jcmVhdGUoU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlKTtcbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29uc3VtZXIgPSBTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogQ3JlYXRlIGEgQmFzaWNTb3VyY2VNYXBDb25zdW1lciBmcm9tIGEgU291cmNlTWFwR2VuZXJhdG9yLlxuICAgKlxuICAgKiBAcGFyYW0gU291cmNlTWFwR2VuZXJhdG9yIGFTb3VyY2VNYXBcbiAgICogICAgICAgIFRoZSBzb3VyY2UgbWFwIHRoYXQgd2lsbCBiZSBjb25zdW1lZC5cbiAgICogQHJldHVybnMgQmFzaWNTb3VyY2VNYXBDb25zdW1lclxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5mcm9tU291cmNlTWFwID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9mcm9tU291cmNlTWFwKGFTb3VyY2VNYXApIHtcbiAgICAgIHZhciBzbWMgPSBPYmplY3QuY3JlYXRlKEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlKTtcblxuICAgICAgdmFyIG5hbWVzID0gc21jLl9uYW1lcyA9IEFycmF5U2V0LmZyb21BcnJheShhU291cmNlTWFwLl9uYW1lcy50b0FycmF5KCksIHRydWUpO1xuICAgICAgdmFyIHNvdXJjZXMgPSBzbWMuX3NvdXJjZXMgPSBBcnJheVNldC5mcm9tQXJyYXkoYVNvdXJjZU1hcC5fc291cmNlcy50b0FycmF5KCksIHRydWUpO1xuICAgICAgc21jLnNvdXJjZVJvb3QgPSBhU291cmNlTWFwLl9zb3VyY2VSb290O1xuICAgICAgc21jLnNvdXJjZXNDb250ZW50ID0gYVNvdXJjZU1hcC5fZ2VuZXJhdGVTb3VyY2VzQ29udGVudChzbWMuX3NvdXJjZXMudG9BcnJheSgpLFxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzbWMuc291cmNlUm9vdCk7XG4gICAgICBzbWMuZmlsZSA9IGFTb3VyY2VNYXAuX2ZpbGU7XG5cbiAgICAgIC8vIEJlY2F1c2Ugd2UgYXJlIG1vZGlmeWluZyB0aGUgZW50cmllcyAoYnkgY29udmVydGluZyBzdHJpbmcgc291cmNlcyBhbmRcbiAgICAgIC8vIG5hbWVzIHRvIGluZGljZXMgaW50byB0aGUgc291cmNlcyBhbmQgbmFtZXMgQXJyYXlTZXRzKSwgd2UgaGF2ZSB0byBtYWtlXG4gICAgICAvLyBhIGNvcHkgb2YgdGhlIGVudHJ5IG9yIGVsc2UgYmFkIHRoaW5ncyBoYXBwZW4uIFNoYXJlZCBtdXRhYmxlIHN0YXRlXG4gICAgICAvLyBzdHJpa2VzIGFnYWluISBTZWUgZ2l0aHViIGlzc3VlICMxOTEuXG5cbiAgICAgIHZhciBnZW5lcmF0ZWRNYXBwaW5ncyA9IGFTb3VyY2VNYXAuX21hcHBpbmdzLnRvQXJyYXkoKS5zbGljZSgpO1xuICAgICAgdmFyIGRlc3RHZW5lcmF0ZWRNYXBwaW5ncyA9IHNtYy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB2YXIgZGVzdE9yaWdpbmFsTWFwcGluZ3MgPSBzbWMuX19vcmlnaW5hbE1hcHBpbmdzID0gW107XG5cbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW5ndGggPSBnZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGg7IGkgPCBsZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc3JjTWFwcGluZyA9IGdlbmVyYXRlZE1hcHBpbmdzW2ldO1xuICAgICAgICB2YXIgZGVzdE1hcHBpbmcgPSBuZXcgTWFwcGluZztcbiAgICAgICAgZGVzdE1hcHBpbmcuZ2VuZXJhdGVkTGluZSA9IHNyY01hcHBpbmcuZ2VuZXJhdGVkTGluZTtcbiAgICAgICAgZGVzdE1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uID0gc3JjTWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG5cbiAgICAgICAgaWYgKHNyY01hcHBpbmcuc291cmNlKSB7XG4gICAgICAgICAgZGVzdE1hcHBpbmcuc291cmNlID0gc291cmNlcy5pbmRleE9mKHNyY01hcHBpbmcuc291cmNlKTtcbiAgICAgICAgICBkZXN0TWFwcGluZy5vcmlnaW5hbExpbmUgPSBzcmNNYXBwaW5nLm9yaWdpbmFsTGluZTtcbiAgICAgICAgICBkZXN0TWFwcGluZy5vcmlnaW5hbENvbHVtbiA9IHNyY01hcHBpbmcub3JpZ2luYWxDb2x1bW47XG5cbiAgICAgICAgICBpZiAoc3JjTWFwcGluZy5uYW1lKSB7XG4gICAgICAgICAgICBkZXN0TWFwcGluZy5uYW1lID0gbmFtZXMuaW5kZXhPZihzcmNNYXBwaW5nLm5hbWUpO1xuICAgICAgICAgIH1cblxuICAgICAgICAgIGRlc3RPcmlnaW5hbE1hcHBpbmdzLnB1c2goZGVzdE1hcHBpbmcpO1xuICAgICAgICB9XG5cbiAgICAgICAgZGVzdEdlbmVyYXRlZE1hcHBpbmdzLnB1c2goZGVzdE1hcHBpbmcpO1xuICAgICAgfVxuXG4gICAgICBxdWlja1NvcnQoc21jLl9fb3JpZ2luYWxNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucyk7XG5cbiAgICAgIHJldHVybiBzbWM7XG4gICAgfTtcblxuICAvKipcbiAgICogVGhlIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXBwaW5nIHNwZWMgdGhhdCB3ZSBhcmUgY29uc3VtaW5nLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX3ZlcnNpb24gPSAzO1xuXG4gIC8qKlxuICAgKiBUaGUgbGlzdCBvZiBvcmlnaW5hbCBzb3VyY2VzLlxuICAgKi9cbiAgT2JqZWN0LmRlZmluZVByb3BlcnR5KEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLCAnc291cmNlcycsIHtcbiAgICBnZXQ6IGZ1bmN0aW9uICgpIHtcbiAgICAgIHJldHVybiB0aGlzLl9zb3VyY2VzLnRvQXJyYXkoKS5tYXAoZnVuY3Rpb24gKHMpIHtcbiAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlUm9vdCAhPSBudWxsID8gdXRpbC5qb2luKHRoaXMuc291cmNlUm9vdCwgcykgOiBzO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfVxuICB9KTtcblxuICAvKipcbiAgICogUHJvdmlkZSB0aGUgSklUIHdpdGggYSBuaWNlIHNoYXBlIC8gaGlkZGVuIGNsYXNzLlxuICAgKi9cbiAgZnVuY3Rpb24gTWFwcGluZygpIHtcbiAgICB0aGlzLmdlbmVyYXRlZExpbmUgPSAwO1xuICAgIHRoaXMuZ2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICB0aGlzLnNvdXJjZSA9IG51bGw7XG4gICAgdGhpcy5vcmlnaW5hbExpbmUgPSBudWxsO1xuICAgIHRoaXMub3JpZ2luYWxDb2x1bW4gPSBudWxsO1xuICAgIHRoaXMubmFtZSA9IG51bGw7XG4gIH1cblxuICAvKipcbiAgICogUGFyc2UgdGhlIG1hcHBpbmdzIGluIGEgc3RyaW5nIGluIHRvIGEgZGF0YSBzdHJ1Y3R1cmUgd2hpY2ggd2UgY2FuIGVhc2lseVxuICAgKiBxdWVyeSAodGhlIG9yZGVyZWQgYXJyYXlzIGluIHRoZSBgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzYCBhbmRcbiAgICogYHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzYCBwcm9wZXJ0aWVzKS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9wYXJzZU1hcHBpbmdzKGFTdHIsIGFTb3VyY2VSb290KSB7XG4gICAgICB2YXIgZ2VuZXJhdGVkTGluZSA9IDE7XG4gICAgICB2YXIgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgdmFyIHByZXZpb3VzT3JpZ2luYWxMaW5lID0gMDtcbiAgICAgIHZhciBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gMDtcbiAgICAgIHZhciBwcmV2aW91c1NvdXJjZSA9IDA7XG4gICAgICB2YXIgcHJldmlvdXNOYW1lID0gMDtcbiAgICAgIHZhciBsZW5ndGggPSBhU3RyLmxlbmd0aDtcbiAgICAgIHZhciBpbmRleCA9IDA7XG4gICAgICB2YXIgY2FjaGVkU2VnbWVudHMgPSB7fTtcbiAgICAgIHZhciB0ZW1wID0ge307XG4gICAgICB2YXIgb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuICAgICAgdmFyIGdlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB2YXIgbWFwcGluZywgc3RyLCBzZWdtZW50LCBlbmQsIHZhbHVlO1xuXG4gICAgICB3aGlsZSAoaW5kZXggPCBsZW5ndGgpIHtcbiAgICAgICAgaWYgKGFTdHIuY2hhckF0KGluZGV4KSA9PT0gJzsnKSB7XG4gICAgICAgICAgZ2VuZXJhdGVkTGluZSsrO1xuICAgICAgICAgIGluZGV4Kys7XG4gICAgICAgICAgcHJldmlvdXNHZW5lcmF0ZWRDb2x1bW4gPSAwO1xuICAgICAgICB9XG4gICAgICAgIGVsc2UgaWYgKGFTdHIuY2hhckF0KGluZGV4KSA9PT0gJywnKSB7XG4gICAgICAgICAgaW5kZXgrKztcbiAgICAgICAgfVxuICAgICAgICBlbHNlIHtcbiAgICAgICAgICBtYXBwaW5nID0gbmV3IE1hcHBpbmcoKTtcbiAgICAgICAgICBtYXBwaW5nLmdlbmVyYXRlZExpbmUgPSBnZW5lcmF0ZWRMaW5lO1xuXG4gICAgICAgICAgLy8gQmVjYXVzZSBlYWNoIG9mZnNldCBpcyBlbmNvZGVkIHJlbGF0aXZlIHRvIHRoZSBwcmV2aW91cyBvbmUsXG4gICAgICAgICAgLy8gbWFueSBzZWdtZW50cyBvZnRlbiBoYXZlIHRoZSBzYW1lIGVuY29kaW5nLiBXZSBjYW4gZXhwbG9pdCB0aGlzXG4gICAgICAgICAgLy8gZmFjdCBieSBjYWNoaW5nIHRoZSBwYXJzZWQgdmFyaWFibGUgbGVuZ3RoIGZpZWxkcyBvZiBlYWNoIHNlZ21lbnQsXG4gICAgICAgICAgLy8gYWxsb3dpbmcgdXMgdG8gYXZvaWQgYSBzZWNvbmQgcGFyc2UgaWYgd2UgZW5jb3VudGVyIHRoZSBzYW1lXG4gICAgICAgICAgLy8gc2VnbWVudCBhZ2Fpbi5cbiAgICAgICAgICBmb3IgKGVuZCA9IGluZGV4OyBlbmQgPCBsZW5ndGg7IGVuZCsrKSB7XG4gICAgICAgICAgICBpZiAodGhpcy5fY2hhcklzTWFwcGluZ1NlcGFyYXRvcihhU3RyLCBlbmQpKSB7XG4gICAgICAgICAgICAgIGJyZWFrO1xuICAgICAgICAgICAgfVxuICAgICAgICAgIH1cbiAgICAgICAgICBzdHIgPSBhU3RyLnNsaWNlKGluZGV4LCBlbmQpO1xuXG4gICAgICAgICAgc2VnbWVudCA9IGNhY2hlZFNlZ21lbnRzW3N0cl07XG4gICAgICAgICAgaWYgKHNlZ21lbnQpIHtcbiAgICAgICAgICAgIGluZGV4ICs9IHN0ci5sZW5ndGg7XG4gICAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICAgIHNlZ21lbnQgPSBbXTtcbiAgICAgICAgICAgIHdoaWxlIChpbmRleCA8IGVuZCkge1xuICAgICAgICAgICAgICBiYXNlNjRWTFEuZGVjb2RlKGFTdHIsIGluZGV4LCB0ZW1wKTtcbiAgICAgICAgICAgICAgdmFsdWUgPSB0ZW1wLnZhbHVlO1xuICAgICAgICAgICAgICBpbmRleCA9IHRlbXAucmVzdDtcbiAgICAgICAgICAgICAgc2VnbWVudC5wdXNoKHZhbHVlKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID09PSAyKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignRm91bmQgYSBzb3VyY2UsIGJ1dCBubyBsaW5lIGFuZCBjb2x1bW4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID09PSAzKSB7XG4gICAgICAgICAgICAgIHRocm93IG5ldyBFcnJvcignRm91bmQgYSBzb3VyY2UgYW5kIGxpbmUsIGJ1dCBubyBjb2x1bW4nKTtcbiAgICAgICAgICAgIH1cblxuICAgICAgICAgICAgY2FjaGVkU2VnbWVudHNbc3RyXSA9IHNlZ21lbnQ7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgLy8gR2VuZXJhdGVkIGNvbHVtbi5cbiAgICAgICAgICBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbiA9IHByZXZpb3VzR2VuZXJhdGVkQ29sdW1uICsgc2VnbWVudFswXTtcbiAgICAgICAgICBwcmV2aW91c0dlbmVyYXRlZENvbHVtbiA9IG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uO1xuXG4gICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID4gMSkge1xuICAgICAgICAgICAgLy8gT3JpZ2luYWwgc291cmNlLlxuICAgICAgICAgICAgbWFwcGluZy5zb3VyY2UgPSBwcmV2aW91c1NvdXJjZSArIHNlZ21lbnRbMV07XG4gICAgICAgICAgICBwcmV2aW91c1NvdXJjZSArPSBzZWdtZW50WzFdO1xuXG4gICAgICAgICAgICAvLyBPcmlnaW5hbCBsaW5lLlxuICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbExpbmUgPSBwcmV2aW91c09yaWdpbmFsTGluZSArIHNlZ21lbnRbMl07XG4gICAgICAgICAgICBwcmV2aW91c09yaWdpbmFsTGluZSA9IG1hcHBpbmcub3JpZ2luYWxMaW5lO1xuICAgICAgICAgICAgLy8gTGluZXMgYXJlIHN0b3JlZCAwLWJhc2VkXG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsTGluZSArPSAxO1xuXG4gICAgICAgICAgICAvLyBPcmlnaW5hbCBjb2x1bW4uXG4gICAgICAgICAgICBtYXBwaW5nLm9yaWdpbmFsQ29sdW1uID0gcHJldmlvdXNPcmlnaW5hbENvbHVtbiArIHNlZ21lbnRbM107XG4gICAgICAgICAgICBwcmV2aW91c09yaWdpbmFsQ29sdW1uID0gbWFwcGluZy5vcmlnaW5hbENvbHVtbjtcblxuICAgICAgICAgICAgaWYgKHNlZ21lbnQubGVuZ3RoID4gNCkge1xuICAgICAgICAgICAgICAvLyBPcmlnaW5hbCBuYW1lLlxuICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUgPSBwcmV2aW91c05hbWUgKyBzZWdtZW50WzRdO1xuICAgICAgICAgICAgICBwcmV2aW91c05hbWUgKz0gc2VnbWVudFs0XTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG5cbiAgICAgICAgICBnZW5lcmF0ZWRNYXBwaW5ncy5wdXNoKG1hcHBpbmcpO1xuICAgICAgICAgIGlmICh0eXBlb2YgbWFwcGluZy5vcmlnaW5hbExpbmUgPT09ICdudW1iZXInKSB7XG4gICAgICAgICAgICBvcmlnaW5hbE1hcHBpbmdzLnB1c2gobWFwcGluZyk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHF1aWNrU29ydChnZW5lcmF0ZWRNYXBwaW5ncywgdXRpbC5jb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZCk7XG4gICAgICB0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MgPSBnZW5lcmF0ZWRNYXBwaW5ncztcblxuICAgICAgcXVpY2tTb3J0KG9yaWdpbmFsTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMpO1xuICAgICAgdGhpcy5fX29yaWdpbmFsTWFwcGluZ3MgPSBvcmlnaW5hbE1hcHBpbmdzO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIEZpbmQgdGhlIG1hcHBpbmcgdGhhdCBiZXN0IG1hdGNoZXMgdGhlIGh5cG90aGV0aWNhbCBcIm5lZWRsZVwiIG1hcHBpbmcgdGhhdFxuICAgKiB3ZSBhcmUgc2VhcmNoaW5nIGZvciBpbiB0aGUgZ2l2ZW4gXCJoYXlzdGFja1wiIG9mIG1hcHBpbmdzLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuX2ZpbmRNYXBwaW5nID1cbiAgICBmdW5jdGlvbiBTb3VyY2VNYXBDb25zdW1lcl9maW5kTWFwcGluZyhhTmVlZGxlLCBhTWFwcGluZ3MsIGFMaW5lTmFtZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhQ29sdW1uTmFtZSwgYUNvbXBhcmF0b3IsIGFCaWFzKSB7XG4gICAgICAvLyBUbyByZXR1cm4gdGhlIHBvc2l0aW9uIHdlIGFyZSBzZWFyY2hpbmcgZm9yLCB3ZSBtdXN0IGZpcnN0IGZpbmQgdGhlXG4gICAgICAvLyBtYXBwaW5nIGZvciB0aGUgZ2l2ZW4gcG9zaXRpb24gYW5kIHRoZW4gcmV0dXJuIHRoZSBvcHBvc2l0ZSBwb3NpdGlvbiBpdFxuICAgICAgLy8gcG9pbnRzIHRvLiBCZWNhdXNlIHRoZSBtYXBwaW5ncyBhcmUgc29ydGVkLCB3ZSBjYW4gdXNlIGJpbmFyeSBzZWFyY2ggdG9cbiAgICAgIC8vIGZpbmQgdGhlIGJlc3QgbWFwcGluZy5cblxuICAgICAgaWYgKGFOZWVkbGVbYUxpbmVOYW1lXSA8PSAwKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0xpbmUgbXVzdCBiZSBncmVhdGVyIHRoYW4gb3IgZXF1YWwgdG8gMSwgZ290ICdcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICArIGFOZWVkbGVbYUxpbmVOYW1lXSk7XG4gICAgICB9XG4gICAgICBpZiAoYU5lZWRsZVthQ29sdW1uTmFtZV0gPCAwKSB7XG4gICAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoJ0NvbHVtbiBtdXN0IGJlIGdyZWF0ZXIgdGhhbiBvciBlcXVhbCB0byAwLCBnb3QgJ1xuICAgICAgICAgICAgICAgICAgICAgICAgICAgICsgYU5lZWRsZVthQ29sdW1uTmFtZV0pO1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gYmluYXJ5U2VhcmNoLnNlYXJjaChhTmVlZGxlLCBhTWFwcGluZ3MsIGFDb21wYXJhdG9yLCBhQmlhcyk7XG4gICAgfTtcblxuICAvKipcbiAgICogQ29tcHV0ZSB0aGUgbGFzdCBjb2x1bW4gZm9yIGVhY2ggZ2VuZXJhdGVkIG1hcHBpbmcuIFRoZSBsYXN0IGNvbHVtbiBpc1xuICAgKiBpbmNsdXNpdmUuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5jb21wdXRlQ29sdW1uU3BhbnMgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX2NvbXB1dGVDb2x1bW5TcGFucygpIHtcbiAgICAgIGZvciAodmFyIGluZGV4ID0gMDsgaW5kZXggPCB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5ncy5sZW5ndGg7ICsraW5kZXgpIHtcbiAgICAgICAgdmFyIG1hcHBpbmcgPSB0aGlzLl9nZW5lcmF0ZWRNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgLy8gTWFwcGluZ3MgZG8gbm90IGNvbnRhaW4gYSBmaWVsZCBmb3IgdGhlIGxhc3QgZ2VuZXJhdGVkIGNvbHVtbnQuIFdlXG4gICAgICAgIC8vIGNhbiBjb21lIHVwIHdpdGggYW4gb3B0aW1pc3RpYyBlc3RpbWF0ZSwgaG93ZXZlciwgYnkgYXNzdW1pbmcgdGhhdFxuICAgICAgICAvLyBtYXBwaW5ncyBhcmUgY29udGlndW91cyAoaS5lLiBnaXZlbiB0d28gY29uc2VjdXRpdmUgbWFwcGluZ3MsIHRoZVxuICAgICAgICAvLyBmaXJzdCBtYXBwaW5nIGVuZHMgd2hlcmUgdGhlIHNlY29uZCBvbmUgc3RhcnRzKS5cbiAgICAgICAgaWYgKGluZGV4ICsgMSA8IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzLmxlbmd0aCkge1xuICAgICAgICAgIHZhciBuZXh0TWFwcGluZyA9IHRoaXMuX2dlbmVyYXRlZE1hcHBpbmdzW2luZGV4ICsgMV07XG5cbiAgICAgICAgICBpZiAobWFwcGluZy5nZW5lcmF0ZWRMaW5lID09PSBuZXh0TWFwcGluZy5nZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgICBtYXBwaW5nLmxhc3RHZW5lcmF0ZWRDb2x1bW4gPSBuZXh0TWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gLSAxO1xuICAgICAgICAgICAgY29udGludWU7XG4gICAgICAgICAgfVxuICAgICAgICB9XG5cbiAgICAgICAgLy8gVGhlIGxhc3QgbWFwcGluZyBmb3IgZWFjaCBsaW5lIHNwYW5zIHRoZSBlbnRpcmUgbGluZS5cbiAgICAgICAgbWFwcGluZy5sYXN0R2VuZXJhdGVkQ29sdW1uID0gSW5maW5pdHk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgb3JpZ2luYWwgc291cmNlLCBsaW5lLCBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgZ2VuZXJhdGVkXG4gICAqIHNvdXJjZSdzIGxpbmUgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdFxuICAgKiB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBiaWFzOiBFaXRoZXIgJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ1NvdXJjZU1hcENvbnN1bWVyLkxFQVNUX1VQUEVSX0JPVU5EJy4gU3BlY2lmaWVzIHdoZXRoZXIgdG8gcmV0dXJuIHRoZVxuICAgKiAgICAgY2xvc2VzdCBlbGVtZW50IHRoYXQgaXMgc21hbGxlciB0aGFuIG9yIGdyZWF0ZXIgdGhhbiB0aGUgb25lIHdlIGFyZVxuICAgKiAgICAgc2VhcmNoaW5nIGZvciwgcmVzcGVjdGl2ZWx5LCBpZiB0aGUgZXhhY3QgZWxlbWVudCBjYW5ub3QgYmUgZm91bmQuXG4gICAqICAgICBEZWZhdWx0cyB0byAnU291cmNlTWFwQ29uc3VtZXIuR1JFQVRFU1RfTE9XRVJfQk9VTkQnLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUsIG9yIG51bGwuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIG5hbWU6IFRoZSBvcmlnaW5hbCBpZGVudGlmaWVyLCBvciBudWxsLlxuICAgKi9cbiAgQmFzaWNTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUub3JpZ2luYWxQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfb3JpZ2luYWxQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyksXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcoXG4gICAgICAgIG5lZWRsZSxcbiAgICAgICAgdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3MsXG4gICAgICAgIFwiZ2VuZXJhdGVkTGluZVwiLFxuICAgICAgICBcImdlbmVyYXRlZENvbHVtblwiLFxuICAgICAgICB1dGlsLmNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkLFxuICAgICAgICB1dGlsLmdldEFyZyhhQXJncywgJ2JpYXMnLCBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORClcbiAgICAgICk7XG5cbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fZ2VuZXJhdGVkTWFwcGluZ3NbaW5kZXhdO1xuXG4gICAgICAgIGlmIChtYXBwaW5nLmdlbmVyYXRlZExpbmUgPT09IG5lZWRsZS5nZW5lcmF0ZWRMaW5lKSB7XG4gICAgICAgICAgdmFyIHNvdXJjZSA9IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdzb3VyY2UnLCBudWxsKTtcbiAgICAgICAgICBpZiAoc291cmNlICE9PSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmF0KHNvdXJjZSk7XG4gICAgICAgICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHRoaXMuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgICAgIH1cbiAgICAgICAgICB9XG4gICAgICAgICAgdmFyIG5hbWUgPSB1dGlsLmdldEFyZyhtYXBwaW5nLCAnbmFtZScsIG51bGwpO1xuICAgICAgICAgIGlmIChuYW1lICE9PSBudWxsKSB7XG4gICAgICAgICAgICBuYW1lID0gdGhpcy5fbmFtZXMuYXQobmFtZSk7XG4gICAgICAgICAgfVxuICAgICAgICAgIHJldHVybiB7XG4gICAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdvcmlnaW5hbExpbmUnLCBudWxsKSxcbiAgICAgICAgICAgIGNvbHVtbjogdXRpbC5nZXRBcmcobWFwcGluZywgJ29yaWdpbmFsQ29sdW1uJywgbnVsbCksXG4gICAgICAgICAgICBuYW1lOiBuYW1lXG4gICAgICAgICAgfTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBzb3VyY2U6IG51bGwsXG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgbmFtZTogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdHJ1ZSBpZiB3ZSBoYXZlIHRoZSBzb3VyY2UgY29udGVudCBmb3IgZXZlcnkgc291cmNlIGluIHRoZSBzb3VyY2VcbiAgICogbWFwLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5oYXNDb250ZW50c09mQWxsU291cmNlcyA9XG4gICAgZnVuY3Rpb24gQmFzaWNTb3VyY2VNYXBDb25zdW1lcl9oYXNDb250ZW50c09mQWxsU291cmNlcygpIHtcbiAgICAgIGlmICghdGhpcy5zb3VyY2VzQ29udGVudCkge1xuICAgICAgICByZXR1cm4gZmFsc2U7XG4gICAgICB9XG4gICAgICByZXR1cm4gdGhpcy5zb3VyY2VzQ29udGVudC5sZW5ndGggPj0gdGhpcy5fc291cmNlcy5zaXplKCkgJiZcbiAgICAgICAgIXRoaXMuc291cmNlc0NvbnRlbnQuc29tZShmdW5jdGlvbiAoc2MpIHsgcmV0dXJuIHNjID09IG51bGw7IH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50LiBUaGUgb25seSBhcmd1bWVudCBpcyB0aGUgdXJsIG9mIHRoZVxuICAgKiBvcmlnaW5hbCBzb3VyY2UgZmlsZS4gUmV0dXJucyBudWxsIGlmIG5vIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50IGlzXG4gICAqIGF2YWlsaWJsZS5cbiAgICovXG4gIEJhc2ljU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLnNvdXJjZUNvbnRlbnRGb3IgPVxuICAgIGZ1bmN0aW9uIFNvdXJjZU1hcENvbnN1bWVyX3NvdXJjZUNvbnRlbnRGb3IoYVNvdXJjZSwgbnVsbE9uTWlzc2luZykge1xuICAgICAgaWYgKCF0aGlzLnNvdXJjZXNDb250ZW50KSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuXG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGwpIHtcbiAgICAgICAgYVNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBhU291cmNlKTtcbiAgICAgIH1cblxuICAgICAgaWYgKHRoaXMuX3NvdXJjZXMuaGFzKGFTb3VyY2UpKSB7XG4gICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihhU291cmNlKV07XG4gICAgICB9XG5cbiAgICAgIHZhciB1cmw7XG4gICAgICBpZiAodGhpcy5zb3VyY2VSb290ICE9IG51bGxcbiAgICAgICAgICAmJiAodXJsID0gdXRpbC51cmxQYXJzZSh0aGlzLnNvdXJjZVJvb3QpKSkge1xuICAgICAgICAvLyBYWFg6IGZpbGU6Ly8gVVJJcyBhbmQgYWJzb2x1dGUgcGF0aHMgbGVhZCB0byB1bmV4cGVjdGVkIGJlaGF2aW9yIGZvclxuICAgICAgICAvLyBtYW55IHVzZXJzLiBXZSBjYW4gaGVscCB0aGVtIG91dCB3aGVuIHRoZXkgZXhwZWN0IGZpbGU6Ly8gVVJJcyB0b1xuICAgICAgICAvLyBiZWhhdmUgbGlrZSBpdCB3b3VsZCBpZiB0aGV5IHdlcmUgcnVubmluZyBhIGxvY2FsIEhUVFAgc2VydmVyLiBTZWVcbiAgICAgICAgLy8gaHR0cHM6Ly9idWd6aWxsYS5tb3ppbGxhLm9yZy9zaG93X2J1Zy5jZ2k/aWQ9ODg1NTk3LlxuICAgICAgICB2YXIgZmlsZVVyaUFic1BhdGggPSBhU291cmNlLnJlcGxhY2UoL15maWxlOlxcL1xcLy8sIFwiXCIpO1xuICAgICAgICBpZiAodXJsLnNjaGVtZSA9PSBcImZpbGVcIlxuICAgICAgICAgICAgJiYgdGhpcy5fc291cmNlcy5oYXMoZmlsZVVyaUFic1BhdGgpKSB7XG4gICAgICAgICAgcmV0dXJuIHRoaXMuc291cmNlc0NvbnRlbnRbdGhpcy5fc291cmNlcy5pbmRleE9mKGZpbGVVcmlBYnNQYXRoKV1cbiAgICAgICAgfVxuXG4gICAgICAgIGlmICgoIXVybC5wYXRoIHx8IHVybC5wYXRoID09IFwiL1wiKVxuICAgICAgICAgICAgJiYgdGhpcy5fc291cmNlcy5oYXMoXCIvXCIgKyBhU291cmNlKSkge1xuICAgICAgICAgIHJldHVybiB0aGlzLnNvdXJjZXNDb250ZW50W3RoaXMuX3NvdXJjZXMuaW5kZXhPZihcIi9cIiArIGFTb3VyY2UpXTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICAvLyBUaGlzIGZ1bmN0aW9uIGlzIHVzZWQgcmVjdXJzaXZlbHkgZnJvbVxuICAgICAgLy8gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5zb3VyY2VDb250ZW50Rm9yLiBJbiB0aGF0IGNhc2UsIHdlXG4gICAgICAvLyBkb24ndCB3YW50IHRvIHRocm93IGlmIHdlIGNhbid0IGZpbmQgdGhlIHNvdXJjZSAtIHdlIGp1c3Qgd2FudCB0b1xuICAgICAgLy8gcmV0dXJuIG51bGwsIHNvIHdlIHByb3ZpZGUgYSBmbGFnIHRvIGV4aXQgZ3JhY2VmdWxseS5cbiAgICAgIGlmIChudWxsT25NaXNzaW5nKSB7XG4gICAgICAgIHJldHVybiBudWxsO1xuICAgICAgfVxuICAgICAgZWxzZSB7XG4gICAgICAgIHRocm93IG5ldyBFcnJvcignXCInICsgYVNvdXJjZSArICdcIiBpcyBub3QgaW4gdGhlIFNvdXJjZU1hcC4nKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm5zIHRoZSBnZW5lcmF0ZWQgbGluZSBhbmQgY29sdW1uIGluZm9ybWF0aW9uIGZvciB0aGUgb3JpZ2luYWwgc291cmNlLFxuICAgKiBsaW5lLCBhbmQgY29sdW1uIHBvc2l0aW9ucyBwcm92aWRlZC4gVGhlIG9ubHkgYXJndW1lbnQgaXMgYW4gb2JqZWN0IHdpdGhcbiAgICogdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgZmlsZW5hbWUgb2YgdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gY29sdW1uOiBUaGUgY29sdW1uIG51bWJlciBpbiB0aGUgb3JpZ2luYWwgc291cmNlLlxuICAgKiAgIC0gYmlhczogRWl0aGVyICdTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORCcgb3JcbiAgICogICAgICdTb3VyY2VNYXBDb25zdW1lci5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ1NvdXJjZU1hcENvbnN1bWVyLkdSRUFURVNUX0xPV0VSX0JPVU5EJy5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqL1xuICBCYXNpY1NvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5nZW5lcmF0ZWRQb3NpdGlvbkZvciA9XG4gICAgZnVuY3Rpb24gU291cmNlTWFwQ29uc3VtZXJfZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIHZhciBzb3VyY2UgPSB1dGlsLmdldEFyZyhhQXJncywgJ3NvdXJjZScpO1xuICAgICAgaWYgKHRoaXMuc291cmNlUm9vdCAhPSBudWxsKSB7XG4gICAgICAgIHNvdXJjZSA9IHV0aWwucmVsYXRpdmUodGhpcy5zb3VyY2VSb290LCBzb3VyY2UpO1xuICAgICAgfVxuICAgICAgaWYgKCF0aGlzLl9zb3VyY2VzLmhhcyhzb3VyY2UpKSB7XG4gICAgICAgIHJldHVybiB7XG4gICAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgICAgbGFzdENvbHVtbjogbnVsbFxuICAgICAgICB9O1xuICAgICAgfVxuICAgICAgc291cmNlID0gdGhpcy5fc291cmNlcy5pbmRleE9mKHNvdXJjZSk7XG5cbiAgICAgIHZhciBuZWVkbGUgPSB7XG4gICAgICAgIHNvdXJjZTogc291cmNlLFxuICAgICAgICBvcmlnaW5hbExpbmU6IHV0aWwuZ2V0QXJnKGFBcmdzLCAnbGluZScpLFxuICAgICAgICBvcmlnaW5hbENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgdmFyIGluZGV4ID0gdGhpcy5fZmluZE1hcHBpbmcoXG4gICAgICAgIG5lZWRsZSxcbiAgICAgICAgdGhpcy5fb3JpZ2luYWxNYXBwaW5ncyxcbiAgICAgICAgXCJvcmlnaW5hbExpbmVcIixcbiAgICAgICAgXCJvcmlnaW5hbENvbHVtblwiLFxuICAgICAgICB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zLFxuICAgICAgICB1dGlsLmdldEFyZyhhQXJncywgJ2JpYXMnLCBTb3VyY2VNYXBDb25zdW1lci5HUkVBVEVTVF9MT1dFUl9CT1VORClcbiAgICAgICk7XG5cbiAgICAgIGlmIChpbmRleCA+PSAwKSB7XG4gICAgICAgIHZhciBtYXBwaW5nID0gdGhpcy5fb3JpZ2luYWxNYXBwaW5nc1tpbmRleF07XG5cbiAgICAgICAgaWYgKG1hcHBpbmcuc291cmNlID09PSBuZWVkbGUuc291cmNlKSB7XG4gICAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICAgIGxpbmU6IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRMaW5lJywgbnVsbCksXG4gICAgICAgICAgICBjb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdnZW5lcmF0ZWRDb2x1bW4nLCBudWxsKSxcbiAgICAgICAgICAgIGxhc3RDb2x1bW46IHV0aWwuZ2V0QXJnKG1hcHBpbmcsICdsYXN0R2VuZXJhdGVkQ29sdW1uJywgbnVsbClcbiAgICAgICAgICB9O1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHJldHVybiB7XG4gICAgICAgIGxpbmU6IG51bGwsXG4gICAgICAgIGNvbHVtbjogbnVsbCxcbiAgICAgICAgbGFzdENvbHVtbjogbnVsbFxuICAgICAgfTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuQmFzaWNTb3VyY2VNYXBDb25zdW1lciA9IEJhc2ljU291cmNlTWFwQ29uc3VtZXI7XG5cbiAgLyoqXG4gICAqIEFuIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lciBpbnN0YW5jZSByZXByZXNlbnRzIGEgcGFyc2VkIHNvdXJjZSBtYXAgd2hpY2hcbiAgICogd2UgY2FuIHF1ZXJ5IGZvciBpbmZvcm1hdGlvbi4gSXQgZGlmZmVycyBmcm9tIEJhc2ljU291cmNlTWFwQ29uc3VtZXIgaW5cbiAgICogdGhhdCBpdCB0YWtlcyBcImluZGV4ZWRcIiBzb3VyY2UgbWFwcyAoaS5lLiBvbmVzIHdpdGggYSBcInNlY3Rpb25zXCIgZmllbGQpIGFzXG4gICAqIGlucHV0LlxuICAgKlxuICAgKiBUaGUgb25seSBwYXJhbWV0ZXIgaXMgYSByYXcgc291cmNlIG1hcCAoZWl0aGVyIGFzIGEgSlNPTiBzdHJpbmcsIG9yIGFscmVhZHlcbiAgICogcGFyc2VkIHRvIGFuIG9iamVjdCkuIEFjY29yZGluZyB0byB0aGUgc3BlYyBmb3IgaW5kZXhlZCBzb3VyY2UgbWFwcywgdGhleVxuICAgKiBoYXZlIHRoZSBmb2xsb3dpbmcgYXR0cmlidXRlczpcbiAgICpcbiAgICogICAtIHZlcnNpb246IFdoaWNoIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXAgc3BlYyB0aGlzIG1hcCBpcyBmb2xsb3dpbmcuXG4gICAqICAgLSBmaWxlOiBPcHRpb25hbC4gVGhlIGdlbmVyYXRlZCBmaWxlIHRoaXMgc291cmNlIG1hcCBpcyBhc3NvY2lhdGVkIHdpdGguXG4gICAqICAgLSBzZWN0aW9uczogQSBsaXN0IG9mIHNlY3Rpb24gZGVmaW5pdGlvbnMuXG4gICAqXG4gICAqIEVhY2ggdmFsdWUgdW5kZXIgdGhlIFwic2VjdGlvbnNcIiBmaWVsZCBoYXMgdHdvIGZpZWxkczpcbiAgICogICAtIG9mZnNldDogVGhlIG9mZnNldCBpbnRvIHRoZSBvcmlnaW5hbCBzcGVjaWZpZWQgYXQgd2hpY2ggdGhpcyBzZWN0aW9uXG4gICAqICAgICAgIGJlZ2lucyB0byBhcHBseSwgZGVmaW5lZCBhcyBhbiBvYmplY3Qgd2l0aCBhIFwibGluZVwiIGFuZCBcImNvbHVtblwiXG4gICAqICAgICAgIGZpZWxkLlxuICAgKiAgIC0gbWFwOiBBIHNvdXJjZSBtYXAgZGVmaW5pdGlvbi4gVGhpcyBzb3VyY2UgbWFwIGNvdWxkIGFsc28gYmUgaW5kZXhlZCxcbiAgICogICAgICAgYnV0IGRvZXNuJ3QgaGF2ZSB0byBiZS5cbiAgICpcbiAgICogSW5zdGVhZCBvZiB0aGUgXCJtYXBcIiBmaWVsZCwgaXQncyBhbHNvIHBvc3NpYmxlIHRvIGhhdmUgYSBcInVybFwiIGZpZWxkXG4gICAqIHNwZWNpZnlpbmcgYSBVUkwgdG8gcmV0cmlldmUgYSBzb3VyY2UgbWFwIGZyb20sIGJ1dCB0aGF0J3MgY3VycmVudGx5XG4gICAqIHVuc3VwcG9ydGVkLlxuICAgKlxuICAgKiBIZXJlJ3MgYW4gZXhhbXBsZSBzb3VyY2UgbWFwLCB0YWtlbiBmcm9tIHRoZSBzb3VyY2UgbWFwIHNwZWNbMF0sIGJ1dFxuICAgKiBtb2RpZmllZCB0byBvbWl0IGEgc2VjdGlvbiB3aGljaCB1c2VzIHRoZSBcInVybFwiIGZpZWxkLlxuICAgKlxuICAgKiAge1xuICAgKiAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgZmlsZTogXCJhcHAuanNcIixcbiAgICogICAgc2VjdGlvbnM6IFt7XG4gICAqICAgICAgb2Zmc2V0OiB7bGluZToxMDAsIGNvbHVtbjoxMH0sXG4gICAqICAgICAgbWFwOiB7XG4gICAqICAgICAgICB2ZXJzaW9uIDogMyxcbiAgICogICAgICAgIGZpbGU6IFwic2VjdGlvbi5qc1wiLFxuICAgKiAgICAgICAgc291cmNlczogW1wiZm9vLmpzXCIsIFwiYmFyLmpzXCJdLFxuICAgKiAgICAgICAgbmFtZXM6IFtcInNyY1wiLCBcIm1hcHNcIiwgXCJhcmVcIiwgXCJmdW5cIl0sXG4gICAqICAgICAgICBtYXBwaW5nczogXCJBQUFBLEU7O0FCQ0RFO1wiXG4gICAqICAgICAgfVxuICAgKiAgICB9XSxcbiAgICogIH1cbiAgICpcbiAgICogWzBdOiBodHRwczovL2RvY3MuZ29vZ2xlLmNvbS9kb2N1bWVudC9kLzFVMVJHQWVoUXdSeXBVVG92RjFLUmxwaU9GemUwYi1fMmdjNmZBSDBLWTBrL2VkaXQjaGVhZGluZz1oLjUzNWVzM3hlcHJndFxuICAgKi9cbiAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyKGFTb3VyY2VNYXApIHtcbiAgICB2YXIgc291cmNlTWFwID0gYVNvdXJjZU1hcDtcbiAgICBpZiAodHlwZW9mIGFTb3VyY2VNYXAgPT09ICdzdHJpbmcnKSB7XG4gICAgICBzb3VyY2VNYXAgPSBKU09OLnBhcnNlKGFTb3VyY2VNYXAucmVwbGFjZSgvXlxcKVxcXVxcfScvLCAnJykpO1xuICAgIH1cblxuICAgIHZhciB2ZXJzaW9uID0gdXRpbC5nZXRBcmcoc291cmNlTWFwLCAndmVyc2lvbicpO1xuICAgIHZhciBzZWN0aW9ucyA9IHV0aWwuZ2V0QXJnKHNvdXJjZU1hcCwgJ3NlY3Rpb25zJyk7XG5cbiAgICBpZiAodmVyc2lvbiAhPSB0aGlzLl92ZXJzaW9uKSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1Vuc3VwcG9ydGVkIHZlcnNpb246ICcgKyB2ZXJzaW9uKTtcbiAgICB9XG5cbiAgICB0aGlzLl9zb3VyY2VzID0gbmV3IEFycmF5U2V0KCk7XG4gICAgdGhpcy5fbmFtZXMgPSBuZXcgQXJyYXlTZXQoKTtcblxuICAgIHZhciBsYXN0T2Zmc2V0ID0ge1xuICAgICAgbGluZTogLTEsXG4gICAgICBjb2x1bW46IDBcbiAgICB9O1xuICAgIHRoaXMuX3NlY3Rpb25zID0gc2VjdGlvbnMubWFwKGZ1bmN0aW9uIChzKSB7XG4gICAgICBpZiAocy51cmwpIHtcbiAgICAgICAgLy8gVGhlIHVybCBmaWVsZCB3aWxsIHJlcXVpcmUgc3VwcG9ydCBmb3IgYXN5bmNocm9uaWNpdHkuXG4gICAgICAgIC8vIFNlZSBodHRwczovL2dpdGh1Yi5jb20vbW96aWxsYS9zb3VyY2UtbWFwL2lzc3Vlcy8xNlxuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1N1cHBvcnQgZm9yIHVybCBmaWVsZCBpbiBzZWN0aW9ucyBub3QgaW1wbGVtZW50ZWQuJyk7XG4gICAgICB9XG4gICAgICB2YXIgb2Zmc2V0ID0gdXRpbC5nZXRBcmcocywgJ29mZnNldCcpO1xuICAgICAgdmFyIG9mZnNldExpbmUgPSB1dGlsLmdldEFyZyhvZmZzZXQsICdsaW5lJyk7XG4gICAgICB2YXIgb2Zmc2V0Q29sdW1uID0gdXRpbC5nZXRBcmcob2Zmc2V0LCAnY29sdW1uJyk7XG5cbiAgICAgIGlmIChvZmZzZXRMaW5lIDwgbGFzdE9mZnNldC5saW5lIHx8XG4gICAgICAgICAgKG9mZnNldExpbmUgPT09IGxhc3RPZmZzZXQubGluZSAmJiBvZmZzZXRDb2x1bW4gPCBsYXN0T2Zmc2V0LmNvbHVtbikpIHtcbiAgICAgICAgdGhyb3cgbmV3IEVycm9yKCdTZWN0aW9uIG9mZnNldHMgbXVzdCBiZSBvcmRlcmVkIGFuZCBub24tb3ZlcmxhcHBpbmcuJyk7XG4gICAgICB9XG4gICAgICBsYXN0T2Zmc2V0ID0gb2Zmc2V0O1xuXG4gICAgICByZXR1cm4ge1xuICAgICAgICBnZW5lcmF0ZWRPZmZzZXQ6IHtcbiAgICAgICAgICAvLyBUaGUgb2Zmc2V0IGZpZWxkcyBhcmUgMC1iYXNlZCwgYnV0IHdlIHVzZSAxLWJhc2VkIGluZGljZXMgd2hlblxuICAgICAgICAgIC8vIGVuY29kaW5nL2RlY29kaW5nIGZyb20gVkxRLlxuICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG9mZnNldExpbmUgKyAxLFxuICAgICAgICAgIGdlbmVyYXRlZENvbHVtbjogb2Zmc2V0Q29sdW1uICsgMVxuICAgICAgICB9LFxuICAgICAgICBjb25zdW1lcjogbmV3IFNvdXJjZU1hcENvbnN1bWVyKHV0aWwuZ2V0QXJnKHMsICdtYXAnKSlcbiAgICAgIH1cbiAgICB9KTtcbiAgfVxuXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUgPSBPYmplY3QuY3JlYXRlKFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSk7XG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuY29uc3RydWN0b3IgPSBTb3VyY2VNYXBDb25zdW1lcjtcblxuICAvKipcbiAgICogVGhlIHZlcnNpb24gb2YgdGhlIHNvdXJjZSBtYXBwaW5nIHNwZWMgdGhhdCB3ZSBhcmUgY29uc3VtaW5nLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5fdmVyc2lvbiA9IDM7XG5cbiAgLyoqXG4gICAqIFRoZSBsaXN0IG9mIG9yaWdpbmFsIHNvdXJjZXMuXG4gICAqL1xuICBPYmplY3QuZGVmaW5lUHJvcGVydHkoSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZSwgJ3NvdXJjZXMnLCB7XG4gICAgZ2V0OiBmdW5jdGlvbiAoKSB7XG4gICAgICB2YXIgc291cmNlcyA9IFtdO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICBmb3IgKHZhciBqID0gMDsgaiA8IHRoaXMuX3NlY3Rpb25zW2ldLmNvbnN1bWVyLnNvdXJjZXMubGVuZ3RoOyBqKyspIHtcbiAgICAgICAgICBzb3VyY2VzLnB1c2godGhpcy5fc2VjdGlvbnNbaV0uY29uc3VtZXIuc291cmNlc1tqXSk7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICAgIHJldHVybiBzb3VyY2VzO1xuICAgIH1cbiAgfSk7XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSwgbGluZSwgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIGdlbmVyYXRlZFxuICAgKiBzb3VyY2UncyBsaW5lIGFuZCBjb2x1bW4gcG9zaXRpb25zIHByb3ZpZGVkLiBUaGUgb25seSBhcmd1bWVudCBpcyBhbiBvYmplY3RcbiAgICogd2l0aCB0aGUgZm9sbG93aW5nIHByb3BlcnRpZXM6XG4gICAqXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UuXG4gICAqICAgLSBjb2x1bW46IFRoZSBjb2x1bW4gbnVtYmVyIGluIHRoZSBnZW5lcmF0ZWQgc291cmNlLlxuICAgKlxuICAgKiBhbmQgYW4gb2JqZWN0IGlzIHJldHVybmVkIHdpdGggdGhlIGZvbGxvd2luZyBwcm9wZXJ0aWVzOlxuICAgKlxuICAgKiAgIC0gc291cmNlOiBUaGUgb3JpZ2luYWwgc291cmNlIGZpbGUsIG9yIG51bGwuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIG5hbWU6IFRoZSBvcmlnaW5hbCBpZGVudGlmaWVyLCBvciBudWxsLlxuICAgKi9cbiAgSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyLnByb3RvdHlwZS5vcmlnaW5hbFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfb3JpZ2luYWxQb3NpdGlvbkZvcihhQXJncykge1xuICAgICAgdmFyIG5lZWRsZSA9IHtcbiAgICAgICAgZ2VuZXJhdGVkTGluZTogdXRpbC5nZXRBcmcoYUFyZ3MsICdsaW5lJyksXG4gICAgICAgIGdlbmVyYXRlZENvbHVtbjogdXRpbC5nZXRBcmcoYUFyZ3MsICdjb2x1bW4nKVxuICAgICAgfTtcblxuICAgICAgLy8gRmluZCB0aGUgc2VjdGlvbiBjb250YWluaW5nIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb24gd2UncmUgdHJ5aW5nIHRvIG1hcFxuICAgICAgLy8gdG8gYW4gb3JpZ2luYWwgcG9zaXRpb24uXG4gICAgICB2YXIgc2VjdGlvbkluZGV4ID0gYmluYXJ5U2VhcmNoLnNlYXJjaChuZWVkbGUsIHRoaXMuX3NlY3Rpb25zLFxuICAgICAgICBmdW5jdGlvbihuZWVkbGUsIHNlY3Rpb24pIHtcbiAgICAgICAgICB2YXIgY21wID0gbmVlZGxlLmdlbmVyYXRlZExpbmUgLSBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lO1xuICAgICAgICAgIGlmIChjbXApIHtcbiAgICAgICAgICAgIHJldHVybiBjbXA7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgcmV0dXJuIChuZWVkbGUuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAgICAgICAgIHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbik7XG4gICAgICAgIH0pO1xuICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tzZWN0aW9uSW5kZXhdO1xuXG4gICAgICBpZiAoIXNlY3Rpb24pIHtcbiAgICAgICAgcmV0dXJuIHtcbiAgICAgICAgICBzb3VyY2U6IG51bGwsXG4gICAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgICBjb2x1bW46IG51bGwsXG4gICAgICAgICAgbmFtZTogbnVsbFxuICAgICAgICB9O1xuICAgICAgfVxuXG4gICAgICByZXR1cm4gc2VjdGlvbi5jb25zdW1lci5vcmlnaW5hbFBvc2l0aW9uRm9yKHtcbiAgICAgICAgbGluZTogbmVlZGxlLmdlbmVyYXRlZExpbmUgLVxuICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgIGNvbHVtbjogbmVlZGxlLmdlbmVyYXRlZENvbHVtbiAtXG4gICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgPT09IG5lZWRsZS5nZW5lcmF0ZWRMaW5lXG4gICAgICAgICAgID8gc2VjdGlvbi5nZW5lcmF0ZWRPZmZzZXQuZ2VuZXJhdGVkQ29sdW1uIC0gMVxuICAgICAgICAgICA6IDApLFxuICAgICAgICBiaWFzOiBhQXJncy5iaWFzXG4gICAgICB9KTtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdHJ1ZSBpZiB3ZSBoYXZlIHRoZSBzb3VyY2UgY29udGVudCBmb3IgZXZlcnkgc291cmNlIGluIHRoZSBzb3VyY2VcbiAgICogbWFwLCBmYWxzZSBvdGhlcndpc2UuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmhhc0NvbnRlbnRzT2ZBbGxTb3VyY2VzID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKSB7XG4gICAgICByZXR1cm4gdGhpcy5fc2VjdGlvbnMuZXZlcnkoZnVuY3Rpb24gKHMpIHtcbiAgICAgICAgcmV0dXJuIHMuY29uc3VtZXIuaGFzQ29udGVudHNPZkFsbFNvdXJjZXMoKTtcbiAgICAgIH0pO1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50LiBUaGUgb25seSBhcmd1bWVudCBpcyB0aGUgdXJsIG9mIHRoZVxuICAgKiBvcmlnaW5hbCBzb3VyY2UgZmlsZS4gUmV0dXJucyBudWxsIGlmIG5vIG9yaWdpbmFsIHNvdXJjZSBjb250ZW50IGlzXG4gICAqIGF2YWlsYWJsZS5cbiAgICovXG4gIEluZGV4ZWRTb3VyY2VNYXBDb25zdW1lci5wcm90b3R5cGUuc291cmNlQ29udGVudEZvciA9XG4gICAgZnVuY3Rpb24gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyX3NvdXJjZUNvbnRlbnRGb3IoYVNvdXJjZSwgbnVsbE9uTWlzc2luZykge1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW2ldO1xuXG4gICAgICAgIHZhciBjb250ZW50ID0gc2VjdGlvbi5jb25zdW1lci5zb3VyY2VDb250ZW50Rm9yKGFTb3VyY2UsIHRydWUpO1xuICAgICAgICBpZiAoY29udGVudCkge1xuICAgICAgICAgIHJldHVybiBjb250ZW50O1xuICAgICAgICB9XG4gICAgICB9XG4gICAgICBpZiAobnVsbE9uTWlzc2luZykge1xuICAgICAgICByZXR1cm4gbnVsbDtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFTb3VyY2UgKyAnXCIgaXMgbm90IGluIHRoZSBTb3VyY2VNYXAuJyk7XG4gICAgICB9XG4gICAgfTtcblxuICAvKipcbiAgICogUmV0dXJucyB0aGUgZ2VuZXJhdGVkIGxpbmUgYW5kIGNvbHVtbiBpbmZvcm1hdGlvbiBmb3IgdGhlIG9yaWdpbmFsIHNvdXJjZSxcbiAgICogbGluZSwgYW5kIGNvbHVtbiBwb3NpdGlvbnMgcHJvdmlkZWQuIFRoZSBvbmx5IGFyZ3VtZW50IGlzIGFuIG9iamVjdCB3aXRoXG4gICAqIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIHNvdXJjZTogVGhlIGZpbGVuYW1lIG9mIHRoZSBvcmlnaW5hbCBzb3VyY2UuXG4gICAqICAgLSBsaW5lOiBUaGUgbGluZSBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIG9yaWdpbmFsIHNvdXJjZS5cbiAgICpcbiAgICogYW5kIGFuIG9iamVjdCBpcyByZXR1cm5lZCB3aXRoIHRoZSBmb2xsb3dpbmcgcHJvcGVydGllczpcbiAgICpcbiAgICogICAtIGxpbmU6IFRoZSBsaW5lIG51bWJlciBpbiB0aGUgZ2VuZXJhdGVkIHNvdXJjZSwgb3IgbnVsbC5cbiAgICogICAtIGNvbHVtbjogVGhlIGNvbHVtbiBudW1iZXIgaW4gdGhlIGdlbmVyYXRlZCBzb3VyY2UsIG9yIG51bGwuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLmdlbmVyYXRlZFBvc2l0aW9uRm9yID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpIHtcbiAgICAgIGZvciAodmFyIGkgPSAwOyBpIDwgdGhpcy5fc2VjdGlvbnMubGVuZ3RoOyBpKyspIHtcbiAgICAgICAgdmFyIHNlY3Rpb24gPSB0aGlzLl9zZWN0aW9uc1tpXTtcblxuICAgICAgICAvLyBPbmx5IGNvbnNpZGVyIHRoaXMgc2VjdGlvbiBpZiB0aGUgcmVxdWVzdGVkIHNvdXJjZSBpcyBpbiB0aGUgbGlzdCBvZlxuICAgICAgICAvLyBzb3VyY2VzIG9mIHRoZSBjb25zdW1lci5cbiAgICAgICAgaWYgKHNlY3Rpb24uY29uc3VtZXIuc291cmNlcy5pbmRleE9mKHV0aWwuZ2V0QXJnKGFBcmdzLCAnc291cmNlJykpID09PSAtMSkge1xuICAgICAgICAgIGNvbnRpbnVlO1xuICAgICAgICB9XG4gICAgICAgIHZhciBnZW5lcmF0ZWRQb3NpdGlvbiA9IHNlY3Rpb24uY29uc3VtZXIuZ2VuZXJhdGVkUG9zaXRpb25Gb3IoYUFyZ3MpO1xuICAgICAgICBpZiAoZ2VuZXJhdGVkUG9zaXRpb24pIHtcbiAgICAgICAgICB2YXIgcmV0ID0ge1xuICAgICAgICAgICAgbGluZTogZ2VuZXJhdGVkUG9zaXRpb24ubGluZSArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgICAgICBjb2x1bW46IGdlbmVyYXRlZFBvc2l0aW9uLmNvbHVtbiArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lID09PSBnZW5lcmF0ZWRQb3NpdGlvbi5saW5lXG4gICAgICAgICAgICAgICA/IHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZENvbHVtbiAtIDFcbiAgICAgICAgICAgICAgIDogMClcbiAgICAgICAgICB9O1xuICAgICAgICAgIHJldHVybiByZXQ7XG4gICAgICAgIH1cbiAgICAgIH1cblxuICAgICAgcmV0dXJuIHtcbiAgICAgICAgbGluZTogbnVsbCxcbiAgICAgICAgY29sdW1uOiBudWxsXG4gICAgICB9O1xuICAgIH07XG5cbiAgLyoqXG4gICAqIFBhcnNlIHRoZSBtYXBwaW5ncyBpbiBhIHN0cmluZyBpbiB0byBhIGRhdGEgc3RydWN0dXJlIHdoaWNoIHdlIGNhbiBlYXNpbHlcbiAgICogcXVlcnkgKHRoZSBvcmRlcmVkIGFycmF5cyBpbiB0aGUgYHRoaXMuX19nZW5lcmF0ZWRNYXBwaW5nc2AgYW5kXG4gICAqIGB0aGlzLl9fb3JpZ2luYWxNYXBwaW5nc2AgcHJvcGVydGllcykuXG4gICAqL1xuICBJbmRleGVkU291cmNlTWFwQ29uc3VtZXIucHJvdG90eXBlLl9wYXJzZU1hcHBpbmdzID1cbiAgICBmdW5jdGlvbiBJbmRleGVkU291cmNlTWFwQ29uc3VtZXJfcGFyc2VNYXBwaW5ncyhhU3RyLCBhU291cmNlUm9vdCkge1xuICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzID0gW107XG4gICAgICB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncyA9IFtdO1xuICAgICAgZm9yICh2YXIgaSA9IDA7IGkgPCB0aGlzLl9zZWN0aW9ucy5sZW5ndGg7IGkrKykge1xuICAgICAgICB2YXIgc2VjdGlvbiA9IHRoaXMuX3NlY3Rpb25zW2ldO1xuICAgICAgICB2YXIgc2VjdGlvbk1hcHBpbmdzID0gc2VjdGlvbi5jb25zdW1lci5fZ2VuZXJhdGVkTWFwcGluZ3M7XG4gICAgICAgIGZvciAodmFyIGogPSAwOyBqIDwgc2VjdGlvbk1hcHBpbmdzLmxlbmd0aDsgaisrKSB7XG4gICAgICAgICAgdmFyIG1hcHBpbmcgPSBzZWN0aW9uTWFwcGluZ3NbaV07XG5cbiAgICAgICAgICB2YXIgc291cmNlID0gc2VjdGlvbi5jb25zdW1lci5fc291cmNlcy5hdChtYXBwaW5nLnNvdXJjZSk7XG4gICAgICAgICAgaWYgKHNlY3Rpb24uY29uc3VtZXIuc291cmNlUm9vdCAhPT0gbnVsbCkge1xuICAgICAgICAgICAgc291cmNlID0gdXRpbC5qb2luKHNlY3Rpb24uY29uc3VtZXIuc291cmNlUm9vdCwgc291cmNlKTtcbiAgICAgICAgICB9XG4gICAgICAgICAgdGhpcy5fc291cmNlcy5hZGQoc291cmNlKTtcbiAgICAgICAgICBzb3VyY2UgPSB0aGlzLl9zb3VyY2VzLmluZGV4T2Yoc291cmNlKTtcblxuICAgICAgICAgIHZhciBuYW1lID0gc2VjdGlvbi5jb25zdW1lci5fbmFtZXMuYXQobWFwcGluZy5uYW1lKTtcbiAgICAgICAgICB0aGlzLl9uYW1lcy5hZGQobmFtZSk7XG4gICAgICAgICAgbmFtZSA9IHRoaXMuX25hbWVzLmluZGV4T2YobmFtZSk7XG5cbiAgICAgICAgICAvLyBUaGUgbWFwcGluZ3MgY29taW5nIGZyb20gdGhlIGNvbnN1bWVyIGZvciB0aGUgc2VjdGlvbiBoYXZlXG4gICAgICAgICAgLy8gZ2VuZXJhdGVkIHBvc2l0aW9ucyByZWxhdGl2ZSB0byB0aGUgc3RhcnQgb2YgdGhlIHNlY3Rpb24sIHNvIHdlXG4gICAgICAgICAgLy8gbmVlZCB0byBvZmZzZXQgdGhlbSB0byBiZSByZWxhdGl2ZSB0byB0aGUgc3RhcnQgb2YgdGhlIGNvbmNhdGVuYXRlZFxuICAgICAgICAgIC8vIGdlbmVyYXRlZCBmaWxlLlxuICAgICAgICAgIHZhciBhZGp1c3RlZE1hcHBpbmcgPSB7XG4gICAgICAgICAgICBzb3VyY2U6IHNvdXJjZSxcbiAgICAgICAgICAgIGdlbmVyYXRlZExpbmU6IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSArXG4gICAgICAgICAgICAgIChzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRMaW5lIC0gMSksXG4gICAgICAgICAgICBnZW5lcmF0ZWRDb2x1bW46IG1hcHBpbmcuY29sdW1uICtcbiAgICAgICAgICAgICAgKHNlY3Rpb24uZ2VuZXJhdGVkT2Zmc2V0LmdlbmVyYXRlZExpbmUgPT09IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSlcbiAgICAgICAgICAgICAgPyBzZWN0aW9uLmdlbmVyYXRlZE9mZnNldC5nZW5lcmF0ZWRDb2x1bW4gLSAxXG4gICAgICAgICAgICAgIDogMCxcbiAgICAgICAgICAgIG9yaWdpbmFsTGluZTogbWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICBvcmlnaW5hbENvbHVtbjogbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgIG5hbWU6IG5hbWVcbiAgICAgICAgICB9O1xuXG4gICAgICAgICAgdGhpcy5fX2dlbmVyYXRlZE1hcHBpbmdzLnB1c2goYWRqdXN0ZWRNYXBwaW5nKTtcbiAgICAgICAgICBpZiAodHlwZW9mIGFkanVzdGVkTWFwcGluZy5vcmlnaW5hbExpbmUgPT09ICdudW1iZXInKSB7XG4gICAgICAgICAgICB0aGlzLl9fb3JpZ2luYWxNYXBwaW5ncy5wdXNoKGFkanVzdGVkTWFwcGluZyk7XG4gICAgICAgICAgfVxuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHF1aWNrU29ydCh0aGlzLl9fZ2VuZXJhdGVkTWFwcGluZ3MsIHV0aWwuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQpO1xuICAgICAgcXVpY2tTb3J0KHRoaXMuX19vcmlnaW5hbE1hcHBpbmdzLCB1dGlsLmNvbXBhcmVCeU9yaWdpbmFsUG9zaXRpb25zKTtcbiAgICB9O1xuXG4gIGV4cG9ydHMuSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyID0gSW5kZXhlZFNvdXJjZU1hcENvbnN1bWVyO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2UtbWFwLWNvbnN1bWVyLmpzXG4gKiogbW9kdWxlIGlkID0gOFxuICoqIG1vZHVsZSBjaHVua3MgPSAwXG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDExIE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EID0gMTtcbiAgZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCA9IDI7XG5cbiAgLyoqXG4gICAqIFJlY3Vyc2l2ZSBpbXBsZW1lbnRhdGlvbiBvZiBiaW5hcnkgc2VhcmNoLlxuICAgKlxuICAgKiBAcGFyYW0gYUxvdyBJbmRpY2VzIGhlcmUgYW5kIGxvd2VyIGRvIG5vdCBjb250YWluIHRoZSBuZWVkbGUuXG4gICAqIEBwYXJhbSBhSGlnaCBJbmRpY2VzIGhlcmUgYW5kIGhpZ2hlciBkbyBub3QgY29udGFpbiB0aGUgbmVlZGxlLlxuICAgKiBAcGFyYW0gYU5lZWRsZSBUaGUgZWxlbWVudCBiZWluZyBzZWFyY2hlZCBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIG5vbi1lbXB0eSBhcnJheSBiZWluZyBzZWFyY2hlZC5cbiAgICogQHBhcmFtIGFDb21wYXJlIEZ1bmN0aW9uIHdoaWNoIHRha2VzIHR3byBlbGVtZW50cyBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKi9cbiAgZnVuY3Rpb24gcmVjdXJzaXZlU2VhcmNoKGFMb3csIGFIaWdoLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIC8vIFRoaXMgZnVuY3Rpb24gdGVybWluYXRlcyB3aGVuIG9uZSBvZiB0aGUgZm9sbG93aW5nIGlzIHRydWU6XG4gICAgLy9cbiAgICAvLyAgIDEuIFdlIGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQgd2UgYXJlIGxvb2tpbmcgZm9yLlxuICAgIC8vXG4gICAgLy8gICAyLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGJ1dCB3ZSBjYW4gcmV0dXJuIHRoZSBpbmRleCBvZlxuICAgIC8vICAgICAgdGhlIG5leHQtY2xvc2VzdCBlbGVtZW50LlxuICAgIC8vXG4gICAgLy8gICAzLiBXZSBkaWQgbm90IGZpbmQgdGhlIGV4YWN0IGVsZW1lbnQsIGFuZCB0aGVyZSBpcyBubyBuZXh0LWNsb3Nlc3RcbiAgICAvLyAgICAgIGVsZW1lbnQgdGhhbiB0aGUgb25lIHdlIGFyZSBzZWFyY2hpbmcgZm9yLCBzbyB3ZSByZXR1cm4gLTEuXG4gICAgdmFyIG1pZCA9IE1hdGguZmxvb3IoKGFIaWdoIC0gYUxvdykgLyAyKSArIGFMb3c7XG4gICAgdmFyIGNtcCA9IGFDb21wYXJlKGFOZWVkbGUsIGFIYXlzdGFja1ttaWRdLCB0cnVlKTtcbiAgICBpZiAoY21wID09PSAwKSB7XG4gICAgICAvLyBGb3VuZCB0aGUgZWxlbWVudCB3ZSBhcmUgbG9va2luZyBmb3IuXG4gICAgICByZXR1cm4gbWlkO1xuICAgIH1cbiAgICBlbHNlIGlmIChjbXAgPiAwKSB7XG4gICAgICAvLyBPdXIgbmVlZGxlIGlzIGdyZWF0ZXIgdGhhbiBhSGF5c3RhY2tbbWlkXS5cbiAgICAgIGlmIChhSGlnaCAtIG1pZCA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIHVwcGVyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2gobWlkLCBhSGlnaCwgYU5lZWRsZSwgYUhheXN0YWNrLCBhQ29tcGFyZSwgYUJpYXMpO1xuICAgICAgfVxuXG4gICAgICAvLyBUaGUgZXhhY3QgbmVlZGxlIGVsZW1lbnQgd2FzIG5vdCBmb3VuZCBpbiB0aGlzIGhheXN0YWNrLiBEZXRlcm1pbmUgaWZcbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gYUhpZ2ggPCBhSGF5c3RhY2subGVuZ3RoID8gYUhpZ2ggOiAtMTtcbiAgICAgIH0gZWxzZSB7XG4gICAgICAgIHJldHVybiBtaWQ7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgLy8gT3VyIG5lZWRsZSBpcyBsZXNzIHRoYW4gYUhheXN0YWNrW21pZF0uXG4gICAgICBpZiAobWlkIC0gYUxvdyA+IDEpIHtcbiAgICAgICAgLy8gVGhlIGVsZW1lbnQgaXMgaW4gdGhlIGxvd2VyIGhhbGYuXG4gICAgICAgIHJldHVybiByZWN1cnNpdmVTZWFyY2goYUxvdywgbWlkLCBhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcyk7XG4gICAgICB9XG5cbiAgICAgIC8vIHdlIGFyZSBpbiB0ZXJtaW5hdGlvbiBjYXNlICgzKSBvciAoMikgYW5kIHJldHVybiB0aGUgYXBwcm9wcmlhdGUgdGhpbmcuXG4gICAgICBpZiAoYUJpYXMgPT0gZXhwb3J0cy5MRUFTVF9VUFBFUl9CT1VORCkge1xuICAgICAgICByZXR1cm4gbWlkO1xuICAgICAgfSBlbHNlIHtcbiAgICAgICAgcmV0dXJuIGFMb3cgPCAwID8gLTEgOiBhTG93O1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBUaGlzIGlzIGFuIGltcGxlbWVudGF0aW9uIG9mIGJpbmFyeSBzZWFyY2ggd2hpY2ggd2lsbCBhbHdheXMgdHJ5IGFuZCByZXR1cm5cbiAgICogdGhlIGluZGV4IG9mIHRoZSBjbG9zZXN0IGVsZW1lbnQgaWYgdGhlcmUgaXMgbm8gZXhhY3QgaGl0LiBUaGlzIGlzIGJlY2F1c2VcbiAgICogbWFwcGluZ3MgYmV0d2VlbiBvcmlnaW5hbCBhbmQgZ2VuZXJhdGVkIGxpbmUvY29sIHBhaXJzIGFyZSBzaW5nbGUgcG9pbnRzLFxuICAgKiBhbmQgdGhlcmUgaXMgYW4gaW1wbGljaXQgcmVnaW9uIGJldHdlZW4gZWFjaCBvZiB0aGVtLCBzbyBhIG1pc3MganVzdCBtZWFuc1xuICAgKiB0aGF0IHlvdSBhcmVuJ3Qgb24gdGhlIHZlcnkgc3RhcnQgb2YgYSByZWdpb24uXG4gICAqXG4gICAqIEBwYXJhbSBhTmVlZGxlIFRoZSBlbGVtZW50IHlvdSBhcmUgbG9va2luZyBmb3IuXG4gICAqIEBwYXJhbSBhSGF5c3RhY2sgVGhlIGFycmF5IHRoYXQgaXMgYmVpbmcgc2VhcmNoZWQuXG4gICAqIEBwYXJhbSBhQ29tcGFyZSBBIGZ1bmN0aW9uIHdoaWNoIHRha2VzIHRoZSBuZWVkbGUgYW5kIGFuIGVsZW1lbnQgaW4gdGhlXG4gICAqICAgICBhcnJheSBhbmQgcmV0dXJucyAtMSwgMCwgb3IgMSBkZXBlbmRpbmcgb24gd2hldGhlciB0aGUgbmVlZGxlIGlzIGxlc3NcbiAgICogICAgIHRoYW4sIGVxdWFsIHRvLCBvciBncmVhdGVyIHRoYW4gdGhlIGVsZW1lbnQsIHJlc3BlY3RpdmVseS5cbiAgICogQHBhcmFtIGFCaWFzIEVpdGhlciAnYmluYXJ5U2VhcmNoLkdSRUFURVNUX0xPV0VSX0JPVU5EJyBvclxuICAgKiAgICAgJ2JpbmFyeVNlYXJjaC5MRUFTVF9VUFBFUl9CT1VORCcuIFNwZWNpZmllcyB3aGV0aGVyIHRvIHJldHVybiB0aGVcbiAgICogICAgIGNsb3Nlc3QgZWxlbWVudCB0aGF0IGlzIHNtYWxsZXIgdGhhbiBvciBncmVhdGVyIHRoYW4gdGhlIG9uZSB3ZSBhcmVcbiAgICogICAgIHNlYXJjaGluZyBmb3IsIHJlc3BlY3RpdmVseSwgaWYgdGhlIGV4YWN0IGVsZW1lbnQgY2Fubm90IGJlIGZvdW5kLlxuICAgKiAgICAgRGVmYXVsdHMgdG8gJ2JpbmFyeVNlYXJjaC5HUkVBVEVTVF9MT1dFUl9CT1VORCcuXG4gICAqL1xuICBleHBvcnRzLnNlYXJjaCA9IGZ1bmN0aW9uIHNlYXJjaChhTmVlZGxlLCBhSGF5c3RhY2ssIGFDb21wYXJlLCBhQmlhcykge1xuICAgIGlmIChhSGF5c3RhY2subGVuZ3RoID09PSAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgdmFyIGluZGV4ID0gcmVjdXJzaXZlU2VhcmNoKC0xLCBhSGF5c3RhY2subGVuZ3RoLCBhTmVlZGxlLCBhSGF5c3RhY2ssXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFDb21wYXJlLCBhQmlhcyB8fCBleHBvcnRzLkdSRUFURVNUX0xPV0VSX0JPVU5EKTtcbiAgICBpZiAoaW5kZXggPCAwKSB7XG4gICAgICByZXR1cm4gLTE7XG4gICAgfVxuXG4gICAgLy8gV2UgaGF2ZSBmb3VuZCBlaXRoZXIgdGhlIGV4YWN0IGVsZW1lbnQsIG9yIHRoZSBuZXh0LWNsb3Nlc3QgZWxlbWVudCB0aGFuXG4gICAgLy8gdGhlIG9uZSB3ZSBhcmUgc2VhcmNoaW5nIGZvci4gSG93ZXZlciwgdGhlcmUgbWF5IGJlIG1vcmUgdGhhbiBvbmUgc3VjaFxuICAgIC8vIGVsZW1lbnQuIE1ha2Ugc3VyZSB3ZSBhbHdheXMgcmV0dXJuIHRoZSBzbWFsbGVzdCBvZiB0aGVzZS5cbiAgICB3aGlsZSAoaW5kZXggLSAxID49IDApIHtcbiAgICAgIGlmIChhQ29tcGFyZShhSGF5c3RhY2tbaW5kZXhdLCBhSGF5c3RhY2tbaW5kZXggLSAxXSwgdHJ1ZSkgIT09IDApIHtcbiAgICAgICAgYnJlYWs7XG4gICAgICB9XG4gICAgICAtLWluZGV4O1xuICAgIH1cblxuICAgIHJldHVybiBpbmRleDtcbiAgfTtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvYmluYXJ5LXNlYXJjaC5qc1xuICoqIG1vZHVsZSBpZCA9IDlcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgLy8gSXQgdHVybnMgb3V0IHRoYXQgc29tZSAobW9zdD8pIEphdmFTY3JpcHQgZW5naW5lcyBkb24ndCBzZWxmLWhvc3RcbiAgLy8gYEFycmF5LnByb3RvdHlwZS5zb3J0YC4gVGhpcyBtYWtlcyBzZW5zZSBiZWNhdXNlIEMrKyB3aWxsIGxpa2VseSByZW1haW5cbiAgLy8gZmFzdGVyIHRoYW4gSlMgd2hlbiBkb2luZyByYXcgQ1BVLWludGVuc2l2ZSBzb3J0aW5nLiBIb3dldmVyLCB3aGVuIHVzaW5nIGFcbiAgLy8gY3VzdG9tIGNvbXBhcmF0b3IgZnVuY3Rpb24sIGNhbGxpbmcgYmFjayBhbmQgZm9ydGggYmV0d2VlbiB0aGUgVk0ncyBDKysgYW5kXG4gIC8vIEpJVCdkIEpTIGlzIHJhdGhlciBzbG93ICphbmQqIGxvc2VzIEpJVCB0eXBlIGluZm9ybWF0aW9uLCByZXN1bHRpbmcgaW5cbiAgLy8gd29yc2UgZ2VuZXJhdGVkIGNvZGUgZm9yIHRoZSBjb21wYXJhdG9yIGZ1bmN0aW9uIHRoYW4gd291bGQgYmUgb3B0aW1hbC4gSW5cbiAgLy8gZmFjdCwgd2hlbiBzb3J0aW5nIHdpdGggYSBjb21wYXJhdG9yLCB0aGVzZSBjb3N0cyBvdXR3ZWlnaCB0aGUgYmVuZWZpdHMgb2ZcbiAgLy8gc29ydGluZyBpbiBDKysuIEJ5IHVzaW5nIG91ciBvd24gSlMtaW1wbGVtZW50ZWQgUXVpY2sgU29ydCAoYmVsb3cpLCB3ZSBnZXRcbiAgLy8gYSB+MzUwMG1zIG1lYW4gc3BlZWQtdXAgaW4gYGJlbmNoL2JlbmNoLmh0bWxgLlxuXG4gIC8qKlxuICAgKiBTd2FwIHRoZSBlbGVtZW50cyBpbmRleGVkIGJ5IGB4YCBhbmQgYHlgIGluIHRoZSBhcnJheSBgYXJ5YC5cbiAgICpcbiAgICogQHBhcmFtIHtBcnJheX0gYXJ5XG4gICAqICAgICAgICBUaGUgYXJyYXkuXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB4XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIGZpcnN0IGl0ZW0uXG4gICAqIEBwYXJhbSB7TnVtYmVyfSB5XG4gICAqICAgICAgICBUaGUgaW5kZXggb2YgdGhlIHNlY29uZCBpdGVtLlxuICAgKi9cbiAgZnVuY3Rpb24gc3dhcChhcnksIHgsIHkpIHtcbiAgICB2YXIgdGVtcCA9IGFyeVt4XTtcbiAgICBhcnlbeF0gPSBhcnlbeV07XG4gICAgYXJ5W3ldID0gdGVtcDtcbiAgfVxuXG4gIC8qKlxuICAgKiBSZXR1cm5zIGEgcmFuZG9tIGludGVnZXIgd2l0aGluIHRoZSByYW5nZSBgbG93IC4uIGhpZ2hgIGluY2x1c2l2ZS5cbiAgICpcbiAgICogQHBhcmFtIHtOdW1iZXJ9IGxvd1xuICAgKiAgICAgICAgVGhlIGxvd2VyIGJvdW5kIG9uIHRoZSByYW5nZS5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IGhpZ2hcbiAgICogICAgICAgIFRoZSB1cHBlciBib3VuZCBvbiB0aGUgcmFuZ2UuXG4gICAqL1xuICBmdW5jdGlvbiByYW5kb21JbnRJblJhbmdlKGxvdywgaGlnaCkge1xuICAgIHJldHVybiBNYXRoLnJvdW5kKGxvdyArIChNYXRoLnJhbmRvbSgpICogKGhpZ2ggLSBsb3cpKSk7XG4gIH1cblxuICAvKipcbiAgICogVGhlIFF1aWNrIFNvcnQgYWxnb3JpdGhtLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICogQHBhcmFtIHtOdW1iZXJ9IHBcbiAgICogICAgICAgIFN0YXJ0IGluZGV4IG9mIHRoZSBhcnJheVxuICAgKiBAcGFyYW0ge051bWJlcn0gclxuICAgKiAgICAgICAgRW5kIGluZGV4IG9mIHRoZSBhcnJheVxuICAgKi9cbiAgZnVuY3Rpb24gZG9RdWlja1NvcnQoYXJ5LCBjb21wYXJhdG9yLCBwLCByKSB7XG4gICAgLy8gSWYgb3VyIGxvd2VyIGJvdW5kIGlzIGxlc3MgdGhhbiBvdXIgdXBwZXIgYm91bmQsIHdlICgxKSBwYXJ0aXRpb24gdGhlXG4gICAgLy8gYXJyYXkgaW50byB0d28gcGllY2VzIGFuZCAoMikgcmVjdXJzZSBvbiBlYWNoIGhhbGYuIElmIGl0IGlzIG5vdCwgdGhpcyBpc1xuICAgIC8vIHRoZSBlbXB0eSBhcnJheSBhbmQgb3VyIGJhc2UgY2FzZS5cblxuICAgIGlmIChwIDwgcikge1xuICAgICAgLy8gKDEpIFBhcnRpdGlvbmluZy5cbiAgICAgIC8vXG4gICAgICAvLyBUaGUgcGFydGl0aW9uaW5nIGNob29zZXMgYSBwaXZvdCBiZXR3ZWVuIGBwYCBhbmQgYHJgIGFuZCBtb3ZlcyBhbGxcbiAgICAgIC8vIGVsZW1lbnRzIHRoYXQgYXJlIGxlc3MgdGhhbiBvciBlcXVhbCB0byB0aGUgcGl2b3QgdG8gdGhlIGJlZm9yZSBpdCwgYW5kXG4gICAgICAvLyBhbGwgdGhlIGVsZW1lbnRzIHRoYXQgYXJlIGdyZWF0ZXIgdGhhbiBpdCBhZnRlciBpdC4gVGhlIGVmZmVjdCBpcyB0aGF0XG4gICAgICAvLyBvbmNlIHBhcnRpdGlvbiBpcyBkb25lLCB0aGUgcGl2b3QgaXMgaW4gdGhlIGV4YWN0IHBsYWNlIGl0IHdpbGwgYmUgd2hlblxuICAgICAgLy8gdGhlIGFycmF5IGlzIHB1dCBpbiBzb3J0ZWQgb3JkZXIsIGFuZCBpdCB3aWxsIG5vdCBuZWVkIHRvIGJlIG1vdmVkXG4gICAgICAvLyBhZ2Fpbi4gVGhpcyBydW5zIGluIE8obikgdGltZS5cblxuICAgICAgLy8gQWx3YXlzIGNob29zZSBhIHJhbmRvbSBwaXZvdCBzbyB0aGF0IGFuIGlucHV0IGFycmF5IHdoaWNoIGlzIHJldmVyc2VcbiAgICAgIC8vIHNvcnRlZCBkb2VzIG5vdCBjYXVzZSBPKG5eMikgcnVubmluZyB0aW1lLlxuICAgICAgdmFyIHBpdm90SW5kZXggPSByYW5kb21JbnRJblJhbmdlKHAsIHIpO1xuICAgICAgdmFyIGkgPSBwIC0gMTtcblxuICAgICAgc3dhcChhcnksIHBpdm90SW5kZXgsIHIpO1xuICAgICAgdmFyIHBpdm90ID0gYXJ5W3JdO1xuXG4gICAgICAvLyBJbW1lZGlhdGVseSBhZnRlciBgamAgaXMgaW5jcmVtZW50ZWQgaW4gdGhpcyBsb29wLCB0aGUgZm9sbG93aW5nIGhvbGRcbiAgICAgIC8vIHRydWU6XG4gICAgICAvL1xuICAgICAgLy8gICAqIEV2ZXJ5IGVsZW1lbnQgaW4gYGFyeVtwIC4uIGldYCBpcyBsZXNzIHRoYW4gb3IgZXF1YWwgdG8gdGhlIHBpdm90LlxuICAgICAgLy9cbiAgICAgIC8vICAgKiBFdmVyeSBlbGVtZW50IGluIGBhcnlbaSsxIC4uIGotMV1gIGlzIGdyZWF0ZXIgdGhhbiB0aGUgcGl2b3QuXG4gICAgICBmb3IgKHZhciBqID0gcDsgaiA8IHI7IGorKykge1xuICAgICAgICBpZiAoY29tcGFyYXRvcihhcnlbal0sIHBpdm90KSA8PSAwKSB7XG4gICAgICAgICAgaSArPSAxO1xuICAgICAgICAgIHN3YXAoYXJ5LCBpLCBqKTtcbiAgICAgICAgfVxuICAgICAgfVxuXG4gICAgICBzd2FwKGFyeSwgaSArIDEsIGopO1xuICAgICAgdmFyIHEgPSBpICsgMTtcblxuICAgICAgLy8gKDIpIFJlY3Vyc2Ugb24gZWFjaCBoYWxmLlxuXG4gICAgICBkb1F1aWNrU29ydChhcnksIGNvbXBhcmF0b3IsIHAsIHEgLSAxKTtcbiAgICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgcSArIDEsIHIpO1xuICAgIH1cbiAgfVxuXG4gIC8qKlxuICAgKiBTb3J0IHRoZSBnaXZlbiBhcnJheSBpbi1wbGFjZSB3aXRoIHRoZSBnaXZlbiBjb21wYXJhdG9yIGZ1bmN0aW9uLlxuICAgKlxuICAgKiBAcGFyYW0ge0FycmF5fSBhcnlcbiAgICogICAgICAgIEFuIGFycmF5IHRvIHNvcnQuXG4gICAqIEBwYXJhbSB7ZnVuY3Rpb259IGNvbXBhcmF0b3JcbiAgICogICAgICAgIEZ1bmN0aW9uIHRvIHVzZSB0byBjb21wYXJlIHR3byBpdGVtcy5cbiAgICovXG4gIGV4cG9ydHMucXVpY2tTb3J0ID0gZnVuY3Rpb24gKGFyeSwgY29tcGFyYXRvcikge1xuICAgIGRvUXVpY2tTb3J0KGFyeSwgY29tcGFyYXRvciwgMCwgYXJ5Lmxlbmd0aCAtIDEpO1xuICB9O1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9xdWljay1zb3J0LmpzXG4gKiogbW9kdWxlIGlkID0gMTBcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyIsIi8qIC0qLSBNb2RlOiBqczsganMtaW5kZW50LWxldmVsOiAyOyAtKi0gKi9cbi8qXG4gKiBDb3B5cmlnaHQgMjAxMSBNb3ppbGxhIEZvdW5kYXRpb24gYW5kIGNvbnRyaWJ1dG9yc1xuICogTGljZW5zZWQgdW5kZXIgdGhlIE5ldyBCU0QgbGljZW5zZS4gU2VlIExJQ0VOU0Ugb3I6XG4gKiBodHRwOi8vb3BlbnNvdXJjZS5vcmcvbGljZW5zZXMvQlNELTMtQ2xhdXNlXG4gKi9cbntcbiAgdmFyIFNvdXJjZU1hcEdlbmVyYXRvciA9IHJlcXVpcmUoJy4vc291cmNlLW1hcC1nZW5lcmF0b3InKS5Tb3VyY2VNYXBHZW5lcmF0b3I7XG4gIHZhciB1dGlsID0gcmVxdWlyZSgnLi91dGlsJyk7XG5cbiAgLy8gTWF0Y2hlcyBhIFdpbmRvd3Mtc3R5bGUgYFxcclxcbmAgbmV3bGluZSBvciBhIGBcXG5gIG5ld2xpbmUgdXNlZCBieSBhbGwgb3RoZXJcbiAgLy8gb3BlcmF0aW5nIHN5c3RlbXMgdGhlc2UgZGF5cyAoY2FwdHVyaW5nIHRoZSByZXN1bHQpLlxuICB2YXIgUkVHRVhfTkVXTElORSA9IC8oXFxyP1xcbikvO1xuXG4gIC8vIE5ld2xpbmUgY2hhcmFjdGVyIGNvZGUgZm9yIGNoYXJDb2RlQXQoKSBjb21wYXJpc29uc1xuICB2YXIgTkVXTElORV9DT0RFID0gMTA7XG5cbiAgLy8gUHJpdmF0ZSBzeW1ib2wgZm9yIGlkZW50aWZ5aW5nIGBTb3VyY2VOb2RlYHMgd2hlbiBtdWx0aXBsZSB2ZXJzaW9ucyBvZlxuICAvLyB0aGUgc291cmNlLW1hcCBsaWJyYXJ5IGFyZSBsb2FkZWQuIFRoaXMgTVVTVCBOT1QgQ0hBTkdFIGFjcm9zc1xuICAvLyB2ZXJzaW9ucyFcbiAgdmFyIGlzU291cmNlTm9kZSA9IFwiJCQkaXNTb3VyY2VOb2RlJCQkXCI7XG5cbiAgLyoqXG4gICAqIFNvdXJjZU5vZGVzIHByb3ZpZGUgYSB3YXkgdG8gYWJzdHJhY3Qgb3ZlciBpbnRlcnBvbGF0aW5nL2NvbmNhdGVuYXRpbmdcbiAgICogc25pcHBldHMgb2YgZ2VuZXJhdGVkIEphdmFTY3JpcHQgc291cmNlIGNvZGUgd2hpbGUgbWFpbnRhaW5pbmcgdGhlIGxpbmUgYW5kXG4gICAqIGNvbHVtbiBpbmZvcm1hdGlvbiBhc3NvY2lhdGVkIHdpdGggdGhlIG9yaWdpbmFsIHNvdXJjZSBjb2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUxpbmUgVGhlIG9yaWdpbmFsIGxpbmUgbnVtYmVyLlxuICAgKiBAcGFyYW0gYUNvbHVtbiBUaGUgb3JpZ2luYWwgY29sdW1uIG51bWJlci5cbiAgICogQHBhcmFtIGFTb3VyY2UgVGhlIG9yaWdpbmFsIHNvdXJjZSdzIGZpbGVuYW1lLlxuICAgKiBAcGFyYW0gYUNodW5rcyBPcHRpb25hbC4gQW4gYXJyYXkgb2Ygc3RyaW5ncyB3aGljaCBhcmUgc25pcHBldHMgb2ZcbiAgICogICAgICAgIGdlbmVyYXRlZCBKUywgb3Igb3RoZXIgU291cmNlTm9kZXMuXG4gICAqIEBwYXJhbSBhTmFtZSBUaGUgb3JpZ2luYWwgaWRlbnRpZmllci5cbiAgICovXG4gIGZ1bmN0aW9uIFNvdXJjZU5vZGUoYUxpbmUsIGFDb2x1bW4sIGFTb3VyY2UsIGFDaHVua3MsIGFOYW1lKSB7XG4gICAgdGhpcy5jaGlsZHJlbiA9IFtdO1xuICAgIHRoaXMuc291cmNlQ29udGVudHMgPSB7fTtcbiAgICB0aGlzLmxpbmUgPSBhTGluZSA9PSBudWxsID8gbnVsbCA6IGFMaW5lO1xuICAgIHRoaXMuY29sdW1uID0gYUNvbHVtbiA9PSBudWxsID8gbnVsbCA6IGFDb2x1bW47XG4gICAgdGhpcy5zb3VyY2UgPSBhU291cmNlID09IG51bGwgPyBudWxsIDogYVNvdXJjZTtcbiAgICB0aGlzLm5hbWUgPSBhTmFtZSA9PSBudWxsID8gbnVsbCA6IGFOYW1lO1xuICAgIHRoaXNbaXNTb3VyY2VOb2RlXSA9IHRydWU7XG4gICAgaWYgKGFDaHVua3MgIT0gbnVsbCkgdGhpcy5hZGQoYUNodW5rcyk7XG4gIH1cblxuICAvKipcbiAgICogQ3JlYXRlcyBhIFNvdXJjZU5vZGUgZnJvbSBnZW5lcmF0ZWQgY29kZSBhbmQgYSBTb3VyY2VNYXBDb25zdW1lci5cbiAgICpcbiAgICogQHBhcmFtIGFHZW5lcmF0ZWRDb2RlIFRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVNvdXJjZU1hcENvbnN1bWVyIFRoZSBTb3VyY2VNYXAgZm9yIHRoZSBnZW5lcmF0ZWQgY29kZVxuICAgKiBAcGFyYW0gYVJlbGF0aXZlUGF0aCBPcHRpb25hbC4gVGhlIHBhdGggdGhhdCByZWxhdGl2ZSBzb3VyY2VzIGluIHRoZVxuICAgKiAgICAgICAgU291cmNlTWFwQ29uc3VtZXIgc2hvdWxkIGJlIHJlbGF0aXZlIHRvLlxuICAgKi9cbiAgU291cmNlTm9kZS5mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9mcm9tU3RyaW5nV2l0aFNvdXJjZU1hcChhR2VuZXJhdGVkQ29kZSwgYVNvdXJjZU1hcENvbnN1bWVyLCBhUmVsYXRpdmVQYXRoKSB7XG4gICAgICAvLyBUaGUgU291cmNlTm9kZSB3ZSB3YW50IHRvIGZpbGwgd2l0aCB0aGUgZ2VuZXJhdGVkIGNvZGVcbiAgICAgIC8vIGFuZCB0aGUgU291cmNlTWFwXG4gICAgICB2YXIgbm9kZSA9IG5ldyBTb3VyY2VOb2RlKCk7XG5cbiAgICAgIC8vIEFsbCBldmVuIGluZGljZXMgb2YgdGhpcyBhcnJheSBhcmUgb25lIGxpbmUgb2YgdGhlIGdlbmVyYXRlZCBjb2RlLFxuICAgICAgLy8gd2hpbGUgYWxsIG9kZCBpbmRpY2VzIGFyZSB0aGUgbmV3bGluZXMgYmV0d2VlbiB0d28gYWRqYWNlbnQgbGluZXNcbiAgICAgIC8vIChzaW5jZSBgUkVHRVhfTkVXTElORWAgY2FwdHVyZXMgaXRzIG1hdGNoKS5cbiAgICAgIC8vIFByb2Nlc3NlZCBmcmFnbWVudHMgYXJlIHJlbW92ZWQgZnJvbSB0aGlzIGFycmF5LCBieSBjYWxsaW5nIGBzaGlmdE5leHRMaW5lYC5cbiAgICAgIHZhciByZW1haW5pbmdMaW5lcyA9IGFHZW5lcmF0ZWRDb2RlLnNwbGl0KFJFR0VYX05FV0xJTkUpO1xuICAgICAgdmFyIHNoaWZ0TmV4dExpbmUgPSBmdW5jdGlvbigpIHtcbiAgICAgICAgdmFyIGxpbmVDb250ZW50cyA9IHJlbWFpbmluZ0xpbmVzLnNoaWZ0KCk7XG4gICAgICAgIC8vIFRoZSBsYXN0IGxpbmUgb2YgYSBmaWxlIG1pZ2h0IG5vdCBoYXZlIGEgbmV3bGluZS5cbiAgICAgICAgdmFyIG5ld0xpbmUgPSByZW1haW5pbmdMaW5lcy5zaGlmdCgpIHx8IFwiXCI7XG4gICAgICAgIHJldHVybiBsaW5lQ29udGVudHMgKyBuZXdMaW5lO1xuICAgICAgfTtcblxuICAgICAgLy8gV2UgbmVlZCB0byByZW1lbWJlciB0aGUgcG9zaXRpb24gb2YgXCJyZW1haW5pbmdMaW5lc1wiXG4gICAgICB2YXIgbGFzdEdlbmVyYXRlZExpbmUgPSAxLCBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcblxuICAgICAgLy8gVGhlIGdlbmVyYXRlIFNvdXJjZU5vZGVzIHdlIG5lZWQgYSBjb2RlIHJhbmdlLlxuICAgICAgLy8gVG8gZXh0cmFjdCBpdCBjdXJyZW50IGFuZCBsYXN0IG1hcHBpbmcgaXMgdXNlZC5cbiAgICAgIC8vIEhlcmUgd2Ugc3RvcmUgdGhlIGxhc3QgbWFwcGluZy5cbiAgICAgIHZhciBsYXN0TWFwcGluZyA9IG51bGw7XG5cbiAgICAgIGFTb3VyY2VNYXBDb25zdW1lci5lYWNoTWFwcGluZyhmdW5jdGlvbiAobWFwcGluZykge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcgIT09IG51bGwpIHtcbiAgICAgICAgICAvLyBXZSBhZGQgdGhlIGNvZGUgZnJvbSBcImxhc3RNYXBwaW5nXCIgdG8gXCJtYXBwaW5nXCI6XG4gICAgICAgICAgLy8gRmlyc3QgY2hlY2sgaWYgdGhlcmUgaXMgYSBuZXcgbGluZSBpbiBiZXR3ZWVuLlxuICAgICAgICAgIGlmIChsYXN0R2VuZXJhdGVkTGluZSA8IG1hcHBpbmcuZ2VuZXJhdGVkTGluZSkge1xuICAgICAgICAgICAgdmFyIGNvZGUgPSBcIlwiO1xuICAgICAgICAgICAgLy8gQXNzb2NpYXRlIGZpcnN0IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICAgIGFkZE1hcHBpbmdXaXRoQ29kZShsYXN0TWFwcGluZywgc2hpZnROZXh0TGluZSgpKTtcbiAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gMDtcbiAgICAgICAgICAgIC8vIFRoZSByZW1haW5pbmcgY29kZSBpcyBhZGRlZCB3aXRob3V0IG1hcHBpbmdcbiAgICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgICAgLy8gVGhlcmUgaXMgbm8gbmV3IGxpbmUgaW4gYmV0d2Vlbi5cbiAgICAgICAgICAgIC8vIEFzc29jaWF0ZSB0aGUgY29kZSBiZXR3ZWVuIFwibGFzdEdlbmVyYXRlZENvbHVtblwiIGFuZFxuICAgICAgICAgICAgLy8gXCJtYXBwaW5nLmdlbmVyYXRlZENvbHVtblwiIHdpdGggXCJsYXN0TWFwcGluZ1wiXG4gICAgICAgICAgICB2YXIgbmV4dExpbmUgPSByZW1haW5pbmdMaW5lc1swXTtcbiAgICAgICAgICAgIHZhciBjb2RlID0gbmV4dExpbmUuc3Vic3RyKDAsIG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uIC1cbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxhc3RHZW5lcmF0ZWRDb2x1bW4pO1xuICAgICAgICAgICAgcmVtYWluaW5nTGluZXNbMF0gPSBuZXh0TGluZS5zdWJzdHIobWFwcGluZy5nZW5lcmF0ZWRDb2x1bW4gLVxuICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGFzdEdlbmVyYXRlZENvbHVtbik7XG4gICAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIGNvZGUpO1xuICAgICAgICAgICAgLy8gTm8gbW9yZSByZW1haW5pbmcgY29kZSwgY29udGludWVcbiAgICAgICAgICAgIGxhc3RNYXBwaW5nID0gbWFwcGluZztcbiAgICAgICAgICAgIHJldHVybjtcbiAgICAgICAgICB9XG4gICAgICAgIH1cbiAgICAgICAgLy8gV2UgYWRkIHRoZSBnZW5lcmF0ZWQgY29kZSB1bnRpbCB0aGUgZmlyc3QgbWFwcGluZ1xuICAgICAgICAvLyB0byB0aGUgU291cmNlTm9kZSB3aXRob3V0IGFueSBtYXBwaW5nLlxuICAgICAgICAvLyBFYWNoIGxpbmUgaXMgYWRkZWQgYXMgc2VwYXJhdGUgc3RyaW5nLlxuICAgICAgICB3aGlsZSAobGFzdEdlbmVyYXRlZExpbmUgPCBtYXBwaW5nLmdlbmVyYXRlZExpbmUpIHtcbiAgICAgICAgICBub2RlLmFkZChzaGlmdE5leHRMaW5lKCkpO1xuICAgICAgICAgIGxhc3RHZW5lcmF0ZWRMaW5lKys7XG4gICAgICAgIH1cbiAgICAgICAgaWYgKGxhc3RHZW5lcmF0ZWRDb2x1bW4gPCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikge1xuICAgICAgICAgIHZhciBuZXh0TGluZSA9IHJlbWFpbmluZ0xpbmVzWzBdO1xuICAgICAgICAgIG5vZGUuYWRkKG5leHRMaW5lLnN1YnN0cigwLCBtYXBwaW5nLmdlbmVyYXRlZENvbHVtbikpO1xuICAgICAgICAgIHJlbWFpbmluZ0xpbmVzWzBdID0gbmV4dExpbmUuc3Vic3RyKG1hcHBpbmcuZ2VuZXJhdGVkQ29sdW1uKTtcbiAgICAgICAgICBsYXN0R2VuZXJhdGVkQ29sdW1uID0gbWFwcGluZy5nZW5lcmF0ZWRDb2x1bW47XG4gICAgICAgIH1cbiAgICAgICAgbGFzdE1hcHBpbmcgPSBtYXBwaW5nO1xuICAgICAgfSwgdGhpcyk7XG4gICAgICAvLyBXZSBoYXZlIHByb2Nlc3NlZCBhbGwgbWFwcGluZ3MuXG4gICAgICBpZiAocmVtYWluaW5nTGluZXMubGVuZ3RoID4gMCkge1xuICAgICAgICBpZiAobGFzdE1hcHBpbmcpIHtcbiAgICAgICAgICAvLyBBc3NvY2lhdGUgdGhlIHJlbWFpbmluZyBjb2RlIGluIHRoZSBjdXJyZW50IGxpbmUgd2l0aCBcImxhc3RNYXBwaW5nXCJcbiAgICAgICAgICBhZGRNYXBwaW5nV2l0aENvZGUobGFzdE1hcHBpbmcsIHNoaWZ0TmV4dExpbmUoKSk7XG4gICAgICAgIH1cbiAgICAgICAgLy8gYW5kIGFkZCB0aGUgcmVtYWluaW5nIGxpbmVzIHdpdGhvdXQgYW55IG1hcHBpbmdcbiAgICAgICAgbm9kZS5hZGQocmVtYWluaW5nTGluZXMuam9pbihcIlwiKSk7XG4gICAgICB9XG5cbiAgICAgIC8vIENvcHkgc291cmNlc0NvbnRlbnQgaW50byBTb3VyY2VOb2RlXG4gICAgICBhU291cmNlTWFwQ29uc3VtZXIuc291cmNlcy5mb3JFYWNoKGZ1bmN0aW9uIChzb3VyY2VGaWxlKSB7XG4gICAgICAgIHZhciBjb250ZW50ID0gYVNvdXJjZU1hcENvbnN1bWVyLnNvdXJjZUNvbnRlbnRGb3Ioc291cmNlRmlsZSk7XG4gICAgICAgIGlmIChjb250ZW50ICE9IG51bGwpIHtcbiAgICAgICAgICBpZiAoYVJlbGF0aXZlUGF0aCAhPSBudWxsKSB7XG4gICAgICAgICAgICBzb3VyY2VGaWxlID0gdXRpbC5qb2luKGFSZWxhdGl2ZVBhdGgsIHNvdXJjZUZpbGUpO1xuICAgICAgICAgIH1cbiAgICAgICAgICBub2RlLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgY29udGVudCk7XG4gICAgICAgIH1cbiAgICAgIH0pO1xuXG4gICAgICByZXR1cm4gbm9kZTtcblxuICAgICAgZnVuY3Rpb24gYWRkTWFwcGluZ1dpdGhDb2RlKG1hcHBpbmcsIGNvZGUpIHtcbiAgICAgICAgaWYgKG1hcHBpbmcgPT09IG51bGwgfHwgbWFwcGluZy5zb3VyY2UgPT09IHVuZGVmaW5lZCkge1xuICAgICAgICAgIG5vZGUuYWRkKGNvZGUpO1xuICAgICAgICB9IGVsc2Uge1xuICAgICAgICAgIHZhciBzb3VyY2UgPSBhUmVsYXRpdmVQYXRoXG4gICAgICAgICAgICA/IHV0aWwuam9pbihhUmVsYXRpdmVQYXRoLCBtYXBwaW5nLnNvdXJjZSlcbiAgICAgICAgICAgIDogbWFwcGluZy5zb3VyY2U7XG4gICAgICAgICAgbm9kZS5hZGQobmV3IFNvdXJjZU5vZGUobWFwcGluZy5vcmlnaW5hbExpbmUsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbWFwcGluZy5vcmlnaW5hbENvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29kZSxcbiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBtYXBwaW5nLm5hbWUpKTtcbiAgICAgICAgfVxuICAgICAgfVxuICAgIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGlzIHNvdXJjZSBub2RlLlxuICAgKlxuICAgKiBAcGFyYW0gYUNodW5rIEEgc3RyaW5nIHNuaXBwZXQgb2YgZ2VuZXJhdGVkIEpTIGNvZGUsIGFub3RoZXIgaW5zdGFuY2Ugb2ZcbiAgICogICAgICAgIFNvdXJjZU5vZGUsIG9yIGFuIGFycmF5IHdoZXJlIGVhY2ggbWVtYmVyIGlzIG9uZSBvZiB0aG9zZSB0aGluZ3MuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5hZGQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX2FkZChhQ2h1bmspIHtcbiAgICBpZiAoQXJyYXkuaXNBcnJheShhQ2h1bmspKSB7XG4gICAgICBhQ2h1bmsuZm9yRWFjaChmdW5jdGlvbiAoY2h1bmspIHtcbiAgICAgICAgdGhpcy5hZGQoY2h1bmspO1xuICAgICAgfSwgdGhpcyk7XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIGlmIChhQ2h1bmspIHtcbiAgICAgICAgdGhpcy5jaGlsZHJlbi5wdXNoKGFDaHVuayk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2Uge1xuICAgICAgdGhyb3cgbmV3IFR5cGVFcnJvcihcbiAgICAgICAgXCJFeHBlY3RlZCBhIFNvdXJjZU5vZGUsIHN0cmluZywgb3IgYW4gYXJyYXkgb2YgU291cmNlTm9kZXMgYW5kIHN0cmluZ3MuIEdvdCBcIiArIGFDaHVua1xuICAgICAgKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIEFkZCBhIGNodW5rIG9mIGdlbmVyYXRlZCBKUyB0byB0aGUgYmVnaW5uaW5nIG9mIHRoaXMgc291cmNlIG5vZGUuXG4gICAqXG4gICAqIEBwYXJhbSBhQ2h1bmsgQSBzdHJpbmcgc25pcHBldCBvZiBnZW5lcmF0ZWQgSlMgY29kZSwgYW5vdGhlciBpbnN0YW5jZSBvZlxuICAgKiAgICAgICAgU291cmNlTm9kZSwgb3IgYW4gYXJyYXkgd2hlcmUgZWFjaCBtZW1iZXIgaXMgb25lIG9mIHRob3NlIHRoaW5ncy5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnByZXBlbmQgPSBmdW5jdGlvbiBTb3VyY2VOb2RlX3ByZXBlbmQoYUNodW5rKSB7XG4gICAgaWYgKEFycmF5LmlzQXJyYXkoYUNodW5rKSkge1xuICAgICAgZm9yICh2YXIgaSA9IGFDaHVuay5sZW5ndGgtMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgICAgdGhpcy5wcmVwZW5kKGFDaHVua1tpXSk7XG4gICAgICB9XG4gICAgfVxuICAgIGVsc2UgaWYgKGFDaHVua1tpc1NvdXJjZU5vZGVdIHx8IHR5cGVvZiBhQ2h1bmsgPT09IFwic3RyaW5nXCIpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4udW5zaGlmdChhQ2h1bmspO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRocm93IG5ldyBUeXBlRXJyb3IoXG4gICAgICAgIFwiRXhwZWN0ZWQgYSBTb3VyY2VOb2RlLCBzdHJpbmcsIG9yIGFuIGFycmF5IG9mIFNvdXJjZU5vZGVzIGFuZCBzdHJpbmdzLiBHb3QgXCIgKyBhQ2h1bmtcbiAgICAgICk7XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgSlMgc25pcHBldHMgaW4gdGhpcyBub2RlIGFuZCBpdHMgY2hpbGRyZW4uIFRoZVxuICAgKiB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBvbmNlIGZvciBlYWNoIHNuaXBwZXQgb2YgSlMgYW5kIGlzIHBhc3NlZCB0aGF0XG4gICAqIHNuaXBwZXQgYW5kIHRoZSBpdHMgb3JpZ2luYWwgYXNzb2NpYXRlZCBzb3VyY2UncyBsaW5lL2NvbHVtbiBsb2NhdGlvbi5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2FsayA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfd2FsayhhRm4pIHtcbiAgICB2YXIgY2h1bms7XG4gICAgZm9yICh2YXIgaSA9IDAsIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoOyBpIDwgbGVuOyBpKyspIHtcbiAgICAgIGNodW5rID0gdGhpcy5jaGlsZHJlbltpXTtcbiAgICAgIGlmIChjaHVua1tpc1NvdXJjZU5vZGVdKSB7XG4gICAgICAgIGNodW5rLndhbGsoYUZuKTtcbiAgICAgIH1cbiAgICAgIGVsc2Uge1xuICAgICAgICBpZiAoY2h1bmsgIT09ICcnKSB7XG4gICAgICAgICAgYUZuKGNodW5rLCB7IHNvdXJjZTogdGhpcy5zb3VyY2UsXG4gICAgICAgICAgICAgICAgICAgICAgIGxpbmU6IHRoaXMubGluZSxcbiAgICAgICAgICAgICAgICAgICAgICAgY29sdW1uOiB0aGlzLmNvbHVtbixcbiAgICAgICAgICAgICAgICAgICAgICAgbmFtZTogdGhpcy5uYW1lIH0pO1xuICAgICAgICB9XG4gICAgICB9XG4gICAgfVxuICB9O1xuXG4gIC8qKlxuICAgKiBMaWtlIGBTdHJpbmcucHJvdG90eXBlLmpvaW5gIGV4Y2VwdCBmb3IgU291cmNlTm9kZXMuIEluc2VydHMgYGFTdHJgIGJldHdlZW5cbiAgICogZWFjaCBvZiBgdGhpcy5jaGlsZHJlbmAuXG4gICAqXG4gICAqIEBwYXJhbSBhU2VwIFRoZSBzZXBhcmF0b3IuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS5qb2luID0gZnVuY3Rpb24gU291cmNlTm9kZV9qb2luKGFTZXApIHtcbiAgICB2YXIgbmV3Q2hpbGRyZW47XG4gICAgdmFyIGk7XG4gICAgdmFyIGxlbiA9IHRoaXMuY2hpbGRyZW4ubGVuZ3RoO1xuICAgIGlmIChsZW4gPiAwKSB7XG4gICAgICBuZXdDaGlsZHJlbiA9IFtdO1xuICAgICAgZm9yIChpID0gMDsgaSA8IGxlbi0xOyBpKyspIHtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaCh0aGlzLmNoaWxkcmVuW2ldKTtcbiAgICAgICAgbmV3Q2hpbGRyZW4ucHVzaChhU2VwKTtcbiAgICAgIH1cbiAgICAgIG5ld0NoaWxkcmVuLnB1c2godGhpcy5jaGlsZHJlbltpXSk7XG4gICAgICB0aGlzLmNoaWxkcmVuID0gbmV3Q2hpbGRyZW47XG4gICAgfVxuICAgIHJldHVybiB0aGlzO1xuICB9O1xuXG4gIC8qKlxuICAgKiBDYWxsIFN0cmluZy5wcm90b3R5cGUucmVwbGFjZSBvbiB0aGUgdmVyeSByaWdodC1tb3N0IHNvdXJjZSBzbmlwcGV0LiBVc2VmdWxcbiAgICogZm9yIHRyaW1taW5nIHdoaXRlc3BhY2UgZnJvbSB0aGUgZW5kIG9mIGEgc291cmNlIG5vZGUsIGV0Yy5cbiAgICpcbiAgICogQHBhcmFtIGFQYXR0ZXJuIFRoZSBwYXR0ZXJuIHRvIHJlcGxhY2UuXG4gICAqIEBwYXJhbSBhUmVwbGFjZW1lbnQgVGhlIHRoaW5nIHRvIHJlcGxhY2UgdGhlIHBhdHRlcm4gd2l0aC5cbiAgICovXG4gIFNvdXJjZU5vZGUucHJvdG90eXBlLnJlcGxhY2VSaWdodCA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfcmVwbGFjZVJpZ2h0KGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpIHtcbiAgICB2YXIgbGFzdENoaWxkID0gdGhpcy5jaGlsZHJlblt0aGlzLmNoaWxkcmVuLmxlbmd0aCAtIDFdO1xuICAgIGlmIChsYXN0Q2hpbGRbaXNTb3VyY2VOb2RlXSkge1xuICAgICAgbGFzdENoaWxkLnJlcGxhY2VSaWdodChhUGF0dGVybiwgYVJlcGxhY2VtZW50KTtcbiAgICB9XG4gICAgZWxzZSBpZiAodHlwZW9mIGxhc3RDaGlsZCA9PT0gJ3N0cmluZycpIHtcbiAgICAgIHRoaXMuY2hpbGRyZW5bdGhpcy5jaGlsZHJlbi5sZW5ndGggLSAxXSA9IGxhc3RDaGlsZC5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpO1xuICAgIH1cbiAgICBlbHNlIHtcbiAgICAgIHRoaXMuY2hpbGRyZW4ucHVzaCgnJy5yZXBsYWNlKGFQYXR0ZXJuLCBhUmVwbGFjZW1lbnQpKTtcbiAgICB9XG4gICAgcmV0dXJuIHRoaXM7XG4gIH07XG5cbiAgLyoqXG4gICAqIFNldCB0aGUgc291cmNlIGNvbnRlbnQgZm9yIGEgc291cmNlIGZpbGUuIFRoaXMgd2lsbCBiZSBhZGRlZCB0byB0aGUgU291cmNlTWFwR2VuZXJhdG9yXG4gICAqIGluIHRoZSBzb3VyY2VzQ29udGVudCBmaWVsZC5cbiAgICpcbiAgICogQHBhcmFtIGFTb3VyY2VGaWxlIFRoZSBmaWxlbmFtZSBvZiB0aGUgc291cmNlIGZpbGVcbiAgICogQHBhcmFtIGFTb3VyY2VDb250ZW50IFRoZSBjb250ZW50IG9mIHRoZSBzb3VyY2UgZmlsZVxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUuc2V0U291cmNlQ29udGVudCA9XG4gICAgZnVuY3Rpb24gU291cmNlTm9kZV9zZXRTb3VyY2VDb250ZW50KGFTb3VyY2VGaWxlLCBhU291cmNlQ29udGVudCkge1xuICAgICAgdGhpcy5zb3VyY2VDb250ZW50c1t1dGlsLnRvU2V0U3RyaW5nKGFTb3VyY2VGaWxlKV0gPSBhU291cmNlQ29udGVudDtcbiAgICB9O1xuXG4gIC8qKlxuICAgKiBXYWxrIG92ZXIgdGhlIHRyZWUgb2YgU291cmNlTm9kZXMuIFRoZSB3YWxraW5nIGZ1bmN0aW9uIGlzIGNhbGxlZCBmb3IgZWFjaFxuICAgKiBzb3VyY2UgZmlsZSBjb250ZW50IGFuZCBpcyBwYXNzZWQgdGhlIGZpbGVuYW1lIGFuZCBzb3VyY2UgY29udGVudC5cbiAgICpcbiAgICogQHBhcmFtIGFGbiBUaGUgdHJhdmVyc2FsIGZ1bmN0aW9uLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUud2Fsa1NvdXJjZUNvbnRlbnRzID1cbiAgICBmdW5jdGlvbiBTb3VyY2VOb2RlX3dhbGtTb3VyY2VDb250ZW50cyhhRm4pIHtcbiAgICAgIGZvciAodmFyIGkgPSAwLCBsZW4gPSB0aGlzLmNoaWxkcmVuLmxlbmd0aDsgaSA8IGxlbjsgaSsrKSB7XG4gICAgICAgIGlmICh0aGlzLmNoaWxkcmVuW2ldW2lzU291cmNlTm9kZV0pIHtcbiAgICAgICAgICB0aGlzLmNoaWxkcmVuW2ldLndhbGtTb3VyY2VDb250ZW50cyhhRm4pO1xuICAgICAgICB9XG4gICAgICB9XG5cbiAgICAgIHZhciBzb3VyY2VzID0gT2JqZWN0LmtleXModGhpcy5zb3VyY2VDb250ZW50cyk7XG4gICAgICBmb3IgKHZhciBpID0gMCwgbGVuID0gc291cmNlcy5sZW5ndGg7IGkgPCBsZW47IGkrKykge1xuICAgICAgICBhRm4odXRpbC5mcm9tU2V0U3RyaW5nKHNvdXJjZXNbaV0pLCB0aGlzLnNvdXJjZUNvbnRlbnRzW3NvdXJjZXNbaV1dKTtcbiAgICAgIH1cbiAgICB9O1xuXG4gIC8qKlxuICAgKiBSZXR1cm4gdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlLiBXYWxrcyBvdmVyIHRoZSB0cmVlXG4gICAqIGFuZCBjb25jYXRlbmF0ZXMgYWxsIHRoZSB2YXJpb3VzIHNuaXBwZXRzIHRvZ2V0aGVyIHRvIG9uZSBzdHJpbmcuXG4gICAqL1xuICBTb3VyY2VOb2RlLnByb3RvdHlwZS50b1N0cmluZyA9IGZ1bmN0aW9uIFNvdXJjZU5vZGVfdG9TdHJpbmcoKSB7XG4gICAgdmFyIHN0ciA9IFwiXCI7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaykge1xuICAgICAgc3RyICs9IGNodW5rO1xuICAgIH0pO1xuICAgIHJldHVybiBzdHI7XG4gIH07XG5cbiAgLyoqXG4gICAqIFJldHVybnMgdGhlIHN0cmluZyByZXByZXNlbnRhdGlvbiBvZiB0aGlzIHNvdXJjZSBub2RlIGFsb25nIHdpdGggYSBzb3VyY2VcbiAgICogbWFwLlxuICAgKi9cbiAgU291cmNlTm9kZS5wcm90b3R5cGUudG9TdHJpbmdXaXRoU291cmNlTWFwID0gZnVuY3Rpb24gU291cmNlTm9kZV90b1N0cmluZ1dpdGhTb3VyY2VNYXAoYUFyZ3MpIHtcbiAgICB2YXIgZ2VuZXJhdGVkID0ge1xuICAgICAgY29kZTogXCJcIixcbiAgICAgIGxpbmU6IDEsXG4gICAgICBjb2x1bW46IDBcbiAgICB9O1xuICAgIHZhciBtYXAgPSBuZXcgU291cmNlTWFwR2VuZXJhdG9yKGFBcmdzKTtcbiAgICB2YXIgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgIHZhciBsYXN0T3JpZ2luYWxTb3VyY2UgPSBudWxsO1xuICAgIHZhciBsYXN0T3JpZ2luYWxMaW5lID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsQ29sdW1uID0gbnVsbDtcbiAgICB2YXIgbGFzdE9yaWdpbmFsTmFtZSA9IG51bGw7XG4gICAgdGhpcy53YWxrKGZ1bmN0aW9uIChjaHVuaywgb3JpZ2luYWwpIHtcbiAgICAgIGdlbmVyYXRlZC5jb2RlICs9IGNodW5rO1xuICAgICAgaWYgKG9yaWdpbmFsLnNvdXJjZSAhPT0gbnVsbFxuICAgICAgICAgICYmIG9yaWdpbmFsLmxpbmUgIT09IG51bGxcbiAgICAgICAgICAmJiBvcmlnaW5hbC5jb2x1bW4gIT09IG51bGwpIHtcbiAgICAgICAgaWYobGFzdE9yaWdpbmFsU291cmNlICE9PSBvcmlnaW5hbC5zb3VyY2VcbiAgICAgICAgICAgfHwgbGFzdE9yaWdpbmFsTGluZSAhPT0gb3JpZ2luYWwubGluZVxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxDb2x1bW4gIT09IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICB8fCBsYXN0T3JpZ2luYWxOYW1lICE9PSBvcmlnaW5hbC5uYW1lKSB7XG4gICAgICAgICAgbWFwLmFkZE1hcHBpbmcoe1xuICAgICAgICAgICAgc291cmNlOiBvcmlnaW5hbC5zb3VyY2UsXG4gICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICBsaW5lOiBvcmlnaW5hbC5saW5lLFxuICAgICAgICAgICAgICBjb2x1bW46IG9yaWdpbmFsLmNvbHVtblxuICAgICAgICAgICAgfSxcbiAgICAgICAgICAgIGdlbmVyYXRlZDoge1xuICAgICAgICAgICAgICBsaW5lOiBnZW5lcmF0ZWQubGluZSxcbiAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICB9LFxuICAgICAgICAgICAgbmFtZTogb3JpZ2luYWwubmFtZVxuICAgICAgICAgIH0pO1xuICAgICAgICB9XG4gICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG9yaWdpbmFsLnNvdXJjZTtcbiAgICAgICAgbGFzdE9yaWdpbmFsTGluZSA9IG9yaWdpbmFsLmxpbmU7XG4gICAgICAgIGxhc3RPcmlnaW5hbENvbHVtbiA9IG9yaWdpbmFsLmNvbHVtbjtcbiAgICAgICAgbGFzdE9yaWdpbmFsTmFtZSA9IG9yaWdpbmFsLm5hbWU7XG4gICAgICAgIHNvdXJjZU1hcHBpbmdBY3RpdmUgPSB0cnVlO1xuICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgIG1hcC5hZGRNYXBwaW5nKHtcbiAgICAgICAgICBnZW5lcmF0ZWQ6IHtcbiAgICAgICAgICAgIGxpbmU6IGdlbmVyYXRlZC5saW5lLFxuICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgfVxuICAgICAgICB9KTtcbiAgICAgICAgbGFzdE9yaWdpbmFsU291cmNlID0gbnVsbDtcbiAgICAgICAgc291cmNlTWFwcGluZ0FjdGl2ZSA9IGZhbHNlO1xuICAgICAgfVxuICAgICAgZm9yICh2YXIgaWR4ID0gMCwgbGVuZ3RoID0gY2h1bmsubGVuZ3RoOyBpZHggPCBsZW5ndGg7IGlkeCsrKSB7XG4gICAgICAgIGlmIChjaHVuay5jaGFyQ29kZUF0KGlkeCkgPT09IE5FV0xJTkVfQ09ERSkge1xuICAgICAgICAgIGdlbmVyYXRlZC5saW5lKys7XG4gICAgICAgICAgZ2VuZXJhdGVkLmNvbHVtbiA9IDA7XG4gICAgICAgICAgLy8gTWFwcGluZ3MgZW5kIGF0IGVvbFxuICAgICAgICAgIGlmIChpZHggKyAxID09PSBsZW5ndGgpIHtcbiAgICAgICAgICAgIGxhc3RPcmlnaW5hbFNvdXJjZSA9IG51bGw7XG4gICAgICAgICAgICBzb3VyY2VNYXBwaW5nQWN0aXZlID0gZmFsc2U7XG4gICAgICAgICAgfSBlbHNlIGlmIChzb3VyY2VNYXBwaW5nQWN0aXZlKSB7XG4gICAgICAgICAgICBtYXAuYWRkTWFwcGluZyh7XG4gICAgICAgICAgICAgIHNvdXJjZTogb3JpZ2luYWwuc291cmNlLFxuICAgICAgICAgICAgICBvcmlnaW5hbDoge1xuICAgICAgICAgICAgICAgIGxpbmU6IG9yaWdpbmFsLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBvcmlnaW5hbC5jb2x1bW5cbiAgICAgICAgICAgICAgfSxcbiAgICAgICAgICAgICAgZ2VuZXJhdGVkOiB7XG4gICAgICAgICAgICAgICAgbGluZTogZ2VuZXJhdGVkLmxpbmUsXG4gICAgICAgICAgICAgICAgY29sdW1uOiBnZW5lcmF0ZWQuY29sdW1uXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIG5hbWU6IG9yaWdpbmFsLm5hbWVcbiAgICAgICAgICAgIH0pO1xuICAgICAgICAgIH1cbiAgICAgICAgfSBlbHNlIHtcbiAgICAgICAgICBnZW5lcmF0ZWQuY29sdW1uKys7XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9KTtcbiAgICB0aGlzLndhbGtTb3VyY2VDb250ZW50cyhmdW5jdGlvbiAoc291cmNlRmlsZSwgc291cmNlQ29udGVudCkge1xuICAgICAgbWFwLnNldFNvdXJjZUNvbnRlbnQoc291cmNlRmlsZSwgc291cmNlQ29udGVudCk7XG4gICAgfSk7XG5cbiAgICByZXR1cm4geyBjb2RlOiBnZW5lcmF0ZWQuY29kZSwgbWFwOiBtYXAgfTtcbiAgfTtcblxuICBleHBvcnRzLlNvdXJjZU5vZGUgPSBTb3VyY2VOb2RlO1xufVxuXG5cblxuLyoqKioqKioqKioqKioqKioqXG4gKiogV0VCUEFDSyBGT09URVJcbiAqKiAuL2xpYi9zb3VyY2Utbm9kZS5qc1xuICoqIG1vZHVsZSBpZCA9IDExXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iXSwic291cmNlUm9vdCI6IiJ9 \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/test_util.js b/devtools/shared/sourcemap/tests/unit/test_util.js
new file mode 100644
index 000000000..f47fc5315
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/test_util.js
@@ -0,0 +1,651 @@
+function run_test() {
+ for (var k in SOURCE_MAP_TEST_MODULE) {
+ if (/^test/.test(k)) {
+ SOURCE_MAP_TEST_MODULE[k](assert);
+ }
+ }
+}
+
+
+var SOURCE_MAP_TEST_MODULE =
+/******/ (function(modules) { // webpackBootstrap
+/******/ // The module cache
+/******/ var installedModules = {};
+/******/
+/******/ // The require function
+/******/ function __webpack_require__(moduleId) {
+/******/
+/******/ // Check if module is in cache
+/******/ if(installedModules[moduleId])
+/******/ return installedModules[moduleId].exports;
+/******/
+/******/ // Create a new module (and put it into the cache)
+/******/ var module = installedModules[moduleId] = {
+/******/ exports: {},
+/******/ id: moduleId,
+/******/ loaded: false
+/******/ };
+/******/
+/******/ // Execute the module function
+/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+/******/
+/******/ // Flag the module as loaded
+/******/ module.loaded = true;
+/******/
+/******/ // Return the exports of the module
+/******/ return module.exports;
+/******/ }
+/******/
+/******/
+/******/ // expose the modules object (__webpack_modules__)
+/******/ __webpack_require__.m = modules;
+/******/
+/******/ // expose the module cache
+/******/ __webpack_require__.c = installedModules;
+/******/
+/******/ // __webpack_public_path__
+/******/ __webpack_require__.p = "";
+/******/
+/******/ // Load entry module and return exports
+/******/ return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2014 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ var libUtil = __webpack_require__(1);
+
+ exports['test urls'] = function (assert) {
+ var assertUrl = function (url) {
+ assert.equal(url, libUtil.urlGenerate(libUtil.urlParse(url)));
+ };
+ assertUrl('http://');
+ assertUrl('http://www.example.com');
+ assertUrl('http://user:pass@www.example.com');
+ assertUrl('http://www.example.com:80');
+ assertUrl('http://www.example.com/');
+ assertUrl('http://www.example.com/foo/bar');
+ assertUrl('http://www.example.com/foo/bar/');
+ assertUrl('http://user:pass@www.example.com:80/foo/bar/');
+
+ assertUrl('//');
+ assertUrl('//www.example.com');
+ assertUrl('file:///www.example.com');
+
+ assert.equal(libUtil.urlParse(''), null);
+ assert.equal(libUtil.urlParse('.'), null);
+ assert.equal(libUtil.urlParse('..'), null);
+ assert.equal(libUtil.urlParse('a'), null);
+ assert.equal(libUtil.urlParse('a/b'), null);
+ assert.equal(libUtil.urlParse('a//b'), null);
+ assert.equal(libUtil.urlParse('/a'), null);
+ assert.equal(libUtil.urlParse('data:foo,bar'), null);
+ };
+
+ exports['test normalize()'] = function (assert) {
+ assert.equal(libUtil.normalize('/..'), '/');
+ assert.equal(libUtil.normalize('/../'), '/');
+ assert.equal(libUtil.normalize('/../../../..'), '/');
+ assert.equal(libUtil.normalize('/../../../../a/b/c'), '/a/b/c');
+ assert.equal(libUtil.normalize('/a/b/c/../../../d/../../e'), '/e');
+
+ assert.equal(libUtil.normalize('..'), '..');
+ assert.equal(libUtil.normalize('../'), '../');
+ assert.equal(libUtil.normalize('../../a/'), '../../a/');
+ assert.equal(libUtil.normalize('a/..'), '.');
+ assert.equal(libUtil.normalize('a/../../..'), '../..');
+
+ assert.equal(libUtil.normalize('/.'), '/');
+ assert.equal(libUtil.normalize('/./'), '/');
+ assert.equal(libUtil.normalize('/./././.'), '/');
+ assert.equal(libUtil.normalize('/././././a/b/c'), '/a/b/c');
+ assert.equal(libUtil.normalize('/a/b/c/./././d/././e'), '/a/b/c/d/e');
+
+ assert.equal(libUtil.normalize(''), '.');
+ assert.equal(libUtil.normalize('.'), '.');
+ assert.equal(libUtil.normalize('./'), '.');
+ assert.equal(libUtil.normalize('././a'), 'a');
+ assert.equal(libUtil.normalize('a/./'), 'a/');
+ assert.equal(libUtil.normalize('a/././.'), 'a');
+
+ assert.equal(libUtil.normalize('/a/b//c////d/////'), '/a/b/c/d/');
+ assert.equal(libUtil.normalize('///a/b//c////d/////'), '///a/b/c/d/');
+ assert.equal(libUtil.normalize('a/b//c////d'), 'a/b/c/d');
+
+ assert.equal(libUtil.normalize('.///.././../a/b//./..'), '../../a')
+
+ assert.equal(libUtil.normalize('http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.normalize('http://www.example.com/'), 'http://www.example.com/');
+ assert.equal(libUtil.normalize('http://www.example.com/./..//a/b/c/.././d//'), 'http://www.example.com/a/b/d/');
+ };
+
+ exports['test join()'] = function (assert) {
+ assert.equal(libUtil.join('a', 'b'), 'a/b');
+ assert.equal(libUtil.join('a/', 'b'), 'a/b');
+ assert.equal(libUtil.join('a//', 'b'), 'a/b');
+ assert.equal(libUtil.join('a', 'b/'), 'a/b/');
+ assert.equal(libUtil.join('a', 'b//'), 'a/b/');
+ assert.equal(libUtil.join('a/', '/b'), '/b');
+ assert.equal(libUtil.join('a//', '//b'), '//b');
+
+ assert.equal(libUtil.join('a', '..'), '.');
+ assert.equal(libUtil.join('a', '../b'), 'b');
+ assert.equal(libUtil.join('a/b', '../c'), 'a/c');
+
+ assert.equal(libUtil.join('a', '.'), 'a');
+ assert.equal(libUtil.join('a', './b'), 'a/b');
+ assert.equal(libUtil.join('a/b', './c'), 'a/b/c');
+
+ assert.equal(libUtil.join('a', 'http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('a', 'data:foo,bar'), 'data:foo,bar');
+
+
+ assert.equal(libUtil.join('', 'b'), 'b');
+ assert.equal(libUtil.join('.', 'b'), 'b');
+ assert.equal(libUtil.join('', 'b/'), 'b/');
+ assert.equal(libUtil.join('.', 'b/'), 'b/');
+ assert.equal(libUtil.join('', 'b//'), 'b/');
+ assert.equal(libUtil.join('.', 'b//'), 'b/');
+
+ assert.equal(libUtil.join('', '..'), '..');
+ assert.equal(libUtil.join('.', '..'), '..');
+ assert.equal(libUtil.join('', '../b'), '../b');
+ assert.equal(libUtil.join('.', '../b'), '../b');
+
+ assert.equal(libUtil.join('', '.'), '.');
+ assert.equal(libUtil.join('.', '.'), '.');
+ assert.equal(libUtil.join('', './b'), 'b');
+ assert.equal(libUtil.join('.', './b'), 'b');
+
+ assert.equal(libUtil.join('', 'http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('.', 'http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('', 'data:foo,bar'), 'data:foo,bar');
+ assert.equal(libUtil.join('.', 'data:foo,bar'), 'data:foo,bar');
+
+
+ assert.equal(libUtil.join('..', 'b'), '../b');
+ assert.equal(libUtil.join('..', 'b/'), '../b/');
+ assert.equal(libUtil.join('..', 'b//'), '../b/');
+
+ assert.equal(libUtil.join('..', '..'), '../..');
+ assert.equal(libUtil.join('..', '../b'), '../../b');
+
+ assert.equal(libUtil.join('..', '.'), '..');
+ assert.equal(libUtil.join('..', './b'), '../b');
+
+ assert.equal(libUtil.join('..', 'http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('..', 'data:foo,bar'), 'data:foo,bar');
+
+
+ assert.equal(libUtil.join('a', ''), 'a');
+ assert.equal(libUtil.join('a', '.'), 'a');
+ assert.equal(libUtil.join('a/', ''), 'a');
+ assert.equal(libUtil.join('a/', '.'), 'a');
+ assert.equal(libUtil.join('a//', ''), 'a');
+ assert.equal(libUtil.join('a//', '.'), 'a');
+ assert.equal(libUtil.join('/a', ''), '/a');
+ assert.equal(libUtil.join('/a', '.'), '/a');
+ assert.equal(libUtil.join('', ''), '.');
+ assert.equal(libUtil.join('.', ''), '.');
+ assert.equal(libUtil.join('.', ''), '.');
+ assert.equal(libUtil.join('.', '.'), '.');
+ assert.equal(libUtil.join('..', ''), '..');
+ assert.equal(libUtil.join('..', '.'), '..');
+ assert.equal(libUtil.join('http://foo.org/a', ''), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a', '.'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a/', ''), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a/', '.'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a//', ''), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a//', '.'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org', ''), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org', '.'), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org/', ''), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org/', '.'), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org//', ''), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org//', '.'), 'http://foo.org/');
+ assert.equal(libUtil.join('//www.example.com', ''), '//www.example.com/');
+ assert.equal(libUtil.join('//www.example.com', '.'), '//www.example.com/');
+
+
+ assert.equal(libUtil.join('http://foo.org/a', 'b'), 'http://foo.org/a/b');
+ assert.equal(libUtil.join('http://foo.org/a/', 'b'), 'http://foo.org/a/b');
+ assert.equal(libUtil.join('http://foo.org/a//', 'b'), 'http://foo.org/a/b');
+ assert.equal(libUtil.join('http://foo.org/a', 'b/'), 'http://foo.org/a/b/');
+ assert.equal(libUtil.join('http://foo.org/a', 'b//'), 'http://foo.org/a/b/');
+ assert.equal(libUtil.join('http://foo.org/a/', '/b'), 'http://foo.org/b');
+ assert.equal(libUtil.join('http://foo.org/a//', '//b'), 'http://b');
+
+ assert.equal(libUtil.join('http://foo.org/a', '..'), 'http://foo.org/');
+ assert.equal(libUtil.join('http://foo.org/a', '../b'), 'http://foo.org/b');
+ assert.equal(libUtil.join('http://foo.org/a/b', '../c'), 'http://foo.org/a/c');
+
+ assert.equal(libUtil.join('http://foo.org/a', '.'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/a', './b'), 'http://foo.org/a/b');
+ assert.equal(libUtil.join('http://foo.org/a/b', './c'), 'http://foo.org/a/b/c');
+
+ assert.equal(libUtil.join('http://foo.org/a', 'http://www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('http://foo.org/a', 'data:foo,bar'), 'data:foo,bar');
+
+
+ assert.equal(libUtil.join('http://foo.org', 'a'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/', 'a'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org//', 'a'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org', '/a'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org/', '/a'), 'http://foo.org/a');
+ assert.equal(libUtil.join('http://foo.org//', '/a'), 'http://foo.org/a');
+
+
+ assert.equal(libUtil.join('http://', 'www.example.com'), 'http://www.example.com');
+ assert.equal(libUtil.join('file:///', 'www.example.com'), 'file:///www.example.com');
+ assert.equal(libUtil.join('http://', 'ftp://example.com'), 'ftp://example.com');
+
+ assert.equal(libUtil.join('http://www.example.com', '//foo.org/bar'), 'http://foo.org/bar');
+ assert.equal(libUtil.join('//www.example.com', '//foo.org/bar'), '//foo.org/bar');
+ };
+
+ // TODO Issue #128: Define and test this function properly.
+ exports['test relative()'] = function (assert) {
+ assert.equal(libUtil.relative('/the/root', '/the/root/one.js'), 'one.js');
+ assert.equal(libUtil.relative('http://the/root', 'http://the/root/one.js'), 'one.js');
+ assert.equal(libUtil.relative('/the/root', '/the/rootone.js'), '../rootone.js');
+ assert.equal(libUtil.relative('http://the/root', 'http://the/rootone.js'), '../rootone.js');
+ assert.equal(libUtil.relative('/the/root', '/therootone.js'), '/therootone.js');
+ assert.equal(libUtil.relative('http://the/root', '/therootone.js'), '/therootone.js');
+
+ assert.equal(libUtil.relative('', '/the/root/one.js'), '/the/root/one.js');
+ assert.equal(libUtil.relative('.', '/the/root/one.js'), '/the/root/one.js');
+ assert.equal(libUtil.relative('', 'the/root/one.js'), 'the/root/one.js');
+ assert.equal(libUtil.relative('.', 'the/root/one.js'), 'the/root/one.js');
+
+ assert.equal(libUtil.relative('/', '/the/root/one.js'), 'the/root/one.js');
+ assert.equal(libUtil.relative('/', 'the/root/one.js'), 'the/root/one.js');
+ };
+ }
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports) {
+
+ /* -*- Mode: js; js-indent-level: 2; -*- */
+ /*
+ * Copyright 2011 Mozilla Foundation and contributors
+ * Licensed under the New BSD license. See LICENSE or:
+ * http://opensource.org/licenses/BSD-3-Clause
+ */
+ {
+ /**
+ * This is a helper function for getting values from parameter/options
+ * objects.
+ *
+ * @param args The object we are extracting values from
+ * @param name The name of the property we are getting.
+ * @param defaultValue An optional value to return if the property is missing
+ * from the object. If this is not specified and the property is missing, an
+ * error will be thrown.
+ */
+ function getArg(aArgs, aName, aDefaultValue) {
+ if (aName in aArgs) {
+ return aArgs[aName];
+ } else if (arguments.length === 3) {
+ return aDefaultValue;
+ } else {
+ throw new Error('"' + aName + '" is a required argument.');
+ }
+ }
+ exports.getArg = getArg;
+
+ var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.]*)(?::(\d+))?(\S*)$/;
+ var dataUrlRegexp = /^data:.+\,.+$/;
+
+ function urlParse(aUrl) {
+ var match = aUrl.match(urlRegexp);
+ if (!match) {
+ return null;
+ }
+ return {
+ scheme: match[1],
+ auth: match[2],
+ host: match[3],
+ port: match[4],
+ path: match[5]
+ };
+ }
+ exports.urlParse = urlParse;
+
+ function urlGenerate(aParsedUrl) {
+ var url = '';
+ if (aParsedUrl.scheme) {
+ url += aParsedUrl.scheme + ':';
+ }
+ url += '//';
+ if (aParsedUrl.auth) {
+ url += aParsedUrl.auth + '@';
+ }
+ if (aParsedUrl.host) {
+ url += aParsedUrl.host;
+ }
+ if (aParsedUrl.port) {
+ url += ":" + aParsedUrl.port
+ }
+ if (aParsedUrl.path) {
+ url += aParsedUrl.path;
+ }
+ return url;
+ }
+ exports.urlGenerate = urlGenerate;
+
+ /**
+ * Normalizes a path, or the path portion of a URL:
+ *
+ * - Replaces consequtive slashes with one slash.
+ * - Removes unnecessary '.' parts.
+ * - Removes unnecessary '<dir>/..' parts.
+ *
+ * Based on code in the Node.js 'path' core module.
+ *
+ * @param aPath The path or url to normalize.
+ */
+ function normalize(aPath) {
+ var path = aPath;
+ var url = urlParse(aPath);
+ if (url) {
+ if (!url.path) {
+ return aPath;
+ }
+ path = url.path;
+ }
+ var isAbsolute = exports.isAbsolute(path);
+
+ var parts = path.split(/\/+/);
+ for (var part, up = 0, i = parts.length - 1; i >= 0; i--) {
+ part = parts[i];
+ if (part === '.') {
+ parts.splice(i, 1);
+ } else if (part === '..') {
+ up++;
+ } else if (up > 0) {
+ if (part === '') {
+ // The first part is blank if the path is absolute. Trying to go
+ // above the root is a no-op. Therefore we can remove all '..' parts
+ // directly after the root.
+ parts.splice(i + 1, up);
+ up = 0;
+ } else {
+ parts.splice(i, 2);
+ up--;
+ }
+ }
+ }
+ path = parts.join('/');
+
+ if (path === '') {
+ path = isAbsolute ? '/' : '.';
+ }
+
+ if (url) {
+ url.path = path;
+ return urlGenerate(url);
+ }
+ return path;
+ }
+ exports.normalize = normalize;
+
+ /**
+ * Joins two paths/URLs.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be joined with the root.
+ *
+ * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a
+ * scheme-relative URL: Then the scheme of aRoot, if any, is prepended
+ * first.
+ * - Otherwise aPath is a path. If aRoot is a URL, then its path portion
+ * is updated with the result and aRoot is returned. Otherwise the result
+ * is returned.
+ * - If aPath is absolute, the result is aPath.
+ * - Otherwise the two paths are joined with a slash.
+ * - Joining for example 'http://' and 'www.example.com' is also supported.
+ */
+ function join(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+ if (aPath === "") {
+ aPath = ".";
+ }
+ var aPathUrl = urlParse(aPath);
+ var aRootUrl = urlParse(aRoot);
+ if (aRootUrl) {
+ aRoot = aRootUrl.path || '/';
+ }
+
+ // `join(foo, '//www.example.org')`
+ if (aPathUrl && !aPathUrl.scheme) {
+ if (aRootUrl) {
+ aPathUrl.scheme = aRootUrl.scheme;
+ }
+ return urlGenerate(aPathUrl);
+ }
+
+ if (aPathUrl || aPath.match(dataUrlRegexp)) {
+ return aPath;
+ }
+
+ // `join('http://', 'www.example.com')`
+ if (aRootUrl && !aRootUrl.host && !aRootUrl.path) {
+ aRootUrl.host = aPath;
+ return urlGenerate(aRootUrl);
+ }
+
+ var joined = aPath.charAt(0) === '/'
+ ? aPath
+ : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath);
+
+ if (aRootUrl) {
+ aRootUrl.path = joined;
+ return urlGenerate(aRootUrl);
+ }
+ return joined;
+ }
+ exports.join = join;
+
+ exports.isAbsolute = function (aPath) {
+ return aPath.charAt(0) === '/' || !!aPath.match(urlRegexp);
+ };
+
+ /**
+ * Make a path relative to a URL or another path.
+ *
+ * @param aRoot The root path or URL.
+ * @param aPath The path or URL to be made relative to aRoot.
+ */
+ function relative(aRoot, aPath) {
+ if (aRoot === "") {
+ aRoot = ".";
+ }
+
+ aRoot = aRoot.replace(/\/$/, '');
+
+ // It is possible for the path to be above the root. In this case, simply
+ // checking whether the root is a prefix of the path won't work. Instead, we
+ // need to remove components from the root one by one, until either we find
+ // a prefix that fits, or we run out of components to remove.
+ var level = 0;
+ while (aPath.indexOf(aRoot + '/') !== 0) {
+ var index = aRoot.lastIndexOf("/");
+ if (index < 0) {
+ return aPath;
+ }
+
+ // If the only part of the root that is left is the scheme (i.e. http://,
+ // file:///, etc.), one or more slashes (/), or simply nothing at all, we
+ // have exhausted all components, so the path is not relative to the root.
+ aRoot = aRoot.slice(0, index);
+ if (aRoot.match(/^([^\/]+:\/)?\/*$/)) {
+ return aPath;
+ }
+
+ ++level;
+ }
+
+ // Make sure we add a "../" for each component we removed from the root.
+ return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1);
+ }
+ exports.relative = relative;
+
+ /**
+ * Because behavior goes wacky when you set `__proto__` on objects, we
+ * have to prefix all the strings in our set with an arbitrary character.
+ *
+ * See https://github.com/mozilla/source-map/pull/31 and
+ * https://github.com/mozilla/source-map/issues/30
+ *
+ * @param String aStr
+ */
+ function toSetString(aStr) {
+ return '$' + aStr;
+ }
+ exports.toSetString = toSetString;
+
+ function fromSetString(aStr) {
+ return aStr.substr(1);
+ }
+ exports.fromSetString = fromSetString;
+
+ /**
+ * Comparator between two mappings where the original positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same original source/line/column, but different generated
+ * line and column the same. Useful when searching for a mapping with a
+ * stubbed out mapping.
+ */
+ function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) {
+ var cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0 || onlyCompareOriginal) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByOriginalPositions = compareByOriginalPositions;
+
+ /**
+ * Comparator between two mappings with deflated source and name indices where
+ * the generated positions are compared.
+ *
+ * Optionally pass in `true` as `onlyCompareGenerated` to consider two
+ * mappings with the same generated line and column, but different
+ * source/name/original line and column the same. Useful when searching for a
+ * mapping with a stubbed out mapping.
+ */
+ function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0 || onlyCompareGenerated) {
+ return cmp;
+ }
+
+ cmp = mappingA.source - mappingB.source;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return mappingA.name - mappingB.name;
+ }
+ exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated;
+
+ function strcmp(aStr1, aStr2) {
+ if (aStr1 === aStr2) {
+ return 0;
+ }
+
+ if (aStr1 > aStr2) {
+ return 1;
+ }
+
+ return -1;
+ }
+
+ /**
+ * Comparator between two mappings with inflated source and name strings where
+ * the generated positions are compared.
+ */
+ function compareByGeneratedPositionsInflated(mappingA, mappingB) {
+ var cmp = mappingA.generatedLine - mappingB.generatedLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.generatedColumn - mappingB.generatedColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = strcmp(mappingA.source, mappingB.source);
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalLine - mappingB.originalLine;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ cmp = mappingA.originalColumn - mappingB.originalColumn;
+ if (cmp !== 0) {
+ return cmp;
+ }
+
+ return strcmp(mappingA.name, mappingB.name);
+ }
+ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated;
+ }
+
+
+/***/ }
+/******/ ]);
+//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIndlYnBhY2s6Ly8vd2VicGFjay9ib290c3RyYXAgYWVhNDgxM2ZiZGRjOWE3MDI3MTgiLCJ3ZWJwYWNrOi8vLy4vdGVzdC90ZXN0LXV0aWwuanMiLCJ3ZWJwYWNrOi8vLy4vbGliL3V0aWwuanMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7QUFBQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQSx1QkFBZTtBQUNmO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOzs7QUFHQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7QUFFQTtBQUNBOzs7Ozs7O0FDdENBLGlCQUFnQixvQkFBb0I7QUFDcEM7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7OztBQUdBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTs7O0FBR0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOzs7QUFHQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7Ozs7Ozs7QUN0TkEsaUJBQWdCLG9CQUFvQjtBQUNwQztBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBLE1BQUs7QUFDTDtBQUNBLE1BQUs7QUFDTDtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBLGlEQUFnRCxRQUFRO0FBQ3hEO0FBQ0E7QUFDQTtBQUNBLFFBQU87QUFDUDtBQUNBLFFBQU87QUFDUDtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxVQUFTO0FBQ1Q7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBO0FBQ0E7O0FBRUE7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBOztBQUVBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQTs7QUFFQTtBQUNBO0FBQ0E7QUFDQSIsImZpbGUiOiJ0ZXN0X3V0aWwuanMiLCJzb3VyY2VzQ29udGVudCI6WyIgXHQvLyBUaGUgbW9kdWxlIGNhY2hlXG4gXHR2YXIgaW5zdGFsbGVkTW9kdWxlcyA9IHt9O1xuXG4gXHQvLyBUaGUgcmVxdWlyZSBmdW5jdGlvblxuIFx0ZnVuY3Rpb24gX193ZWJwYWNrX3JlcXVpcmVfXyhtb2R1bGVJZCkge1xuXG4gXHRcdC8vIENoZWNrIGlmIG1vZHVsZSBpcyBpbiBjYWNoZVxuIFx0XHRpZihpbnN0YWxsZWRNb2R1bGVzW21vZHVsZUlkXSlcbiBcdFx0XHRyZXR1cm4gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0uZXhwb3J0cztcblxuIFx0XHQvLyBDcmVhdGUgYSBuZXcgbW9kdWxlIChhbmQgcHV0IGl0IGludG8gdGhlIGNhY2hlKVxuIFx0XHR2YXIgbW9kdWxlID0gaW5zdGFsbGVkTW9kdWxlc1ttb2R1bGVJZF0gPSB7XG4gXHRcdFx0ZXhwb3J0czoge30sXG4gXHRcdFx0aWQ6IG1vZHVsZUlkLFxuIFx0XHRcdGxvYWRlZDogZmFsc2VcbiBcdFx0fTtcblxuIFx0XHQvLyBFeGVjdXRlIHRoZSBtb2R1bGUgZnVuY3Rpb25cbiBcdFx0bW9kdWxlc1ttb2R1bGVJZF0uY2FsbChtb2R1bGUuZXhwb3J0cywgbW9kdWxlLCBtb2R1bGUuZXhwb3J0cywgX193ZWJwYWNrX3JlcXVpcmVfXyk7XG5cbiBcdFx0Ly8gRmxhZyB0aGUgbW9kdWxlIGFzIGxvYWRlZFxuIFx0XHRtb2R1bGUubG9hZGVkID0gdHJ1ZTtcblxuIFx0XHQvLyBSZXR1cm4gdGhlIGV4cG9ydHMgb2YgdGhlIG1vZHVsZVxuIFx0XHRyZXR1cm4gbW9kdWxlLmV4cG9ydHM7XG4gXHR9XG5cblxuIFx0Ly8gZXhwb3NlIHRoZSBtb2R1bGVzIG9iamVjdCAoX193ZWJwYWNrX21vZHVsZXNfXylcbiBcdF9fd2VicGFja19yZXF1aXJlX18ubSA9IG1vZHVsZXM7XG5cbiBcdC8vIGV4cG9zZSB0aGUgbW9kdWxlIGNhY2hlXG4gXHRfX3dlYnBhY2tfcmVxdWlyZV9fLmMgPSBpbnN0YWxsZWRNb2R1bGVzO1xuXG4gXHQvLyBfX3dlYnBhY2tfcHVibGljX3BhdGhfX1xuIFx0X193ZWJwYWNrX3JlcXVpcmVfXy5wID0gXCJcIjtcblxuIFx0Ly8gTG9hZCBlbnRyeSBtb2R1bGUgYW5kIHJldHVybiBleHBvcnRzXG4gXHRyZXR1cm4gX193ZWJwYWNrX3JlcXVpcmVfXygwKTtcblxuXG5cbi8qKiBXRUJQQUNLIEZPT1RFUiAqKlxuICoqIHdlYnBhY2svYm9vdHN0cmFwIGFlYTQ4MTNmYmRkYzlhNzAyNzE4XG4gKiovIiwiLyogLSotIE1vZGU6IGpzOyBqcy1pbmRlbnQtbGV2ZWw6IDI7IC0qLSAqL1xuLypcbiAqIENvcHlyaWdodCAyMDE0IE1vemlsbGEgRm91bmRhdGlvbiBhbmQgY29udHJpYnV0b3JzXG4gKiBMaWNlbnNlZCB1bmRlciB0aGUgTmV3IEJTRCBsaWNlbnNlLiBTZWUgTElDRU5TRSBvcjpcbiAqIGh0dHA6Ly9vcGVuc291cmNlLm9yZy9saWNlbnNlcy9CU0QtMy1DbGF1c2VcbiAqL1xue1xuICB2YXIgbGliVXRpbCA9IHJlcXVpcmUoJy4uL2xpYi91dGlsJyk7XG5cbiAgZXhwb3J0c1sndGVzdCB1cmxzJ10gPSBmdW5jdGlvbiAoYXNzZXJ0KSB7XG4gICAgdmFyIGFzc2VydFVybCA9IGZ1bmN0aW9uICh1cmwpIHtcbiAgICAgIGFzc2VydC5lcXVhbCh1cmwsIGxpYlV0aWwudXJsR2VuZXJhdGUobGliVXRpbC51cmxQYXJzZSh1cmwpKSk7XG4gICAgfTtcbiAgICBhc3NlcnRVcmwoJ2h0dHA6Ly8nKTtcbiAgICBhc3NlcnRVcmwoJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20nKTtcbiAgICBhc3NlcnRVcmwoJ2h0dHA6Ly91c2VyOnBhc3NAd3d3LmV4YW1wbGUuY29tJyk7XG4gICAgYXNzZXJ0VXJsKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tOjgwJyk7XG4gICAgYXNzZXJ0VXJsKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tLycpO1xuICAgIGFzc2VydFVybCgnaHR0cDovL3d3dy5leGFtcGxlLmNvbS9mb28vYmFyJyk7XG4gICAgYXNzZXJ0VXJsKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tL2Zvby9iYXIvJyk7XG4gICAgYXNzZXJ0VXJsKCdodHRwOi8vdXNlcjpwYXNzQHd3dy5leGFtcGxlLmNvbTo4MC9mb28vYmFyLycpO1xuXG4gICAgYXNzZXJ0VXJsKCcvLycpO1xuICAgIGFzc2VydFVybCgnLy93d3cuZXhhbXBsZS5jb20nKTtcbiAgICBhc3NlcnRVcmwoJ2ZpbGU6Ly8vd3d3LmV4YW1wbGUuY29tJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC51cmxQYXJzZSgnJyksIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnVybFBhcnNlKCcuJyksIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnVybFBhcnNlKCcuLicpLCBudWxsKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC51cmxQYXJzZSgnYScpLCBudWxsKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC51cmxQYXJzZSgnYS9iJyksIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnVybFBhcnNlKCdhLy9iJyksIG51bGwpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnVybFBhcnNlKCcvYScpLCBudWxsKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC51cmxQYXJzZSgnZGF0YTpmb28sYmFyJyksIG51bGwpO1xuICB9O1xuXG4gIGV4cG9ydHNbJ3Rlc3Qgbm9ybWFsaXplKCknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5ub3JtYWxpemUoJy8uLicpLCAnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLy4uLycpLCAnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLy4uLy4uLy4uLy4uJyksICcvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcvLi4vLi4vLi4vLi4vYS9iL2MnKSwgJy9hL2IvYycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnL2EvYi9jLy4uLy4uLy4uL2QvLi4vLi4vZScpLCAnL2UnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLi4nKSwgJy4uJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcuLi8nKSwgJy4uLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLi4vLi4vYS8nKSwgJy4uLy4uL2EvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdhLy4uJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdhLy4uLy4uLy4uJyksICcuLi8uLicpO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcvLicpLCAnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLy4vJyksICcvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcvLi8uLy4vLicpLCAnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLy4vLi8uLy4vYS9iL2MnKSwgJy9hL2IvYycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnL2EvYi9jLy4vLi8uL2QvLi8uL2UnKSwgJy9hL2IvYy9kL2UnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcuJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcuLycpLCAnLicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLm5vcm1hbGl6ZSgnLi8uL2EnKSwgJ2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5ub3JtYWxpemUoJ2EvLi8nKSwgJ2EvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdhLy4vLi8uJyksICdhJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5ub3JtYWxpemUoJy9hL2IvL2MvLy8vZC8vLy8vJyksICcvYS9iL2MvZC8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5ub3JtYWxpemUoJy8vL2EvYi8vYy8vLy9kLy8vLy8nKSwgJy8vL2EvYi9jL2QvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdhL2IvL2MvLy8vZCcpLCAnYS9iL2MvZCcpO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCcuLy8vLi4vLi8uLi9hL2IvLy4vLi4nKSwgJy4uLy4uL2EnKVxuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tJyksICdodHRwOi8vd3d3LmV4YW1wbGUuY29tJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwubm9ybWFsaXplKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tLycpLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbS8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5ub3JtYWxpemUoJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vLi8uLi8vYS9iL2MvLi4vLi9kLy8nKSwgJ2h0dHA6Ly93d3cuZXhhbXBsZS5jb20vYS9iL2QvJyk7XG4gIH07XG5cbiAgZXhwb3J0c1sndGVzdCBqb2luKCknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJ2InKSwgJ2EvYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EvJywgJ2InKSwgJ2EvYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EvLycsICdiJyksICdhL2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJ2IvJyksICdhL2IvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignYScsICdiLy8nKSwgJ2EvYi8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhLycsICcvYicpLCAnL2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhLy8nLCAnLy9iJyksICcvL2InKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EnLCAnLi4nKSwgJy4nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJy4uL2InKSwgJ2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhL2InLCAnLi4vYycpLCAnYS9jJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJy4nKSwgJ2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJy4vYicpLCAnYS9iJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignYS9iJywgJy4vYycpLCAnYS9iL2MnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EnLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EnLCAnZGF0YTpmb28sYmFyJyksICdkYXRhOmZvbyxiYXInKTtcblxuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignJywgJ2InKSwgJ2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcuJywgJ2InKSwgJ2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcnLCAnYi8nKSwgJ2IvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLicsICdiLycpLCAnYi8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcnLCAnYi8vJyksICdiLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnYi8vJyksICdiLycpO1xuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignJywgJy4uJyksICcuLicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnLi4nKSwgJy4uJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignJywgJy4uL2InKSwgJy4uL2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcuJywgJy4uL2InKSwgJy4uL2InKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJycsICcuJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLicsICcuJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignJywgJy4vYicpLCAnYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnLi9iJyksICdiJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcnLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJycsICdkYXRhOmZvbyxiYXInKSwgJ2RhdGE6Zm9vLGJhcicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnZGF0YTpmb28sYmFyJyksICdkYXRhOmZvbyxiYXInKTtcblxuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLi4nLCAnYicpLCAnLi4vYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4uJywgJ2IvJyksICcuLi9iLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4uJywgJ2IvLycpLCAnLi4vYi8nKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4uJywgJy4uJyksICcuLi8uLicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4uJywgJy4uL2InKSwgJy4uLy4uL2InKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4uJywgJy4nKSwgJy4uJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLi4nLCAnLi9iJyksICcuLi9iJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcuLicsICdodHRwOi8vd3d3LmV4YW1wbGUuY29tJyksICdodHRwOi8vd3d3LmV4YW1wbGUuY29tJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLi4nLCAnZGF0YTpmb28sYmFyJyksICdkYXRhOmZvbyxiYXInKTtcblxuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignYScsICcnKSwgJ2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhJywgJy4nKSwgJ2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhLycsICcnKSwgJ2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdhLycsICcuJyksICdhJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignYS8vJywgJycpLCAnYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2EvLycsICcuJyksICdhJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignL2EnLCAnJyksICcvYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy9hJywgJy4nKSwgJy9hJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignJywgJycpLCAnLicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy4nLCAnJyksICcuJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLicsICcnKSwgJy4nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcuJywgJy4nKSwgJy4nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcuLicsICcnKSwgJy4uJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignLi4nLCAnLicpLCAnLi4nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJycpLCAnaHR0cDovL2Zvby5vcmcvYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EnLCAnLicpLCAnaHR0cDovL2Zvby5vcmcvYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EvJywgJycpLCAnaHR0cDovL2Zvby5vcmcvYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EvJywgJy4nKSwgJ2h0dHA6Ly9mb28ub3JnL2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hLy8nLCAnJyksICdodHRwOi8vZm9vLm9yZy9hJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcvYS8vJywgJy4nKSwgJ2h0dHA6Ly9mb28ub3JnL2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZycsICcnKSwgJ2h0dHA6Ly9mb28ub3JnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnJywgJy4nKSwgJ2h0dHA6Ly9mb28ub3JnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnLycsICcnKSwgJ2h0dHA6Ly9mb28ub3JnLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnLycsICcuJyksICdodHRwOi8vZm9vLm9yZy8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy8vJywgJycpLCAnaHR0cDovL2Zvby5vcmcvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcvLycsICcuJyksICdodHRwOi8vZm9vLm9yZy8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCcvL3d3dy5leGFtcGxlLmNvbScsICcnKSwgJy8vd3d3LmV4YW1wbGUuY29tLycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy8vd3d3LmV4YW1wbGUuY29tJywgJy4nKSwgJy8vd3d3LmV4YW1wbGUuY29tLycpO1xuXG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJ2InKSwgJ2h0dHA6Ly9mb28ub3JnL2EvYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EvJywgJ2InKSwgJ2h0dHA6Ly9mb28ub3JnL2EvYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EvLycsICdiJyksICdodHRwOi8vZm9vLm9yZy9hL2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJ2IvJyksICdodHRwOi8vZm9vLm9yZy9hL2IvJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcvYScsICdiLy8nKSwgJ2h0dHA6Ly9mb28ub3JnL2EvYi8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hLycsICcvYicpLCAnaHR0cDovL2Zvby5vcmcvYicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EvLycsICcvL2InKSwgJ2h0dHA6Ly9iJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJy4uJyksICdodHRwOi8vZm9vLm9yZy8nKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJy4uL2InKSwgJ2h0dHA6Ly9mb28ub3JnL2InKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hL2InLCAnLi4vYycpLCAnaHR0cDovL2Zvby5vcmcvYS9jJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJy4nKSwgJ2h0dHA6Ly9mb28ub3JnL2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy9hJywgJy4vYicpLCAnaHR0cDovL2Zvby5vcmcvYS9iJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcvYS9iJywgJy4vYycpLCAnaHR0cDovL2Zvby5vcmcvYS9iL2MnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EnLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpLCAnaHR0cDovL3d3dy5leGFtcGxlLmNvbScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnL2EnLCAnZGF0YTpmb28sYmFyJyksICdkYXRhOmZvbyxiYXInKTtcblxuXG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcnLCAnYScpLCAnaHR0cDovL2Zvby5vcmcvYScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly9mb28ub3JnLycsICdhJyksICdodHRwOi8vZm9vLm9yZy9hJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcvLycsICdhJyksICdodHRwOi8vZm9vLm9yZy9hJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignaHR0cDovL2Zvby5vcmcnLCAnL2EnKSwgJ2h0dHA6Ly9mb28ub3JnL2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy8nLCAnL2EnKSwgJ2h0dHA6Ly9mb28ub3JnL2EnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vZm9vLm9yZy8vJywgJy9hJyksICdodHRwOi8vZm9vLm9yZy9hJyk7XG5cblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly8nLCAnd3d3LmV4YW1wbGUuY29tJyksICdodHRwOi8vd3d3LmV4YW1wbGUuY29tJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwuam9pbignZmlsZTovLy8nLCAnd3d3LmV4YW1wbGUuY29tJyksICdmaWxlOi8vL3d3dy5leGFtcGxlLmNvbScpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJ2h0dHA6Ly8nLCAnZnRwOi8vZXhhbXBsZS5jb20nKSwgJ2Z0cDovL2V4YW1wbGUuY29tJyk7XG5cbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5qb2luKCdodHRwOi8vd3d3LmV4YW1wbGUuY29tJywgJy8vZm9vLm9yZy9iYXInKSwgJ2h0dHA6Ly9mb28ub3JnL2JhcicpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLmpvaW4oJy8vd3d3LmV4YW1wbGUuY29tJywgJy8vZm9vLm9yZy9iYXInKSwgJy8vZm9vLm9yZy9iYXInKTtcbiAgfTtcblxuICAvLyBUT0RPIElzc3VlICMxMjg6IERlZmluZSBhbmQgdGVzdCB0aGlzIGZ1bmN0aW9uIHByb3Blcmx5LlxuICBleHBvcnRzWyd0ZXN0IHJlbGF0aXZlKCknXSA9IGZ1bmN0aW9uIChhc3NlcnQpIHtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5yZWxhdGl2ZSgnL3RoZS9yb290JywgJy90aGUvcm9vdC9vbmUuanMnKSwgJ29uZS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCdodHRwOi8vdGhlL3Jvb3QnLCAnaHR0cDovL3RoZS9yb290L29uZS5qcycpLCAnb25lLmpzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwucmVsYXRpdmUoJy90aGUvcm9vdCcsICcvdGhlL3Jvb3RvbmUuanMnKSwgJy4uL3Jvb3RvbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5yZWxhdGl2ZSgnaHR0cDovL3RoZS9yb290JywgJ2h0dHA6Ly90aGUvcm9vdG9uZS5qcycpLCAnLi4vcm9vdG9uZS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCcvdGhlL3Jvb3QnLCAnL3RoZXJvb3RvbmUuanMnKSwgJy90aGVyb290b25lLmpzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwucmVsYXRpdmUoJ2h0dHA6Ly90aGUvcm9vdCcsICcvdGhlcm9vdG9uZS5qcycpLCAnL3RoZXJvb3RvbmUuanMnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCcnLCAnL3RoZS9yb290L29uZS5qcycpLCAnL3RoZS9yb290L29uZS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCcuJywgJy90aGUvcm9vdC9vbmUuanMnKSwgJy90aGUvcm9vdC9vbmUuanMnKTtcbiAgICBhc3NlcnQuZXF1YWwobGliVXRpbC5yZWxhdGl2ZSgnJywgJ3RoZS9yb290L29uZS5qcycpLCAndGhlL3Jvb3Qvb25lLmpzJyk7XG4gICAgYXNzZXJ0LmVxdWFsKGxpYlV0aWwucmVsYXRpdmUoJy4nLCAndGhlL3Jvb3Qvb25lLmpzJyksICd0aGUvcm9vdC9vbmUuanMnKTtcblxuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCcvJywgJy90aGUvcm9vdC9vbmUuanMnKSwgJ3RoZS9yb290L29uZS5qcycpO1xuICAgIGFzc2VydC5lcXVhbChsaWJVdGlsLnJlbGF0aXZlKCcvJywgJ3RoZS9yb290L29uZS5qcycpLCAndGhlL3Jvb3Qvb25lLmpzJyk7XG4gIH07XG59XG5cblxuXG4vKioqKioqKioqKioqKioqKipcbiAqKiBXRUJQQUNLIEZPT1RFUlxuICoqIC4vdGVzdC90ZXN0LXV0aWwuanNcbiAqKiBtb2R1bGUgaWQgPSAwXG4gKiogbW9kdWxlIGNodW5rcyA9IDBcbiAqKi8iLCIvKiAtKi0gTW9kZToganM7IGpzLWluZGVudC1sZXZlbDogMjsgLSotICovXG4vKlxuICogQ29weXJpZ2h0IDIwMTEgTW96aWxsYSBGb3VuZGF0aW9uIGFuZCBjb250cmlidXRvcnNcbiAqIExpY2Vuc2VkIHVuZGVyIHRoZSBOZXcgQlNEIGxpY2Vuc2UuIFNlZSBMSUNFTlNFIG9yOlxuICogaHR0cDovL29wZW5zb3VyY2Uub3JnL2xpY2Vuc2VzL0JTRC0zLUNsYXVzZVxuICovXG57XG4gIC8qKlxuICAgKiBUaGlzIGlzIGEgaGVscGVyIGZ1bmN0aW9uIGZvciBnZXR0aW5nIHZhbHVlcyBmcm9tIHBhcmFtZXRlci9vcHRpb25zXG4gICAqIG9iamVjdHMuXG4gICAqXG4gICAqIEBwYXJhbSBhcmdzIFRoZSBvYmplY3Qgd2UgYXJlIGV4dHJhY3RpbmcgdmFsdWVzIGZyb21cbiAgICogQHBhcmFtIG5hbWUgVGhlIG5hbWUgb2YgdGhlIHByb3BlcnR5IHdlIGFyZSBnZXR0aW5nLlxuICAgKiBAcGFyYW0gZGVmYXVsdFZhbHVlIEFuIG9wdGlvbmFsIHZhbHVlIHRvIHJldHVybiBpZiB0aGUgcHJvcGVydHkgaXMgbWlzc2luZ1xuICAgKiBmcm9tIHRoZSBvYmplY3QuIElmIHRoaXMgaXMgbm90IHNwZWNpZmllZCBhbmQgdGhlIHByb3BlcnR5IGlzIG1pc3NpbmcsIGFuXG4gICAqIGVycm9yIHdpbGwgYmUgdGhyb3duLlxuICAgKi9cbiAgZnVuY3Rpb24gZ2V0QXJnKGFBcmdzLCBhTmFtZSwgYURlZmF1bHRWYWx1ZSkge1xuICAgIGlmIChhTmFtZSBpbiBhQXJncykge1xuICAgICAgcmV0dXJuIGFBcmdzW2FOYW1lXTtcbiAgICB9IGVsc2UgaWYgKGFyZ3VtZW50cy5sZW5ndGggPT09IDMpIHtcbiAgICAgIHJldHVybiBhRGVmYXVsdFZhbHVlO1xuICAgIH0gZWxzZSB7XG4gICAgICB0aHJvdyBuZXcgRXJyb3IoJ1wiJyArIGFOYW1lICsgJ1wiIGlzIGEgcmVxdWlyZWQgYXJndW1lbnQuJyk7XG4gICAgfVxuICB9XG4gIGV4cG9ydHMuZ2V0QXJnID0gZ2V0QXJnO1xuXG4gIHZhciB1cmxSZWdleHAgPSAvXig/OihbXFx3K1xcLS5dKyk6KT9cXC9cXC8oPzooXFx3KzpcXHcrKUApPyhbXFx3Ll0qKSg/OjooXFxkKykpPyhcXFMqKSQvO1xuICB2YXIgZGF0YVVybFJlZ2V4cCA9IC9eZGF0YTouK1xcLC4rJC87XG5cbiAgZnVuY3Rpb24gdXJsUGFyc2UoYVVybCkge1xuICAgIHZhciBtYXRjaCA9IGFVcmwubWF0Y2godXJsUmVnZXhwKTtcbiAgICBpZiAoIW1hdGNoKSB7XG4gICAgICByZXR1cm4gbnVsbDtcbiAgICB9XG4gICAgcmV0dXJuIHtcbiAgICAgIHNjaGVtZTogbWF0Y2hbMV0sXG4gICAgICBhdXRoOiBtYXRjaFsyXSxcbiAgICAgIGhvc3Q6IG1hdGNoWzNdLFxuICAgICAgcG9ydDogbWF0Y2hbNF0sXG4gICAgICBwYXRoOiBtYXRjaFs1XVxuICAgIH07XG4gIH1cbiAgZXhwb3J0cy51cmxQYXJzZSA9IHVybFBhcnNlO1xuXG4gIGZ1bmN0aW9uIHVybEdlbmVyYXRlKGFQYXJzZWRVcmwpIHtcbiAgICB2YXIgdXJsID0gJyc7XG4gICAgaWYgKGFQYXJzZWRVcmwuc2NoZW1lKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5zY2hlbWUgKyAnOic7XG4gICAgfVxuICAgIHVybCArPSAnLy8nO1xuICAgIGlmIChhUGFyc2VkVXJsLmF1dGgpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLmF1dGggKyAnQCc7XG4gICAgfVxuICAgIGlmIChhUGFyc2VkVXJsLmhvc3QpIHtcbiAgICAgIHVybCArPSBhUGFyc2VkVXJsLmhvc3Q7XG4gICAgfVxuICAgIGlmIChhUGFyc2VkVXJsLnBvcnQpIHtcbiAgICAgIHVybCArPSBcIjpcIiArIGFQYXJzZWRVcmwucG9ydFxuICAgIH1cbiAgICBpZiAoYVBhcnNlZFVybC5wYXRoKSB7XG4gICAgICB1cmwgKz0gYVBhcnNlZFVybC5wYXRoO1xuICAgIH1cbiAgICByZXR1cm4gdXJsO1xuICB9XG4gIGV4cG9ydHMudXJsR2VuZXJhdGUgPSB1cmxHZW5lcmF0ZTtcblxuICAvKipcbiAgICogTm9ybWFsaXplcyBhIHBhdGgsIG9yIHRoZSBwYXRoIHBvcnRpb24gb2YgYSBVUkw6XG4gICAqXG4gICAqIC0gUmVwbGFjZXMgY29uc2VxdXRpdmUgc2xhc2hlcyB3aXRoIG9uZSBzbGFzaC5cbiAgICogLSBSZW1vdmVzIHVubmVjZXNzYXJ5ICcuJyBwYXJ0cy5cbiAgICogLSBSZW1vdmVzIHVubmVjZXNzYXJ5ICc8ZGlyPi8uLicgcGFydHMuXG4gICAqXG4gICAqIEJhc2VkIG9uIGNvZGUgaW4gdGhlIE5vZGUuanMgJ3BhdGgnIGNvcmUgbW9kdWxlLlxuICAgKlxuICAgKiBAcGFyYW0gYVBhdGggVGhlIHBhdGggb3IgdXJsIHRvIG5vcm1hbGl6ZS5cbiAgICovXG4gIGZ1bmN0aW9uIG5vcm1hbGl6ZShhUGF0aCkge1xuICAgIHZhciBwYXRoID0gYVBhdGg7XG4gICAgdmFyIHVybCA9IHVybFBhcnNlKGFQYXRoKTtcbiAgICBpZiAodXJsKSB7XG4gICAgICBpZiAoIXVybC5wYXRoKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cbiAgICAgIHBhdGggPSB1cmwucGF0aDtcbiAgICB9XG4gICAgdmFyIGlzQWJzb2x1dGUgPSBleHBvcnRzLmlzQWJzb2x1dGUocGF0aCk7XG5cbiAgICB2YXIgcGFydHMgPSBwYXRoLnNwbGl0KC9cXC8rLyk7XG4gICAgZm9yICh2YXIgcGFydCwgdXAgPSAwLCBpID0gcGFydHMubGVuZ3RoIC0gMTsgaSA+PSAwOyBpLS0pIHtcbiAgICAgIHBhcnQgPSBwYXJ0c1tpXTtcbiAgICAgIGlmIChwYXJ0ID09PSAnLicpIHtcbiAgICAgICAgcGFydHMuc3BsaWNlKGksIDEpO1xuICAgICAgfSBlbHNlIGlmIChwYXJ0ID09PSAnLi4nKSB7XG4gICAgICAgIHVwKys7XG4gICAgICB9IGVsc2UgaWYgKHVwID4gMCkge1xuICAgICAgICBpZiAocGFydCA9PT0gJycpIHtcbiAgICAgICAgICAvLyBUaGUgZmlyc3QgcGFydCBpcyBibGFuayBpZiB0aGUgcGF0aCBpcyBhYnNvbHV0ZS4gVHJ5aW5nIHRvIGdvXG4gICAgICAgICAgLy8gYWJvdmUgdGhlIHJvb3QgaXMgYSBuby1vcC4gVGhlcmVmb3JlIHdlIGNhbiByZW1vdmUgYWxsICcuLicgcGFydHNcbiAgICAgICAgICAvLyBkaXJlY3RseSBhZnRlciB0aGUgcm9vdC5cbiAgICAgICAgICBwYXJ0cy5zcGxpY2UoaSArIDEsIHVwKTtcbiAgICAgICAgICB1cCA9IDA7XG4gICAgICAgIH0gZWxzZSB7XG4gICAgICAgICAgcGFydHMuc3BsaWNlKGksIDIpO1xuICAgICAgICAgIHVwLS07XG4gICAgICAgIH1cbiAgICAgIH1cbiAgICB9XG4gICAgcGF0aCA9IHBhcnRzLmpvaW4oJy8nKTtcblxuICAgIGlmIChwYXRoID09PSAnJykge1xuICAgICAgcGF0aCA9IGlzQWJzb2x1dGUgPyAnLycgOiAnLic7XG4gICAgfVxuXG4gICAgaWYgKHVybCkge1xuICAgICAgdXJsLnBhdGggPSBwYXRoO1xuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKHVybCk7XG4gICAgfVxuICAgIHJldHVybiBwYXRoO1xuICB9XG4gIGV4cG9ydHMubm9ybWFsaXplID0gbm9ybWFsaXplO1xuXG4gIC8qKlxuICAgKiBKb2lucyB0d28gcGF0aHMvVVJMcy5cbiAgICpcbiAgICogQHBhcmFtIGFSb290IFRoZSByb290IHBhdGggb3IgVVJMLlxuICAgKiBAcGFyYW0gYVBhdGggVGhlIHBhdGggb3IgVVJMIHRvIGJlIGpvaW5lZCB3aXRoIHRoZSByb290LlxuICAgKlxuICAgKiAtIElmIGFQYXRoIGlzIGEgVVJMIG9yIGEgZGF0YSBVUkksIGFQYXRoIGlzIHJldHVybmVkLCB1bmxlc3MgYVBhdGggaXMgYVxuICAgKiAgIHNjaGVtZS1yZWxhdGl2ZSBVUkw6IFRoZW4gdGhlIHNjaGVtZSBvZiBhUm9vdCwgaWYgYW55LCBpcyBwcmVwZW5kZWRcbiAgICogICBmaXJzdC5cbiAgICogLSBPdGhlcndpc2UgYVBhdGggaXMgYSBwYXRoLiBJZiBhUm9vdCBpcyBhIFVSTCwgdGhlbiBpdHMgcGF0aCBwb3J0aW9uXG4gICAqICAgaXMgdXBkYXRlZCB3aXRoIHRoZSByZXN1bHQgYW5kIGFSb290IGlzIHJldHVybmVkLiBPdGhlcndpc2UgdGhlIHJlc3VsdFxuICAgKiAgIGlzIHJldHVybmVkLlxuICAgKiAgIC0gSWYgYVBhdGggaXMgYWJzb2x1dGUsIHRoZSByZXN1bHQgaXMgYVBhdGguXG4gICAqICAgLSBPdGhlcndpc2UgdGhlIHR3byBwYXRocyBhcmUgam9pbmVkIHdpdGggYSBzbGFzaC5cbiAgICogLSBKb2luaW5nIGZvciBleGFtcGxlICdodHRwOi8vJyBhbmQgJ3d3dy5leGFtcGxlLmNvbScgaXMgYWxzbyBzdXBwb3J0ZWQuXG4gICAqL1xuICBmdW5jdGlvbiBqb2luKGFSb290LCBhUGF0aCkge1xuICAgIGlmIChhUm9vdCA9PT0gXCJcIikge1xuICAgICAgYVJvb3QgPSBcIi5cIjtcbiAgICB9XG4gICAgaWYgKGFQYXRoID09PSBcIlwiKSB7XG4gICAgICBhUGF0aCA9IFwiLlwiO1xuICAgIH1cbiAgICB2YXIgYVBhdGhVcmwgPSB1cmxQYXJzZShhUGF0aCk7XG4gICAgdmFyIGFSb290VXJsID0gdXJsUGFyc2UoYVJvb3QpO1xuICAgIGlmIChhUm9vdFVybCkge1xuICAgICAgYVJvb3QgPSBhUm9vdFVybC5wYXRoIHx8ICcvJztcbiAgICB9XG5cbiAgICAvLyBgam9pbihmb28sICcvL3d3dy5leGFtcGxlLm9yZycpYFxuICAgIGlmIChhUGF0aFVybCAmJiAhYVBhdGhVcmwuc2NoZW1lKSB7XG4gICAgICBpZiAoYVJvb3RVcmwpIHtcbiAgICAgICAgYVBhdGhVcmwuc2NoZW1lID0gYVJvb3RVcmwuc2NoZW1lO1xuICAgICAgfVxuICAgICAgcmV0dXJuIHVybEdlbmVyYXRlKGFQYXRoVXJsKTtcbiAgICB9XG5cbiAgICBpZiAoYVBhdGhVcmwgfHwgYVBhdGgubWF0Y2goZGF0YVVybFJlZ2V4cCkpIHtcbiAgICAgIHJldHVybiBhUGF0aDtcbiAgICB9XG5cbiAgICAvLyBgam9pbignaHR0cDovLycsICd3d3cuZXhhbXBsZS5jb20nKWBcbiAgICBpZiAoYVJvb3RVcmwgJiYgIWFSb290VXJsLmhvc3QgJiYgIWFSb290VXJsLnBhdGgpIHtcbiAgICAgIGFSb290VXJsLmhvc3QgPSBhUGF0aDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUm9vdFVybCk7XG4gICAgfVxuXG4gICAgdmFyIGpvaW5lZCA9IGFQYXRoLmNoYXJBdCgwKSA9PT0gJy8nXG4gICAgICA/IGFQYXRoXG4gICAgICA6IG5vcm1hbGl6ZShhUm9vdC5yZXBsYWNlKC9cXC8rJC8sICcnKSArICcvJyArIGFQYXRoKTtcblxuICAgIGlmIChhUm9vdFVybCkge1xuICAgICAgYVJvb3RVcmwucGF0aCA9IGpvaW5lZDtcbiAgICAgIHJldHVybiB1cmxHZW5lcmF0ZShhUm9vdFVybCk7XG4gICAgfVxuICAgIHJldHVybiBqb2luZWQ7XG4gIH1cbiAgZXhwb3J0cy5qb2luID0gam9pbjtcblxuICBleHBvcnRzLmlzQWJzb2x1dGUgPSBmdW5jdGlvbiAoYVBhdGgpIHtcbiAgICByZXR1cm4gYVBhdGguY2hhckF0KDApID09PSAnLycgfHwgISFhUGF0aC5tYXRjaCh1cmxSZWdleHApO1xuICB9O1xuXG4gIC8qKlxuICAgKiBNYWtlIGEgcGF0aCByZWxhdGl2ZSB0byBhIFVSTCBvciBhbm90aGVyIHBhdGguXG4gICAqXG4gICAqIEBwYXJhbSBhUm9vdCBUaGUgcm9vdCBwYXRoIG9yIFVSTC5cbiAgICogQHBhcmFtIGFQYXRoIFRoZSBwYXRoIG9yIFVSTCB0byBiZSBtYWRlIHJlbGF0aXZlIHRvIGFSb290LlxuICAgKi9cbiAgZnVuY3Rpb24gcmVsYXRpdmUoYVJvb3QsIGFQYXRoKSB7XG4gICAgaWYgKGFSb290ID09PSBcIlwiKSB7XG4gICAgICBhUm9vdCA9IFwiLlwiO1xuICAgIH1cblxuICAgIGFSb290ID0gYVJvb3QucmVwbGFjZSgvXFwvJC8sICcnKTtcblxuICAgIC8vIEl0IGlzIHBvc3NpYmxlIGZvciB0aGUgcGF0aCB0byBiZSBhYm92ZSB0aGUgcm9vdC4gSW4gdGhpcyBjYXNlLCBzaW1wbHlcbiAgICAvLyBjaGVja2luZyB3aGV0aGVyIHRoZSByb290IGlzIGEgcHJlZml4IG9mIHRoZSBwYXRoIHdvbid0IHdvcmsuIEluc3RlYWQsIHdlXG4gICAgLy8gbmVlZCB0byByZW1vdmUgY29tcG9uZW50cyBmcm9tIHRoZSByb290IG9uZSBieSBvbmUsIHVudGlsIGVpdGhlciB3ZSBmaW5kXG4gICAgLy8gYSBwcmVmaXggdGhhdCBmaXRzLCBvciB3ZSBydW4gb3V0IG9mIGNvbXBvbmVudHMgdG8gcmVtb3ZlLlxuICAgIHZhciBsZXZlbCA9IDA7XG4gICAgd2hpbGUgKGFQYXRoLmluZGV4T2YoYVJvb3QgKyAnLycpICE9PSAwKSB7XG4gICAgICB2YXIgaW5kZXggPSBhUm9vdC5sYXN0SW5kZXhPZihcIi9cIik7XG4gICAgICBpZiAoaW5kZXggPCAwKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cblxuICAgICAgLy8gSWYgdGhlIG9ubHkgcGFydCBvZiB0aGUgcm9vdCB0aGF0IGlzIGxlZnQgaXMgdGhlIHNjaGVtZSAoaS5lLiBodHRwOi8vLFxuICAgICAgLy8gZmlsZTovLy8sIGV0Yy4pLCBvbmUgb3IgbW9yZSBzbGFzaGVzICgvKSwgb3Igc2ltcGx5IG5vdGhpbmcgYXQgYWxsLCB3ZVxuICAgICAgLy8gaGF2ZSBleGhhdXN0ZWQgYWxsIGNvbXBvbmVudHMsIHNvIHRoZSBwYXRoIGlzIG5vdCByZWxhdGl2ZSB0byB0aGUgcm9vdC5cbiAgICAgIGFSb290ID0gYVJvb3Quc2xpY2UoMCwgaW5kZXgpO1xuICAgICAgaWYgKGFSb290Lm1hdGNoKC9eKFteXFwvXSs6XFwvKT9cXC8qJC8pKSB7XG4gICAgICAgIHJldHVybiBhUGF0aDtcbiAgICAgIH1cblxuICAgICAgKytsZXZlbDtcbiAgICB9XG5cbiAgICAvLyBNYWtlIHN1cmUgd2UgYWRkIGEgXCIuLi9cIiBmb3IgZWFjaCBjb21wb25lbnQgd2UgcmVtb3ZlZCBmcm9tIHRoZSByb290LlxuICAgIHJldHVybiBBcnJheShsZXZlbCArIDEpLmpvaW4oXCIuLi9cIikgKyBhUGF0aC5zdWJzdHIoYVJvb3QubGVuZ3RoICsgMSk7XG4gIH1cbiAgZXhwb3J0cy5yZWxhdGl2ZSA9IHJlbGF0aXZlO1xuXG4gIC8qKlxuICAgKiBCZWNhdXNlIGJlaGF2aW9yIGdvZXMgd2Fja3kgd2hlbiB5b3Ugc2V0IGBfX3Byb3RvX19gIG9uIG9iamVjdHMsIHdlXG4gICAqIGhhdmUgdG8gcHJlZml4IGFsbCB0aGUgc3RyaW5ncyBpbiBvdXIgc2V0IHdpdGggYW4gYXJiaXRyYXJ5IGNoYXJhY3Rlci5cbiAgICpcbiAgICogU2VlIGh0dHBzOi8vZ2l0aHViLmNvbS9tb3ppbGxhL3NvdXJjZS1tYXAvcHVsbC8zMSBhbmRcbiAgICogaHR0cHM6Ly9naXRodWIuY29tL21vemlsbGEvc291cmNlLW1hcC9pc3N1ZXMvMzBcbiAgICpcbiAgICogQHBhcmFtIFN0cmluZyBhU3RyXG4gICAqL1xuICBmdW5jdGlvbiB0b1NldFN0cmluZyhhU3RyKSB7XG4gICAgcmV0dXJuICckJyArIGFTdHI7XG4gIH1cbiAgZXhwb3J0cy50b1NldFN0cmluZyA9IHRvU2V0U3RyaW5nO1xuXG4gIGZ1bmN0aW9uIGZyb21TZXRTdHJpbmcoYVN0cikge1xuICAgIHJldHVybiBhU3RyLnN1YnN0cigxKTtcbiAgfVxuICBleHBvcnRzLmZyb21TZXRTdHJpbmcgPSBmcm9tU2V0U3RyaW5nO1xuXG4gIC8qKlxuICAgKiBDb21wYXJhdG9yIGJldHdlZW4gdHdvIG1hcHBpbmdzIHdoZXJlIHRoZSBvcmlnaW5hbCBwb3NpdGlvbnMgYXJlIGNvbXBhcmVkLlxuICAgKlxuICAgKiBPcHRpb25hbGx5IHBhc3MgaW4gYHRydWVgIGFzIGBvbmx5Q29tcGFyZUdlbmVyYXRlZGAgdG8gY29uc2lkZXIgdHdvXG4gICAqIG1hcHBpbmdzIHdpdGggdGhlIHNhbWUgb3JpZ2luYWwgc291cmNlL2xpbmUvY29sdW1uLCBidXQgZGlmZmVyZW50IGdlbmVyYXRlZFxuICAgKiBsaW5lIGFuZCBjb2x1bW4gdGhlIHNhbWUuIFVzZWZ1bCB3aGVuIHNlYXJjaGluZyBmb3IgYSBtYXBwaW5nIHdpdGggYVxuICAgKiBzdHViYmVkIG91dCBtYXBwaW5nLlxuICAgKi9cbiAgZnVuY3Rpb24gY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMobWFwcGluZ0EsIG1hcHBpbmdCLCBvbmx5Q29tcGFyZU9yaWdpbmFsKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLnNvdXJjZSAtIG1hcHBpbmdCLnNvdXJjZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsTGluZSAtIG1hcHBpbmdCLm9yaWdpbmFsTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsQ29sdW1uIC0gbWFwcGluZ0Iub3JpZ2luYWxDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCB8fCBvbmx5Q29tcGFyZU9yaWdpbmFsKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZENvbHVtbiAtIG1hcHBpbmdCLmdlbmVyYXRlZENvbHVtbjtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIG1hcHBpbmdBLm5hbWUgLSBtYXBwaW5nQi5uYW1lO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5T3JpZ2luYWxQb3NpdGlvbnMgPSBjb21wYXJlQnlPcmlnaW5hbFBvc2l0aW9ucztcblxuICAvKipcbiAgICogQ29tcGFyYXRvciBiZXR3ZWVuIHR3byBtYXBwaW5ncyB3aXRoIGRlZmxhdGVkIHNvdXJjZSBhbmQgbmFtZSBpbmRpY2VzIHdoZXJlXG4gICAqIHRoZSBnZW5lcmF0ZWQgcG9zaXRpb25zIGFyZSBjb21wYXJlZC5cbiAgICpcbiAgICogT3B0aW9uYWxseSBwYXNzIGluIGB0cnVlYCBhcyBgb25seUNvbXBhcmVHZW5lcmF0ZWRgIHRvIGNvbnNpZGVyIHR3b1xuICAgKiBtYXBwaW5ncyB3aXRoIHRoZSBzYW1lIGdlbmVyYXRlZCBsaW5lIGFuZCBjb2x1bW4sIGJ1dCBkaWZmZXJlbnRcbiAgICogc291cmNlL25hbWUvb3JpZ2luYWwgbGluZSBhbmQgY29sdW1uIHRoZSBzYW1lLiBVc2VmdWwgd2hlbiBzZWFyY2hpbmcgZm9yIGFcbiAgICogbWFwcGluZyB3aXRoIGEgc3R1YmJlZCBvdXQgbWFwcGluZy5cbiAgICovXG4gIGZ1bmN0aW9uIGNvbXBhcmVCeUdlbmVyYXRlZFBvc2l0aW9uc0RlZmxhdGVkKG1hcHBpbmdBLCBtYXBwaW5nQiwgb25seUNvbXBhcmVHZW5lcmF0ZWQpIHtcbiAgICB2YXIgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkTGluZSAtIG1hcHBpbmdCLmdlbmVyYXRlZExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5nZW5lcmF0ZWRDb2x1bW4gLSBtYXBwaW5nQi5nZW5lcmF0ZWRDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCB8fCBvbmx5Q29tcGFyZUdlbmVyYXRlZCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5zb3VyY2UgLSBtYXBwaW5nQi5zb3VyY2U7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbExpbmUgLSBtYXBwaW5nQi5vcmlnaW5hbExpbmU7XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICBjbXAgPSBtYXBwaW5nQS5vcmlnaW5hbENvbHVtbiAtIG1hcHBpbmdCLm9yaWdpbmFsQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgcmV0dXJuIG1hcHBpbmdBLm5hbWUgLSBtYXBwaW5nQi5uYW1lO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zRGVmbGF0ZWQgPSBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNEZWZsYXRlZDtcblxuICBmdW5jdGlvbiBzdHJjbXAoYVN0cjEsIGFTdHIyKSB7XG4gICAgaWYgKGFTdHIxID09PSBhU3RyMikge1xuICAgICAgcmV0dXJuIDA7XG4gICAgfVxuXG4gICAgaWYgKGFTdHIxID4gYVN0cjIpIHtcbiAgICAgIHJldHVybiAxO1xuICAgIH1cblxuICAgIHJldHVybiAtMTtcbiAgfVxuXG4gIC8qKlxuICAgKiBDb21wYXJhdG9yIGJldHdlZW4gdHdvIG1hcHBpbmdzIHdpdGggaW5mbGF0ZWQgc291cmNlIGFuZCBuYW1lIHN0cmluZ3Mgd2hlcmVcbiAgICogdGhlIGdlbmVyYXRlZCBwb3NpdGlvbnMgYXJlIGNvbXBhcmVkLlxuICAgKi9cbiAgZnVuY3Rpb24gY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQobWFwcGluZ0EsIG1hcHBpbmdCKSB7XG4gICAgdmFyIGNtcCA9IG1hcHBpbmdBLmdlbmVyYXRlZExpbmUgLSBtYXBwaW5nQi5nZW5lcmF0ZWRMaW5lO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gbWFwcGluZ0EuZ2VuZXJhdGVkQ29sdW1uIC0gbWFwcGluZ0IuZ2VuZXJhdGVkQ29sdW1uO1xuICAgIGlmIChjbXAgIT09IDApIHtcbiAgICAgIHJldHVybiBjbXA7XG4gICAgfVxuXG4gICAgY21wID0gc3RyY21wKG1hcHBpbmdBLnNvdXJjZSwgbWFwcGluZ0Iuc291cmNlKTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsTGluZSAtIG1hcHBpbmdCLm9yaWdpbmFsTGluZTtcbiAgICBpZiAoY21wICE9PSAwKSB7XG4gICAgICByZXR1cm4gY21wO1xuICAgIH1cblxuICAgIGNtcCA9IG1hcHBpbmdBLm9yaWdpbmFsQ29sdW1uIC0gbWFwcGluZ0Iub3JpZ2luYWxDb2x1bW47XG4gICAgaWYgKGNtcCAhPT0gMCkge1xuICAgICAgcmV0dXJuIGNtcDtcbiAgICB9XG5cbiAgICByZXR1cm4gc3RyY21wKG1hcHBpbmdBLm5hbWUsIG1hcHBpbmdCLm5hbWUpO1xuICB9XG4gIGV4cG9ydHMuY29tcGFyZUJ5R2VuZXJhdGVkUG9zaXRpb25zSW5mbGF0ZWQgPSBjb21wYXJlQnlHZW5lcmF0ZWRQb3NpdGlvbnNJbmZsYXRlZDtcbn1cblxuXG5cbi8qKioqKioqKioqKioqKioqKlxuICoqIFdFQlBBQ0sgRk9PVEVSXG4gKiogLi9saWIvdXRpbC5qc1xuICoqIG1vZHVsZSBpZCA9IDFcbiAqKiBtb2R1bGUgY2h1bmtzID0gMFxuICoqLyJdLCJzb3VyY2VSb290IjoiIn0= \ No newline at end of file
diff --git a/devtools/shared/sourcemap/tests/unit/xpcshell.ini b/devtools/shared/sourcemap/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..f83e4a1c9
--- /dev/null
+++ b/devtools/shared/sourcemap/tests/unit/xpcshell.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+tags = devtools
+head = head_sourcemap.js
+tail =
+
+[test_util.js]
+[test_source_node.js]
+[test_source_map_generator.js]
+[test_source_map_consumer.js]
+[test_quick_sort.js]
+[test_dog_fooding.js]
+[test_binary_search.js]
+[test_base64_vlq.js]
+[test_base64.js]
+[test_array_set.js]
+[test_api.js]
diff --git a/devtools/shared/specs/actor-registry.js b/devtools/shared/specs/actor-registry.js
new file mode 100644
index 000000000..0f57dc8d2
--- /dev/null
+++ b/devtools/shared/specs/actor-registry.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ generateActorSpec,
+} = require("devtools/shared/protocol");
+
+const actorActorSpec = generateActorSpec({
+ typeName: "actorActor",
+
+ methods: {
+ unregister: {
+ request: {},
+ response: {}
+ }
+ },
+});
+
+exports.actorActorSpec = actorActorSpec;
+
+const actorRegistrySpec = generateActorSpec({
+ typeName: "actorRegistry",
+
+ methods: {
+ registerActor: {
+ request: {
+ sourceText: Arg(0, "string"),
+ filename: Arg(1, "string"),
+ options: Arg(2, "json")
+ },
+
+ response: {
+ actorActor: RetVal("actorActor")
+ }
+ }
+ }
+});
+
+exports.actorRegistrySpec = actorRegistrySpec;
diff --git a/devtools/shared/specs/addons.js b/devtools/shared/specs/addons.js
new file mode 100644
index 000000000..6246b9d1f
--- /dev/null
+++ b/devtools/shared/specs/addons.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const addonsSpec = generateActorSpec({
+ typeName: "addons",
+
+ methods: {
+ installTemporaryAddon: {
+ request: { addonPath: Arg(0, "string") },
+ response: { addon: RetVal("json") },
+ },
+ },
+});
+
+exports.addonsSpec = addonsSpec;
diff --git a/devtools/shared/specs/animation.js b/devtools/shared/specs/animation.js
new file mode 100644
index 000000000..20af12c23
--- /dev/null
+++ b/devtools/shared/specs/animation.js
@@ -0,0 +1,151 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+require("devtools/shared/specs/inspector");
+
+/**
+ * Sent with the 'mutations' event as part of an array of changes, used to
+ * inform fronts of the type of change that occured.
+ */
+types.addDictType("animationMutationChange", {
+ // The type of change ("added" or "removed").
+ type: "string",
+ // The changed AnimationPlayerActor.
+ player: "animationplayer"
+});
+
+const animationPlayerSpec = generateActorSpec({
+ typeName: "animationplayer",
+
+ events: {
+ "changed": {
+ type: "changed",
+ state: Arg(0, "json")
+ }
+ },
+
+ methods: {
+ release: { release: true },
+ getCurrentState: {
+ request: {},
+ response: {
+ data: RetVal("json")
+ }
+ },
+ pause: {
+ request: {},
+ response: {}
+ },
+ play: {
+ request: {},
+ response: {}
+ },
+ ready: {
+ request: {},
+ response: {}
+ },
+ setCurrentTime: {
+ request: {
+ currentTime: Arg(0, "number")
+ },
+ response: {}
+ },
+ setPlaybackRate: {
+ request: {
+ currentTime: Arg(0, "number")
+ },
+ response: {}
+ },
+ getFrames: {
+ request: {},
+ response: {
+ frames: RetVal("json")
+ }
+ },
+ getProperties: {
+ request: {},
+ response: {
+ properties: RetVal("array:json")
+ }
+ }
+ }
+});
+
+exports.animationPlayerSpec = animationPlayerSpec;
+
+const animationsSpec = generateActorSpec({
+ typeName: "animations",
+
+ events: {
+ "mutations": {
+ type: "mutations",
+ changes: Arg(0, "array:animationMutationChange")
+ }
+ },
+
+ methods: {
+ setWalkerActor: {
+ request: {
+ walker: Arg(0, "domwalker")
+ },
+ response: {}
+ },
+ getAnimationPlayersForNode: {
+ request: {
+ actorID: Arg(0, "domnode")
+ },
+ response: {
+ players: RetVal("array:animationplayer")
+ }
+ },
+ stopAnimationPlayerUpdates: {
+ request: {},
+ response: {}
+ },
+ pauseAll: {
+ request: {},
+ response: {}
+ },
+ playAll: {
+ request: {},
+ response: {}
+ },
+ toggleAll: {
+ request: {},
+ response: {}
+ },
+ toggleSeveral: {
+ request: {
+ players: Arg(0, "array:animationplayer"),
+ shouldPause: Arg(1, "boolean")
+ },
+ response: {}
+ },
+ setCurrentTimes: {
+ request: {
+ players: Arg(0, "array:animationplayer"),
+ time: Arg(1, "number"),
+ shouldPause: Arg(2, "boolean")
+ },
+ response: {}
+ },
+ setPlaybackRates: {
+ request: {
+ players: Arg(0, "array:animationplayer"),
+ rate: Arg(1, "number")
+ },
+ response: {}
+ }
+ }
+});
+
+exports.animationsSpec = animationsSpec;
+
diff --git a/devtools/shared/specs/breakpoint.js b/devtools/shared/specs/breakpoint.js
new file mode 100644
index 000000000..e30c03ce8
--- /dev/null
+++ b/devtools/shared/specs/breakpoint.js
@@ -0,0 +1,16 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {generateActorSpec} = require("devtools/shared/protocol");
+
+const breakpointSpec = generateActorSpec({
+ typeName: "breakpoint",
+
+ methods: {
+ delete: {}
+ },
+});
+
+exports.breakpointSpec = breakpointSpec;
diff --git a/devtools/shared/specs/call-watcher.js b/devtools/shared/specs/call-watcher.js
new file mode 100644
index 000000000..34d63111a
--- /dev/null
+++ b/devtools/shared/specs/call-watcher.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const { Arg, RetVal, Option, generateActorSpec } = protocol;
+
+/**
+ * Type describing a single function call in a stack trace.
+ */
+protocol.types.addDictType("call-stack-item", {
+ name: "string",
+ file: "string",
+ line: "number"
+});
+
+/**
+ * Type describing an overview of a function call.
+ */
+protocol.types.addDictType("call-details", {
+ type: "number",
+ name: "string",
+ stack: "array:call-stack-item"
+});
+
+const functionCallSpec = generateActorSpec({
+ typeName: "function-call",
+
+ methods: {
+ getDetails: {
+ response: { info: RetVal("call-details") }
+ },
+ },
+});
+
+exports.functionCallSpec = functionCallSpec;
+
+const callWatcherSpec = generateActorSpec({
+ typeName: "call-watcher",
+
+ events: {
+ /**
+ * Events emitted when the `onCall` function isn't provided.
+ */
+ "call": {
+ type: "call",
+ function: Arg(0, "function-call")
+ }
+ },
+
+ methods: {
+ setup: {
+ request: {
+ tracedGlobals: Option(0, "nullable:array:string"),
+ tracedFunctions: Option(0, "nullable:array:string"),
+ startRecording: Option(0, "boolean"),
+ performReload: Option(0, "boolean"),
+ holdWeak: Option(0, "boolean"),
+ storeCalls: Option(0, "boolean")
+ },
+ oneway: true
+ },
+ finalize: {
+ oneway: true
+ },
+ isRecording: {
+ response: RetVal("boolean")
+ },
+ initTimestampEpoch: {},
+ resumeRecording: {},
+ pauseRecording: {
+ response: { calls: RetVal("array:function-call") }
+ },
+ eraseRecording: {},
+ }
+});
+
+exports.callWatcherSpec = callWatcherSpec;
diff --git a/devtools/shared/specs/canvas.js b/devtools/shared/specs/canvas.js
new file mode 100644
index 000000000..dcb26f9a9
--- /dev/null
+++ b/devtools/shared/specs/canvas.js
@@ -0,0 +1,131 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const {Arg, Option, RetVal, generateActorSpec} = protocol;
+
+/**
+ * Type representing an ArrayBufferView, serialized fast(er).
+ *
+ * Don't create a new array buffer view from the parsed array on the frontend.
+ * Consumers may copy the data into an existing buffer, or create a new one if
+ * necesasry. For example, this avoids the need for a redundant copy when
+ * populating ImageData objects, at the expense of transferring char views
+ * of a pixel buffer over the protocol instead of a packed int view.
+ *
+ * XXX: It would be nice if on local connections (only), we could just *give*
+ * the buffer directly to the front, instead of going through all this
+ * serialization redundancy.
+ */
+protocol.types.addType("array-buffer-view", {
+ write: (v) => "[" + Array.join(v, ",") + "]",
+ read: (v) => JSON.parse(v)
+});
+
+/**
+ * Type describing a thumbnail or screenshot in a recorded animation frame.
+ */
+protocol.types.addDictType("snapshot-image", {
+ index: "number",
+ width: "number",
+ height: "number",
+ scaling: "number",
+ flipped: "boolean",
+ pixels: "array-buffer-view"
+});
+
+/**
+ * Type describing an overview of a recorded animation frame.
+ */
+protocol.types.addDictType("snapshot-overview", {
+ calls: "array:function-call",
+ thumbnails: "array:snapshot-image",
+ screenshot: "snapshot-image"
+});
+
+exports.CANVAS_CONTEXTS = [
+ "CanvasRenderingContext2D",
+ "WebGLRenderingContext"
+];
+
+exports.ANIMATION_GENERATORS = [
+ "requestAnimationFrame"
+];
+
+exports.LOOP_GENERATORS = [
+ "setTimeout"
+];
+
+exports.DRAW_CALLS = [
+ // 2D canvas
+ "fill",
+ "stroke",
+ "clearRect",
+ "fillRect",
+ "strokeRect",
+ "fillText",
+ "strokeText",
+ "drawImage",
+
+ // WebGL
+ "clear",
+ "drawArrays",
+ "drawElements",
+ "finish",
+ "flush"
+];
+
+exports.INTERESTING_CALLS = [
+ // 2D canvas
+ "save",
+ "restore",
+
+ // WebGL
+ "useProgram"
+];
+
+const frameSnapshotSpec = generateActorSpec({
+ typeName: "frame-snapshot",
+
+ methods: {
+ getOverview: {
+ response: { overview: RetVal("snapshot-overview") }
+ },
+ generateScreenshotFor: {
+ request: { call: Arg(0, "function-call") },
+ response: { screenshot: RetVal("snapshot-image") }
+ },
+ },
+});
+
+exports.frameSnapshotSpec = frameSnapshotSpec;
+
+const canvasSpec = generateActorSpec({
+ typeName: "canvas",
+
+ methods: {
+ setup: {
+ request: { reload: Option(0, "boolean") },
+ oneway: true
+ },
+ finalize: {
+ oneway: true
+ },
+ isInitialized: {
+ response: { initialized: RetVal("boolean") }
+ },
+ isRecording: {
+ response: { recording: RetVal("boolean") }
+ },
+ recordAnimationFrame: {
+ response: { snapshot: RetVal("nullable:frame-snapshot") }
+ },
+ stopRecordingAnimationFrame: {
+ oneway: true
+ },
+ }
+});
+
+exports.canvasSpec = canvasSpec;
diff --git a/devtools/shared/specs/css-properties.js b/devtools/shared/specs/css-properties.js
new file mode 100644
index 000000000..76c85bd74
--- /dev/null
+++ b/devtools/shared/specs/css-properties.js
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const cssPropertiesSpec = generateActorSpec({
+ typeName: "cssProperties",
+
+ methods: {
+ getCSSDatabase: {
+ request: {},
+
+ response: RetVal("json"),
+ }
+ }
+});
+
+exports.cssPropertiesSpec = cssPropertiesSpec;
diff --git a/devtools/shared/specs/csscoverage.js b/devtools/shared/specs/csscoverage.js
new file mode 100644
index 000000000..4c58b09a9
--- /dev/null
+++ b/devtools/shared/specs/csscoverage.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+require("devtools/shared/specs/stylesheets");
+
+const cssUsageSpec = generateActorSpec({
+ typeName: "cssUsage",
+
+ events: {
+ "state-change": {
+ type: "stateChange",
+ stateChange: Arg(0, "json")
+ }
+ },
+
+ methods: {
+ start: {
+ request: { url: Arg(0, "boolean") }
+ },
+ stop: {},
+ toggle: {},
+ oneshot: {},
+ createEditorReport: {
+ request: { url: Arg(0, "string") },
+ response: { reports: RetVal("array:json") }
+ },
+ createEditorReportForSheet: {
+ request: { url: Arg(0, "stylesheet") },
+ response: { reports: RetVal("array:json") }
+ },
+ createPageReport: {
+ response: RetVal("json")
+ },
+ _testOnlyVisitedPages: {
+ response: { value: RetVal("array:string") }
+ },
+ },
+});
+
+exports.cssUsageSpec = cssUsageSpec;
diff --git a/devtools/shared/specs/device.js b/devtools/shared/specs/device.js
new file mode 100644
index 000000000..3237d1593
--- /dev/null
+++ b/devtools/shared/specs/device.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const deviceSpec = generateActorSpec({
+ typeName: "device",
+
+ methods: {
+ getDescription: {request: {}, response: { value: RetVal("json")}},
+ getWallpaper: {request: {}, response: { value: RetVal("longstring")}},
+ screenshotToDataURL: {request: {}, response: { value: RetVal("longstring")}},
+ getRawPermissionsTable: {request: {}, response: { value: RetVal("json")}},
+ },
+});
+
+exports.deviceSpec = deviceSpec;
diff --git a/devtools/shared/specs/director-manager.js b/devtools/shared/specs/director-manager.js
new file mode 100644
index 000000000..8a9bf77ee
--- /dev/null
+++ b/devtools/shared/specs/director-manager.js
@@ -0,0 +1,190 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ types,
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec,
+} = require("devtools/shared/protocol");
+
+/**
+ * Type describing a messageport event
+ */
+types.addDictType("messageportevent", {
+ isTrusted: "boolean",
+ data: "nullable:primitive",
+ origin: "nullable:string",
+ lastEventId: "nullable:string",
+ source: "messageport",
+ ports: "nullable:array:messageport"
+});
+
+const messagePortSpec = generateActorSpec({
+ typeName: "messageport",
+
+ /**
+ * Events emitted by this actor.
+ */
+ events: {
+ message: {
+ type: "message",
+ msg: Arg(0, "nullable:messageportevent")
+ }
+ },
+
+ methods: {
+ postMessage: {
+ oneway: true,
+ request: {
+ msg: Arg(0, "nullable:json")
+ }
+ },
+ start: {
+ oneway: true,
+ request: {}
+ },
+ close: {
+ oneway: true,
+ request: {}
+ },
+ finalize: {
+ oneway: true
+ },
+ },
+});
+
+exports.messagePortSpec = messagePortSpec;
+
+/**
+ * Type describing a director-script error
+ */
+types.addDictType("director-script-error", {
+ directorScriptId: "string",
+ message: "nullable:string",
+ stack: "nullable:string",
+ fileName: "nullable:string",
+ lineNumber: "nullable:number",
+ columnNumber: "nullable:number"
+});
+
+/**
+ * Type describing a director-script attach event
+ */
+types.addDictType("director-script-attach", {
+ directorScriptId: "string",
+ url: "string",
+ innerId: "number",
+ port: "nullable:messageport"
+});
+
+/**
+ * Type describing a director-script detach event
+ */
+types.addDictType("director-script-detach", {
+ directorScriptId: "string",
+ innerId: "number"
+});
+
+const directorScriptSpec = generateActorSpec({
+ typeName: "director-script",
+
+ /**
+ * Events emitted by this actor.
+ */
+ events: {
+ error: {
+ type: "error",
+ data: Arg(0, "director-script-error")
+ },
+ attach: {
+ type: "attach",
+ data: Arg(0, "director-script-attach")
+ },
+ detach: {
+ type: "detach",
+ data: Arg(0, "director-script-detach")
+ }
+ },
+
+ methods: {
+ setup: {
+ request: {
+ reload: Option(0, "boolean"),
+ skipAttach: Option(0, "boolean")
+ },
+ oneway: true
+ },
+ getMessagePort: {
+ request: { },
+ response: {
+ port: RetVal("nullable:messageport")
+ }
+ },
+ finalize: {
+ oneway: true
+ },
+ },
+});
+
+exports.directorScriptSpec = directorScriptSpec;
+
+const directorManagerSpec = generateActorSpec({
+ typeName: "director-manager",
+
+ /**
+ * Events emitted by this actor.
+ */
+ events: {
+ "director-script-error": {
+ type: "error",
+ data: Arg(0, "director-script-error")
+ },
+ "director-script-attach": {
+ type: "attach",
+ data: Arg(0, "director-script-attach")
+ },
+ "director-script-detach": {
+ type: "detach",
+ data: Arg(0, "director-script-detach")
+ }
+ },
+
+ methods: {
+ list: {
+ response: {
+ directorScripts: RetVal("json")
+ }
+ },
+ enableByScriptIds: {
+ oneway: true,
+ request: {
+ selectedIds: Arg(0, "array:string"),
+ reload: Option(1, "boolean")
+ }
+ },
+ disableByScriptIds: {
+ oneway: true,
+ request: {
+ selectedIds: Arg(0, "array:string"),
+ reload: Option(1, "boolean")
+ }
+ },
+ getByScriptId: {
+ request: {
+ scriptId: Arg(0, "string")
+ },
+ response: {
+ directorScript: RetVal("director-script")
+ }
+ },
+ finalize: {
+ oneway: true
+ },
+ },
+});
+
+exports.directorManagerSpec = directorManagerSpec;
diff --git a/devtools/shared/specs/director-registry.js b/devtools/shared/specs/director-registry.js
new file mode 100644
index 000000000..8ebb08eea
--- /dev/null
+++ b/devtools/shared/specs/director-registry.js
@@ -0,0 +1,41 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, Option, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const directorRegistrySpec = generateActorSpec({
+ typeName: "director-registry",
+
+ methods: {
+ finalize: {
+ oneway: true
+ },
+ install: {
+ request: {
+ scriptId: Arg(0, "string"),
+ scriptCode: Option(1, "string"),
+ scriptOptions: Option(1, "nullable:json")
+ },
+ response: {
+ success: RetVal("boolean")
+ }
+ },
+ uninstall: {
+ request: {
+ scritpId: Arg(0, "string")
+ },
+ response: {
+ success: RetVal("boolean")
+ }
+ },
+ list: {
+ response: {
+ directorScripts: RetVal("array:string")
+ }
+ },
+ },
+});
+
+exports.directorRegistrySpec = directorRegistrySpec;
diff --git a/devtools/shared/specs/emulation.js b/devtools/shared/specs/emulation.js
new file mode 100644
index 000000000..1267feaf9
--- /dev/null
+++ b/devtools/shared/specs/emulation.js
@@ -0,0 +1,106 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const emulationSpec = generateActorSpec({
+ typeName: "emulation",
+
+ methods: {
+ setDPPXOverride: {
+ request: {
+ dppx: Arg(0, "number")
+ },
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ getDPPXOverride: {
+ request: {},
+ response: {
+ dppx: RetVal("number")
+ }
+ },
+
+ clearDPPXOverride: {
+ request: {},
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ setNetworkThrottling: {
+ request: {
+ options: Arg(0, "json")
+ },
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ getNetworkThrottling: {
+ request: {},
+ response: {
+ state: RetVal("json")
+ }
+ },
+
+ clearNetworkThrottling: {
+ request: {},
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ setTouchEventsOverride: {
+ request: {
+ flag: Arg(0, "number")
+ },
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ getTouchEventsOverride: {
+ request: {},
+ response: {
+ flag: RetVal("number")
+ }
+ },
+
+ clearTouchEventsOverride: {
+ request: {},
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ setUserAgentOverride: {
+ request: {
+ flag: Arg(0, "string")
+ },
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+
+ getUserAgentOverride: {
+ request: {},
+ response: {
+ userAgent: RetVal("string")
+ }
+ },
+
+ clearUserAgentOverride: {
+ request: {},
+ response: {
+ valueChanged: RetVal("boolean")
+ }
+ },
+ }
+});
+
+exports.emulationSpec = emulationSpec;
diff --git a/devtools/shared/specs/environment.js b/devtools/shared/specs/environment.js
new file mode 100644
index 000000000..d320d98a4
--- /dev/null
+++ b/devtools/shared/specs/environment.js
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const environmentSpec = generateActorSpec({
+ typeName: "environment",
+
+ methods: {
+ assign: {
+ request: {
+ name: Arg(1),
+ value: Arg(2)
+ }
+ },
+ bindings: {
+ request: {},
+ response: {
+ bindings: RetVal("json")
+ }
+ },
+ },
+});
+
+exports.environmentSpec = environmentSpec;
diff --git a/devtools/shared/specs/eventlooplag.js b/devtools/shared/specs/eventlooplag.js
new file mode 100644
index 000000000..1498d5f63
--- /dev/null
+++ b/devtools/shared/specs/eventlooplag.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const eventLoopLagSpec = generateActorSpec({
+ typeName: "eventLoopLag",
+
+ events: {
+ "event-loop-lag": {
+ type: "event-loop-lag",
+ // duration of the lag in milliseconds.
+ time: Arg(0, "number")
+ }
+ },
+
+ methods: {
+ start: {
+ request: {},
+ response: {success: RetVal("number")}
+ },
+ stop: {
+ request: {},
+ response: {}
+ }
+ }
+});
+
+exports.eventLoopLagSpec = eventLoopLagSpec;
diff --git a/devtools/shared/specs/frame.js b/devtools/shared/specs/frame.js
new file mode 100644
index 000000000..6365d6e90
--- /dev/null
+++ b/devtools/shared/specs/frame.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {generateActorSpec} = require("devtools/shared/protocol");
+
+const frameSpec = generateActorSpec({
+ typeName: "frame",
+
+ methods: {},
+});
+
+exports.frameSpec = frameSpec;
diff --git a/devtools/shared/specs/framerate.js b/devtools/shared/specs/framerate.js
new file mode 100644
index 000000000..9fb2dff5d
--- /dev/null
+++ b/devtools/shared/specs/framerate.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const framerateSpec = generateActorSpec({
+ typeName: "framerate",
+
+ methods: {
+ startRecording: {},
+ stopRecording: {
+ request: {
+ beginAt: Arg(0, "nullable:number"),
+ endAt: Arg(1, "nullable:number")
+ },
+ response: { ticks: RetVal("array:number") }
+ },
+ cancelRecording: {},
+ isRecording: {
+ response: { recording: RetVal("boolean") }
+ },
+ getPendingTicks: {
+ request: {
+ beginAt: Arg(0, "nullable:number"),
+ endAt: Arg(1, "nullable:number")
+ },
+ response: { ticks: RetVal("array:number") }
+ }
+ }
+});
+
+exports.framerateSpec = framerateSpec;
diff --git a/devtools/shared/specs/gcli.js b/devtools/shared/specs/gcli.js
new file mode 100644
index 000000000..2e0768bc8
--- /dev/null
+++ b/devtools/shared/specs/gcli.js
@@ -0,0 +1,86 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const gcliSpec = generateActorSpec({
+ typeName: "gcli",
+
+ events: {
+ "commands-changed": {
+ type: "commandsChanged"
+ }
+ },
+
+ methods: {
+ _testOnlyAddItemsByModule: {
+ request: {
+ customProps: Arg(0, "array:string")
+ }
+ },
+ _testOnlyRemoveItemsByModule: {
+ request: {
+ customProps: Arg(0, "array:string")
+ }
+ },
+ specs: {
+ request: {
+ customProps: Arg(0, "nullable:array:string")
+ },
+ response: {
+ value: RetVal("array:json")
+ }
+ },
+ execute: {
+ request: {
+ // The command string
+ typed: Arg(0, "string")
+ },
+ response: RetVal("json")
+ },
+ state: {
+ request: {
+ // The command string
+ typed: Arg(0, "string"),
+ // Cursor start position
+ start: Arg(1, "number"),
+ // The prediction offset (# times UP/DOWN pressed)
+ rank: Arg(2, "number")
+ },
+ response: RetVal("json")
+ },
+ parseType: {
+ request: {
+ // The command string
+ typed: Arg(0, "string"),
+ // The name of the parameter to parse
+ paramName: Arg(1, "string")
+ },
+ response: RetVal("json")
+ },
+ nudgeType: {
+ request: {
+ // The command string
+ typed: Arg(0, "string"),
+ // +1/-1 for increment / decrement
+ by: Arg(1, "number"),
+ // The name of the parameter to parse
+ paramName: Arg(2, "string")
+ },
+ response: RetVal("string")
+ },
+ getSelectionLookup: {
+ request: {
+ // The command containing the parameter in question
+ commandName: Arg(0, "string"),
+ // The name of the parameter
+ paramName: Arg(1, "string"),
+ },
+ response: RetVal("json")
+ }
+ }
+});
+
+exports.gcliSpec = gcliSpec;
diff --git a/devtools/shared/specs/heap-snapshot-file.js b/devtools/shared/specs/heap-snapshot-file.js
new file mode 100644
index 000000000..dfd03e1fa
--- /dev/null
+++ b/devtools/shared/specs/heap-snapshot-file.js
@@ -0,0 +1,20 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, generateActorSpec } = require("devtools/shared/protocol");
+
+const heapSnapshotFileSpec = generateActorSpec({
+ typeName: "heapSnapshotFile",
+
+ methods: {
+ transferHeapSnapshot: {
+ request: {
+ snapshotId: Arg(0, "string")
+ }
+ }
+ },
+});
+
+exports.heapSnapshotFileSpec = heapSnapshotFileSpec;
diff --git a/devtools/shared/specs/highlighters.js b/devtools/shared/specs/highlighters.js
new file mode 100644
index 000000000..8335b22d9
--- /dev/null
+++ b/devtools/shared/specs/highlighters.js
@@ -0,0 +1,63 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec
+} = require("devtools/shared/protocol");
+
+const highlighterSpec = generateActorSpec({
+ typeName: "highlighter",
+
+ methods: {
+ showBoxModel: {
+ request: {
+ node: Arg(0, "domnode"),
+ region: Option(1),
+ hideInfoBar: Option(1),
+ hideGuides: Option(1),
+ showOnly: Option(1),
+ onlyRegionArea: Option(1)
+ }
+ },
+ hideBoxModel: {
+ request: {}
+ },
+ pick: {},
+ pickAndFocus: {},
+ cancelPick: {}
+ }
+});
+
+exports.highlighterSpec = highlighterSpec;
+
+const customHighlighterSpec = generateActorSpec({
+ typeName: "customhighlighter",
+
+ methods: {
+ release: {
+ release: true
+ },
+ show: {
+ request: {
+ node: Arg(0, "domnode"),
+ options: Arg(1, "nullable:json")
+ },
+ response: {
+ value: RetVal("nullable:boolean")
+ }
+ },
+ hide: {
+ request: {}
+ },
+ finalize: {
+ oneway: true
+ }
+ }
+});
+
+exports.customHighlighterSpec = customHighlighterSpec;
diff --git a/devtools/shared/specs/inspector.js b/devtools/shared/specs/inspector.js
new file mode 100644
index 000000000..28736ed34
--- /dev/null
+++ b/devtools/shared/specs/inspector.js
@@ -0,0 +1,445 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+const { nodeSpec } = require("devtools/shared/specs/node");
+require("devtools/shared/specs/styles");
+require("devtools/shared/specs/highlighters");
+require("devtools/shared/specs/layout");
+
+exports.nodeSpec = nodeSpec;
+
+/**
+ * Returned from any call that might return a node that isn't connected to root
+ * by nodes the child has seen, such as querySelector.
+ */
+types.addDictType("disconnectedNode", {
+ // The actual node to return
+ node: "domnode",
+
+ // Nodes that are needed to connect the node to a node the client has already
+ // seen
+ newParents: "array:domnode"
+});
+
+types.addDictType("disconnectedNodeArray", {
+ // The actual node list to return
+ nodes: "array:domnode",
+
+ // Nodes that are needed to connect those nodes to the root.
+ newParents: "array:domnode"
+});
+
+types.addDictType("dommutation", {});
+
+types.addDictType("searchresult", {
+ list: "domnodelist",
+ // Right now there is isn't anything required for metadata,
+ // but it's json so it can be extended with extra data.
+ metadata: "array:json"
+});
+
+const nodeListSpec = generateActorSpec({
+ typeName: "domnodelist",
+
+ methods: {
+ item: {
+ request: { item: Arg(0) },
+ response: RetVal("disconnectedNode")
+ },
+ items: {
+ request: {
+ start: Arg(0, "nullable:number"),
+ end: Arg(1, "nullable:number")
+ },
+ response: RetVal("disconnectedNodeArray")
+ },
+ release: {
+ release: true
+ }
+ }
+});
+
+exports.nodeListSpec = nodeListSpec;
+
+// Some common request/response templates for the dom walker
+
+var nodeArrayMethod = {
+ request: {
+ node: Arg(0, "domnode"),
+ maxNodes: Option(1),
+ center: Option(1, "domnode"),
+ start: Option(1, "domnode"),
+ whatToShow: Option(1)
+ },
+ response: RetVal(types.addDictType("domtraversalarray", {
+ nodes: "array:domnode"
+ }))
+};
+
+var traversalMethod = {
+ request: {
+ node: Arg(0, "domnode"),
+ whatToShow: Option(1)
+ },
+ response: {
+ node: RetVal("nullable:domnode")
+ }
+};
+
+const walkerSpec = generateActorSpec({
+ typeName: "domwalker",
+
+ events: {
+ "new-mutations": {
+ type: "newMutations"
+ },
+ "picker-node-picked": {
+ type: "pickerNodePicked",
+ node: Arg(0, "disconnectedNode")
+ },
+ "picker-node-previewed": {
+ type: "pickerNodePreviewed",
+ node: Arg(0, "disconnectedNode")
+ },
+ "picker-node-hovered": {
+ type: "pickerNodeHovered",
+ node: Arg(0, "disconnectedNode")
+ },
+ "picker-node-canceled": {
+ type: "pickerNodeCanceled"
+ },
+ "highlighter-ready": {
+ type: "highlighter-ready"
+ },
+ "highlighter-hide": {
+ type: "highlighter-hide"
+ },
+ "display-change": {
+ type: "display-change",
+ nodes: Arg(0, "array:domnode")
+ },
+ // The walker actor emits a useful "resize" event to its front to let
+ // clients know when the browser window gets resized. This may be useful
+ // for refreshing a DOM node's styles for example, since those may depend on
+ // media-queries.
+ "resize": {
+ type: "resize"
+ }
+ },
+
+ methods: {
+ release: {
+ release: true
+ },
+ pick: {
+ request: {},
+ response: RetVal("disconnectedNode")
+ },
+ cancelPick: {},
+ highlight: {
+ request: {node: Arg(0, "nullable:domnode")}
+ },
+ document: {
+ request: { node: Arg(0, "nullable:domnode") },
+ response: { node: RetVal("domnode") },
+ },
+ documentElement: {
+ request: { node: Arg(0, "nullable:domnode") },
+ response: { node: RetVal("domnode") },
+ },
+ parents: {
+ request: {
+ node: Arg(0, "domnode"),
+ sameDocument: Option(1),
+ sameTypeRootTreeItem: Option(1)
+ },
+ response: {
+ nodes: RetVal("array:domnode")
+ },
+ },
+ retainNode: {
+ request: { node: Arg(0, "domnode") },
+ response: {}
+ },
+ unretainNode: {
+ request: { node: Arg(0, "domnode") },
+ response: {},
+ },
+ releaseNode: {
+ request: {
+ node: Arg(0, "domnode"),
+ force: Option(1)
+ }
+ },
+ children: nodeArrayMethod,
+ siblings: nodeArrayMethod,
+ nextSibling: traversalMethod,
+ previousSibling: traversalMethod,
+ findInspectingNode: {
+ request: {},
+ response: RetVal("disconnectedNode")
+ },
+ querySelector: {
+ request: {
+ node: Arg(0, "domnode"),
+ selector: Arg(1)
+ },
+ response: RetVal("disconnectedNode")
+ },
+ querySelectorAll: {
+ request: {
+ node: Arg(0, "domnode"),
+ selector: Arg(1)
+ },
+ response: {
+ list: RetVal("domnodelist")
+ }
+ },
+ multiFrameQuerySelectorAll: {
+ request: {
+ selector: Arg(0)
+ },
+ response: {
+ list: RetVal("domnodelist")
+ }
+ },
+ search: {
+ request: {
+ query: Arg(0),
+ },
+ response: {
+ list: RetVal("searchresult"),
+ }
+ },
+ getSuggestionsForQuery: {
+ request: {
+ query: Arg(0),
+ completing: Arg(1),
+ selectorState: Arg(2)
+ },
+ response: {
+ list: RetVal("array:array:string")
+ }
+ },
+ addPseudoClassLock: {
+ request: {
+ node: Arg(0, "domnode"),
+ pseudoClass: Arg(1),
+ parents: Option(2)
+ },
+ response: {}
+ },
+ hideNode: {
+ request: { node: Arg(0, "domnode") }
+ },
+ unhideNode: {
+ request: { node: Arg(0, "domnode") }
+ },
+ removePseudoClassLock: {
+ request: {
+ node: Arg(0, "domnode"),
+ pseudoClass: Arg(1),
+ parents: Option(2)
+ },
+ response: {}
+ },
+ clearPseudoClassLocks: {
+ request: {
+ node: Arg(0, "nullable:domnode")
+ },
+ response: {}
+ },
+ innerHTML: {
+ request: {
+ node: Arg(0, "domnode")
+ },
+ response: {
+ value: RetVal("longstring")
+ }
+ },
+ setInnerHTML: {
+ request: {
+ node: Arg(0, "domnode"),
+ value: Arg(1, "string"),
+ },
+ response: {}
+ },
+ outerHTML: {
+ request: {
+ node: Arg(0, "domnode")
+ },
+ response: {
+ value: RetVal("longstring")
+ }
+ },
+ setOuterHTML: {
+ request: {
+ node: Arg(0, "domnode"),
+ value: Arg(1, "string"),
+ },
+ response: {}
+ },
+ insertAdjacentHTML: {
+ request: {
+ node: Arg(0, "domnode"),
+ position: Arg(1, "string"),
+ value: Arg(2, "string")
+ },
+ response: RetVal("disconnectedNodeArray")
+ },
+ duplicateNode: {
+ request: {
+ node: Arg(0, "domnode")
+ },
+ response: {}
+ },
+ removeNode: {
+ request: {
+ node: Arg(0, "domnode")
+ },
+ response: {
+ nextSibling: RetVal("nullable:domnode")
+ }
+ },
+ removeNodes: {
+ request: {
+ node: Arg(0, "array:domnode")
+ },
+ response: {}
+ },
+ insertBefore: {
+ request: {
+ node: Arg(0, "domnode"),
+ parent: Arg(1, "domnode"),
+ sibling: Arg(2, "nullable:domnode")
+ },
+ response: {}
+ },
+ editTagName: {
+ request: {
+ node: Arg(0, "domnode"),
+ tagName: Arg(1, "string")
+ },
+ response: {}
+ },
+ getMutations: {
+ request: {
+ cleanup: Option(0)
+ },
+ response: {
+ mutations: RetVal("array:dommutation")
+ }
+ },
+ isInDOMTree: {
+ request: { node: Arg(0, "domnode") },
+ response: { attached: RetVal("boolean") }
+ },
+ getNodeActorFromObjectActor: {
+ request: {
+ objectActorID: Arg(0, "string")
+ },
+ response: {
+ nodeFront: RetVal("nullable:disconnectedNode")
+ }
+ },
+ getStyleSheetOwnerNode: {
+ request: {
+ styleSheetActorID: Arg(0, "string")
+ },
+ response: {
+ ownerNode: RetVal("nullable:disconnectedNode")
+ }
+ },
+ getNodeFromActor: {
+ request: {
+ actorID: Arg(0, "string"),
+ path: Arg(1, "array:string")
+ },
+ response: {
+ node: RetVal("nullable:disconnectedNode")
+ }
+ },
+ getLayoutInspector: {
+ request: {},
+ response: {
+ actor: RetVal("layout")
+ }
+ }
+ }
+});
+
+exports.walkerSpec = walkerSpec;
+
+const inspectorSpec = generateActorSpec({
+ typeName: "inspector",
+
+ events: {
+ "color-picked": {
+ type: "colorPicked",
+ color: Arg(0, "string")
+ },
+ "color-pick-canceled": {
+ type: "colorPickCanceled"
+ }
+ },
+
+ methods: {
+ getWalker: {
+ request: {
+ options: Arg(0, "nullable:json")
+ },
+ response: {
+ walker: RetVal("domwalker")
+ }
+ },
+ getPageStyle: {
+ request: {},
+ response: {
+ pageStyle: RetVal("pagestyle")
+ }
+ },
+ getHighlighter: {
+ request: {
+ autohide: Arg(0, "boolean")
+ },
+ response: {
+ highligter: RetVal("highlighter")
+ }
+ },
+ getHighlighterByType: {
+ request: {
+ typeName: Arg(0)
+ },
+ response: {
+ highlighter: RetVal("nullable:customhighlighter")
+ }
+ },
+ getImageDataFromURL: {
+ request: {url: Arg(0), maxDim: Arg(1, "nullable:number")},
+ response: RetVal("imageData")
+ },
+ resolveRelativeURL: {
+ request: {url: Arg(0, "string"), node: Arg(1, "nullable:domnode")},
+ response: {value: RetVal("string")}
+ },
+ pickColorFromPage: {
+ request: {options: Arg(0, "nullable:json")},
+ response: {}
+ },
+ cancelPickColorFromPage: {
+ request: {},
+ response: {}
+ }
+ }
+});
+
+exports.inspectorSpec = inspectorSpec;
diff --git a/devtools/shared/specs/layout.js b/devtools/shared/specs/layout.js
new file mode 100644
index 000000000..80a0d5f9b
--- /dev/null
+++ b/devtools/shared/specs/layout.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Arg, generateActorSpec, RetVal } = require("devtools/shared/protocol");
+require("devtools/shared/specs/node");
+
+const gridSpec = generateActorSpec({
+ typeName: "grid",
+
+ methods: {},
+});
+
+const layoutSpec = generateActorSpec({
+ typeName: "layout",
+
+ methods: {
+ getAllGrids: {
+ request: {
+ rootNode: Arg(0, "domnode"),
+ traverseFrames: Arg(1, "boolean")
+ },
+ response: {
+ grids: RetVal("array:grid")
+ }
+ }
+ },
+});
+
+exports.gridSpec = gridSpec;
+exports.layoutSpec = layoutSpec;
diff --git a/devtools/shared/specs/memory.js b/devtools/shared/specs/memory.js
new file mode 100644
index 000000000..3bff6dc38
--- /dev/null
+++ b/devtools/shared/specs/memory.js
@@ -0,0 +1,124 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ types,
+ generateActorSpec,
+} = require("devtools/shared/protocol");
+
+types.addDictType("AllocationsRecordingOptions", {
+ // The probability we sample any given allocation when recording
+ // allocations. Must be between 0.0 and 1.0. Defaults to 1.0, or sampling
+ // every allocation.
+ probability: "number",
+
+ // The maximum number of of allocation events to keep in the allocations
+ // log. If new allocations arrive, when we are already at capacity, the oldest
+ // allocation event is lost. This number must fit in a 32 bit signed integer.
+ maxLogLength: "number"
+});
+
+const memorySpec = generateActorSpec({
+ typeName: "memory",
+
+ /**
+ * The set of unsolicited events the MemoryActor emits that will be sent over
+ * the RDP (by protocol.js).
+ */
+ events: {
+ // Same format as the data passed to the
+ // `Debugger.Memory.prototype.onGarbageCollection` hook. See
+ // `js/src/doc/Debugger/Debugger.Memory.md` for documentation.
+ "garbage-collection": {
+ type: "garbage-collection",
+ data: Arg(0, "json"),
+ },
+
+ // Same data as the data from `getAllocations` -- only fired if
+ // `autoDrain` set during `startRecordingAllocations`.
+ "allocations": {
+ type: "allocations",
+ data: Arg(0, "json"),
+ },
+ },
+
+ methods: {
+ attach: {
+ request: {},
+ response: {
+ type: "attached"
+ }
+ },
+ detach: {
+ request: {},
+ response: {
+ type: "detached"
+ }
+ },
+ getState: {
+ response: {
+ state: RetVal(0, "string")
+ }
+ },
+ takeCensus: {
+ request: {},
+ response: RetVal("json")
+ },
+ startRecordingAllocations: {
+ request: {
+ options: Arg(0, "nullable:AllocationsRecordingOptions")
+ },
+ response: {
+ // Accept `nullable` in the case of server Gecko <= 37, handled on the front
+ value: RetVal(0, "nullable:number")
+ }
+ },
+ stopRecordingAllocations: {
+ request: {},
+ response: {
+ // Accept `nullable` in the case of server Gecko <= 37, handled on the front
+ value: RetVal(0, "nullable:number")
+ }
+ },
+ getAllocationsSettings: {
+ request: {},
+ response: {
+ options: RetVal(0, "json")
+ }
+ },
+ getAllocations: {
+ request: {},
+ response: RetVal("json")
+ },
+ forceGarbageCollection: {
+ request: {},
+ response: {}
+ },
+ forceCycleCollection: {
+ request: {},
+ response: {}
+ },
+ measure: {
+ request: {},
+ response: RetVal("json"),
+ },
+ residentUnique: {
+ request: {},
+ response: { value: RetVal("number") }
+ },
+ saveHeapSnapshot: {
+ request: {
+ boundaries: Arg(0, "nullable:json")
+ },
+ response: {
+ snapshotId: RetVal("string")
+ }
+ },
+ },
+});
+
+exports.memorySpec = memorySpec;
diff --git a/devtools/shared/specs/moz.build b/devtools/shared/specs/moz.build
new file mode 100644
index 000000000..52a810637
--- /dev/null
+++ b/devtools/shared/specs/moz.build
@@ -0,0 +1,50 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'actor-registry.js',
+ 'addons.js',
+ 'animation.js',
+ 'breakpoint.js',
+ 'call-watcher.js',
+ 'canvas.js',
+ 'css-properties.js',
+ 'csscoverage.js',
+ 'device.js',
+ 'director-manager.js',
+ 'director-registry.js',
+ 'emulation.js',
+ 'environment.js',
+ 'eventlooplag.js',
+ 'frame.js',
+ 'framerate.js',
+ 'gcli.js',
+ 'heap-snapshot-file.js',
+ 'highlighters.js',
+ 'inspector.js',
+ 'layout.js',
+ 'memory.js',
+ 'node.js',
+ 'performance-entries.js',
+ 'performance-recording.js',
+ 'performance.js',
+ 'preference.js',
+ 'profiler.js',
+ 'promises.js',
+ 'reflow.js',
+ 'script.js',
+ 'settings.js',
+ 'source.js',
+ 'storage.js',
+ 'string.js',
+ 'styleeditor.js',
+ 'styles.js',
+ 'stylesheets.js',
+ 'timeline.js',
+ 'webaudio.js',
+ 'webgl.js',
+ 'worker.js'
+)
diff --git a/devtools/shared/specs/node.js b/devtools/shared/specs/node.js
new file mode 100644
index 000000000..ea3d1b264
--- /dev/null
+++ b/devtools/shared/specs/node.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol.js");
+
+types.addDictType("imageData", {
+ // The image data
+ data: "nullable:longstring",
+ // The original image dimensions
+ size: "json"
+});
+
+const nodeSpec = generateActorSpec({
+ typeName: "domnode",
+
+ methods: {
+ getNodeValue: {
+ request: {},
+ response: {
+ value: RetVal("longstring")
+ }
+ },
+ setNodeValue: {
+ request: { value: Arg(0) },
+ response: {}
+ },
+ getUniqueSelector: {
+ request: {},
+ response: {
+ value: RetVal("string")
+ }
+ },
+ scrollIntoView: {
+ request: {},
+ response: {}
+ },
+ getImageData: {
+ request: {maxDim: Arg(0, "nullable:number")},
+ response: RetVal("imageData")
+ },
+ getEventListenerInfo: {
+ request: {},
+ response: {
+ events: RetVal("json")
+ }
+ },
+ modifyAttributes: {
+ request: {
+ modifications: Arg(0, "array:json")
+ },
+ response: {}
+ },
+ getFontFamilyDataURL: {
+ request: {font: Arg(0, "string"), fillStyle: Arg(1, "nullable:string")},
+ response: RetVal("imageData")
+ }
+ }
+});
+
+exports.nodeSpec = nodeSpec;
diff --git a/devtools/shared/specs/performance-entries.js b/devtools/shared/specs/performance-entries.js
new file mode 100644
index 000000000..107afb779
--- /dev/null
+++ b/devtools/shared/specs/performance-entries.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, generateActorSpec } = require("devtools/shared/protocol");
+
+const performanceEntriesSpec = generateActorSpec({
+ typeName: "performanceEntries",
+
+ events: {
+ "entry": {
+ type: "entry",
+ // object containing performance entry name, type, origin, and epoch.
+ detail: Arg(0, "json")
+ }
+ },
+
+ methods: {
+ start: {},
+ stop: {}
+ }
+});
+
+exports.performanceEntriesSpec = performanceEntriesSpec;
diff --git a/devtools/shared/specs/performance-recording.js b/devtools/shared/specs/performance-recording.js
new file mode 100644
index 000000000..562abc6f7
--- /dev/null
+++ b/devtools/shared/specs/performance-recording.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { generateActorSpec } = require("devtools/shared/protocol");
+
+const performanceRecordingSpec = generateActorSpec({
+ typeName: "performance-recording"
+});
+
+exports.performanceRecordingSpec = performanceRecordingSpec;
diff --git a/devtools/shared/specs/performance.js b/devtools/shared/specs/performance.js
new file mode 100644
index 000000000..30a570e9a
--- /dev/null
+++ b/devtools/shared/specs/performance.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+require("devtools/shared/specs/performance-recording");
+
+const performanceSpec = generateActorSpec({
+ typeName: "performance",
+
+ /**
+ * The set of events the PerformanceActor emits over RDP.
+ */
+ events: {
+ "recording-started": {
+ recording: Arg(0, "performance-recording"),
+ },
+ "recording-stopping": {
+ recording: Arg(0, "performance-recording"),
+ },
+ "recording-stopped": {
+ recording: Arg(0, "performance-recording"),
+ data: Arg(1, "json"),
+ },
+ "profiler-status": {
+ data: Arg(0, "json"),
+ },
+ "console-profile-start": {},
+ "timeline-data": {
+ name: Arg(0, "string"),
+ data: Arg(1, "json"),
+ recordings: Arg(2, "array:performance-recording"),
+ },
+ },
+
+ methods: {
+ connect: {
+ request: { options: Arg(0, "nullable:json") },
+ response: RetVal("json")
+ },
+
+ canCurrentlyRecord: {
+ request: {},
+ response: { value: RetVal("json") }
+ },
+
+ startRecording: {
+ request: {
+ options: Arg(0, "nullable:json"),
+ },
+ response: {
+ recording: RetVal("nullable:performance-recording")
+ }
+ },
+
+ stopRecording: {
+ request: {
+ options: Arg(0, "performance-recording"),
+ },
+ response: {
+ recording: RetVal("performance-recording")
+ }
+ },
+
+ isRecording: {
+ request: {},
+ response: { isRecording: RetVal("boolean") }
+ },
+
+ getRecordings: {
+ request: {},
+ response: { recordings: RetVal("array:performance-recording") }
+ },
+
+ getConfiguration: {
+ request: {},
+ response: { config: RetVal("json") }
+ },
+
+ setProfilerStatusInterval: {
+ request: { interval: Arg(0, "number") },
+ response: { oneway: true }
+ },
+ }
+});
+
+exports.performanceSpec = performanceSpec;
diff --git a/devtools/shared/specs/preference.js b/devtools/shared/specs/preference.js
new file mode 100644
index 000000000..54123d813
--- /dev/null
+++ b/devtools/shared/specs/preference.js
@@ -0,0 +1,47 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const preferenceSpec = generateActorSpec({
+ typeName: "preference",
+
+ methods: {
+ getBoolPref: {
+ request: { value: Arg(0) },
+ response: { value: RetVal("boolean") }
+ },
+ getCharPref: {
+ request: { value: Arg(0) },
+ response: { value: RetVal("string") }
+ },
+ getIntPref: {
+ request: { value: Arg(0) },
+ response: { value: RetVal("number") }
+ },
+ getAllPrefs: {
+ request: {},
+ response: { value: RetVal("json") }
+ },
+ setBoolPref: {
+ request: { name: Arg(0), value: Arg(1) },
+ response: {}
+ },
+ setCharPref: {
+ request: { name: Arg(0), value: Arg(1) },
+ response: {}
+ },
+ setIntPref: {
+ request: { name: Arg(0), value: Arg(1) },
+ response: {}
+ },
+ clearUserPref: {
+ request: { name: Arg(0) },
+ response: {}
+ }
+ },
+});
+
+exports.preferenceSpec = preferenceSpec;
diff --git a/devtools/shared/specs/profiler.js b/devtools/shared/specs/profiler.js
new file mode 100644
index 000000000..a4a6f384e
--- /dev/null
+++ b/devtools/shared/specs/profiler.js
@@ -0,0 +1,121 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+
+types.addType("profiler-data", {
+ // On Fx42+, the profile is only deserialized on the front; older
+ // servers will get the profiler data as an object from nsIProfiler,
+ // causing one parse/stringify cycle, then again implicitly in a packet.
+ read: (v) => {
+ if (typeof v.profile === "string") {
+ // Create a new response object since `profile` is read only.
+ let newValue = Object.create(null);
+ newValue.profile = JSON.parse(v.profile);
+ newValue.currentTime = v.currentTime;
+ return newValue;
+ }
+ return v;
+ }
+});
+
+const profilerSpec = generateActorSpec({
+ typeName: "profiler",
+
+ /**
+ * The set of events the ProfilerActor emits over RDP.
+ */
+ events: {
+ "console-api-profiler": {
+ data: Arg(0, "json"),
+ },
+ "profiler-started": {
+ data: Arg(0, "json"),
+ },
+ "profiler-stopped": {
+ data: Arg(0, "json"),
+ },
+ "profiler-status": {
+ data: Arg(0, "json"),
+ },
+
+ // Only for older geckos, pre-protocol.js ProfilerActor (<Fx42).
+ // Emitted on other events as a transition from older profiler events
+ // to newer ones.
+ "eventNotification": {
+ subject: Option(0, "json"),
+ topic: Option(0, "string"),
+ details: Option(0, "json")
+ }
+ },
+
+ methods: {
+ startProfiler: {
+ // Write out every property in the request, since we want all these options to be
+ // on the packet's top-level for backwards compatibility, when the profiler actor
+ // was not using protocol.js (<Fx42)
+ request: {
+ entries: Option(0, "nullable:number"),
+ interval: Option(0, "nullable:number"),
+ features: Option(0, "nullable:array:string"),
+ threadFilters: Option(0, "nullable:array:string"),
+ },
+ response: RetVal("json"),
+ },
+ stopProfiler: {
+ response: RetVal("json"),
+ },
+ getProfile: {
+ request: {
+ startTime: Option(0, "nullable:number"),
+ stringify: Option(0, "nullable:boolean")
+ },
+ response: RetVal("profiler-data")
+ },
+ getFeatures: {
+ response: RetVal("json")
+ },
+ getBufferInfo: {
+ response: RetVal("json")
+ },
+ getStartOptions: {
+ response: RetVal("json")
+ },
+ isActive: {
+ response: RetVal("json")
+ },
+ getSharedLibraryInformation: {
+ response: RetVal("json")
+ },
+ registerEventNotifications: {
+ // Explicitly enumerate the arguments
+ // @see ProfilerActor#startProfiler
+ request: {
+ events: Option(0, "nullable:array:string"),
+ },
+ response: RetVal("json")
+ },
+ unregisterEventNotifications: {
+ // Explicitly enumerate the arguments
+ // @see ProfilerActor#startProfiler
+ request: {
+ events: Option(0, "nullable:array:string"),
+ },
+ response: RetVal("json")
+ },
+ setProfilerStatusInterval: {
+ request: { interval: Arg(0, "number") },
+ oneway: true
+ }
+ }
+});
+
+exports.profilerSpec = profilerSpec;
diff --git a/devtools/shared/specs/promises.js b/devtools/shared/specs/promises.js
new file mode 100644
index 000000000..ef57c3bae
--- /dev/null
+++ b/devtools/shared/specs/promises.js
@@ -0,0 +1,56 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+
+// Teach protocol.js how to deal with legacy actor types
+types.addType("ObjectActor", {
+ write: actor => actor.grip(),
+ read: grip => grip
+});
+
+const promisesSpec = generateActorSpec({
+ typeName: "promises",
+
+ events: {
+ // Event emitted for new promises allocated in debuggee and bufferred by
+ // sending the list of promise objects in a batch.
+ "new-promises": {
+ type: "new-promises",
+ data: Arg(0, "array:ObjectActor"),
+ },
+ // Event emitted for promise settlements.
+ "promises-settled": {
+ type: "promises-settled",
+ data: Arg(0, "array:ObjectActor")
+ }
+ },
+
+ methods: {
+ attach: {
+ request: {},
+ response: {},
+ },
+
+ detach: {
+ request: {},
+ response: {},
+ },
+
+ listPromises: {
+ request: {},
+ response: {
+ promises: RetVal("array:ObjectActor"),
+ },
+ }
+ }
+});
+
+exports.promisesSpec = promisesSpec;
diff --git a/devtools/shared/specs/reflow.js b/devtools/shared/specs/reflow.js
new file mode 100644
index 000000000..a33c7a2cf
--- /dev/null
+++ b/devtools/shared/specs/reflow.js
@@ -0,0 +1,33 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Arg, generateActorSpec} = require("devtools/shared/protocol");
+
+const reflowSpec = generateActorSpec({
+ typeName: "reflow",
+
+ events: {
+ /**
+ * The reflows event is emitted when reflows have been detected. The event
+ * is sent with an array of reflows that occured. Each item has the
+ * following properties:
+ * - start {Number}
+ * - end {Number}
+ * - isInterruptible {Boolean}
+ */
+ reflows: {
+ type: "reflows",
+ reflows: Arg(0, "array:json")
+ }
+ },
+
+ methods: {
+ start: {oneway: true},
+ stop: {oneway: true},
+ },
+});
+
+exports.reflowSpec = reflowSpec;
diff --git a/devtools/shared/specs/script.js b/devtools/shared/specs/script.js
new file mode 100644
index 000000000..f24426dee
--- /dev/null
+++ b/devtools/shared/specs/script.js
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {generateActorSpec} = require("devtools/shared/protocol");
+
+const threadSpec = generateActorSpec({
+ typeName: "context",
+
+ methods: {},
+});
+
+exports.threadSpec = threadSpec;
diff --git a/devtools/shared/specs/settings.js b/devtools/shared/specs/settings.js
new file mode 100644
index 000000000..482c8f473
--- /dev/null
+++ b/devtools/shared/specs/settings.js
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const settingsSpec = generateActorSpec({
+ typeName: "settings",
+
+ methods: {
+ getSetting: {
+ request: { value: Arg(0) },
+ response: { value: RetVal("json") }
+ },
+ setSetting: {
+ request: { name: Arg(0), value: Arg(1) },
+ response: {}
+ },
+ getAllSettings: {
+ request: {},
+ response: { value: RetVal("json") }
+ },
+ clearUserSetting: {
+ request: { name: Arg(0) },
+ response: {}
+ }
+ },
+});
+
+exports.settingsSpec = settingsSpec;
diff --git a/devtools/shared/specs/source.js b/devtools/shared/specs/source.js
new file mode 100644
index 000000000..66bcf89e2
--- /dev/null
+++ b/devtools/shared/specs/source.js
@@ -0,0 +1,40 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const sourceSpec = generateActorSpec({
+ typeName: "source",
+
+ methods: {
+ getExecutableLines: { response: { lines: RetVal("json") } },
+ onSource: {
+ request: { type: "source" },
+ response: RetVal("json")
+ },
+ prettyPrint: {
+ request: { indent: Arg(0, "number") },
+ response: RetVal("json")
+ },
+ disablePrettyPrint: {
+ response: RetVal("json")
+ },
+ blackbox: { response: { pausedInSource: RetVal("boolean") } },
+ unblackbox: {},
+ setBreakpoint: {
+ request: {
+ location: {
+ line: Arg(0, "number"),
+ column: Arg(1, "nullable:number")
+ },
+ condition: Arg(2, "nullable:string"),
+ noSliding: Arg(3, "nullable:boolean")
+ },
+ response: RetVal("json")
+ },
+ },
+});
+
+exports.sourceSpec = sourceSpec;
diff --git a/devtools/shared/specs/storage.js b/devtools/shared/specs/storage.js
new file mode 100644
index 000000000..d6ddaefe5
--- /dev/null
+++ b/devtools/shared/specs/storage.js
@@ -0,0 +1,279 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const { Arg, RetVal, types } = protocol;
+
+let childSpecs = {};
+
+function createStorageSpec(options) {
+ // common methods for all storage types
+ let methods = {
+ getStoreObjects: {
+ request: {
+ host: Arg(0),
+ names: Arg(1, "nullable:array:string"),
+ options: Arg(2, "nullable:json")
+ },
+ response: RetVal(options.storeObjectType)
+ },
+ getFields: {
+ request: {
+ subType: Arg(0, "nullable:string")
+ },
+ response: {
+ value: RetVal("json")
+ }
+ }
+ };
+
+ // extra methods specific for storage type
+ Object.assign(methods, options.methods);
+
+ childSpecs[options.typeName] = protocol.generateActorSpec({
+ typeName: options.typeName,
+ methods
+ });
+}
+
+// Cookies store object
+types.addDictType("cookieobject", {
+ name: "string",
+ value: "longstring",
+ path: "nullable:string",
+ host: "string",
+ isDomain: "boolean",
+ isSecure: "boolean",
+ isHttpOnly: "boolean",
+ creationTime: "number",
+ lastAccessed: "number",
+ expires: "number"
+});
+
+// Array of cookie store objects
+types.addDictType("cookiestoreobject", {
+ total: "number",
+ offset: "number",
+ data: "array:nullable:cookieobject"
+});
+
+// Common methods for edit/remove
+const editRemoveMethods = {
+ getEditableFields: {
+ request: {},
+ response: {
+ value: RetVal("json")
+ }
+ },
+ editItem: {
+ request: {
+ data: Arg(0, "json"),
+ },
+ response: {}
+ },
+ removeItem: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: {}
+ },
+};
+
+// Cookies actor spec
+createStorageSpec({
+ typeName: "cookies",
+ storeObjectType: "cookiestoreobject",
+ methods: Object.assign({},
+ editRemoveMethods,
+ {
+ removeAll: {
+ request: {
+ host: Arg(0, "string"),
+ domain: Arg(1, "nullable:string")
+ },
+ response: {}
+ }
+ }
+ )
+});
+
+// Local Storage / Session Storage store object
+types.addDictType("storageobject", {
+ name: "string",
+ value: "longstring"
+});
+
+// Common methods for local/session storage
+const storageMethods = Object.assign({},
+ editRemoveMethods,
+ {
+ removeAll: {
+ request: {
+ host: Arg(0, "string")
+ },
+ response: {}
+ }
+ }
+);
+
+// Array of Local Storage / Session Storage store objects
+types.addDictType("storagestoreobject", {
+ total: "number",
+ offset: "number",
+ data: "array:nullable:storageobject"
+});
+
+createStorageSpec({
+ typeName: "localStorage",
+ storeObjectType: "storagestoreobject",
+ methods: storageMethods
+});
+
+createStorageSpec({
+ typeName: "sessionStorage",
+ storeObjectType: "storagestoreobject",
+ methods: storageMethods
+});
+
+types.addDictType("cacheobject", {
+ "url": "string",
+ "status": "string"
+});
+
+// Array of Cache store objects
+types.addDictType("cachestoreobject", {
+ total: "number",
+ offset: "number",
+ data: "array:nullable:cacheobject"
+});
+
+// Cache storage spec
+createStorageSpec({
+ typeName: "Cache",
+ storeObjectType: "cachestoreobject",
+ methods: {
+ removeAll: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: {}
+ },
+ removeItem: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: {}
+ },
+ }
+});
+
+// Indexed DB store object
+// This is a union on idb object, db metadata object and object store metadata
+// object
+types.addDictType("idbobject", {
+ name: "nullable:string",
+ db: "nullable:string",
+ objectStore: "nullable:string",
+ origin: "nullable:string",
+ version: "nullable:number",
+ objectStores: "nullable:number",
+ keyPath: "nullable:string",
+ autoIncrement: "nullable:boolean",
+ indexes: "nullable:string",
+ value: "nullable:longstring"
+});
+
+// Array of Indexed DB store objects
+types.addDictType("idbstoreobject", {
+ total: "number",
+ offset: "number",
+ data: "array:nullable:idbobject"
+});
+
+// Result of Indexed DB delete operation: can block or throw error
+types.addDictType("idbdeleteresult", {
+ blocked: "nullable:boolean",
+ error: "nullable:string"
+});
+
+createStorageSpec({
+ typeName: "indexedDB",
+ storeObjectType: "idbstoreobject",
+ methods: {
+ removeDatabase: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: RetVal("idbdeleteresult")
+ },
+ removeAll: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: {}
+ },
+ removeItem: {
+ request: {
+ host: Arg(0, "string"),
+ name: Arg(1, "string"),
+ },
+ response: {}
+ },
+ }
+});
+
+// Update notification object
+types.addDictType("storeUpdateObject", {
+ changed: "nullable:json",
+ deleted: "nullable:json",
+ added: "nullable:json"
+});
+
+// Generate a type definition for an object with actors for all storage types.
+types.addDictType("storelist", Object.keys(childSpecs).reduce((obj, type) => {
+ obj[type] = type;
+ return obj;
+}, {}));
+
+exports.childSpecs = childSpecs;
+
+exports.storageSpec = protocol.generateActorSpec({
+ typeName: "storage",
+
+ /**
+ * List of event notifications that the server can send to the client.
+ *
+ * - stores-update : When any store object in any storage type changes.
+ * - stores-cleared : When all the store objects are removed.
+ * - stores-reloaded : When all stores are reloaded. This generally mean that
+ * we should refetch everything again.
+ */
+ events: {
+ "stores-update": {
+ type: "storesUpdate",
+ data: Arg(0, "storeUpdateObject")
+ },
+ "stores-cleared": {
+ type: "storesCleared",
+ data: Arg(0, "json")
+ },
+ "stores-reloaded": {
+ type: "storesReloaded",
+ data: Arg(0, "json")
+ }
+ },
+
+ methods: {
+ listStores: {
+ request: {},
+ response: RetVal("storelist")
+ },
+ }
+});
diff --git a/devtools/shared/specs/string.js b/devtools/shared/specs/string.js
new file mode 100644
index 000000000..f759d4052
--- /dev/null
+++ b/devtools/shared/specs/string.js
@@ -0,0 +1,87 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const protocol = require("devtools/shared/protocol");
+const {Arg, RetVal, generateActorSpec} = protocol;
+const promise = require("promise");
+const {Class} = require("sdk/core/heritage");
+
+const longStringSpec = generateActorSpec({
+ typeName: "longstractor",
+
+ methods: {
+ substring: {
+ request: {
+ start: Arg(0),
+ end: Arg(1)
+ },
+ response: { substring: RetVal() },
+ },
+ release: { release: true },
+ },
+});
+
+exports.longStringSpec = longStringSpec;
+
+/**
+ * When a caller is expecting a LongString actor but the string is already available on
+ * client, the SimpleStringFront can be used as it shares the same API as a
+ * LongStringFront but will not make unnecessary trips to the server.
+ */
+const SimpleStringFront = Class({
+ initialize: function (str) {
+ this.str = str;
+ },
+
+ get length() {
+ return this.str.length;
+ },
+
+ get initial() {
+ return this.str;
+ },
+
+ string: function () {
+ return promise.resolve(this.str);
+ },
+
+ substring: function (start, end) {
+ return promise.resolve(this.str.substring(start, end));
+ },
+
+ release: function () {
+ this.str = null;
+ return promise.resolve(undefined);
+ }
+});
+
+exports.SimpleStringFront = SimpleStringFront;
+
+// The long string actor needs some custom marshalling, because it is sometimes
+// returned as a primitive rather than a complete form.
+
+var stringActorType = protocol.types.getType("longstractor");
+protocol.types.addType("longstring", {
+ _actor: true,
+ write: (value, context, detail) => {
+ if (!(context instanceof protocol.Actor)) {
+ throw Error("Passing a longstring as an argument isn't supported.");
+ }
+
+ if (value.short) {
+ return value.str;
+ }
+ return stringActorType.write(value, context, detail);
+ },
+ read: (value, context, detail) => {
+ if (context instanceof protocol.Actor) {
+ throw Error("Passing a longstring as an argument isn't supported.");
+ }
+ if (typeof (value) === "string") {
+ return new SimpleStringFront(value);
+ }
+ return stringActorType.read(value, context, detail);
+ }
+});
diff --git a/devtools/shared/specs/styleeditor.js b/devtools/shared/specs/styleeditor.js
new file mode 100644
index 000000000..e93b7e56f
--- /dev/null
+++ b/devtools/shared/specs/styleeditor.js
@@ -0,0 +1,61 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Arg, RetVal, generateActorSpec } = require("devtools/shared/protocol");
+
+const oldStyleSheetSpec = generateActorSpec({
+ typeName: "old-stylesheet",
+
+ events: {
+ "property-change": {
+ type: "propertyChange",
+ property: Arg(0, "string"),
+ value: Arg(1, "json")
+ },
+ "source-load": {
+ type: "sourceLoad",
+ source: Arg(0, "string")
+ },
+ "style-applied": {
+ type: "styleApplied"
+ }
+ },
+
+ methods: {
+ toggleDisabled: {
+ response: { disabled: RetVal("boolean")}
+ },
+ fetchSource: {},
+ update: {
+ request: {
+ text: Arg(0, "string"),
+ transition: Arg(1, "boolean")
+ }
+ }
+ }
+});
+
+exports.oldStyleSheetSpec = oldStyleSheetSpec;
+
+const styleEditorSpec = generateActorSpec({
+ typeName: "styleeditor",
+
+ events: {
+ "document-load": {
+ type: "documentLoad",
+ styleSheets: Arg(0, "array:old-stylesheet")
+ }
+ },
+
+ method: {
+ newDocument: {},
+ newStyleSheet: {
+ request: { text: Arg(0, "string") },
+ response: { styleSheet: RetVal("old-stylesheet") }
+ }
+ }
+});
+
+exports.styleEditorSpec = styleEditorSpec;
diff --git a/devtools/shared/specs/styles.js b/devtools/shared/specs/styles.js
new file mode 100644
index 000000000..225d712a4
--- /dev/null
+++ b/devtools/shared/specs/styles.js
@@ -0,0 +1,206 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+require("devtools/shared/specs/node");
+require("devtools/shared/specs/stylesheets");
+
+// Predeclare the domstylerule actor type
+types.addActorType("domstylerule");
+
+/**
+ * DOM Nodes returned by the style actor will be owned by the DOM walker
+ * for the connection.
+ */
+types.addLifetime("walker", "walker");
+
+/**
+ * When asking for the styles applied to a node, we return a list of
+ * appliedstyle json objects that lists the rules that apply to the node
+ * and which element they were inherited from (if any).
+ *
+ * Note appliedstyle only sends the list of actorIDs and is not a valid return
+ * value on its own. appliedstyle should be returned with the actual list of
+ * StyleRuleActor and StyleSheetActor. See appliedStylesReturn.
+ */
+types.addDictType("appliedstyle", {
+ rule: "domstylerule#actorid",
+ inherited: "nullable:domnode#actorid",
+ keyframes: "nullable:domstylerule#actorid"
+});
+
+types.addDictType("matchedselector", {
+ rule: "domstylerule#actorid",
+ selector: "string",
+ value: "string",
+ status: "number"
+});
+
+types.addDictType("appliedStylesReturn", {
+ entries: "array:appliedstyle",
+ rules: "array:domstylerule",
+ sheets: "array:stylesheet"
+});
+
+types.addDictType("modifiedStylesReturn", {
+ isMatching: RetVal("boolean"),
+ ruleProps: RetVal("nullable:appliedStylesReturn")
+});
+
+types.addDictType("fontpreview", {
+ data: "nullable:longstring",
+ size: "json"
+});
+
+types.addDictType("fontface", {
+ name: "string",
+ CSSFamilyName: "string",
+ rule: "nullable:domstylerule",
+ srcIndex: "number",
+ URI: "string",
+ format: "string",
+ preview: "nullable:fontpreview",
+ localName: "string",
+ metadata: "string"
+});
+
+const pageStyleSpec = generateActorSpec({
+ typeName: "pagestyle",
+
+ events: {
+ "stylesheet-updated": {
+ type: "styleSheetUpdated",
+ styleSheet: Arg(0, "stylesheet")
+ }
+ },
+
+ methods: {
+ getComputed: {
+ request: {
+ node: Arg(0, "domnode"),
+ markMatched: Option(1, "boolean"),
+ onlyMatched: Option(1, "boolean"),
+ filter: Option(1, "string"),
+ },
+ response: {
+ computed: RetVal("json")
+ }
+ },
+ getAllUsedFontFaces: {
+ request: {
+ includePreviews: Option(0, "boolean"),
+ previewText: Option(0, "string"),
+ previewFontSize: Option(0, "string"),
+ previewFillStyle: Option(0, "string")
+ },
+ response: {
+ fontFaces: RetVal("array:fontface")
+ }
+ },
+ getUsedFontFaces: {
+ request: {
+ node: Arg(0, "domnode"),
+ includePreviews: Option(1, "boolean"),
+ previewText: Option(1, "string"),
+ previewFontSize: Option(1, "string"),
+ previewFillStyle: Option(1, "string")
+ },
+ response: {
+ fontFaces: RetVal("array:fontface")
+ }
+ },
+ getMatchedSelectors: {
+ request: {
+ node: Arg(0, "domnode"),
+ property: Arg(1, "string"),
+ filter: Option(2, "string")
+ },
+ response: RetVal(types.addDictType("matchedselectorresponse", {
+ rules: "array:domstylerule",
+ sheets: "array:stylesheet",
+ matched: "array:matchedselector"
+ }))
+ },
+ getApplied: {
+ request: {
+ node: Arg(0, "domnode"),
+ inherited: Option(1, "boolean"),
+ matchedSelectors: Option(1, "boolean"),
+ filter: Option(1, "string")
+ },
+ response: RetVal("appliedStylesReturn")
+ },
+ isPositionEditable: {
+ request: { node: Arg(0, "domnode")},
+ response: { value: RetVal("boolean") }
+ },
+ getLayout: {
+ request: {
+ node: Arg(0, "domnode"),
+ autoMargins: Option(1, "boolean")
+ },
+ response: RetVal("json")
+ },
+ addNewRule: {
+ request: {
+ node: Arg(0, "domnode"),
+ pseudoClasses: Arg(1, "nullable:array:string"),
+ editAuthored: Arg(2, "boolean")
+ },
+ response: RetVal("appliedStylesReturn")
+ }
+ }
+});
+
+exports.pageStyleSpec = pageStyleSpec;
+
+const styleRuleSpec = generateActorSpec({
+ typeName: "domstylerule",
+
+ events: {
+ "location-changed": {
+ type: "locationChanged",
+ line: Arg(0, "number"),
+ column: Arg(1, "number")
+ },
+ },
+
+ methods: {
+ setRuleText: {
+ request: { modification: Arg(0, "string") },
+ response: { rule: RetVal("domstylerule") }
+ },
+ modifyProperties: {
+ request: { modifications: Arg(0, "array:json") },
+ response: { rule: RetVal("domstylerule") }
+ },
+ modifySelector: {
+ request: { selector: Arg(0, "string") },
+ response: { isModified: RetVal("boolean") },
+ },
+ modifySelector2: {
+ request: {
+ node: Arg(0, "domnode"),
+ value: Arg(1, "string"),
+ editAuthored: Arg(2, "boolean")
+ },
+ response: RetVal("modifiedStylesReturn")
+ }
+ }
+});
+
+exports.styleRuleSpec = styleRuleSpec;
+
+// The PageStyle actor flattens the DOM CSS objects a little bit, merging
+// Rules and their Styles into one actor. For elements (which have a style
+// but no associated rule) we fake a rule with the following style id.
+const ELEMENT_STYLE = 100;
+exports.ELEMENT_STYLE = ELEMENT_STYLE;
diff --git a/devtools/shared/specs/stylesheets.js b/devtools/shared/specs/stylesheets.js
new file mode 100644
index 000000000..c89a7c088
--- /dev/null
+++ b/devtools/shared/specs/stylesheets.js
@@ -0,0 +1,120 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+
+const originalSourceSpec = generateActorSpec({
+ typeName: "originalsource",
+
+ methods: {
+ getText: {
+ response: {
+ text: RetVal("longstring")
+ }
+ }
+ }
+});
+
+exports.originalSourceSpec = originalSourceSpec;
+
+const mediaRuleSpec = generateActorSpec({
+ typeName: "mediarule",
+
+ events: {
+ "matches-change": {
+ type: "matchesChange",
+ matches: Arg(0, "boolean"),
+ }
+ }
+});
+
+exports.mediaRuleSpec = mediaRuleSpec;
+
+types.addActorType("stylesheet");
+
+const styleSheetSpec = generateActorSpec({
+ typeName: "stylesheet",
+
+ events: {
+ "property-change": {
+ type: "propertyChange",
+ property: Arg(0, "string"),
+ value: Arg(1, "json")
+ },
+ "style-applied": {
+ type: "styleApplied",
+ kind: Arg(0, "number"),
+ styleSheet: Arg(1, "stylesheet")
+ },
+ "media-rules-changed": {
+ type: "mediaRulesChanged",
+ rules: Arg(0, "array:mediarule")
+ }
+ },
+
+ methods: {
+ toggleDisabled: {
+ response: { disabled: RetVal("boolean")}
+ },
+ getText: {
+ response: {
+ text: RetVal("longstring")
+ }
+ },
+ getOriginalSources: {
+ request: {},
+ response: {
+ originalSources: RetVal("nullable:array:originalsource")
+ }
+ },
+ getOriginalLocation: {
+ request: {
+ line: Arg(0, "number"),
+ column: Arg(1, "number")
+ },
+ response: RetVal(types.addDictType("originallocationresponse", {
+ source: "string",
+ line: "number",
+ column: "number"
+ }))
+ },
+ getMediaRules: {
+ request: {},
+ response: {
+ mediaRules: RetVal("nullable:array:mediarule")
+ }
+ },
+ update: {
+ request: {
+ text: Arg(0, "string"),
+ transition: Arg(1, "boolean")
+ }
+ }
+ }
+});
+
+exports.styleSheetSpec = styleSheetSpec;
+
+const styleSheetsSpec = generateActorSpec({
+ typeName: "stylesheets",
+
+ methods: {
+ getStyleSheets: {
+ request: {},
+ response: { styleSheets: RetVal("array:stylesheet") }
+ },
+ addStyleSheet: {
+ request: { text: Arg(0, "string") },
+ response: { styleSheet: RetVal("stylesheet") }
+ }
+ }
+});
+
+exports.styleSheetsSpec = styleSheetsSpec;
diff --git a/devtools/shared/specs/timeline.js b/devtools/shared/specs/timeline.js
new file mode 100644
index 000000000..653ebef49
--- /dev/null
+++ b/devtools/shared/specs/timeline.js
@@ -0,0 +1,118 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ RetVal,
+ Option,
+ generateActorSpec,
+ types
+} = require("devtools/shared/protocol");
+
+/**
+ * Type representing an array of numbers as strings, serialized fast(er).
+ * http://jsperf.com/json-stringify-parse-vs-array-join-split/3
+ *
+ * XXX: It would be nice if on local connections (only), we could just *give*
+ * the array directly to the front, instead of going through all this
+ * serialization redundancy.
+ */
+types.addType("array-of-numbers-as-strings", {
+ write: (v) => v.join(","),
+ // In Gecko <= 37, `v` is an array; do not transform in this case.
+ read: (v) => typeof v === "string" ? v.split(",") : v
+});
+
+const timelineSpec = generateActorSpec({
+ typeName: "timeline",
+
+ events: {
+ /**
+ * Events emitted when "DOMContentLoaded" and "Load" markers are received.
+ */
+ "doc-loading": {
+ type: "doc-loading",
+ marker: Arg(0, "json"),
+ endTime: Arg(0, "number")
+ },
+
+ /**
+ * The "markers" events emitted every DEFAULT_TIMELINE_DATA_PULL_TIMEOUT ms
+ * at most, when profile markers are found. The timestamps on each marker
+ * are relative to when recording was started.
+ */
+ "markers": {
+ type: "markers",
+ markers: Arg(0, "json"),
+ endTime: Arg(1, "number")
+ },
+
+ /**
+ * The "memory" events emitted in tandem with "markers", if this was enabled
+ * when the recording started. The `delta` timestamp on this measurement is
+ * relative to when recording was started.
+ */
+ "memory": {
+ type: "memory",
+ delta: Arg(0, "number"),
+ measurement: Arg(1, "json")
+ },
+
+ /**
+ * The "ticks" events (from the refresh driver) emitted in tandem with
+ * "markers", if this was enabled when the recording started. All ticks
+ * are timestamps with a zero epoch.
+ */
+ "ticks": {
+ type: "ticks",
+ delta: Arg(0, "number"),
+ timestamps: Arg(1, "array-of-numbers-as-strings")
+ },
+
+ /**
+ * The "frames" events emitted in tandem with "markers", containing
+ * JS stack frames. The `delta` timestamp on this frames packet is
+ * relative to when recording was started.
+ */
+ "frames": {
+ type: "frames",
+ delta: Arg(0, "number"),
+ frames: Arg(1, "json")
+ }
+ },
+
+ methods: {
+ isRecording: {
+ request: {},
+ response: {
+ value: RetVal("boolean")
+ }
+ },
+
+ start: {
+ request: {
+ withMarkers: Option(0, "boolean"),
+ withTicks: Option(0, "boolean"),
+ withMemory: Option(0, "boolean"),
+ withFrames: Option(0, "boolean"),
+ withGCEvents: Option(0, "boolean"),
+ withDocLoadingEvents: Option(0, "boolean")
+ },
+ response: {
+ value: RetVal("number")
+ }
+ },
+
+ stop: {
+ response: {
+ // Set as possibly nullable due to the end time possibly being
+ // undefined during destruction
+ value: RetVal("nullable:number")
+ }
+ },
+ },
+});
+
+exports.timelineSpec = timelineSpec;
diff --git a/devtools/shared/specs/webaudio.js b/devtools/shared/specs/webaudio.js
new file mode 100644
index 000000000..9dfb92467
--- /dev/null
+++ b/devtools/shared/specs/webaudio.js
@@ -0,0 +1,163 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {
+ Arg,
+ Option,
+ RetVal,
+ generateActorSpec,
+ types,
+} = require("devtools/shared/protocol");
+
+exports.NODE_CREATION_METHODS = [
+ "createBufferSource", "createMediaElementSource", "createMediaStreamSource",
+ "createMediaStreamDestination", "createScriptProcessor", "createAnalyser",
+ "createGain", "createDelay", "createBiquadFilter", "createWaveShaper",
+ "createPanner", "createConvolver", "createChannelSplitter", "createChannelMerger",
+ "createDynamicsCompressor", "createOscillator", "createStereoPanner"
+];
+
+exports.AUTOMATION_METHODS = [
+ "setValueAtTime", "linearRampToValueAtTime", "exponentialRampToValueAtTime",
+ "setTargetAtTime", "setValueCurveAtTime", "cancelScheduledValues"
+];
+
+exports.NODE_ROUTING_METHODS = [
+ "connect", "disconnect"
+];
+
+types.addActorType("audionode");
+const audionodeSpec = generateActorSpec({
+ typeName: "audionode",
+
+ methods: {
+ getType: { response: { type: RetVal("string") }},
+ isBypassed: {
+ response: { bypassed: RetVal("boolean") }
+ },
+ bypass: {
+ request: { enable: Arg(0, "boolean") },
+ response: { bypassed: RetVal("boolean") }
+ },
+ setParam: {
+ request: {
+ param: Arg(0, "string"),
+ value: Arg(1, "nullable:primitive")
+ },
+ response: { error: RetVal("nullable:json") }
+ },
+ getParam: {
+ request: {
+ param: Arg(0, "string")
+ },
+ response: { text: RetVal("nullable:primitive") }
+ },
+ getParamFlags: {
+ request: { param: Arg(0, "string") },
+ response: { flags: RetVal("nullable:primitive") }
+ },
+ getParams: {
+ response: { params: RetVal("json") }
+ },
+ connectParam: {
+ request: {
+ destActor: Arg(0, "audionode"),
+ paramName: Arg(1, "string"),
+ output: Arg(2, "nullable:number")
+ },
+ response: { error: RetVal("nullable:json") }
+ },
+ connectNode: {
+ request: {
+ destActor: Arg(0, "audionode"),
+ output: Arg(1, "nullable:number"),
+ input: Arg(2, "nullable:number")
+ },
+ response: { error: RetVal("nullable:json") }
+ },
+ disconnect: {
+ request: { output: Arg(0, "nullable:number") },
+ response: { error: RetVal("nullable:json") }
+ },
+ getAutomationData: {
+ request: { paramName: Arg(0, "string") },
+ response: { values: RetVal("nullable:json") }
+ },
+ addAutomationEvent: {
+ request: {
+ paramName: Arg(0, "string"),
+ eventName: Arg(1, "string"),
+ args: Arg(2, "nullable:json")
+ },
+ response: { error: RetVal("nullable:json") }
+ },
+ }
+});
+
+exports.audionodeSpec = audionodeSpec;
+
+const webAudioSpec = generateActorSpec({
+ typeName: "webaudio",
+
+ /**
+ * Events emitted by this actor.
+ */
+ events: {
+ "start-context": {
+ type: "startContext"
+ },
+ "connect-node": {
+ type: "connectNode",
+ source: Option(0, "audionode"),
+ dest: Option(0, "audionode")
+ },
+ "disconnect-node": {
+ type: "disconnectNode",
+ source: Arg(0, "audionode")
+ },
+ "connect-param": {
+ type: "connectParam",
+ source: Option(0, "audionode"),
+ dest: Option(0, "audionode"),
+ param: Option(0, "string")
+ },
+ "change-param": {
+ type: "changeParam",
+ source: Option(0, "audionode"),
+ param: Option(0, "string"),
+ value: Option(0, "string")
+ },
+ "create-node": {
+ type: "createNode",
+ source: Arg(0, "audionode")
+ },
+ "destroy-node": {
+ type: "destroyNode",
+ source: Arg(0, "audionode")
+ },
+ "automation-event": {
+ type: "automationEvent",
+ node: Option(0, "audionode"),
+ paramName: Option(0, "string"),
+ eventName: Option(0, "string"),
+ args: Option(0, "json")
+ }
+ },
+
+ methods: {
+ getDefinition: {
+ response: { definition: RetVal("json") }
+ },
+ setup: {
+ request: { reload: Option(0, "boolean") },
+ oneway: true
+ },
+ finalize: {
+ oneway: true
+ }
+ }
+});
+
+exports.webAudioSpec = webAudioSpec;
diff --git a/devtools/shared/specs/webgl.js b/devtools/shared/specs/webgl.js
new file mode 100644
index 000000000..f97610f3c
--- /dev/null
+++ b/devtools/shared/specs/webgl.js
@@ -0,0 +1,101 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, Option, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const shaderSpec = generateActorSpec({
+ typeName: "gl-shader",
+
+ methods: {
+ getText: {
+ response: { text: RetVal("string") }
+ },
+ compile: {
+ request: { text: Arg(0, "string") },
+ response: { error: RetVal("nullable:json") }
+ },
+ },
+});
+
+exports.shaderSpec = shaderSpec;
+
+const programSpec = generateActorSpec({
+ typeName: "gl-program",
+
+ methods: {
+ getVertexShader: {
+ response: { shader: RetVal("gl-shader") }
+ },
+ getFragmentShader: {
+ response: { shader: RetVal("gl-shader") }
+ },
+ highlight: {
+ request: { tint: Arg(0, "array:number") },
+ oneway: true
+ },
+ unhighlight: {
+ oneway: true
+ },
+ blackbox: {
+ oneway: true
+ },
+ unblackbox: {
+ oneway: true
+ },
+ }
+});
+
+exports.programSpec = programSpec;
+
+const webGLSpec = generateActorSpec({
+ typeName: "webgl",
+
+ /**
+ * Events emitted by this actor. The "program-linked" event is fired every
+ * time a WebGL program was linked with its respective two shaders.
+ */
+ events: {
+ "program-linked": {
+ type: "programLinked",
+ program: Arg(0, "gl-program")
+ },
+ "global-destroyed": {
+ type: "globalDestroyed",
+ program: Arg(0, "number")
+ },
+ "global-created": {
+ type: "globalCreated",
+ program: Arg(0, "number")
+ }
+ },
+
+ methods: {
+ setup: {
+ request: { reload: Option(0, "boolean") },
+ oneway: true
+ },
+ finalize: {
+ oneway: true
+ },
+ getPrograms: {
+ response: { programs: RetVal("array:gl-program") }
+ },
+ waitForFrame: {
+ response: { success: RetVal("nullable:json") }
+ },
+ getPixel: {
+ request: {
+ selector: Option(0, "string"),
+ position: Option(0, "json")
+ },
+ response: { pixels: RetVal("json") }
+ },
+ _getAllPrograms: {
+ response: { programs: RetVal("array:gl-program") }
+ }
+ }
+});
+
+exports.webGLSpec = webGLSpec;
diff --git a/devtools/shared/specs/worker.js b/devtools/shared/specs/worker.js
new file mode 100644
index 000000000..568f55ec0
--- /dev/null
+++ b/devtools/shared/specs/worker.js
@@ -0,0 +1,78 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const {Arg, RetVal, generateActorSpec} = require("devtools/shared/protocol");
+
+const workerSpec = generateActorSpec({
+ typeName: "worker",
+
+ methods: {
+ attach: {
+ request: {},
+ response: RetVal("json")
+ },
+ detach: {
+ request: {},
+ response: RetVal("json")
+ },
+ connect: {
+ request: {
+ options: Arg(0, "json"),
+ },
+ response: RetVal("json")
+ },
+ push: {
+ request: {},
+ response: RetVal("json")
+ },
+ },
+});
+
+exports.workerSpec = workerSpec;
+
+const pushSubscriptionSpec = generateActorSpec({
+ typeName: "pushSubscription",
+});
+
+exports.pushSubscriptionSpec = pushSubscriptionSpec;
+
+const serviceWorkerRegistrationSpec = generateActorSpec({
+ typeName: "serviceWorkerRegistration",
+
+ events: {
+ "push-subscription-modified": {
+ type: "push-subscription-modified"
+ },
+ "registration-changed": {
+ type: "registration-changed"
+ }
+ },
+
+ methods: {
+ start: {
+ request: {},
+ response: RetVal("json")
+ },
+ unregister: {
+ request: {},
+ response: RetVal("json")
+ },
+ getPushSubscription: {
+ request: {},
+ response: {
+ subscription: RetVal("nullable:pushSubscription")
+ }
+ },
+ },
+});
+
+exports.serviceWorkerRegistrationSpec = serviceWorkerRegistrationSpec;
+
+const serviceWorkerSpec = generateActorSpec({
+ typeName: "serviceWorker",
+});
+
+exports.serviceWorkerSpec = serviceWorkerSpec;
+
diff --git a/devtools/shared/sprintfjs/UPGRADING.md b/devtools/shared/sprintfjs/UPGRADING.md
new file mode 100644
index 000000000..482361f3f
--- /dev/null
+++ b/devtools/shared/sprintfjs/UPGRADING.md
@@ -0,0 +1,12 @@
+SPRINTF JS UPGRADING
+
+Original library at https://github.com/alexei/sprintf.js
+By default the library only supports string placeholders using %s (lowercase) while we use
+%S (uppercase). The library has to be manually patched in order to support it.
+
+- grab the unminified version at https://github.com/alexei/sprintf.js/blob/master/src/sprintf.js
+- update the re.placeholder regexp to allow "S" as well as "s"
+- update the switch statement in the format() method to make case "S" equivalent to case "s"
+
+The original changeset adding support for "%S" can be found on this fork:
+- https://github.com/juliandescottes/sprintf.js/commit/a60ea5d7c4cd9a006002ba9f0afc1e2689107eec \ No newline at end of file
diff --git a/devtools/shared/sprintfjs/moz.build b/devtools/shared/sprintfjs/moz.build
new file mode 100644
index 000000000..6f4f1f873
--- /dev/null
+++ b/devtools/shared/sprintfjs/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'sprintf.js'
+)
diff --git a/devtools/shared/sprintfjs/sprintf.js b/devtools/shared/sprintfjs/sprintf.js
new file mode 100644
index 000000000..8e3344e02
--- /dev/null
+++ b/devtools/shared/sprintfjs/sprintf.js
@@ -0,0 +1,274 @@
+/**
+ * Copyright (c) 2007-2016, Alexandru Marasteanu <hello [at) alexei (dot] ro>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * * Neither the name of this software nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
+
+/* globals window, exports, define */
+
+(function(window) {
+ 'use strict'
+
+ var re = {
+ not_string: /[^s]/,
+ not_bool: /[^t]/,
+ not_type: /[^T]/,
+ not_primitive: /[^v]/,
+ number: /[diefg]/,
+ numeric_arg: /bcdiefguxX/,
+ json: /[j]/,
+ not_json: /[^j]/,
+ text: /^[^\x25]+/,
+ modulo: /^\x25{2}/,
+ placeholder: /^\x25(?:([1-9]\d*)\$|\(([^\)]+)\))?(\+)?(0|'[^$])?(-)?(\d+)?(?:\.(\d+))?([b-gijosStTuvxX])/,
+ key: /^([a-z_][a-z_\d]*)/i,
+ key_access: /^\.([a-z_][a-z_\d]*)/i,
+ index_access: /^\[(\d+)\]/,
+ sign: /^[\+\-]/
+ }
+
+ function sprintf() {
+ var key = arguments[0], cache = sprintf.cache
+ if (!(cache[key] && cache.hasOwnProperty(key))) {
+ cache[key] = sprintf.parse(key)
+ }
+ return sprintf.format.call(null, cache[key], arguments)
+ }
+
+ sprintf.format = function(parse_tree, argv) {
+ var cursor = 1, tree_length = parse_tree.length, node_type = '', arg, output = [], i, k, match, pad, pad_character, pad_length, is_positive = true, sign = ''
+ for (i = 0; i < tree_length; i++) {
+ node_type = get_type(parse_tree[i])
+ if (node_type === 'string') {
+ output[output.length] = parse_tree[i]
+ }
+ else if (node_type === 'array') {
+ match = parse_tree[i] // convenience purposes only
+ if (match[2]) { // keyword argument
+ arg = argv[cursor]
+ for (k = 0; k < match[2].length; k++) {
+ if (!arg.hasOwnProperty(match[2][k])) {
+ throw new Error(sprintf('[sprintf] property "%s" does not exist', match[2][k]))
+ }
+ arg = arg[match[2][k]]
+ }
+ }
+ else if (match[1]) { // positional argument (explicit)
+ arg = argv[match[1]]
+ }
+ else { // positional argument (implicit)
+ arg = argv[cursor++]
+ }
+
+ if (re.not_type.test(match[8]) && re.not_primitive.test(match[8]) && get_type(arg) == 'function') {
+ arg = arg()
+ }
+
+ if (re.numeric_arg.test(match[8]) && (get_type(arg) != 'number' && isNaN(arg))) {
+ throw new TypeError(sprintf("[sprintf] expecting number but found %s", get_type(arg)))
+ }
+
+ if (re.number.test(match[8])) {
+ is_positive = arg >= 0
+ }
+
+ switch (match[8]) {
+ case 'b':
+ arg = parseInt(arg, 10).toString(2)
+ break
+ case 'c':
+ arg = String.fromCharCode(parseInt(arg, 10))
+ break
+ case 'd':
+ case 'i':
+ arg = parseInt(arg, 10)
+ break
+ case 'j':
+ arg = JSON.stringify(arg, null, match[6] ? parseInt(match[6]) : 0)
+ break
+ case 'e':
+ arg = match[7] ? parseFloat(arg).toExponential(match[7]) : parseFloat(arg).toExponential()
+ break
+ case 'f':
+ arg = match[7] ? parseFloat(arg).toFixed(match[7]) : parseFloat(arg)
+ break
+ case 'g':
+ arg = match[7] ? parseFloat(arg).toPrecision(match[7]) : parseFloat(arg)
+ break
+ case 'o':
+ arg = arg.toString(8)
+ break
+ case 's':
+ case 'S':
+ arg = String(arg)
+ arg = (match[7] ? arg.substring(0, match[7]) : arg)
+ break
+ case 't':
+ arg = String(!!arg)
+ arg = (match[7] ? arg.substring(0, match[7]) : arg)
+ break
+ case 'T':
+ arg = get_type(arg)
+ arg = (match[7] ? arg.substring(0, match[7]) : arg)
+ break
+ case 'u':
+ arg = parseInt(arg, 10) >>> 0
+ break
+ case 'v':
+ arg = arg.valueOf()
+ arg = (match[7] ? arg.substring(0, match[7]) : arg)
+ break
+ case 'x':
+ arg = parseInt(arg, 10).toString(16)
+ break
+ case 'X':
+ arg = parseInt(arg, 10).toString(16).toUpperCase()
+ break
+ }
+ if (re.json.test(match[8])) {
+ output[output.length] = arg
+ }
+ else {
+ if (re.number.test(match[8]) && (!is_positive || match[3])) {
+ sign = is_positive ? '+' : '-'
+ arg = arg.toString().replace(re.sign, '')
+ }
+ else {
+ sign = ''
+ }
+ pad_character = match[4] ? match[4] === '0' ? '0' : match[4].charAt(1) : ' '
+ pad_length = match[6] - (sign + arg).length
+ pad = match[6] ? (pad_length > 0 ? str_repeat(pad_character, pad_length) : '') : ''
+ output[output.length] = match[5] ? sign + arg + pad : (pad_character === '0' ? sign + pad + arg : pad + sign + arg)
+ }
+ }
+ }
+ return output.join('')
+ }
+
+ sprintf.cache = {}
+
+ sprintf.parse = function(fmt) {
+ var _fmt = fmt, match = [], parse_tree = [], arg_names = 0
+ while (_fmt) {
+ if ((match = re.text.exec(_fmt)) !== null) {
+ parse_tree[parse_tree.length] = match[0]
+ }
+ else if ((match = re.modulo.exec(_fmt)) !== null) {
+ parse_tree[parse_tree.length] = '%'
+ }
+ else if ((match = re.placeholder.exec(_fmt)) !== null) {
+ if (match[2]) {
+ arg_names |= 1
+ var field_list = [], replacement_field = match[2], field_match = []
+ if ((field_match = re.key.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1]
+ while ((replacement_field = replacement_field.substring(field_match[0].length)) !== '') {
+ if ((field_match = re.key_access.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1]
+ }
+ else if ((field_match = re.index_access.exec(replacement_field)) !== null) {
+ field_list[field_list.length] = field_match[1]
+ }
+ else {
+ throw new SyntaxError("[sprintf] failed to parse named argument key")
+ }
+ }
+ }
+ else {
+ throw new SyntaxError("[sprintf] failed to parse named argument key")
+ }
+ match[2] = field_list
+ }
+ else {
+ arg_names |= 2
+ }
+ if (arg_names === 3) {
+ throw new Error("[sprintf] mixing positional and named placeholders is not (yet) supported")
+ }
+ parse_tree[parse_tree.length] = match
+ }
+ else {
+ throw new SyntaxError("[sprintf] unexpected placeholder")
+ }
+ _fmt = _fmt.substring(match[0].length)
+ }
+ return parse_tree
+ }
+
+ var vsprintf = function(fmt, argv, _argv) {
+ _argv = (argv || []).slice(0)
+ _argv.splice(0, 0, fmt)
+ return sprintf.apply(null, _argv)
+ }
+
+ /**
+ * helpers
+ */
+ function get_type(variable) {
+ if (typeof variable === 'number') {
+ return 'number'
+ }
+ else if (typeof variable === 'string') {
+ return 'string'
+ }
+ else {
+ return Object.prototype.toString.call(variable).slice(8, -1).toLowerCase()
+ }
+ }
+
+ var preformattedPadding = {
+ '0': ['', '0', '00', '000', '0000', '00000', '000000', '0000000'],
+ ' ': ['', ' ', ' ', ' ', ' ', ' ', ' ', ' '],
+ '_': ['', '_', '__', '___', '____', '_____', '______', '_______'],
+ }
+ function str_repeat(input, multiplier) {
+ if (multiplier >= 0 && multiplier <= 7 && preformattedPadding[input]) {
+ return preformattedPadding[input][multiplier]
+ }
+ return Array(multiplier + 1).join(input)
+ }
+
+ /**
+ * export to either browser or node.js
+ */
+ if (typeof exports !== 'undefined') {
+ exports.sprintf = sprintf
+ exports.vsprintf = vsprintf
+ }
+ else {
+ window.sprintf = sprintf
+ window.vsprintf = vsprintf
+
+ if (typeof define === 'function' && define.amd) {
+ define(function() {
+ return {
+ sprintf: sprintf,
+ vsprintf: vsprintf
+ }
+ })
+ }
+ }
+})(typeof window === 'undefined' ? this : window);
diff --git a/devtools/shared/system.js b/devtools/shared/system.js
new file mode 100644
index 000000000..ae46016e7
--- /dev/null
+++ b/devtools/shared/system.js
@@ -0,0 +1,339 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const { Task } = require("devtools/shared/task");
+
+loader.lazyRequireGetter(this, "Services");
+loader.lazyRequireGetter(this, "promise");
+loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
+loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os.js");
+loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
+loader.lazyRequireGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm", true);
+loader.lazyGetter(this, "screenManager", () => {
+ return Cc["@mozilla.org/gfx/screenmanager;1"].getService(Ci.nsIScreenManager);
+});
+loader.lazyGetter(this, "oscpu", () => {
+ return Cc["@mozilla.org/network/protocol;1?name=http"]
+ .getService(Ci.nsIHttpProtocolHandler).oscpu;
+});
+
+const APP_MAP = {
+ "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}": "firefox",
+ "{3550f703-e582-4d05-9a08-453d09bdfdc6}": "thunderbird",
+ "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}": "seamonkey",
+ "{718e30fb-e89b-41dd-9da7-e25a45638b28}": "sunbird",
+ "{3c2e2abc-06d4-11e1-ac3b-374f68613e61}": "b2g",
+ "{aa3c5121-dab2-40e2-81ca-7ea25febc110}": "mobile/android",
+ "{a23983c0-fd0e-11dc-95ff-0800200c9a66}": "mobile/xul"
+};
+
+var CACHED_INFO = null;
+
+function* getSystemInfo() {
+ if (CACHED_INFO) {
+ return CACHED_INFO;
+ }
+
+ let appInfo = Services.appinfo;
+ let win = Services.wm.getMostRecentWindow(DebuggerServer.chromeWindowType);
+ let [processor, compiler] = appInfo.XPCOMABI.split("-");
+ let dpi,
+ useragent,
+ width,
+ height,
+ physicalWidth,
+ physicalHeight,
+ os,
+ brandName;
+ let appid = appInfo.ID;
+ let apptype = APP_MAP[appid];
+ let geckoVersion = appInfo.platformVersion;
+ let hardware = "unknown";
+ let version = "unknown";
+
+ // B2G specific
+ if (apptype === "b2g") {
+ os = "B2G";
+ // `getSetting` does not work in child processes on b2g.
+ // TODO bug 1205797, make this work in child processes.
+ try {
+ hardware = yield exports.getSetting("deviceinfo.hardware");
+ version = yield exports.getSetting("deviceinfo.os");
+ } catch (e) {
+ }
+ }
+ // Not B2G
+ else {
+ os = appInfo.OS;
+ version = appInfo.version;
+ }
+
+ let bundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
+ if (bundle) {
+ brandName = bundle.GetStringFromName("brandFullName");
+ } else {
+ brandName = null;
+ }
+
+ if (win) {
+ let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+ dpi = utils.displayDPI;
+ useragent = win.navigator.userAgent;
+ width = win.screen.width;
+ height = win.screen.height;
+ physicalWidth = win.screen.width * win.devicePixelRatio;
+ physicalHeight = win.screen.height * win.devicePixelRatio;
+ }
+
+ let info = {
+
+ /**
+ * Information from nsIXULAppInfo, regarding
+ * the application itself.
+ */
+
+ // The XUL application's UUID.
+ appid,
+
+ // Name of the app, "firefox", "thunderbird", etc., listed in APP_MAP
+ apptype,
+
+ // Mixed-case or empty string of vendor, like "Mozilla"
+ vendor: appInfo.vendor,
+
+ // Name of the application, like "Firefox", "Thunderbird".
+ name: appInfo.name,
+
+ // The application's version, for example "0.8.0+" or "3.7a1pre".
+ // Typically, the version of Firefox, for example.
+ // It is different than the version of Gecko or the XULRunner platform.
+ // On B2G, this is the Gaia version.
+ version,
+
+ // The application's build ID/date, for example "2004051604".
+ appbuildid: appInfo.appBuildID,
+
+ // The application's changeset.
+ changeset: exports.getAppIniString("App", "SourceStamp"),
+
+ // The build ID/date of Gecko and the XULRunner platform.
+ platformbuildid: appInfo.platformBuildID,
+ geckobuildid: appInfo.platformBuildID,
+
+ // The version of Gecko or XULRunner platform, for example "1.8.1.19" or
+ // "1.9.3pre". In "Firefox 3.7 alpha 1" the application version is "3.7a1pre"
+ // while the platform version is "1.9.3pre"
+ platformversion: geckoVersion,
+ geckoversion: geckoVersion,
+
+ // Locale used in this build
+ locale: Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"),
+
+ /**
+ * Information regarding the operating system.
+ */
+
+ // Returns the endianness of the architecture: either "LE" or "BE"
+ endianness: OS.endianness(),
+
+ // Returns the hostname of the machine
+ hostname: OS.hostname(),
+
+ // Name of the OS type. Typically the same as `uname -s`. Possible values:
+ // https://developer.mozilla.org/en/OS_TARGET
+ // Also may be "B2G".
+ os,
+ platform: os,
+
+ // hardware and version info from `deviceinfo.hardware`
+ // and `deviceinfo.os`.
+ hardware,
+
+ // Type of process architecture running:
+ // "arm", "ia32", "x86", "x64"
+ // Alias to both `arch` and `processor` for node/deviceactor compat
+ arch: processor,
+ processor,
+
+ // Name of compiler used for build:
+ // `'msvc', 'n32', 'gcc2', 'gcc3', 'sunc', 'ibmc'...`
+ compiler,
+
+ // Location for the current profile
+ profile: getProfileLocation(),
+
+ // Update channel
+ channel: AppConstants.MOZ_UPDATE_CHANNEL,
+
+ dpi,
+ useragent,
+ width,
+ height,
+ physicalWidth,
+ physicalHeight,
+ brandName,
+ };
+
+ CACHED_INFO = info;
+ return info;
+}
+
+function getProfileLocation() {
+ // In child processes, we cannot access the profile location.
+ try {
+ let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
+ let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
+ var profiles = profservice.profiles;
+ while (profiles.hasMoreElements()) {
+ let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
+ if (profile.rootDir.path == profd.path) {
+ return profile = profile.name;
+ }
+ }
+
+ return profd.leafName;
+ } catch (e) {
+ return "";
+ }
+}
+
+function getAppIniString(section, key) {
+ let inifile = Services.dirsvc.get("GreD", Ci.nsIFile);
+ inifile.append("application.ini");
+
+ if (!inifile.exists()) {
+ inifile = Services.dirsvc.get("CurProcD", Ci.nsIFile);
+ inifile.append("application.ini");
+ }
+
+ if (!inifile.exists()) {
+ return undefined;
+ }
+
+ let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(Ci.nsIINIParserFactory).createINIParser(inifile);
+ try {
+ return iniParser.getString(section, key);
+ } catch (e) {
+ return undefined;
+ }
+}
+
+/**
+ * Function for fetching screen dimensions and returning
+ * an enum for Telemetry.
+ */
+function getScreenDimensions() {
+ let width = {};
+ let height = {};
+
+ screenManager.primaryScreen.GetRect({}, {}, width, height);
+ let dims = width.value + "x" + height.value;
+
+ if (width.value < 800 || height.value < 600) {
+ return 0;
+ }
+ if (dims === "800x600") {
+ return 1;
+ }
+ if (dims === "1024x768") {
+ return 2;
+ }
+ if (dims === "1280x800") {
+ return 3;
+ }
+ if (dims === "1280x1024") {
+ return 4;
+ }
+ if (dims === "1366x768") {
+ return 5;
+ }
+ if (dims === "1440x900") {
+ return 6;
+ }
+ if (dims === "1920x1080") {
+ return 7;
+ }
+ if (dims === "2560×1440") {
+ return 8;
+ }
+ if (dims === "2560×1600") {
+ return 9;
+ }
+ if (dims === "2880x1800") {
+ return 10;
+ }
+ if (width.value > 2880 || height.value > 1800) {
+ return 12;
+ }
+
+ // Other dimension such as a VM.
+ return 11;
+}
+
+/**
+ * Function for fetching OS CPU and returning
+ * an enum for Telemetry.
+ */
+function getOSCPU() {
+ if (oscpu.includes("NT 5.1") || oscpu.includes("NT 5.2")) {
+ return 0;
+ }
+ if (oscpu.includes("NT 6.0")) {
+ return 1;
+ }
+ if (oscpu.includes("NT 6.1")) {
+ return 2;
+ }
+ if (oscpu.includes("NT 6.2")) {
+ return 3;
+ }
+ if (oscpu.includes("NT 6.3")) {
+ return 4;
+ }
+ if (oscpu.includes("OS X")) {
+ return 5;
+ }
+ if (oscpu.includes("Linux")) {
+ return 6;
+ }
+ if (oscpu.includes("NT 10.")) {
+ return 7;
+ }
+ // Other OS.
+ return 12;
+}
+
+function getSetting(name) {
+ let deferred = defer();
+
+ if ("@mozilla.org/settingsService;1" in Cc) {
+ let settingsService;
+
+ // settingsService fails in b2g child processes
+ // TODO bug 1205797, make this work in child processes.
+ try {
+ settingsService = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
+ } catch (e) {
+ return promise.reject(e);
+ }
+
+ let req = settingsService.createLock().get(name, {
+ handle: (name, value) => deferred.resolve(value),
+ handleError: (error) => deferred.reject(error),
+ });
+ } else {
+ deferred.reject(new Error("No settings service"));
+ }
+ return deferred.promise;
+}
+
+exports.getSystemInfo = Task.async(getSystemInfo);
+exports.getAppIniString = getAppIniString;
+exports.getSetting = getSetting;
+exports.getScreenDimensions = getScreenDimensions;
+exports.getOSCPU = getOSCPU;
+exports.constants = AppConstants;
diff --git a/devtools/shared/task.js b/devtools/shared/task.js
new file mode 100644
index 000000000..501e05e5b
--- /dev/null
+++ b/devtools/shared/task.js
@@ -0,0 +1,515 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80 filetype=javascript: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* eslint-disable spaced-comment */
+/* globals StopIteration */
+
+/**
+ * This module implements a subset of "Task.js" <http://taskjs.org/>.
+ * It is a copy of toolkit/modules/Task.jsm. Please try not to
+ * diverge the API here.
+ *
+ * Paraphrasing from the Task.js site, tasks make sequential, asynchronous
+ * operations simple, using the power of JavaScript's "yield" operator.
+ *
+ * Tasks are built upon generator functions and promises, documented here:
+ *
+ * <https://developer.mozilla.org/en/JavaScript/Guide/Iterators_and_Generators>
+ * <http://wiki.commonjs.org/wiki/Promises/A>
+ *
+ * The "Task.spawn" function takes a generator function and starts running it as
+ * a task. Every time the task yields a promise, it waits until the promise is
+ * fulfilled. "Task.spawn" returns a promise that is resolved when the task
+ * completes successfully, or is rejected if an exception occurs.
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * const {Task} = require("devtools/shared/task");
+ *
+ * Task.spawn(function* () {
+ *
+ * // This is our task. Let's create a promise object, wait on it and capture
+ * // its resolution value.
+ * let myPromise = getPromiseResolvedOnTimeoutWithValue(1000, "Value");
+ * let result = yield myPromise;
+ *
+ * // This part is executed only after the promise above is fulfilled (after
+ * // one second, in this imaginary example). We can easily loop while
+ * // calling asynchronous functions, and wait multiple times.
+ * for (let i = 0; i < 3; i++) {
+ * result += yield getPromiseResolvedOnTimeoutWithValue(50, "!");
+ * }
+ *
+ * return "Resolution result for the task: " + result;
+ * }).then(function (result) {
+ *
+ * // result == "Resolution result for the task: Value!!!"
+ *
+ * // The result is undefined if no value was returned.
+ *
+ * }, function (exception) {
+ *
+ * // Failure! We can inspect or report the exception.
+ *
+ * });
+ *
+ * -----------------------------------------------------------------------------
+ *
+ * This module implements only the "Task.js" interfaces described above, with no
+ * additional features to control the task externally, or do custom scheduling.
+ * It also provides the following extensions that simplify task usage in the
+ * most common cases:
+ *
+ * - The "Task.spawn" function also accepts an iterator returned by a generator
+ * function, in addition to a generator function. This way, you can call into
+ * the generator function with the parameters you want, and with "this" bound
+ * to the correct value. Also, "this" is never bound to the task object when
+ * "Task.spawn" calls the generator function.
+ *
+ * - In addition to a promise object, a task can yield the iterator returned by
+ * a generator function. The iterator is turned into a task automatically.
+ * This reduces the syntax overhead of calling "Task.spawn" explicitly when
+ * you want to recurse into other task functions.
+ *
+ * - The "Task.spawn" function also accepts a primitive value, or a function
+ * returning a primitive value, and treats the value as the result of the
+ * task. This makes it possible to call an externally provided function and
+ * spawn a task from it, regardless of whether it is an asynchronous generator
+ * or a synchronous function. This comes in handy when iterating over
+ * function lists where some items have been converted to tasks and some not.
+ */
+
+////////////////////////////////////////////////////////////////////////////////
+//// Globals
+
+const Promise = require("promise");
+const defer = require("devtools/shared/defer");
+
+// The following error types are considered programmer errors, which should be
+// reported (possibly redundantly) so as to let programmers fix their code.
+const ERRORS_TO_REPORT = ["EvalError", "RangeError", "ReferenceError",
+ "TypeError"];
+
+/**
+ * The Task currently being executed
+ */
+var gCurrentTask = null;
+
+/**
+ * If `true`, capture stacks whenever entering a Task and rewrite the
+ * stack any exception thrown through a Task.
+ */
+var gMaintainStack = false;
+
+/**
+ * Iterate through the lines of a string.
+ *
+ * @return Iterator<string>
+ */
+function* linesOf(string) {
+ let reLine = /([^\r\n])+/g;
+ let match;
+ while ((match = reLine.exec(string))) {
+ yield [match[0], match.index];
+ }
+}
+
+/**
+ * Detect whether a value is a generator.
+ *
+ * @param aValue
+ * The value to identify.
+ * @return A boolean indicating whether the value is a generator.
+ */
+function isGenerator(value) {
+ return Object.prototype.toString.call(value) == "[object Generator]";
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// Task
+
+/**
+ * This object provides the public module functions.
+ */
+var Task = {
+ /**
+ * Creates and starts a new task.
+ *
+ * @param task
+ * - If you specify a generator function, it is called with no
+ * arguments to retrieve the associated iterator. The generator
+ * function is a task, that is can yield promise objects to wait
+ * upon.
+ * - If you specify the iterator returned by a generator function you
+ * called, the generator function is also executed as a task. This
+ * allows you to call the function with arguments.
+ * - If you specify a function that is not a generator, it is called
+ * with no arguments, and its return value is used to resolve the
+ * returned promise.
+ * - If you specify anything else, you get a promise that is already
+ * resolved with the specified value.
+ *
+ * @return A promise object where you can register completion callbacks to be
+ * called when the task terminates.
+ */
+ spawn: function (task) {
+ return createAsyncFunction(task).call(undefined);
+ },
+
+ /**
+ * Create and return an 'async function' that starts a new task.
+ *
+ * This is similar to 'spawn' except that it doesn't immediately start
+ * the task, it binds the task to the async function's 'this' object and
+ * arguments, and it requires the task to be a function.
+ *
+ * It simplifies the common pattern of implementing a method via a task,
+ * like this simple object with a 'greet' method that has a 'name' parameter
+ * and spawns a task to send a greeting and return its reply:
+ *
+ * let greeter = {
+ * message: "Hello, NAME!",
+ * greet: function(name) {
+ * return Task.spawn((function* () {
+ * return yield sendGreeting(this.message.replace(/NAME/, name));
+ * }).bind(this);
+ * })
+ * };
+ *
+ * With Task.async, the method can be declared succinctly:
+ *
+ * let greeter = {
+ * message: "Hello, NAME!",
+ * greet: Task.async(function* (name) {
+ * return yield sendGreeting(this.message.replace(/NAME/, name));
+ * })
+ * };
+ *
+ * While maintaining identical semantics:
+ *
+ * greeter.greet("Mitchell").then((reply) => { ... }); // behaves the same
+ *
+ * @param task
+ * The task function to start.
+ *
+ * @return A function that starts the task function and returns its promise.
+ */
+ async: function (task) {
+ if (typeof (task) != "function") {
+ throw new TypeError("task argument must be a function");
+ }
+
+ return createAsyncFunction(task);
+ },
+
+ /**
+ * Constructs a special exception that, when thrown inside a legacy generator
+ * function (non-star generator), allows the associated task to be resolved
+ * with a specific value.
+ *
+ * Example: throw new Task.Result("Value");
+ */
+ Result: function (value) {
+ this.value = value;
+ }
+};
+
+function createAsyncFunction(task) {
+ let asyncFunction = function () {
+ let result = task;
+ if (task && typeof (task) == "function") {
+ if (task.isAsyncFunction) {
+ throw new TypeError(
+ "Cannot use an async function in place of a promise. " +
+ "You should either invoke the async function first " +
+ "or use 'Task.spawn' instead of 'Task.async' to start " +
+ "the Task and return its promise.");
+ }
+
+ try {
+ // Let's call into the function ourselves.
+ result = task.apply(this, arguments);
+ } catch (ex) {
+ if (ex instanceof Task.Result) {
+ return Promise.resolve(ex.value);
+ }
+ return Promise.reject(ex);
+ }
+ }
+
+ if (isGenerator(result)) {
+ // This is an iterator resulting from calling a generator function.
+ return new TaskImpl(result).deferred.promise;
+ }
+
+ // Just propagate the given value to the caller as a resolved promise.
+ return Promise.resolve(result);
+ };
+
+ asyncFunction.isAsyncFunction = true;
+
+ return asyncFunction;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+//// TaskImpl
+
+/**
+ * Executes the specified iterator as a task, and gives access to the promise
+ * that is fulfilled when the task terminates.
+ */
+function TaskImpl(iterator) {
+ if (gMaintainStack) {
+ this._stack = (new Error()).stack;
+ }
+ this.deferred = defer();
+ this._iterator = iterator;
+ this._isStarGenerator = !("send" in iterator);
+ this._run(true);
+}
+
+TaskImpl.prototype = {
+ /**
+ * Includes the promise object where task completion callbacks are registered,
+ * and methods to resolve or reject the promise at task completion.
+ */
+ deferred: null,
+
+ /**
+ * The iterator returned by the generator function associated with this task.
+ */
+ _iterator: null,
+
+ /**
+ * Whether this Task is using a star generator.
+ */
+ _isStarGenerator: false,
+
+ /**
+ * Main execution routine, that calls into the generator function.
+ *
+ * @param sendResolved
+ * If true, indicates that we should continue into the generator
+ * function regularly (if we were waiting on a promise, it was
+ * resolved). If true, indicates that we should cause an exception to
+ * be thrown into the generator function (if we were waiting on a
+ * promise, it was rejected).
+ * @param sendValue
+ * Resolution result or rejection exception, if any.
+ */
+ _run: function (sendResolved, sendValue) {
+ try {
+ gCurrentTask = this;
+
+ if (this._isStarGenerator) {
+ try {
+ let result = sendResolved ? this._iterator.next(sendValue)
+ : this._iterator.throw(sendValue);
+
+ if (result.done) {
+ // The generator function returned.
+ this.deferred.resolve(result.value);
+ } else {
+ // The generator function yielded.
+ this._handleResultValue(result.value);
+ }
+ } catch (ex) {
+ // The generator function failed with an uncaught exception.
+ this._handleException(ex);
+ }
+ } else {
+ try {
+ let yielded = sendResolved ? this._iterator.send(sendValue)
+ : this._iterator.throw(sendValue);
+ this._handleResultValue(yielded);
+ } catch (ex) {
+ if (ex instanceof Task.Result) {
+ // The generator function threw the special exception that
+ // allows it to return a specific value on resolution.
+ this.deferred.resolve(ex.value);
+ } else if (ex instanceof StopIteration) {
+ // The generator function terminated with no specific result.
+ this.deferred.resolve(undefined);
+ } else {
+ // The generator function failed with an uncaught exception.
+ this._handleException(ex);
+ }
+ }
+ }
+ } finally {
+ //
+ // At this stage, the Task may have finished executing, or have
+ // walked through a `yield` or passed control to a sub-Task.
+ // Regardless, if we still own `gCurrentTask`, reset it. If we
+ // have not finished execution of this Task, re-entering `_run`
+ // will set `gCurrentTask` to `this` as needed.
+ //
+ // We just need to be careful here in case we hit the following
+ // pattern:
+ //
+ // Task.spawn(foo);
+ // Task.spawn(bar);
+ //
+ // Here, `foo` and `bar` may be interleaved, so when we finish
+ // executing `foo`, `gCurrentTask` may actually either `foo` or
+ // `bar`. If `gCurrentTask` has already been set to `bar`, leave
+ // it be and it will be reset to `null` once `bar` is complete.
+ //
+ if (gCurrentTask == this) {
+ gCurrentTask = null;
+ }
+ }
+ },
+
+ /**
+ * Handle a value yielded by a generator.
+ *
+ * @param value
+ * The yielded value to handle.
+ */
+ _handleResultValue: function (value) {
+ // If our task yielded an iterator resulting from calling another
+ // generator function, automatically spawn a task from it, effectively
+ // turning it into a promise that is fulfilled on task completion.
+ if (isGenerator(value)) {
+ value = Task.spawn(value);
+ }
+
+ if (value && typeof (value.then) == "function") {
+ // We have a promise object now. When fulfilled, call again into this
+ // function to continue the task, with either a resolution or rejection
+ // condition.
+ value.then(this._run.bind(this, true),
+ this._run.bind(this, false));
+ } else {
+ // If our task yielded a value that is not a promise, just continue and
+ // pass it directly as the result of the yield statement.
+ this._run(true, value);
+ }
+ },
+
+ /**
+ * Handle an uncaught exception thrown from a generator.
+ *
+ * @param exception
+ * The uncaught exception to handle.
+ */
+ _handleException: function (exception) {
+ gCurrentTask = this;
+
+ if (exception && typeof exception == "object" && "stack" in exception) {
+ let stack = exception.stack;
+
+ if (gMaintainStack &&
+ exception._capturedTaskStack != this._stack &&
+ typeof stack == "string") {
+ // Rewrite the stack for more readability.
+
+ let bottomStack = this._stack;
+
+ stack = Task.Debugging.generateReadableStack(stack);
+
+ exception.stack = stack;
+
+ // If exception is reinjected in the same task and rethrown,
+ // we don't want to perform the rewrite again.
+ exception._capturedTaskStack = bottomStack;
+ } else if (!stack) {
+ stack = "Not available";
+ }
+
+ if ("name" in exception &&
+ ERRORS_TO_REPORT.indexOf(exception.name) != -1) {
+ // We suspect that the exception is a programmer error, so we now
+ // display it using dump(). Note that we do not use Cu.reportError as
+ // we assume that this is a programming error, so we do not want end
+ // users to see it. Also, if the programmer handles errors correctly,
+ // they will either treat the error or log them somewhere.
+
+ dump("*************************\n");
+ dump("A coding exception was thrown and uncaught in a Task.\n\n");
+ dump("Full message: " + exception + "\n");
+ dump("Full stack: " + exception.stack + "\n");
+ dump("*************************\n");
+ }
+ }
+
+ this.deferred.reject(exception);
+ },
+
+ get callerStack() {
+ // Cut `this._stack` at the last line of the first block that
+ // contains task.js, keep the tail.
+ for (let [line, index] of linesOf(this._stack || "")) {
+ if (line.indexOf("/task.js:") == -1) {
+ return this._stack.substring(index);
+ }
+ }
+ return "";
+ }
+};
+
+Task.Debugging = {
+
+ /**
+ * Control stack rewriting.
+ *
+ * If `true`, any exception thrown from a Task will be rewritten to
+ * provide a human-readable stack trace. Otherwise, stack traces will
+ * be left unchanged.
+ *
+ * There is a (small but existing) runtime cost associated to stack
+ * rewriting, so you should probably not activate this in production
+ * code.
+ *
+ * @type {bool}
+ */
+ get maintainStack() {
+ return gMaintainStack;
+ },
+ set maintainStack(x) {
+ if (!x) {
+ gCurrentTask = null;
+ }
+ gMaintainStack = x;
+ return x;
+ },
+
+ /**
+ * Generate a human-readable stack for an error raised in
+ * a Task.
+ *
+ * @param {string} topStack The stack provided by the error.
+ * @param {string=} prefix Optionally, a prefix for each line.
+ */
+ generateReadableStack: function (topStack, prefix = "") {
+ if (!gCurrentTask) {
+ return topStack;
+ }
+
+ // Cut `topStack` at the first line that contains task.js, keep the head.
+ let lines = [];
+ for (let [line] of linesOf(topStack)) {
+ if (line.indexOf("/task.js:") != -1) {
+ break;
+ }
+ lines.push(prefix + line);
+ }
+ if (!prefix) {
+ lines.push(gCurrentTask.callerStack);
+ } else {
+ for (let [line] of linesOf(gCurrentTask.callerStack)) {
+ lines.push(prefix + line);
+ }
+ }
+
+ return lines.join("\n");
+ }
+};
+
+exports.Task = Task;
diff --git a/devtools/shared/tests/browser/.eslintrc.js b/devtools/shared/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..8d15a76d9
--- /dev/null
+++ b/devtools/shared/tests/browser/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/shared/tests/browser/browser.ini b/devtools/shared/tests/browser/browser.ini
new file mode 100644
index 000000000..4acac6521
--- /dev/null
+++ b/devtools/shared/tests/browser/browser.ini
@@ -0,0 +1,8 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ ../../../server/tests/browser/head.js
+
+[browser_async_storage.js]
+[browser_l10n_localizeMarkup.js]
diff --git a/devtools/shared/tests/browser/browser_async_storage.js b/devtools/shared/tests/browser/browser_async_storage.js
new file mode 100644
index 000000000..4329d639a
--- /dev/null
+++ b/devtools/shared/tests/browser/browser_async_storage.js
@@ -0,0 +1,77 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test the basic functionality of async-storage.
+// Adapted from https://github.com/mozilla-b2g/gaia/blob/f09993563fb5fec4393eb71816ce76cb00463190/apps/sharedtest/test/unit/async_storage_test.js.
+
+const asyncStorage = require("devtools/shared/async-storage");
+add_task(function* () {
+ is(typeof asyncStorage.length, "function", "API exists.");
+ is(typeof asyncStorage.key, "function", "API exists.");
+ is(typeof asyncStorage.getItem, "function", "API exists.");
+ is(typeof asyncStorage.setItem, "function", "API exists.");
+ is(typeof asyncStorage.removeItem, "function", "API exists.");
+ is(typeof asyncStorage.clear, "function", "API exists.");
+});
+
+add_task(function* () {
+ yield asyncStorage.setItem("foo", "bar");
+ let value = yield asyncStorage.getItem("foo");
+ is(value, "bar", "value is correct");
+ yield asyncStorage.setItem("foo", "overwritten");
+ value = yield asyncStorage.getItem("foo");
+ is(value, "overwritten", "value is correct");
+ yield asyncStorage.removeItem("foo");
+ value = yield asyncStorage.getItem("foo");
+ is(value, null, "value is correct");
+});
+
+add_task(function* () {
+ var object = {
+ x: 1,
+ y: "foo",
+ z: true
+ };
+
+ yield asyncStorage.setItem("myobj", object);
+ let value = yield asyncStorage.getItem("myobj");
+ is(object.x, value.x, "value is correct");
+ is(object.y, value.y, "value is correct");
+ is(object.z, value.z, "value is correct");
+ yield asyncStorage.removeItem("myobj");
+ value = yield asyncStorage.getItem("myobj");
+ is(value, null, "value is correct");
+});
+
+add_task(function* () {
+ yield asyncStorage.clear();
+ let len = yield asyncStorage.length();
+ is(len, 0, "length is correct");
+ yield asyncStorage.setItem("key1", "value1");
+ len = yield asyncStorage.length();
+ is(len, 1, "length is correct");
+ yield asyncStorage.setItem("key2", "value2");
+ len = yield asyncStorage.length();
+ is(len, 2, "length is correct");
+ yield asyncStorage.setItem("key3", "value3");
+ len = yield asyncStorage.length();
+ is(len, 3, "length is correct");
+
+ let key = yield asyncStorage.key(0);
+ is(key, "key1", "key is correct");
+ key = yield asyncStorage.key(1);
+ is(key, "key2", "key is correct");
+ key = yield asyncStorage.key(2);
+ is(key, "key3", "key is correct");
+ key = yield asyncStorage.key(3);
+ is(key, null, "key is correct");
+ yield asyncStorage.clear();
+ key = yield asyncStorage.key(0);
+ is(key, null, "key is correct");
+
+ len = yield asyncStorage.length();
+ is(len, 0, "length is correct");
+});
diff --git a/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js b/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
new file mode 100644
index 000000000..f33a5a331
--- /dev/null
+++ b/devtools/shared/tests/browser/browser_l10n_localizeMarkup.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the markup localization works properly.
+
+const { localizeMarkup, LocalizationHelper } = require("devtools/shared/l10n");
+
+add_task(function* () {
+ info("Check that the strings used for this test are still valid");
+ let STARTUP_L10N = new LocalizationHelper("devtools/client/locales/startup.properties");
+ let TOOLBOX_L10N = new LocalizationHelper("devtools/client/locales/toolbox.properties");
+ let str1 = STARTUP_L10N.getStr("inspector.label");
+ let str2 = STARTUP_L10N.getStr("inspector.commandkey");
+ let str3 = TOOLBOX_L10N.getStr("toolbox.defaultTitle");
+ ok(str1 && str2 && str3, "If this failed, strings should be updated in the test");
+
+ info("Create the test markup");
+ let div = document.createElement("div");
+ div.innerHTML =
+ `<div data-localization-bundle="devtools/client/locales/startup.properties">
+ <div id="d0" data-localization="content=inspector.someInvalidKey"></div>
+ <div id="d1" data-localization="content=inspector.label">Text will disappear</div>
+ <div id="d2" data-localization="content=inspector.label;title=inspector.commandkey">
+ </div>
+ <!-- keep the following data-localization on two separate lines -->
+ <div id="d3" data-localization="content=inspector.label;
+ title=inspector.commandkey"></div>
+ <div id="d4" data-localization="aria-label=inspector.label">Some content</div>
+ <div data-localization-bundle="devtools/client/locales/toolbox.properties">
+ <div id="d5" data-localization="content=toolbox.defaultTitle"></div>
+ </div>
+ </div>
+ `;
+
+ info("Use localization helper to localize the test markup");
+ localizeMarkup(div);
+
+ let div1 = div.querySelector("#d1");
+ let div2 = div.querySelector("#d2");
+ let div3 = div.querySelector("#d3");
+ let div4 = div.querySelector("#d4");
+ let div5 = div.querySelector("#d5");
+
+ is(div1.innerHTML, str1, "The content of #d1 is localized");
+ is(div2.innerHTML, str1, "The content of #d2 is localized");
+ is(div2.getAttribute("title"), str2, "The title of #d2 is localized");
+ is(div3.innerHTML, str1, "The content of #d3 is localized");
+ is(div3.getAttribute("title"), str2, "The title of #d3 is localized");
+ is(div4.innerHTML, "Some content", "The content of #d4 is not replaced");
+ is(div4.getAttribute("aria-label"), str1, "The aria-label of #d4 is localized");
+ is(div5.innerHTML, str3, "The content of #d5 is localized with another bundle");
+});
diff --git a/devtools/shared/tests/mochitest/chrome.ini b/devtools/shared/tests/mochitest/chrome.ini
new file mode 100644
index 000000000..85ece7c48
--- /dev/null
+++ b/devtools/shared/tests/mochitest/chrome.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+tags = devtools
+skip-if = os == 'android'
+
+[test_eventemitter_basic.html]
+[test_devtools_extensions.html]
+skip-if = os == 'linux' && debug # Bug 1205739
diff --git a/devtools/shared/tests/mochitest/test_devtools_extensions.html b/devtools/shared/tests/mochitest/test_devtools_extensions.html
new file mode 100644
index 000000000..04cc82b22
--- /dev/null
+++ b/devtools/shared/tests/mochitest/test_devtools_extensions.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+
+ <script type="application/javascript;version=1.8">
+ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+ let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const contentGlobals = require("devtools/server/content-globals");
+ const Services = require("Services");
+ const tabs = require('sdk/tabs');
+ const { getMostRecentBrowserWindow, getInnerId } = require('sdk/window/utils');
+ const { PageMod } = require('sdk/page-mod');
+
+ var _tests = [];
+ function addTest(test) {
+ _tests.push(test);
+ }
+
+ function runNextTest() {
+ if (_tests.length == 0) {
+ SimpleTest.finish()
+ return;
+ }
+ _tests.shift()();
+ }
+
+ window.onload = function() {
+ SimpleTest.waitForExplicitFinish();
+ runNextTest();
+ }
+
+ addTest(function () {
+ let TEST_URL = 'data:text/html;charset=utf-8,test';
+
+ let mod = PageMod({
+ include: TEST_URL,
+ contentScriptWhen: 'ready',
+ contentScript: 'null;'
+ });
+
+ tabs.open({
+ url: TEST_URL,
+ onLoad: function(tab) {
+ let id = getInnerId(getMostRecentBrowserWindow().gBrowser.selectedBrowser.contentWindow);
+
+ // getting
+ is(contentGlobals.getContentGlobals({
+ 'inner-window-id': id
+ }).length, 1, 'found a global for inner-id = ' + id);
+
+ Services.obs.addObserver(function observer(subject, topic, data) {
+ if (id == subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data) {
+ Services.obs.removeObserver(observer, 'inner-window-destroyed');
+ setTimeout(function() {
+ // closing the tab window should have removed the global
+ is(contentGlobals.getContentGlobals({
+ 'inner-window-id': id
+ }).length, 0, 'did not find a global for inner-id = ' + id);
+
+ mod.destroy();
+ runNextTest();
+ })
+ }
+ }, 'inner-window-destroyed', false);
+
+ tab.close();
+ }
+ });
+ })
+
+ addTest(function testAddRemoveGlobal() {
+ let global = {};
+ let globalDetails = {
+ global: global,
+ 'inner-window-id': 5
+ };
+
+ // adding
+ contentGlobals.addContentGlobal(globalDetails);
+
+ // getting
+ is(contentGlobals.getContentGlobals({
+ 'inner-window-id': 5
+ }).length, 1, 'found a global for inner-id = 5');
+ is(contentGlobals.getContentGlobals({
+ 'inner-window-id': 4
+ }).length, 0, 'did not find a global for inner-id = 4');
+
+ // remove
+ contentGlobals.removeContentGlobal(globalDetails);
+
+ // getting again
+ is(contentGlobals.getContentGlobals({
+ 'inner-window-id': 5
+ }).length, 0, 'did not find a global for inner-id = 5');
+
+ runNextTest();
+ });
+
+ </script>
+ </head>
+ <body></body>
+</html>
diff --git a/devtools/shared/tests/mochitest/test_eventemitter_basic.html b/devtools/shared/tests/mochitest/test_eventemitter_basic.html
new file mode 100644
index 000000000..9826eebca
--- /dev/null
+++ b/devtools/shared/tests/mochitest/test_eventemitter_basic.html
@@ -0,0 +1,194 @@
+<!DOCTYPE html>
+<!--
+ Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+-->
+
+<html>
+
+ <head>
+ <meta charset="utf8">
+ <title></title>
+
+ <script type="application/javascript"
+ src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <link rel="stylesheet" type="text/css"
+ href="chrome://mochikit/content/tests/SimpleTest/test.css">
+ </head>
+
+ <body>
+
+ <script type="application/javascript;version=1.8">
+ "use strict";
+
+ const { utils: Cu } = Components;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ const promise = require("promise");
+ const EventEmitter = require("devtools/shared/event-emitter");
+ const { Task } = require("devtools/shared/task");
+
+ SimpleTest.waitForExplicitFinish();
+
+ testEmitter();
+ testEmitter({});
+
+ Task.spawn(testPromise)
+ .then(null, ok.bind(null, false))
+ .then(SimpleTest.finish);
+
+ function testEmitter(aObject) {
+ let emitter;
+
+ if (aObject) {
+ emitter = aObject;
+ EventEmitter.decorate(emitter);
+ } else {
+ emitter = new EventEmitter();
+ }
+
+ ok(emitter, "We have an event emitter");
+
+ let beenHere1 = false;
+ let beenHere2 = false;
+
+ emitter.on("next", next);
+ emitter.emit("next", "abc", "def");
+
+ function next(eventName, str1, str2) {
+ is(eventName, "next", "Got event");
+ is(str1, "abc", "Argument 1 is correct");
+ is(str2, "def", "Argument 2 is correct");
+
+ ok(!beenHere1, "first time in next callback");
+ beenHere1 = true;
+
+ emitter.off("next", next);
+
+ emitter.emit("next");
+
+ emitter.once("onlyonce", onlyOnce);
+
+ emitter.emit("onlyonce");
+ emitter.emit("onlyonce");
+ }
+
+ function onlyOnce() {
+ ok(!beenHere2, "\"once\" listener has been called once");
+ beenHere2 = true;
+ emitter.emit("onlyonce");
+
+ testThrowingExceptionInListener();
+ }
+
+ function testThrowingExceptionInListener() {
+ function throwListener() {
+ emitter.off("throw-exception");
+ throw {
+ toString: () => "foo",
+ stack: "bar",
+ };
+ }
+
+ emitter.on("throw-exception", throwListener);
+ emitter.emit("throw-exception");
+
+ killItWhileEmitting();
+ }
+
+ function killItWhileEmitting() {
+ function c1() {
+ ok(true, "c1 called");
+ }
+ function c2() {
+ ok(true, "c2 called");
+ emitter.off("tick", c3);
+ }
+ function c3() {
+ ok(false, "c3 should not be called");
+ }
+ function c4() {
+ ok(true, "c4 called");
+ }
+
+ emitter.on("tick", c1);
+ emitter.on("tick", c2);
+ emitter.on("tick", c3);
+ emitter.on("tick", c4);
+
+ emitter.emit("tick");
+
+ offAfterOnce();
+ }
+
+ function offAfterOnce() {
+ let enteredC1 = false;
+
+ function c1() {
+ enteredC1 = true;
+ }
+
+ emitter.once("oao", c1);
+ emitter.off("oao", c1);
+
+ emitter.emit("oao");
+
+ ok(!enteredC1, "c1 should not be called");
+ }
+ }
+
+ function testPromise() {
+ let emitter = new EventEmitter();
+ let p = emitter.once("thing");
+
+ // Check that the promise is only resolved once event though we
+ // emit("thing") more than once
+ let firstCallbackCalled = false;
+ let check1 = p.then(arg => {
+ is(firstCallbackCalled, false, "first callback called only once");
+ firstCallbackCalled = true;
+ is(arg, "happened", "correct arg in promise");
+ return "rval from c1";
+ });
+
+ emitter.emit("thing", "happened", "ignored");
+
+ // Check that the promise is resolved asynchronously
+ let secondCallbackCalled = false;
+ let check2 = p.then(arg => {
+ ok(true, "second callback called");
+ is(arg, "happened", "correct arg in promise");
+ secondCallbackCalled = true;
+ is(arg, "happened", "correct arg in promise (a second time)");
+ return "rval from c2";
+ });
+
+ // Shouldn't call any of the above listeners
+ emitter.emit("thing", "trashinate");
+
+ // Check that we can still separate events with different names
+ // and that it works with no parameters
+ let pfoo = emitter.once("foo");
+ let pbar = emitter.once("bar");
+
+ let check3 = pfoo.then(arg => {
+ ok(arg === undefined, "no arg for foo event");
+ return "rval from c3";
+ });
+
+ pbar.then(() => {
+ ok(false, "pbar should not be called");
+ });
+
+ emitter.emit("foo");
+
+ is(secondCallbackCalled, false, "second callback not called yet");
+
+ return promise.all([ check1, check2, check3 ]).then(args => {
+ is(args[0], "rval from c1", "callback 1 done good");
+ is(args[1], "rval from c2", "callback 2 done good");
+ is(args[2], "rval from c3", "callback 3 done good");
+ });
+ }
+ </script>
+ </body>
+</html>
diff --git a/devtools/shared/tests/unit/.eslintrc.js b/devtools/shared/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..012428019
--- /dev/null
+++ b/devtools/shared/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/tests/unit/exposeLoader.js b/devtools/shared/tests/unit/exposeLoader.js
new file mode 100644
index 000000000..949640a03
--- /dev/null
+++ b/devtools/shared/tests/unit/exposeLoader.js
@@ -0,0 +1,8 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+exports.exerciseLazyRequire = (name, path) => {
+ const o = {};
+ loader.lazyRequireGetter(o, name, path);
+ return o;
+};
diff --git a/devtools/shared/tests/unit/head_devtools.js b/devtools/shared/tests/unit/head_devtools.js
new file mode 100644
index 000000000..f0f47c93a
--- /dev/null
+++ b/devtools/shared/tests/unit/head_devtools.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+
+const {require, DevToolsLoader, devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
+
+flags.testing = true;
+do_register_cleanup(() => {
+ flags.testing = false;
+});
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+
+// If for whatever reason the test needs to post console errors that aren't
+// failures, set this to true.
+var ALLOW_CONSOLE_ERRORS = false;
+
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+ DebuggerServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ if (!ALLOW_CONSOLE_ERRORS) {
+ do_throw("head_devtools.js got console message: " + string + "\n");
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
diff --git a/devtools/shared/tests/unit/test_assert.js b/devtools/shared/tests/unit/test_assert.js
new file mode 100644
index 000000000..b87171751
--- /dev/null
+++ b/devtools/shared/tests/unit/test_assert.js
@@ -0,0 +1,36 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test DevToolsUtils.assert
+
+ALLOW_CONSOLE_ERRORS = true;
+
+function run_test() {
+ // Enable assertions.
+ flags.testing = true;
+
+ const { assert } = DevToolsUtils;
+ equal(typeof assert, "function");
+
+ try {
+ assert(true, "this assertion should not fail");
+ } catch (e) {
+ // If you catch assertion failures in practice, I will hunt you down. I get
+ // email notifications every time it happens.
+ ok(false, "Should not get an error for an assertion that should not fail. Got "
+ + DevToolsUtils.safeErrorString(e));
+ }
+
+ let assertionFailed = false;
+ try {
+ assert(false, "this assertion should fail");
+ } catch (e) {
+ ok(e.message.startsWith("Assertion failure:"),
+ "Should be an assertion failure error");
+ assertionFailed = true;
+ }
+
+ ok(assertionFailed,
+ "The assertion should have failed, which should throw an error when assertions are enabled.");
+}
diff --git a/devtools/shared/tests/unit/test_async-utils.js b/devtools/shared/tests/unit/test_async-utils.js
new file mode 100644
index 000000000..2b09b8260
--- /dev/null
+++ b/devtools/shared/tests/unit/test_async-utils.js
@@ -0,0 +1,157 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test async-utils.js
+
+const {Task} = require("devtools/shared/task");
+// |const| will not work because
+// it will make the Promise object immutable before assigning.
+// Using Object.defineProperty() instead.
+Object.defineProperty(this, "Promise", {
+ value: require("promise"),
+ writable: false, configurable: false
+});
+const {asyncOnce, promiseInvoke, promiseCall} = require("devtools/shared/async-utils");
+
+function run_test() {
+ do_test_pending();
+ Task.spawn(function* () {
+ yield test_async_args(asyncOnce);
+ yield test_async_return(asyncOnce);
+ yield test_async_throw(asyncOnce);
+
+ yield test_async_once();
+ yield test_async_invoke();
+ do_test_finished();
+ }).then(null, error => {
+ do_throw(error);
+ });
+}
+
+// Test that arguments are correctly passed through to the async function.
+function test_async_args(async) {
+ let obj = {
+ method: async(function* (a, b) {
+ do_check_eq(this, obj);
+ do_check_eq(a, "foo");
+ do_check_eq(b, "bar");
+ })
+ };
+
+ return obj.method("foo", "bar");
+}
+
+// Test that the return value from the async function is resolution value of
+// the promise.
+function test_async_return(async) {
+ let obj = {
+ method: async(function* (a, b) {
+ return a + b;
+ })
+ };
+
+ return obj.method("foo", "bar").then(ret => {
+ do_check_eq(ret, "foobar");
+ });
+}
+
+// Test that the throwing from an async function rejects the promise.
+function test_async_throw(async) {
+ let obj = {
+ method: async(function* () {
+ throw "boom";
+ })
+ };
+
+ return obj.method().then(null, error => {
+ do_check_eq(error, "boom");
+ });
+}
+
+// Test that asyncOnce only runs the async function once per instance and
+// returns the same promise for that instance.
+function test_async_once() {
+ let counter = 0;
+
+ function Foo() {}
+ Foo.prototype = {
+ ran: false,
+ method: asyncOnce(function* () {
+ yield Promise.resolve();
+ if (this.ran) {
+ do_throw("asyncOnce function unexpectedly ran twice on the same object");
+ }
+ this.ran = true;
+ return counter++;
+ })
+ };
+
+ let foo1 = new Foo();
+ let foo2 = new Foo();
+ let p1 = foo1.method();
+ let p2 = foo2.method();
+
+ do_check_neq(p1, p2);
+
+ let p3 = foo1.method();
+ do_check_eq(p1, p3);
+ do_check_false(foo1.ran);
+
+ let p4 = foo2.method();
+ do_check_eq(p2, p4);
+ do_check_false(foo2.ran);
+
+ return p1.then(ret => {
+ do_check_true(foo1.ran);
+ do_check_eq(ret, 0);
+ return p2;
+ }).then(ret => {
+ do_check_true(foo2.ran);
+ do_check_eq(ret, 1);
+ });
+}
+
+// Test invoke and call.
+function test_async_invoke() {
+ return Task.spawn(function* () {
+ function func(a, b, expectedThis, callback) {
+ "use strict";
+ do_check_eq(a, "foo");
+ do_check_eq(b, "bar");
+ do_check_eq(this, expectedThis);
+ callback(a + b);
+ }
+
+ // Test call.
+ let callResult = yield promiseCall(func, "foo", "bar", undefined);
+ do_check_eq(callResult, "foobar");
+
+
+ // Test invoke.
+ let obj = { method: func };
+ let invokeResult = yield promiseInvoke(obj, obj.method, "foo", "bar", obj);
+ do_check_eq(invokeResult, "foobar");
+
+
+ // Test passing multiple values to the callback.
+ function multipleResults(callback) {
+ callback("foo", "bar");
+ }
+
+ let results = yield promiseCall(multipleResults);
+ do_check_eq(results.length, 2);
+ do_check_eq(results[0], "foo");
+ do_check_eq(results[1], "bar");
+
+
+ // Test throwing from the function.
+ function thrower() {
+ throw "boom";
+ }
+
+ yield promiseCall(thrower).then(null, error => {
+ do_check_eq(error, "boom");
+ });
+ });
+}
diff --git a/devtools/shared/tests/unit/test_console_filtering.js b/devtools/shared/tests/unit/test_console_filtering.js
new file mode 100644
index 000000000..431e2b234
--- /dev/null
+++ b/devtools/shared/tests/unit/test_console_filtering.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { console, ConsoleAPI } = require("resource://gre/modules/Console.jsm");
+const { ConsoleAPIListener } = require("devtools/server/actors/utils/webconsole-utils");
+const Services = require("Services");
+
+var seenMessages = 0;
+var seenTypes = 0;
+
+var callback = {
+ onConsoleAPICall: function (aMessage) {
+ if (aMessage.consoleID && aMessage.consoleID == "addon/foo") {
+ do_check_eq(aMessage.level, "warn");
+ do_check_eq(aMessage.arguments[0], "Warning from foo");
+ seenTypes |= 1;
+ } else if (aMessage.originAttributes &&
+ aMessage.originAttributes.addonId == "bar") {
+ do_check_eq(aMessage.level, "error");
+ do_check_eq(aMessage.arguments[0], "Error from bar");
+ seenTypes |= 2;
+ } else {
+ do_check_eq(aMessage.level, "log");
+ do_check_eq(aMessage.arguments[0], "Hello from default console");
+ seenTypes |= 4;
+ }
+ seenMessages++;
+ }
+};
+
+function createFakeAddonWindow({addonId} = {}) {
+ let baseURI = Services.io.newURI("about:blank", null, null);
+ let originAttributes = {addonId};
+ let principal = Services.scriptSecurityManager
+ .createCodebasePrincipal(baseURI, originAttributes);
+ let chromeWebNav = Services.appShell.createWindowlessBrowser(true);
+ let docShell = chromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDocShell);
+ docShell.createAboutBlankContentViewer(principal);
+ let addonWindow = docShell.contentViewer.DOMDocument.defaultView;
+
+ return {addonWindow, chromeWebNav};
+}
+
+/**
+ * Tests that the consoleID property of the ConsoleAPI options gets passed
+ * through to console messages.
+ */
+function run_test() {
+ // console1 Test Console.jsm messages tagged by the Addon SDK
+ // are still filtered correctly.
+ let console1 = new ConsoleAPI({
+ consoleID: "addon/foo"
+ });
+
+ // console2 - WebExtension page's console messages tagged
+ // by 'originAttributes.addonId' are filtered correctly.
+ let {addonWindow, chromeWebNav} = createFakeAddonWindow({addonId: "bar"});
+ let console2 = addonWindow.console;
+
+ // console - Plain console object (messages are tagged with window ids
+ // and originAttributes, but the addonId will be empty).
+ console.log("Hello from default console");
+
+ console1.warn("Warning from foo");
+ console2.error("Error from bar");
+
+ let listener = new ConsoleAPIListener(null, callback);
+ listener.init();
+ let messages = listener.getCachedMessages();
+
+ seenTypes = 0;
+ seenMessages = 0;
+ messages.forEach(callback.onConsoleAPICall);
+ do_check_eq(seenMessages, 3);
+ do_check_eq(seenTypes, 7);
+
+ seenTypes = 0;
+ seenMessages = 0;
+ console.log("Hello from default console");
+ console1.warn("Warning from foo");
+ console2.error("Error from bar");
+ do_check_eq(seenMessages, 3);
+ do_check_eq(seenTypes, 7);
+
+ listener.destroy();
+
+ listener = new ConsoleAPIListener(null, callback, {addonId: "foo"});
+ listener.init();
+ messages = listener.getCachedMessages();
+
+ seenTypes = 0;
+ seenMessages = 0;
+ messages.forEach(callback.onConsoleAPICall);
+ do_check_eq(seenMessages, 2);
+ do_check_eq(seenTypes, 1);
+
+ seenTypes = 0;
+ seenMessages = 0;
+ console.log("Hello from default console");
+ console1.warn("Warning from foo");
+ console2.error("Error from bar");
+ do_check_eq(seenMessages, 1);
+ do_check_eq(seenTypes, 1);
+
+ listener.destroy();
+
+ listener = new ConsoleAPIListener(null, callback, {addonId: "bar"});
+ listener.init();
+ messages = listener.getCachedMessages();
+
+ seenTypes = 0;
+ seenMessages = 0;
+ messages.forEach(callback.onConsoleAPICall);
+ do_check_eq(seenMessages, 3);
+ do_check_eq(seenTypes, 2);
+
+ seenTypes = 0;
+ seenMessages = 0;
+ console.log("Hello from default console");
+ console1.warn("Warning from foo");
+ console2.error("Error from bar");
+
+ do_check_eq(seenMessages, 1);
+ do_check_eq(seenTypes, 2);
+
+ listener.destroy();
+
+ // Close the addon window's chromeWebNav.
+ chromeWebNav.close();
+}
diff --git a/devtools/shared/tests/unit/test_css-properties-db.js b/devtools/shared/tests/unit/test_css-properties-db.js
new file mode 100644
index 000000000..108650a3e
--- /dev/null
+++ b/devtools/shared/tests/unit/test_css-properties-db.js
@@ -0,0 +1,136 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that the devtool's client-side CSS properties database is in sync with the values
+ * on the platform (in Nightly only). If they are not, then `mach devtools-css-db` needs
+ * to be run to make everything up to date. Nightly, aurora, beta, and release may have
+ * different CSS properties and values. These are based on preferences and compiler flags.
+ *
+ * This test broke uplifts as the database needed to be regenerated every uplift. The
+ * combination of compiler flags and preferences means that it's too difficult to
+ * statically determine which properties are enabled between Firefox releases.
+ *
+ * Because of these difficulties, the database only needs to be up to date with Nightly.
+ * It is a fallback that is only used if the remote debugging protocol doesn't support
+ * providing a CSS database, so it's ok if the provided properties don't exactly match
+ * the inspected target in this particular case.
+ */
+
+"use strict";
+
+const DOMUtils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Components.interfaces.inIDOMUtils);
+
+const {PSEUDO_ELEMENTS, CSS_PROPERTIES} = require("devtools/shared/css/generated/properties-db");
+const {generateCssProperties} = require("devtools/server/actors/css-properties");
+
+function run_test() {
+ const propertiesErrorMessage = "If this assertion fails, then the client side CSS " +
+ "properties list in devtools is out of sync with the " +
+ "CSS properties on the platform. To fix this " +
+ "assertion run `mach devtools-css-db` to re-generate " +
+ "the client side properties.";
+
+ // Check that the platform and client match for pseudo elements.
+ deepEqual(PSEUDO_ELEMENTS, DOMUtils.getCSSPseudoElementNames(), `The pseudo elements ` +
+ `match on the client and platform. ${propertiesErrorMessage}`);
+
+ /**
+ * Check that the platform and client match for the details on their CSS properties.
+ * Enumerate each property to aid in debugging.
+ */
+ const platformProperties = generateCssProperties();
+
+ for (let propertyName in CSS_PROPERTIES) {
+ const platformProperty = platformProperties[propertyName];
+ const clientProperty = CSS_PROPERTIES[propertyName];
+ const deepEqual = isJsonDeepEqual(platformProperty, clientProperty);
+
+ if (deepEqual) {
+ ok(true, `The static database and platform match for "${propertyName}".`);
+ } else {
+ const prefMessage = `The static database and platform do not match ` +
+ `for "${propertyName}".`;
+ if (getPreference(propertyName) === false) {
+ ok(true, `${prefMessage} However, there is a preference for disabling this ` +
+ `property on the current build.`);
+ } else {
+ ok(false, `${prefMessage} ${propertiesErrorMessage}`);
+ }
+ }
+ }
+
+ /**
+ * Check that the list of properties on the platform and client are the same. If
+ * they are not, check that there may be preferences that are disabling them on the
+ * target platform.
+ */
+ const mismatches = getKeyMismatches(platformProperties, CSS_PROPERTIES)
+ // Filter out OS-specific properties.
+ .filter(name => name && name.indexOf("-moz-osx-") === -1);
+
+ if (mismatches.length === 0) {
+ ok(true, "No client and platform CSS property database mismatches were found.");
+ }
+
+ mismatches.forEach(propertyName => {
+ ok(false, `The static database and platform do not agree on the property ` +
+ `"${propertyName}" ${propertiesErrorMessage}`);
+ });
+}
+
+/**
+ * Check JSON-serializable objects for deep equality.
+ */
+function isJsonDeepEqual(a, b) {
+ // Handle primitives.
+ if (a === b) {
+ return true;
+ }
+
+ // Handle arrays.
+ if (Array.isArray(a) && Array.isArray(b)) {
+ if (a.length !== b.length) {
+ return false;
+ }
+ for (let i = 0; i < a.length; i++) {
+ if (!isJsonDeepEqual(a[i], b[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ // Handle objects
+ if (typeof a === "object" && typeof b === "object") {
+ for (let key in a) {
+ if (!isJsonDeepEqual(a[key], b[key])) {
+ return false;
+ }
+ }
+
+ return Object.keys(a).length === Object.keys(b).length;
+ }
+
+ // Not something handled by these cases, therefore not equal.
+ return false;
+}
+
+/**
+ * Take the keys of two objects, and return the ones that don't match.
+ *
+ * @param {Object} a
+ * @param {Object} b
+ * @return {Array} keys
+ */
+function getKeyMismatches(a, b) {
+ const aNames = Object.keys(a);
+ const bNames = Object.keys(b);
+ const aMismatches = aNames.filter(key => !bNames.includes(key));
+ const bMismatches = bNames.filter(key => {
+ return !aNames.includes(key) && !aMismatches.includes(key);
+ });
+
+ return aMismatches.concat(bMismatches);
+}
diff --git a/devtools/shared/tests/unit/test_csslexer.js b/devtools/shared/tests/unit/test_csslexer.js
new file mode 100644
index 000000000..35855640b
--- /dev/null
+++ b/devtools/shared/tests/unit/test_csslexer.js
@@ -0,0 +1,242 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+// This file is a copy of layout/style/test/test_csslexer.js, modified
+// to use both our pure-JS lexer and the DOMUtils lexer for
+// cross-checking.
+
+"use strict";
+
+const jsLexer = require("devtools/shared/css/lexer");
+const domutils = Components.classes["@mozilla.org/inspector/dom-utils;1"]
+ .getService(Components.interfaces.inIDOMUtils);
+
+// An object that acts like a CSSLexer but verifies that the DOM lexer
+// and the JS lexer do the same thing.
+function DoubleLexer(input) {
+ do_print("DoubleLexer input: " + input);
+ this.domLexer = domutils.getCSSLexer(input);
+ this.jsLexer = jsLexer.getCSSLexer(input);
+}
+
+DoubleLexer.prototype = {
+ checkState: function () {
+ equal(this.domLexer.lineNumber, this.jsLexer.lineNumber,
+ "check line number");
+ equal(this.domLexer.columnNumber, this.jsLexer.columnNumber,
+ "check column number");
+ },
+
+ get lineNumber() {
+ return this.domLexer.lineNumber;
+ },
+
+ get columnNumber() {
+ return this.domLexer.columnNumber;
+ },
+
+ performEOFFixup: function (inputString, preserveBackslash) {
+ let d = this.domLexer.performEOFFixup(inputString, preserveBackslash);
+ let j = this.jsLexer.performEOFFixup(inputString, preserveBackslash);
+
+ equal(d, j);
+ return d;
+ },
+
+ mungeNumber: function (token) {
+ if (token && (token.tokenType === "number" ||
+ token.tokenType === "percentage") &&
+ !token.isInteger) {
+ // The JS lexer does its computations in double, but the
+ // platform lexer does its computations in float. Account for
+ // this discrepancy in a way that's sufficient for this test.
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1163047
+ token.number = parseFloat(token.number.toPrecision(8));
+ }
+ },
+
+ nextToken: function () {
+ // Check state both before and after.
+ this.checkState();
+
+ let d = this.domLexer.nextToken();
+ let j = this.jsLexer.nextToken();
+
+ this.mungeNumber(d);
+ this.mungeNumber(j);
+
+ deepEqual(d, j);
+
+ this.checkState();
+
+ return d;
+ }
+};
+
+function test_lexer(cssText, tokenTypes) {
+ let lexer = new DoubleLexer(cssText);
+ let reconstructed = "";
+ let lastTokenEnd = 0;
+ let i = 0;
+ while (true) {
+ let token = lexer.nextToken();
+ if (!token) {
+ break;
+ }
+ let combined = token.tokenType;
+ if (token.text) {
+ combined += ":" + token.text;
+ }
+ equal(combined, tokenTypes[i]);
+ ok(token.endOffset > token.startOffset);
+ equal(token.startOffset, lastTokenEnd);
+ lastTokenEnd = token.endOffset;
+ reconstructed += cssText.substring(token.startOffset, token.endOffset);
+ ++i;
+ }
+ // Ensure that we saw the correct number of tokens.
+ equal(i, tokenTypes.length);
+ // Ensure that the reported offsets cover all the text.
+ equal(reconstructed, cssText);
+}
+
+var LEX_TESTS = [
+ ["simple", ["ident:simple"]],
+ ["simple: { hi; }",
+ ["ident:simple", "symbol::",
+ "whitespace", "symbol:{",
+ "whitespace", "ident:hi",
+ "symbol:;", "whitespace",
+ "symbol:}"]],
+ ["/* whatever */", ["comment"]],
+ ["'string'", ["string:string"]],
+ ['"string"', ["string:string"]],
+ ["rgb(1,2,3)", ["function:rgb", "number",
+ "symbol:,", "number",
+ "symbol:,", "number",
+ "symbol:)"]],
+ ["@media", ["at:media"]],
+ ["#hibob", ["id:hibob"]],
+ ["#123", ["hash:123"]],
+ ["23px", ["dimension:px"]],
+ ["23%", ["percentage"]],
+ ["url(http://example.com)", ["url:http://example.com"]],
+ ["url('http://example.com')", ["url:http://example.com"]],
+ ["url( 'http://example.com' )",
+ ["url:http://example.com"]],
+ // In CSS Level 3, this is an ordinary URL, not a BAD_URL.
+ ["url(http://example.com", ["url:http://example.com"]],
+ // See bug 1153981 to understand why this gets a SYMBOL token.
+ ["url(http://example.com @", ["bad_url:http://example.com", "symbol:@"]],
+ ["quo\\ting", ["ident:quoting"]],
+ ["'bad string\n", ["bad_string:bad string", "whitespace"]],
+ ["~=", ["includes"]],
+ ["|=", ["dashmatch"]],
+ ["^=", ["beginsmatch"]],
+ ["$=", ["endsmatch"]],
+ ["*=", ["containsmatch"]],
+
+ // URANGE may be on the way out, and it isn't used by devutils, so
+ // let's skip it.
+
+ ["<!-- html comment -->", ["htmlcomment", "whitespace", "ident:html",
+ "whitespace", "ident:comment", "whitespace",
+ "htmlcomment"]],
+
+ // earlier versions of CSS had "bad comment" tokens, but in level 3,
+ // unterminated comments are just comments.
+ ["/* bad comment", ["comment"]]
+];
+
+function test_lexer_linecol(cssText, locations) {
+ let lexer = new DoubleLexer(cssText);
+ let i = 0;
+ while (true) {
+ let token = lexer.nextToken();
+ let startLine = lexer.lineNumber;
+ let startColumn = lexer.columnNumber;
+
+ // We do this in a bit of a funny way so that we can also test the
+ // location of the EOF.
+ let combined = ":" + startLine + ":" + startColumn;
+ if (token) {
+ combined = token.tokenType + combined;
+ }
+
+ equal(combined, locations[i]);
+ ++i;
+
+ if (!token) {
+ break;
+ }
+ }
+ // Ensure that we saw the correct number of tokens.
+ equal(i, locations.length);
+}
+
+function test_lexer_eofchar(cssText, argText, expectedAppend,
+ expectedNoAppend) {
+ let lexer = new DoubleLexer(cssText);
+ while (lexer.nextToken()) {
+ // Nothing.
+ }
+
+ do_print("EOF char test, input = " + cssText);
+
+ let result = lexer.performEOFFixup(argText, true);
+ equal(result, expectedAppend);
+
+ result = lexer.performEOFFixup(argText, false);
+ equal(result, expectedNoAppend);
+}
+
+var LINECOL_TESTS = [
+ ["simple", ["ident:0:0", ":0:6"]],
+ ["\n stuff", ["whitespace:0:0", "ident:1:4", ":1:9"]],
+ ['"string with \\\nnewline" \r\n', ["string:0:0", "whitespace:1:8",
+ ":2:0"]]
+];
+
+var EOFCHAR_TESTS = [
+ ["hello", "hello"],
+ ["hello \\", "hello \\\\", "hello \\\uFFFD"],
+ ["'hello", "'hello'"],
+ ["\"hello", "\"hello\""],
+ ["'hello\\", "'hello\\\\'", "'hello'"],
+ ["\"hello\\", "\"hello\\\\\"", "\"hello\""],
+ ["/*hello", "/*hello*/"],
+ ["/*hello*", "/*hello*/"],
+ ["/*hello\\", "/*hello\\*/"],
+ ["url(hello", "url(hello)"],
+ ["url('hello", "url('hello')"],
+ ["url(\"hello", "url(\"hello\")"],
+ ["url(hello\\", "url(hello\\\\)", "url(hello\\\uFFFD)"],
+ ["url('hello\\", "url('hello\\\\')", "url('hello')"],
+ ["url(\"hello\\", "url(\"hello\\\\\")", "url(\"hello\")"],
+];
+
+function run_test() {
+ let text, result;
+ for ([text, result] of LEX_TESTS) {
+ test_lexer(text, result);
+ }
+
+ for ([text, result] of LINECOL_TESTS) {
+ test_lexer_linecol(text, result);
+ }
+
+ let expectedAppend, expectedNoAppend;
+ for ([text, expectedAppend, expectedNoAppend] of EOFCHAR_TESTS) {
+ if (!expectedNoAppend) {
+ expectedNoAppend = expectedAppend;
+ }
+ test_lexer_eofchar(text, text, expectedAppend, expectedNoAppend);
+ }
+
+ // Ensure that passing a different inputString to performEOFFixup
+ // doesn't cause an assertion trying to strip a backslash from the
+ // end of an empty string.
+ test_lexer_eofchar("'\\", "", "\\'", "'");
+}
diff --git a/devtools/shared/tests/unit/test_defer.js b/devtools/shared/tests/unit/test_defer.js
new file mode 100644
index 000000000..c0babe961
--- /dev/null
+++ b/devtools/shared/tests/unit/test_defer.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const defer = require("devtools/shared/defer");
+
+function testResolve() {
+ const deferred = defer();
+ deferred.resolve("success");
+ return deferred.promise;
+}
+
+function testReject() {
+ const deferred = defer();
+ deferred.reject("error");
+ return deferred.promise;
+}
+
+add_task(function* () {
+ const success = yield testResolve();
+ equal(success, "success");
+
+ let error;
+ try {
+ yield testReject();
+ } catch (e) {
+ error = e;
+ }
+
+ equal(error, "error");
+});
diff --git a/devtools/shared/tests/unit/test_defineLazyPrototypeGetter.js b/devtools/shared/tests/unit/test_defineLazyPrototypeGetter.js
new file mode 100644
index 000000000..d729e7473
--- /dev/null
+++ b/devtools/shared/tests/unit/test_defineLazyPrototypeGetter.js
@@ -0,0 +1,68 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test DevToolsUtils.defineLazyPrototypeGetter
+
+function Class() {}
+DevToolsUtils.defineLazyPrototypeGetter(Class.prototype, "foo", () => []);
+
+
+function run_test() {
+ test_prototype_attributes();
+ test_instance_attributes();
+ test_multiple_instances();
+ test_callback_receiver();
+}
+
+function test_prototype_attributes() {
+ // Check that the prototype has a getter property with expected attributes.
+ let descriptor = Object.getOwnPropertyDescriptor(Class.prototype, "foo");
+ do_check_eq(typeof descriptor.get, "function");
+ do_check_eq(descriptor.set, undefined);
+ do_check_eq(descriptor.enumerable, false);
+ do_check_eq(descriptor.configurable, true);
+}
+
+function test_instance_attributes() {
+ // Instances should not have an own property until the lazy getter has been
+ // activated.
+ let instance = new Class();
+ do_check_false(instance.hasOwnProperty("foo"));
+ instance.foo;
+ do_check_true(instance.hasOwnProperty("foo"));
+
+ // Check that the instance has an own property with the expecred value and
+ // attributes after the lazy getter is activated.
+ let descriptor = Object.getOwnPropertyDescriptor(instance, "foo");
+ do_check_true(descriptor.value instanceof Array);
+ do_check_eq(descriptor.writable, true);
+ do_check_eq(descriptor.enumerable, false);
+ do_check_eq(descriptor.configurable, true);
+}
+
+function test_multiple_instances() {
+ let instance1 = new Class();
+ let instance2 = new Class();
+ let foo1 = instance1.foo;
+ let foo2 = instance2.foo;
+ // Check that the lazy getter returns the expected type of value.
+ do_check_true(foo1 instanceof Array);
+ do_check_true(foo2 instanceof Array);
+ // Make sure the lazy getter runs once and only once per instance.
+ do_check_eq(instance1.foo, foo1);
+ do_check_eq(instance2.foo, foo2);
+ // Make sure each instance gets its own unique value.
+ do_check_neq(foo1, foo2);
+}
+
+function test_callback_receiver() {
+ function Foo() {}
+ DevToolsUtils.defineLazyPrototypeGetter(Foo.prototype, "foo", function () {
+ return this;
+ });
+
+ // Check that the |this| value in the callback is the instance itself.
+ let instance = new Foo();
+ do_check_eq(instance.foo, instance);
+}
diff --git a/devtools/shared/tests/unit/test_executeSoon.js b/devtools/shared/tests/unit/test_executeSoon.js
new file mode 100644
index 000000000..6154a3e67
--- /dev/null
+++ b/devtools/shared/tests/unit/test_executeSoon.js
@@ -0,0 +1,48 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Client request stacks should span the entire process from before making the
+ * request to handling the reply from the server. The server frames are not
+ * included, nor can they be in most cases, since the server can be a remote
+ * device.
+ */
+
+var { executeSoon } = require("devtools/shared/DevToolsUtils");
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var Services = require("Services");
+
+var asyncStackEnabled =
+ Services.prefs.getBoolPref("javascript.options.asyncstack");
+
+do_register_cleanup(() => {
+ Services.prefs.setBoolPref("javascript.options.asyncstack",
+ asyncStackEnabled);
+});
+
+add_task(function* () {
+ Services.prefs.setBoolPref("javascript.options.asyncstack", true);
+
+ yield waitForTick();
+
+ let stack = Components.stack;
+ while (stack) {
+ do_print(stack.name);
+ if (stack.name == "waitForTick") {
+ // Reached back to outer function before executeSoon
+ ok(true, "Complete stack");
+ return;
+ }
+ stack = stack.asyncCaller || stack.caller;
+ }
+ ok(false, "Incomplete stack");
+});
+
+function waitForTick() {
+ let deferred = defer();
+ executeSoon(deferred.resolve);
+ return deferred.promise;
+}
diff --git a/devtools/shared/tests/unit/test_fetch-bom.js b/devtools/shared/tests/unit/test_fetch-bom.js
new file mode 100644
index 000000000..7f299b1ce
--- /dev/null
+++ b/devtools/shared/tests/unit/test_fetch-bom.js
@@ -0,0 +1,76 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests for DevToolsUtils.fetch BOM detection.
+
+const CC = Components.Constructor;
+
+const { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
+const BinaryOutputStream = CC("@mozilla.org/binaryoutputstream;1",
+ "nsIBinaryOutputStream", "setOutputStream");
+
+function write8(bos) {
+ bos.write8(0xef);
+ bos.write8(0xbb);
+ bos.write8(0xbf);
+ bos.write8(0x68);
+ bos.write8(0xc4);
+ bos.write8(0xb1);
+}
+
+function write16be(bos) {
+ bos.write8(0xfe);
+ bos.write8(0xff);
+ bos.write8(0x00);
+ bos.write8(0x68);
+ bos.write8(0x01);
+ bos.write8(0x31);
+}
+
+function write16le(bos) {
+ bos.write8(0xff);
+ bos.write8(0xfe);
+ bos.write8(0x68);
+ bos.write8(0x00);
+ bos.write8(0x31);
+ bos.write8(0x01);
+}
+
+function getHandler(writer) {
+ return function (request, response) {
+ response.setStatusLine(request.httpVersion, 200, "OK");
+
+ let bos = new BinaryOutputStream(response.bodyOutputStream);
+ writer(bos);
+ };
+}
+
+const server = new HttpServer();
+server.registerDirectory("/", do_get_cwd());
+server.registerPathHandler("/u8", getHandler(write8));
+server.registerPathHandler("/u16be", getHandler(write16be));
+server.registerPathHandler("/u16le", getHandler(write16le));
+server.start(-1);
+
+const port = server.identity.primaryPort;
+const serverURL = "http://localhost:" + port;
+
+do_register_cleanup(() => {
+ return new Promise(resolve => server.stop(resolve));
+});
+
+add_task(function* () {
+ yield test_one(serverURL + "/u8", "UTF-8");
+ yield test_one(serverURL + "/u16be", "UTF-16BE");
+ yield test_one(serverURL + "/u16le", "UTF-16LE");
+});
+
+function* test_one(url, encoding) {
+ // Be sure to set the encoding to something that will yield an
+ // invalid result if BOM sniffing is not done.
+ yield DevToolsUtils.fetch(url, { charset: "ISO-8859-1" }).then(({content}) => {
+ do_check_eq(content, "hı", "The content looks correct for " + encoding);
+ });
+}
diff --git a/devtools/shared/tests/unit/test_fetch-chrome.js b/devtools/shared/tests/unit/test_fetch-chrome.js
new file mode 100644
index 000000000..441d4c7ba
--- /dev/null
+++ b/devtools/shared/tests/unit/test_fetch-chrome.js
@@ -0,0 +1,31 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests for DevToolsUtils.fetch on chrome:// URI's.
+
+const URL_FOUND = "chrome://devtools-shared/locale/debugger.properties";
+const URL_NOT_FOUND = "chrome://this/is/not/here.js";
+
+/**
+ * Test that non-existent files are handled correctly.
+ */
+add_task(function* test_missing() {
+ yield DevToolsUtils.fetch(URL_NOT_FOUND).then(result => {
+ do_print(result);
+ ok(false, "fetch resolved unexpectedly for non-existent chrome:// URI");
+ }, () => {
+ ok(true, "fetch rejected as the chrome:// URI was non-existent.");
+ });
+});
+
+/**
+ * Tests that existing files are handled correctly.
+ */
+add_task(function* test_normal() {
+ yield DevToolsUtils.fetch(URL_FOUND).then(result => {
+ notDeepEqual(result.content, "",
+ "chrome:// URI seems to be read correctly.");
+ });
+});
diff --git a/devtools/shared/tests/unit/test_fetch-file.js b/devtools/shared/tests/unit/test_fetch-file.js
new file mode 100644
index 000000000..d9d712322
--- /dev/null
+++ b/devtools/shared/tests/unit/test_fetch-file.js
@@ -0,0 +1,104 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests for DevToolsUtils.fetch on file:// URI's.
+
+const { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+const { OS } = Cu.import("resource://gre/modules/osfile.jsm", {});
+
+const TEST_CONTENT = "aéd";
+
+// The TEST_CONTENT encoded as UTF-8.
+const UTF8_TEST_BUFFER = new Uint8Array([0x61, 0xc3, 0xa9, 0x64]);
+
+// The TEST_CONTENT encoded as ISO 8859-1.
+const ISO_8859_1_BUFFER = new Uint8Array([0x61, 0xe9, 0x64]);
+
+/**
+ * Tests that URLs with arrows pointing to an actual source are handled properly
+ * (bug 808960). For example 'resource://gre/modules/XPIProvider.jsm ->
+ * file://l10n.js' should load 'file://l10n.js'.
+ */
+add_task(function* test_arrow_urls() {
+ let { path } = createTemporaryFile(".js");
+ let url = "resource://gre/modules/XPIProvider.jsm -> file://" + path;
+
+ yield OS.File.writeAtomic(path, TEST_CONTENT, { encoding: "utf-8" });
+ let { content } = yield DevToolsUtils.fetch(url);
+
+ deepEqual(content, TEST_CONTENT, "The file contents were correctly read.");
+});
+
+/**
+ * Tests that empty files are read correctly.
+ */
+add_task(function* test_empty() {
+ let { path } = createTemporaryFile();
+ let { content } = yield DevToolsUtils.fetch("file://" + path);
+ deepEqual(content, "", "The empty file was read correctly.");
+});
+
+/**
+ * Tests that UTF-8 encoded files are correctly read.
+ */
+add_task(function* test_encoding_utf8() {
+ let { path } = createTemporaryFile();
+ yield OS.File.writeAtomic(path, UTF8_TEST_BUFFER);
+
+ let { content } = yield DevToolsUtils.fetch(path);
+ deepEqual(content, TEST_CONTENT,
+ "The UTF-8 encoded file was correctly read.");
+});
+
+/**
+ * Tests that ISO 8859-1 (Latin-1) encoded files are correctly read.
+ */
+add_task(function* test_encoding_iso_8859_1() {
+ let { path } = createTemporaryFile();
+ yield OS.File.writeAtomic(path, ISO_8859_1_BUFFER);
+
+ let { content } = yield DevToolsUtils.fetch(path);
+ deepEqual(content, TEST_CONTENT,
+ "The ISO 8859-1 encoded file was correctly read.");
+});
+
+/**
+ * Test that non-existent files are handled correctly.
+ */
+add_task(function* test_missing() {
+ yield DevToolsUtils.fetch("file:///file/not/found.right").then(result => {
+ do_print(result);
+ ok(false, "Fetch resolved unexpectedly when the file was not found.");
+ }, () => {
+ ok(true, "Fetch rejected as expected because the file was not found.");
+ });
+});
+
+/**
+ * Test that URLs without file:// scheme work.
+ */
+add_task(function* test_schemeless_files() {
+ let { path } = createTemporaryFile();
+
+ yield OS.File.writeAtomic(path, TEST_CONTENT, { encoding: "utf-8" });
+
+ let { content } = yield DevToolsUtils.fetch(path);
+ deepEqual(content, TEST_CONTENT, "The content was correct.");
+});
+
+/**
+ * Creates a temporary file that is removed after the test completes.
+ */
+function createTemporaryFile(extension) {
+ let name = "test_fetch-file-" + Math.random() + (extension || "");
+ let file = FileUtils.getFile("TmpD", [name]);
+ file.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("0755", 8));
+
+ do_register_cleanup(() => {
+ file.remove(false);
+ });
+
+ return file;
+}
diff --git a/devtools/shared/tests/unit/test_fetch-http.js b/devtools/shared/tests/unit/test_fetch-http.js
new file mode 100644
index 000000000..028fa33ee
--- /dev/null
+++ b/devtools/shared/tests/unit/test_fetch-http.js
@@ -0,0 +1,61 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests for DevToolsUtils.fetch on http:// URI's.
+
+const { HttpServer } = Cu.import("resource://testing-common/httpd.js", {});
+
+const server = new HttpServer();
+server.registerDirectory("/", do_get_cwd());
+server.registerPathHandler("/cached.json", cacheRequestHandler);
+server.start(-1);
+
+const port = server.identity.primaryPort;
+const serverURL = "http://localhost:" + port;
+const CACHED_URL = serverURL + "/cached.json";
+const NORMAL_URL = serverURL + "/test_fetch-http.js";
+
+function cacheRequestHandler(request, response) {
+ do_print("Got request for " + request.path);
+ response.setHeader("Cache-Control", "max-age=10000", false);
+ response.setStatusLine(request.httpVersion, 200, "OK");
+ response.setHeader("Content-Type", "application/json", false);
+
+ let body = "[" + Math.random() + "]";
+ response.bodyOutputStream.write(body, body.length);
+}
+
+do_register_cleanup(() => {
+ return new Promise(resolve => server.stop(resolve));
+});
+
+add_task(function* test_normal() {
+ yield DevToolsUtils.fetch(NORMAL_URL).then(({content}) => {
+ ok(content.includes("The content looks correct."),
+ "The content looks correct.");
+ });
+});
+
+add_task(function* test_caching() {
+ let initialContent = null;
+
+ do_print("Performing the first request.");
+ yield DevToolsUtils.fetch(CACHED_URL).then(({content}) => {
+ do_print("Got the first response: " + content);
+ initialContent = content;
+ });
+
+ do_print("Performing another request, expecting to get cached response.");
+ yield DevToolsUtils.fetch(CACHED_URL).then(({content}) => {
+ deepEqual(content, initialContent, "The content was loaded from cache.");
+ });
+
+ do_print("Performing a third request with cache bypassed.");
+ let opts = { loadFromCache: false };
+ yield DevToolsUtils.fetch(CACHED_URL, opts).then(({content}) => {
+ notDeepEqual(content, initialContent,
+ "The URL wasn't loaded from cache with loadFromCache: false.");
+ });
+});
diff --git a/devtools/shared/tests/unit/test_fetch-resource.js b/devtools/shared/tests/unit/test_fetch-resource.js
new file mode 100644
index 000000000..45e79cfa1
--- /dev/null
+++ b/devtools/shared/tests/unit/test_fetch-resource.js
@@ -0,0 +1,31 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests for DevToolsUtils.fetch on resource:// URI's.
+
+const URL_FOUND = "resource://devtools/shared/DevToolsUtils.js";
+const URL_NOT_FOUND = "resource://devtools/this/is/not/here.js";
+
+/**
+ * Test that non-existent files are handled correctly.
+ */
+add_task(function* test_missing() {
+ yield DevToolsUtils.fetch(URL_NOT_FOUND).then(result => {
+ do_print(result);
+ ok(false, "fetch resolved unexpectedly for non-existent resource:// URI");
+ }, () => {
+ ok(true, "fetch rejected as the resource:// URI was non-existent.");
+ });
+});
+
+/**
+ * Tests that existing files are handled correctly.
+ */
+add_task(function* test_normal() {
+ yield DevToolsUtils.fetch(URL_FOUND).then(result => {
+ notDeepEqual(result.content, "",
+ "resource:// URI seems to be read correctly.");
+ });
+});
diff --git a/devtools/shared/tests/unit/test_flatten.js b/devtools/shared/tests/unit/test_flatten.js
new file mode 100644
index 000000000..f5a186770
--- /dev/null
+++ b/devtools/shared/tests/unit/test_flatten.js
@@ -0,0 +1,24 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test ThreadSafeDevToolsUtils.flatten
+
+function run_test() {
+ const { flatten } = DevToolsUtils;
+
+ const flat = flatten([["a", "b", "c"],
+ ["d", "e", "f"],
+ ["g", "h", "i"]]);
+
+ equal(flat.length, 9);
+ equal(flat[0], "a");
+ equal(flat[1], "b");
+ equal(flat[2], "c");
+ equal(flat[3], "d");
+ equal(flat[4], "e");
+ equal(flat[5], "f");
+ equal(flat[6], "g");
+ equal(flat[7], "h");
+ equal(flat[8], "i");
+}
diff --git a/devtools/shared/tests/unit/test_indentation.js b/devtools/shared/tests/unit/test_indentation.js
new file mode 100644
index 000000000..f7e4403d4
--- /dev/null
+++ b/devtools/shared/tests/unit/test_indentation.js
@@ -0,0 +1,133 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Services = require("Services");
+const {
+ EXPAND_TAB,
+ TAB_SIZE,
+ DETECT_INDENT,
+ getIndentationFromPrefs,
+ getIndentationFromIteration,
+ getIndentationFromString,
+} = require("devtools/shared/indentation");
+
+function test_indent_from_prefs() {
+ Services.prefs.setBoolPref(DETECT_INDENT, true);
+ equal(getIndentationFromPrefs(), false,
+ "getIndentationFromPrefs returning false");
+
+ Services.prefs.setIntPref(TAB_SIZE, 73);
+ Services.prefs.setBoolPref(EXPAND_TAB, false);
+ Services.prefs.setBoolPref(DETECT_INDENT, false);
+ deepEqual(getIndentationFromPrefs(), {indentUnit: 73, indentWithTabs: true},
+ "getIndentationFromPrefs basic test");
+}
+
+const TESTS = [
+ {
+ desc: "two spaces",
+ input: [
+ "/*",
+ " * tricky comment block",
+ " */",
+ "div {",
+ " color: red;",
+ " background: blue;",
+ "}",
+ " ",
+ "span {",
+ " padding-left: 10px;",
+ "}"
+ ],
+ expected: {indentUnit: 2, indentWithTabs: false}
+ },
+ {
+ desc: "four spaces",
+ input: [
+ "var obj = {",
+ " addNumbers: function() {",
+ " var x = 5;",
+ " var y = 18;",
+ " return x + y;",
+ " },",
+ " ",
+ " /*",
+ " * Do some stuff to two numbers",
+ " * ",
+ " * @param x",
+ " * @param y",
+ " * ",
+ " * @return the result of doing stuff",
+ " */",
+ " subtractNumbers: function(x, y) {",
+ " var x += 7;",
+ " var y += 18;",
+ " var result = x - y;",
+ " result %= 2;",
+ " }",
+ "}"
+ ],
+ expected: {indentUnit: 4, indentWithTabs: false}
+ },
+ {
+ desc: "tabs",
+ input: [
+ "/*",
+ " * tricky comment block",
+ " */",
+ "div {",
+ "\tcolor: red;",
+ "\tbackground: blue;",
+ "}",
+ "",
+ "span {",
+ "\tpadding-left: 10px;",
+ "}"
+ ],
+ expected: {indentUnit: 2, indentWithTabs: true}
+ },
+ {
+ desc: "no indent",
+ input: [
+ "var x = 0;",
+ " // stray thing",
+ "var y = 9;",
+ " ",
+ ""
+ ],
+ expected: {indentUnit: 2, indentWithTabs: false}
+ },
+];
+
+function test_indent_detection() {
+ Services.prefs.setIntPref(TAB_SIZE, 2);
+ Services.prefs.setBoolPref(EXPAND_TAB, true);
+ Services.prefs.setBoolPref(DETECT_INDENT, true);
+
+ for (let test of TESTS) {
+ let iterFn = function (start, end, callback) {
+ test.input.slice(start, end).forEach(callback);
+ };
+
+ deepEqual(getIndentationFromIteration(iterFn), test.expected,
+ "test getIndentationFromIteration " + test.desc);
+ }
+
+ for (let test of TESTS) {
+ deepEqual(getIndentationFromString(test.input.join("\n")), test.expected,
+ "test getIndentationFromString " + test.desc);
+ }
+}
+
+function run_test() {
+ try {
+ test_indent_from_prefs();
+ test_indent_detection();
+ } finally {
+ Services.prefs.clearUserPref(TAB_SIZE);
+ Services.prefs.clearUserPref(EXPAND_TAB);
+ Services.prefs.clearUserPref(DETECT_INDENT);
+ }
+}
diff --git a/devtools/shared/tests/unit/test_independent_loaders.js b/devtools/shared/tests/unit/test_independent_loaders.js
new file mode 100644
index 000000000..20abed27d
--- /dev/null
+++ b/devtools/shared/tests/unit/test_independent_loaders.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Ensure that each instance of the Dev Tools loader contains its own loader
+ * instance, and also returns unique objects. This ensures there is no sharing
+ * in place between loaders.
+ */
+function run_test() {
+ let loader1 = new DevToolsLoader();
+ let loader2 = new DevToolsLoader();
+
+ let indent1 = loader1.require("devtools/shared/indentation");
+ let indent2 = loader2.require("devtools/shared/indentation");
+
+ do_check_true(indent1 !== indent2);
+
+ do_check_true(loader1._provider !== loader2._provider);
+ do_check_true(loader1._provider.loader !== loader2._provider.loader);
+}
diff --git a/devtools/shared/tests/unit/test_invisible_loader.js b/devtools/shared/tests/unit/test_invisible_loader.js
new file mode 100644
index 000000000..96a68abc4
--- /dev/null
+++ b/devtools/shared/tests/unit/test_invisible_loader.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const { addDebuggerToGlobal } = Cu.import("resource://gre/modules/jsdebugger.jsm", {});
+addDebuggerToGlobal(this);
+
+/**
+ * Ensure that sandboxes created via the Dev Tools loader respect the
+ * invisibleToDebugger flag.
+ */
+function run_test() {
+ visible_loader();
+ invisible_loader();
+}
+
+function visible_loader() {
+ let loader = new DevToolsLoader();
+ loader.invisibleToDebugger = false;
+ loader.require("devtools/shared/indentation");
+
+ let dbg = new Debugger();
+ let sandbox = loader._provider.loader.sharedGlobalSandbox;
+
+ try {
+ dbg.addDebuggee(sandbox);
+ do_check_true(true);
+ } catch (e) {
+ do_throw("debugger could not add visible value");
+ }
+
+ // Check that for common loader used for tabs, promise modules is Promise.jsm
+ // Which is required to support unhandled promises rejection in mochitests
+ const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+ do_check_eq(loader.require("promise"), promise);
+}
+
+function invisible_loader() {
+ let loader = new DevToolsLoader();
+ loader.invisibleToDebugger = true;
+ loader.require("devtools/shared/indentation");
+
+ let dbg = new Debugger();
+ let sandbox = loader._provider.loader.sharedGlobalSandbox;
+
+ try {
+ dbg.addDebuggee(sandbox);
+ do_throw("debugger added invisible value");
+ } catch (e) {
+ do_check_true(true);
+ }
+
+ // But for browser toolbox loader, promise is loaded as a regular modules out
+ // of Promise-backend.js, that to be invisible to the debugger and not step
+ // into it.
+ const promise = loader.require("promise");
+ const promiseModule = loader._provider.loader.modules["resource://gre/modules/Promise-backend.js"];
+ do_check_eq(promise, promiseModule.exports);
+}
diff --git a/devtools/shared/tests/unit/test_isSet.js b/devtools/shared/tests/unit/test_isSet.js
new file mode 100644
index 000000000..f85d6ae3c
--- /dev/null
+++ b/devtools/shared/tests/unit/test_isSet.js
@@ -0,0 +1,25 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test ThreadSafeDevToolsUtils.isSet
+
+function run_test() {
+ const { isSet } = DevToolsUtils;
+
+ equal(isSet(new Set()), true);
+ equal(isSet(new Map()), false);
+ equal(isSet({}), false);
+ equal(isSet("I swear I'm a Set"), false);
+ equal(isSet(5), false);
+
+ const systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+ const sandbox = new Cu.Sandbox(systemPrincipal);
+
+ equal(isSet(Cu.evalInSandbox("new Set()", sandbox)), true);
+ equal(isSet(Cu.evalInSandbox("new Map()", sandbox)), false);
+ equal(isSet(Cu.evalInSandbox("({})", sandbox)), false);
+ equal(isSet(Cu.evalInSandbox("'I swear I\\'m a Set'", sandbox)), false);
+ equal(isSet(Cu.evalInSandbox("5", sandbox)), false);
+}
diff --git a/devtools/shared/tests/unit/test_pluralForm-english.js b/devtools/shared/tests/unit/test_pluralForm-english.js
new file mode 100644
index 000000000..67cd3b712
--- /dev/null
+++ b/devtools/shared/tests/unit/test_pluralForm-english.js
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This unit test makes sure the plural form for Irish Gaeilge is working by
+ * using the makeGetter method instead of using the default language (by
+ * development), English.
+ */
+
+const {PluralForm} = require("devtools/shared/plural-form");
+
+function run_test() {
+ // English has 2 plural forms
+ do_check_eq(2, PluralForm.numForms());
+
+ // Make sure for good inputs, things work as expected
+ for (let num = 0; num <= 200; num++) {
+ do_check_eq(num == 1 ? "word" : "words", PluralForm.get(num, "word;words"));
+ }
+
+ // Not having enough plural forms defaults to the first form
+ do_check_eq("word", PluralForm.get(2, "word"));
+
+ // Empty forms defaults to the first form
+ do_check_eq("word", PluralForm.get(2, "word;"));
+}
diff --git a/devtools/shared/tests/unit/test_pluralForm-makeGetter.js b/devtools/shared/tests/unit/test_pluralForm-makeGetter.js
new file mode 100644
index 000000000..a9d4928b1
--- /dev/null
+++ b/devtools/shared/tests/unit/test_pluralForm-makeGetter.js
@@ -0,0 +1,38 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * This unit test makes sure the plural form for Irish Gaeilge is working by
+ * using the makeGetter method instead of using the default language (by
+ * development), English.
+ */
+
+const {PluralForm} = require("devtools/shared/plural-form");
+
+function run_test() {
+ // Irish is plural rule #11
+ let [get, numForms] = PluralForm.makeGetter(11);
+
+ // Irish has 5 plural forms
+ do_check_eq(5, numForms());
+
+ // I don't really know Irish, so I'll stick in some dummy text
+ let words = "is 1;is 2;is 3-6;is 7-10;everything else";
+
+ let test = function (text, low, high) {
+ for (let num = low; num <= high; num++) {
+ do_check_eq(text, get(num, words));
+ }
+ };
+
+ // Make sure for good inputs, things work as expected
+ test("everything else", 0, 0);
+ test("is 1", 1, 1);
+ test("is 2", 2, 2);
+ test("is 3-6", 3, 6);
+ test("is 7-10", 7, 10);
+ test("everything else", 11, 200);
+}
diff --git a/devtools/shared/tests/unit/test_prettifyCSS.js b/devtools/shared/tests/unit/test_prettifyCSS.js
new file mode 100644
index 000000000..8415c9b83
--- /dev/null
+++ b/devtools/shared/tests/unit/test_prettifyCSS.js
@@ -0,0 +1,68 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test prettifyCSS.
+
+"use strict";
+
+const {prettifyCSS} = require("devtools/shared/inspector/css-logic");
+
+const TESTS = [
+ { name: "simple test",
+ input: "div { font-family:'Arial Black', Arial, sans-serif; }",
+ expected: [
+ "div {",
+ "\tfont-family:'Arial Black', Arial, sans-serif;",
+ "}"
+ ]
+ },
+
+ { name: "whitespace before open brace",
+ input: "div{}",
+ expected: [
+ "div {",
+ "}"
+ ]
+ },
+
+ { name: "minified with trailing newline",
+ input: "\nbody{background:white;}div{font-size:4em;color:red}span{color:green;}\n",
+ expected: [
+ "body {",
+ "\tbackground:white;",
+ "}",
+ "div {",
+ "\tfont-size:4em;",
+ "\tcolor:red",
+ "}",
+ "span {",
+ "\tcolor:green;",
+ "}"
+ ]
+ },
+
+ { name: "leading whitespace",
+ input: "\n div{color: red;}",
+ expected: [
+ "div {",
+ "\tcolor: red;",
+ "}"
+ ]
+ },
+];
+
+function run_test() {
+ // Note that prettifyCSS.LINE_SEPARATOR is computed lazily, so we
+ // ensure it is set.
+ prettifyCSS("");
+
+ for (let test of TESTS) {
+ do_print(test.name);
+
+ let input = test.input.split("\n").join(prettifyCSS.LINE_SEPARATOR);
+ let output = prettifyCSS(input);
+ let expected = test.expected.join(prettifyCSS.LINE_SEPARATOR) +
+ prettifyCSS.LINE_SEPARATOR;
+ equal(output, expected, test.name);
+ }
+}
diff --git a/devtools/shared/tests/unit/test_require.js b/devtools/shared/tests/unit/test_require.js
new file mode 100644
index 000000000..005e56656
--- /dev/null
+++ b/devtools/shared/tests/unit/test_require.js
@@ -0,0 +1,20 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test require
+
+// Ensure that DevtoolsLoader.require doesn't spawn multiple
+// loader/modules when early cached
+function testBug1091706() {
+ let loader = new DevToolsLoader();
+ let require = loader.require;
+
+ let indent1 = require("devtools/shared/indentation");
+ let indent2 = require("devtools/shared/indentation");
+
+ do_check_true(indent1 === indent2);
+}
+
+function run_test() {
+ testBug1091706();
+}
diff --git a/devtools/shared/tests/unit/test_require_lazy.js b/devtools/shared/tests/unit/test_require_lazy.js
new file mode 100644
index 000000000..8ef5a7d23
--- /dev/null
+++ b/devtools/shared/tests/unit/test_require_lazy.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test devtools.lazyRequireGetter
+
+function run_test() {
+ const name = "asyncUtils";
+ const path = "devtools/shared/async-utils";
+ const o = {};
+ devtools.lazyRequireGetter(o, name, path);
+ const asyncUtils = require(path);
+ // XXX: do_check_eq only works on primitive types, so we have this
+ // do_check_true of an equality expression.
+ do_check_true(o.asyncUtils === asyncUtils);
+
+ // A non-main loader should get a new object via |lazyRequireGetter|, just
+ // as it would via a direct |require|.
+ const o2 = {};
+ let loader = new DevToolsLoader();
+
+ // We have to init the loader by loading any module before lazyRequireGetter is available
+ loader.require("devtools/shared/DevToolsUtils");
+
+ loader.lazyRequireGetter(o2, name, path);
+ do_check_true(o2.asyncUtils !== asyncUtils);
+
+ // A module required via a non-main loader that then uses |lazyRequireGetter|
+ // should also get the same object from that non-main loader.
+ let exposeLoader = loader.require("xpcshell-test/exposeLoader");
+ const o3 = exposeLoader.exerciseLazyRequire(name, path);
+ do_check_true(o3.asyncUtils === o2.asyncUtils);
+}
diff --git a/devtools/shared/tests/unit/test_require_raw.js b/devtools/shared/tests/unit/test_require_raw.js
new file mode 100644
index 000000000..5bec82b55
--- /dev/null
+++ b/devtools/shared/tests/unit/test_require_raw.js
@@ -0,0 +1,19 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test require using "raw!".
+
+function run_test() {
+ let loader = new DevToolsLoader();
+ let require = loader.require;
+
+ let variableFileContents = require("raw!devtools/client/themes/variables.css");
+ ok(variableFileContents.length > 0, "raw browserRequire worked");
+
+ let propertiesFileContents = require("raw!devtools/client/locales/shared.properties");
+ ok(propertiesFileContents.length > 0, "unprefixed properties raw require worked");
+
+ let chromePropertiesFileContents =
+ require("raw!chrome://devtools/locale/shared.properties");
+ ok(chromePropertiesFileContents.length > 0, "prefixed properties raw require worked");
+}
diff --git a/devtools/shared/tests/unit/test_safeErrorString.js b/devtools/shared/tests/unit/test_safeErrorString.js
new file mode 100644
index 000000000..9892f34d1
--- /dev/null
+++ b/devtools/shared/tests/unit/test_safeErrorString.js
@@ -0,0 +1,58 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test DevToolsUtils.safeErrorString
+
+function run_test() {
+ test_with_error();
+ test_with_tricky_error();
+ test_with_string();
+ test_with_thrower();
+ test_with_psychotic();
+}
+
+function test_with_error() {
+ let s = DevToolsUtils.safeErrorString(new Error("foo bar"));
+ // Got the message.
+ do_check_true(s.includes("foo bar"));
+ // Got the stack.
+ do_check_true(s.includes("test_with_error"));
+ do_check_true(s.includes("test_safeErrorString.js"));
+ // Got the lineNumber and columnNumber.
+ do_check_true(s.includes("Line"));
+ do_check_true(s.includes("column"));
+}
+
+function test_with_tricky_error() {
+ let e = new Error("batman");
+ e.stack = { toString: Object.create(null) };
+ let s = DevToolsUtils.safeErrorString(e);
+ // Still got the message, despite a bad stack property.
+ do_check_true(s.includes("batman"));
+}
+
+function test_with_string() {
+ let s = DevToolsUtils.safeErrorString("not really an error");
+ // Still get the message.
+ do_check_true(s.includes("not really an error"));
+}
+
+function test_with_thrower() {
+ let s = DevToolsUtils.safeErrorString({
+ toString: () => {
+ throw new Error("Muahahaha");
+ }
+ });
+ // Still don't fail, get string back.
+ do_check_eq(typeof s, "string");
+}
+
+function test_with_psychotic() {
+ let s = DevToolsUtils.safeErrorString({
+ toString: () => Object.create(null)
+ });
+ // Still get a string out, and no exceptions thrown
+ do_check_eq(typeof s, "string");
+ do_check_eq(s, "[object Object]");
+}
diff --git a/devtools/shared/tests/unit/test_stack.js b/devtools/shared/tests/unit/test_stack.js
new file mode 100644
index 000000000..ef747c83f
--- /dev/null
+++ b/devtools/shared/tests/unit/test_stack.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Test stack.js.
+
+function run_test() {
+ let loader = new DevToolsLoader();
+ let require = loader.require;
+
+ const {StackFrameCache} = require("devtools/server/actors/utils/stack");
+
+ let cache = new StackFrameCache();
+ cache.initFrames();
+ let baseFrame = {
+ line: 23,
+ column: 77,
+ source: "nowhere",
+ functionDisplayName: "nobody",
+ parent: null,
+ asyncParent: null,
+ asyncCause: null
+ };
+ cache.addFrame(baseFrame);
+
+ let event = cache.makeEvent();
+ do_check_eq(event[0], null);
+ do_check_eq(event[1].functionDisplayName, "nobody");
+ do_check_eq(event.length, 2);
+
+ cache.addFrame({
+ line: 24,
+ column: 78,
+ source: "nowhere",
+ functionDisplayName: "still nobody",
+ parent: null,
+ asyncParent: baseFrame,
+ asyncCause: "async"
+ });
+
+ event = cache.makeEvent();
+ do_check_eq(event[0].functionDisplayName, "still nobody");
+ do_check_eq(event[0].parent, 0);
+ do_check_eq(event[0].asyncParent, 1);
+ do_check_eq(event.length, 1);
+}
diff --git a/devtools/shared/tests/unit/xpcshell.ini b/devtools/shared/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..d0323c813
--- /dev/null
+++ b/devtools/shared/tests/unit/xpcshell.ini
@@ -0,0 +1,40 @@
+[DEFAULT]
+tags = devtools
+head = head_devtools.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+ exposeLoader.js
+
+[test_assert.js]
+[test_csslexer.js]
+[test_css-properties-db.js]
+# This test only enforces that the CSS database is up to date with nightly. The DB is
+# only used when inspecting a target that doesn't support the getCSSDatabase actor.
+# CSS properties are behind compile-time flags, and there is no automatic rebuild
+# process for uplifts, so this test breaks on uplift.
+run-if = nightly_build
+[test_fetch-bom.js]
+[test_fetch-chrome.js]
+[test_fetch-file.js]
+[test_fetch-http.js]
+[test_fetch-resource.js]
+[test_flatten.js]
+[test_indentation.js]
+[test_independent_loaders.js]
+[test_invisible_loader.js]
+[test_isSet.js]
+[test_safeErrorString.js]
+[test_defineLazyPrototypeGetter.js]
+[test_async-utils.js]
+[test_console_filtering.js]
+[test_pluralForm-english.js]
+[test_pluralForm-makeGetter.js]
+[test_prettifyCSS.js]
+[test_require_lazy.js]
+[test_require_raw.js]
+[test_require.js]
+[test_stack.js]
+[test_defer.js]
+[test_executeSoon.js]
diff --git a/devtools/shared/touch/moz.build b/devtools/shared/touch/moz.build
new file mode 100644
index 000000000..96fbac8eb
--- /dev/null
+++ b/devtools/shared/touch/moz.build
@@ -0,0 +1,11 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'simulator-content.js',
+ 'simulator-core.js',
+ 'simulator.js',
+)
diff --git a/devtools/shared/touch/simulator-content.js b/devtools/shared/touch/simulator-content.js
new file mode 100644
index 000000000..0b10579ca
--- /dev/null
+++ b/devtools/shared/touch/simulator-content.js
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+ /* globals addMessageListener, sendAsyncMessage */
+"use strict";
+
+const { utils: Cu } = Components;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { SimulatorCore } = require("devtools/shared/touch/simulator-core");
+
+/**
+ * Launches SimulatorCore in the content window to simulate touch events
+ * This frame script is managed by `simulator.js`.
+ */
+
+var simulator = {
+ messages: [
+ "TouchEventSimulator:Start",
+ "TouchEventSimulator:Stop",
+ ],
+
+ init() {
+ this.simulatorCore = new SimulatorCore(docShell.chromeEventHandler);
+ this.messages.forEach(msgName => {
+ addMessageListener(msgName, this);
+ });
+ },
+
+ receiveMessage(msg) {
+ switch (msg.name) {
+ case "TouchEventSimulator:Start":
+ this.simulatorCore.start();
+ sendAsyncMessage("TouchEventSimulator:Started");
+ break;
+ case "TouchEventSimulator:Stop":
+ this.simulatorCore.stop();
+ sendAsyncMessage("TouchEventSimulator:Stopped");
+ break;
+ }
+ },
+};
+
+simulator.init();
diff --git a/devtools/shared/touch/simulator-core.js b/devtools/shared/touch/simulator-core.js
new file mode 100644
index 000000000..6933f9207
--- /dev/null
+++ b/devtools/shared/touch/simulator-core.js
@@ -0,0 +1,366 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { Ci, Cu } = require("chrome");
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+var systemAppOrigin = (function () {
+ let systemOrigin = "_";
+ try {
+ systemOrigin = Services.io.newURI(
+ Services.prefs.getCharPref("b2g.system_manifest_url"), null, null)
+ .prePath;
+ } catch (e) {
+ // Fall back to default value
+ }
+ return systemOrigin;
+})();
+
+var threshold = 25;
+try {
+ threshold = Services.prefs.getIntPref("ui.dragThresholdX");
+} catch (e) {
+ // Fall back to default value
+}
+
+var delay = 500;
+try {
+ delay = Services.prefs.getIntPref("ui.click_hold_context_menus.delay");
+} catch (e) {
+ // Fall back to default value
+}
+
+function SimulatorCore(simulatorTarget) {
+ this.simulatorTarget = simulatorTarget;
+}
+
+/**
+ * Simulate touch events for platforms where they aren't generally available.
+ */
+SimulatorCore.prototype = {
+ events: [
+ "mousedown",
+ "mousemove",
+ "mouseup",
+ "touchstart",
+ "touchend",
+ "mouseenter",
+ "mouseover",
+ "mouseout",
+ "mouseleave"
+ ],
+
+ contextMenuTimeout: null,
+
+ simulatorTarget: null,
+
+ enabled: false,
+
+ start() {
+ if (this.enabled) {
+ // Simulator is already started
+ return;
+ }
+ this.events.forEach(evt => {
+ // Only listen trusted events to prevent messing with
+ // event dispatched manually within content documents
+ this.simulatorTarget.addEventListener(evt, this, true, false);
+ });
+ this.enabled = true;
+ },
+
+ stop() {
+ if (!this.enabled) {
+ // Simulator isn't running
+ return;
+ }
+ this.events.forEach(evt => {
+ this.simulatorTarget.removeEventListener(evt, this, true);
+ });
+ this.enabled = false;
+ },
+
+ handleEvent(evt) {
+ // The gaia system window use an hybrid system even on the device which is
+ // a mix of mouse/touch events. So let's not cancel *all* mouse events
+ // if it is the current target.
+ let content = this.getContent(evt.target);
+ if (!content) {
+ return;
+ }
+ let isSystemWindow = content.location.toString()
+ .startsWith(systemAppOrigin);
+
+ // App touchstart & touchend should also be dispatched on the system app
+ // to match on-device behavior.
+ if (evt.type.startsWith("touch") && !isSystemWindow) {
+ let sysFrame = content.realFrameElement;
+ if (!sysFrame) {
+ return;
+ }
+ let sysDocument = sysFrame.ownerDocument;
+ let sysWindow = sysDocument.defaultView;
+
+ let touchEvent = sysDocument.createEvent("touchevent");
+ let touch = evt.touches[0] || evt.changedTouches[0];
+ let point = sysDocument.createTouch(sysWindow, sysFrame, 0,
+ touch.pageX, touch.pageY,
+ touch.screenX, touch.screenY,
+ touch.clientX, touch.clientY,
+ 1, 1, 0, 0);
+
+ let touches = sysDocument.createTouchList(point);
+ let targetTouches = touches;
+ let changedTouches = touches;
+ touchEvent.initTouchEvent(evt.type, true, true, sysWindow, 0,
+ false, false, false, false,
+ touches, targetTouches, changedTouches);
+ sysFrame.dispatchEvent(touchEvent);
+ return;
+ }
+
+ // Ignore all but real mouse event coming from physical mouse
+ // (especially ignore mouse event being dispatched from a touch event)
+ if (evt.button ||
+ evt.mozInputSource != Ci.nsIDOMMouseEvent.MOZ_SOURCE_MOUSE ||
+ evt.isSynthesized) {
+ return;
+ }
+
+ let eventTarget = this.target;
+ let type = "";
+ switch (evt.type) {
+ case "mouseenter":
+ case "mouseover":
+ case "mouseout":
+ case "mouseleave":
+ // Don't propagate events which are not related to touch events
+ evt.stopPropagation();
+ break;
+
+ case "mousedown":
+ this.target = evt.target;
+
+ this.contextMenuTimeout = this.sendContextMenu(evt);
+
+ this.cancelClick = false;
+ this.startX = evt.pageX;
+ this.startY = evt.pageY;
+
+ // Capture events so if a different window show up the events
+ // won't be dispatched to something else.
+ evt.target.setCapture(false);
+
+ type = "touchstart";
+ break;
+
+ case "mousemove":
+ if (!eventTarget) {
+ // Don't propagate mousemove event when touchstart event isn't fired
+ evt.stopPropagation();
+ return;
+ }
+
+ if (!this.cancelClick) {
+ if (Math.abs(this.startX - evt.pageX) > threshold ||
+ Math.abs(this.startY - evt.pageY) > threshold) {
+ this.cancelClick = true;
+ content.clearTimeout(this.contextMenuTimeout);
+ }
+ }
+
+ type = "touchmove";
+ break;
+
+ case "mouseup":
+ if (!eventTarget) {
+ return;
+ }
+ this.target = null;
+
+ content.clearTimeout(this.contextMenuTimeout);
+ type = "touchend";
+
+ // Only register click listener after mouseup to ensure
+ // catching only real user click. (Especially ignore click
+ // being dispatched on form submit)
+ if (evt.detail == 1) {
+ this.simulatorTarget.addEventListener("click", this, true, false);
+ }
+ break;
+
+ case "click":
+ // Mouse events has been cancelled so dispatch a sequence
+ // of events to where touchend has been fired
+ evt.preventDefault();
+ evt.stopImmediatePropagation();
+
+ this.simulatorTarget.removeEventListener("click", this, true, false);
+
+ if (this.cancelClick) {
+ return;
+ }
+
+ content.setTimeout(function dispatchMouseEvents(self) {
+ try {
+ self.fireMouseEvent("mousedown", evt);
+ self.fireMouseEvent("mousemove", evt);
+ self.fireMouseEvent("mouseup", evt);
+ } catch (e) {
+ console.error("Exception in touch event helper: " + e);
+ }
+ }, this.getDelayBeforeMouseEvent(evt), this);
+ return;
+ }
+
+ let target = eventTarget || this.target;
+ if (target && type) {
+ this.sendTouchEvent(evt, target, type);
+ }
+
+ if (!isSystemWindow) {
+ evt.preventDefault();
+ evt.stopImmediatePropagation();
+ }
+ },
+
+ fireMouseEvent(type, evt) {
+ let content = this.getContent(evt.target);
+ let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+ utils.sendMouseEvent(type, evt.clientX, evt.clientY, 0, 1, 0, true, 0,
+ Ci.nsIDOMMouseEvent.MOZ_SOURCE_TOUCH);
+ },
+
+ sendContextMenu({ target, clientX, clientY, screenX, screenY }) {
+ let view = target.ownerDocument.defaultView;
+ let { MouseEvent } = view;
+ let evt = new MouseEvent("contextmenu", {
+ bubbles: true,
+ cancelable: true,
+ view,
+ screenX,
+ screenY,
+ clientX,
+ clientY,
+ });
+ let content = this.getContent(target);
+ let timeout = content.setTimeout((function contextMenu() {
+ target.dispatchEvent(evt);
+ this.cancelClick = true;
+ }).bind(this), delay);
+
+ return timeout;
+ },
+
+ sendTouchEvent(evt, target, name) {
+ function clone(obj) {
+ return Cu.cloneInto(obj, target);
+ }
+ // When running OOP b2g desktop, we need to send the touch events
+ // using the mozbrowser api on the unwrapped frame.
+ if (target.localName == "iframe" && target.mozbrowser === true) {
+ if (name == "touchstart") {
+ this.touchstartTime = Date.now();
+ } else if (name == "touchend") {
+ // If we have a "fast" tap, don't send a click as both will be turned
+ // into a click and that breaks eg. checkboxes.
+ if (Date.now() - this.touchstartTime < delay) {
+ this.cancelClick = true;
+ }
+ }
+ let unwrapped = XPCNativeWrapper.unwrap(target);
+ unwrapped.sendTouchEvent(name, clone([0]), // event type, id
+ clone([evt.clientX]), // x
+ clone([evt.clientY]), // y
+ clone([1]), clone([1]), // rx, ry
+ clone([0]), clone([0]), // rotation, force
+ 1); // count
+ return;
+ }
+ let document = target.ownerDocument;
+ let content = this.getContent(target);
+ if (!content) {
+ return;
+ }
+
+ let touchEvent = document.createEvent("touchevent");
+ let point = document.createTouch(content, target, 0,
+ evt.pageX, evt.pageY,
+ evt.screenX, evt.screenY,
+ evt.clientX, evt.clientY,
+ 1, 1, 0, 0);
+
+ let touches = document.createTouchList(point);
+ let targetTouches = touches;
+ let changedTouches = touches;
+ if (name === "touchend" || name === "touchcancel") {
+ // "touchend" and "touchcancel" events should not have the removed touch
+ // neither in touches nor in targetTouches
+ touches = targetTouches = document.createTouchList();
+ }
+
+ touchEvent.initTouchEvent(name, true, true, content, 0,
+ false, false, false, false,
+ touches, targetTouches, changedTouches);
+ target.dispatchEvent(touchEvent);
+ },
+
+ getContent(target) {
+ let win = (target && target.ownerDocument)
+ ? target.ownerDocument.defaultView
+ : null;
+ return win;
+ },
+
+ getDelayBeforeMouseEvent(evt) {
+ // On mobile platforms, Firefox inserts a 300ms delay between
+ // touch events and accompanying mouse events, except if the
+ // content window is not zoomable and the content window is
+ // auto-zoomed to device-width.
+
+ // If the preference dom.meta-viewport.enabled is set to false,
+ // we couldn't read viewport's information from getViewportInfo().
+ // So we always simulate 300ms delay when the
+ // dom.meta-viewport.enabled is false.
+ let savedMetaViewportEnabled =
+ Services.prefs.getBoolPref("dom.meta-viewport.enabled");
+ if (!savedMetaViewportEnabled) {
+ return 300;
+ }
+
+ let content = this.getContent(evt.target);
+ if (!content) {
+ return 0;
+ }
+
+ let utils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils);
+
+ let allowZoom = {},
+ minZoom = {},
+ maxZoom = {},
+ autoSize = {};
+
+ utils.getViewportInfo(content.innerWidth, content.innerHeight, {},
+ allowZoom, minZoom, maxZoom, {}, {}, autoSize);
+
+ // FIXME: On Safari and Chrome mobile platform, if the css property
+ // touch-action set to none or manipulation would also suppress 300ms
+ // delay. But Firefox didn't support this property now, we can't get
+ // this value from utils.getVisitedDependentComputedStyle() to check
+ // if we should suppress 300ms delay.
+ if (!allowZoom.value || // user-scalable = no
+ minZoom.value === maxZoom.value || // minimum-scale = maximum-scale
+ autoSize.value // width = device-width
+ ) {
+ return 0;
+ } else {
+ return 300;
+ }
+ }
+};
+
+exports.SimulatorCore = SimulatorCore;
diff --git a/devtools/shared/touch/simulator.js b/devtools/shared/touch/simulator.js
new file mode 100644
index 000000000..0e6d29282
--- /dev/null
+++ b/devtools/shared/touch/simulator.js
@@ -0,0 +1,77 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var promise = require("promise");
+var defer = require("devtools/shared/defer");
+var Services = require("Services");
+
+const FRAME_SCRIPT =
+ "resource://devtools/shared/touch/simulator-content.js";
+
+var trackedBrowsers = new WeakMap();
+var savedTouchEventsEnabled =
+ Services.prefs.getIntPref("dom.w3c_touch_events.enabled");
+
+/**
+ * Simulate touch events for platforms where they aren't generally available.
+ * Defers to the `simulator-content.js` frame script to perform the real work.
+ */
+function TouchEventSimulator(browser) {
+ // Returns an already instantiated simulator for this browser
+ let simulator = trackedBrowsers.get(browser);
+ if (simulator) {
+ return simulator;
+ }
+
+ let mm = browser.frameLoader.messageManager;
+ mm.loadFrameScript(FRAME_SCRIPT, true);
+
+ simulator = {
+ enabled: false,
+
+ start() {
+ if (this.enabled) {
+ return promise.resolve({ isReloadNeeded: false });
+ }
+ this.enabled = true;
+
+ let deferred = defer();
+ let isReloadNeeded =
+ Services.prefs.getIntPref("dom.w3c_touch_events.enabled") != 1;
+ Services.prefs.setIntPref("dom.w3c_touch_events.enabled", 1);
+ let onStarted = () => {
+ mm.removeMessageListener("TouchEventSimulator:Started", onStarted);
+ deferred.resolve({ isReloadNeeded });
+ };
+ mm.addMessageListener("TouchEventSimulator:Started", onStarted);
+ mm.sendAsyncMessage("TouchEventSimulator:Start");
+ return deferred.promise;
+ },
+
+ stop() {
+ if (!this.enabled) {
+ return promise.resolve();
+ }
+ this.enabled = false;
+
+ let deferred = defer();
+ Services.prefs.setIntPref("dom.w3c_touch_events.enabled",
+ savedTouchEventsEnabled);
+ let onStopped = () => {
+ mm.removeMessageListener("TouchEventSimulator:Stopped", onStopped);
+ deferred.resolve();
+ };
+ mm.addMessageListener("TouchEventSimulator:Stopped", onStopped);
+ mm.sendAsyncMessage("TouchEventSimulator:Stop");
+ return deferred.promise;
+ }
+ };
+
+ trackedBrowsers.set(browser, simulator);
+
+ return simulator;
+}
+
+exports.TouchEventSimulator = TouchEventSimulator;
diff --git a/devtools/shared/transport/moz.build b/devtools/shared/transport/moz.build
new file mode 100644
index 000000000..fbf0444b8
--- /dev/null
+++ b/devtools/shared/transport/moz.build
@@ -0,0 +1,14 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'packets.js',
+ 'stream-utils.js',
+ 'transport.js',
+ 'websocket-transport.js',
+)
diff --git a/devtools/shared/transport/packets.js b/devtools/shared/transport/packets.js
new file mode 100644
index 000000000..3ebcd8f13
--- /dev/null
+++ b/devtools/shared/transport/packets.js
@@ -0,0 +1,414 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ * Packets contain read / write functionality for the different packet types
+ * supported by the debugging protocol, so that a transport can focus on
+ * delivery and queue management without worrying too much about the specific
+ * packet types.
+ *
+ * They are intended to be "one use only", so a new packet should be
+ * instantiated for each incoming or outgoing packet.
+ *
+ * A complete Packet type should expose at least the following:
+ * * read(stream, scriptableStream)
+ * Called when the input stream has data to read
+ * * write(stream)
+ * Called when the output stream is ready to write
+ * * get done()
+ * Returns true once the packet is done being read / written
+ * * destroy()
+ * Called to clean up at the end of use
+ */
+
+const { Cc, Ci, Cu } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { dumpn, dumpv } = DevToolsUtils;
+const flags = require("devtools/shared/flags");
+const StreamUtils = require("devtools/shared/transport/stream-utils");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+
+DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
+ const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ unicodeConverter.charset = "UTF-8";
+ return unicodeConverter;
+});
+
+// The transport's previous check ensured the header length did not exceed 20
+// characters. Here, we opt for the somewhat smaller, but still large limit of
+// 1 TiB.
+const PACKET_LENGTH_MAX = Math.pow(2, 40);
+
+/**
+ * A generic Packet processing object (extended by two subtypes below).
+ */
+function Packet(transport) {
+ this._transport = transport;
+ this._length = 0;
+}
+
+/**
+ * Attempt to initialize a new Packet based on the incoming packet header we've
+ * received so far. We try each of the types in succession, trying JSON packets
+ * first since they are much more common.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return Packet
+ * The parsed packet of the matching type, or null if no types matched.
+ */
+Packet.fromHeader = function (header, transport) {
+ return JSONPacket.fromHeader(header, transport) ||
+ BulkPacket.fromHeader(header, transport);
+};
+
+Packet.prototype = {
+
+ get length() {
+ return this._length;
+ },
+
+ set length(length) {
+ if (length > PACKET_LENGTH_MAX) {
+ throw Error("Packet length " + length + " exceeds the max length of " +
+ PACKET_LENGTH_MAX);
+ }
+ this._length = length;
+ },
+
+ destroy: function () {
+ this._transport = null;
+ }
+
+};
+
+exports.Packet = Packet;
+
+/**
+ * With a JSON packet (the typical packet type sent via the transport), data is
+ * transferred as a JSON packet serialized into a string, with the string length
+ * prepended to the packet, followed by a colon ([length]:[packet]). The
+ * contents of the JSON packet are specified in the Remote Debugging Protocol
+ * specification.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ */
+function JSONPacket(transport) {
+ Packet.call(this, transport);
+ this._data = "";
+ this._done = false;
+}
+
+/**
+ * Attempt to initialize a new JSONPacket based on the incoming packet header
+ * we've received so far.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return JSONPacket
+ * The parsed packet, or null if it's not a match.
+ */
+JSONPacket.fromHeader = function (header, transport) {
+ let match = this.HEADER_PATTERN.exec(header);
+
+ if (!match) {
+ return null;
+ }
+
+ dumpv("Header matches JSON packet");
+ let packet = new JSONPacket(transport);
+ packet.length = +match[1];
+ return packet;
+};
+
+JSONPacket.HEADER_PATTERN = /^(\d+):$/;
+
+JSONPacket.prototype = Object.create(Packet.prototype);
+
+Object.defineProperty(JSONPacket.prototype, "object", {
+ /**
+ * Gets the object (not the serialized string) being read or written.
+ */
+ get: function () { return this._object; },
+
+ /**
+ * Sets the object to be sent when write() is called.
+ */
+ set: function (object) {
+ this._object = object;
+ let data = JSON.stringify(object);
+ this._data = unicodeConverter.ConvertFromUnicode(data);
+ this.length = this._data.length;
+ }
+});
+
+JSONPacket.prototype.read = function (stream, scriptableStream) {
+ dumpv("Reading JSON packet");
+
+ // Read in more packet data.
+ this._readData(stream, scriptableStream);
+
+ if (!this.done) {
+ // Don't have a complete packet yet.
+ return;
+ }
+
+ let json = this._data;
+ try {
+ json = unicodeConverter.ConvertToUnicode(json);
+ this._object = JSON.parse(json);
+ } catch (e) {
+ let msg = "Error parsing incoming packet: " + json + " (" + e +
+ " - " + e.stack + ")";
+ console.error(msg);
+ dumpn(msg);
+ return;
+ }
+
+ this._transport._onJSONObjectReady(this._object);
+};
+
+JSONPacket.prototype._readData = function (stream, scriptableStream) {
+ if (flags.wantVerbose) {
+ dumpv("Reading JSON data: _l: " + this.length + " dL: " +
+ this._data.length + " sA: " + stream.available());
+ }
+ let bytesToRead = Math.min(this.length - this._data.length,
+ stream.available());
+ this._data += scriptableStream.readBytes(bytesToRead);
+ this._done = this._data.length === this.length;
+};
+
+JSONPacket.prototype.write = function (stream) {
+ dumpv("Writing JSON packet");
+
+ if (this._outgoing === undefined) {
+ // Format the serialized packet to a buffer
+ this._outgoing = this.length + ":" + this._data;
+ }
+
+ let written = stream.write(this._outgoing, this._outgoing.length);
+ this._outgoing = this._outgoing.slice(written);
+ this._done = !this._outgoing.length;
+};
+
+Object.defineProperty(JSONPacket.prototype, "done", {
+ get: function () { return this._done; }
+});
+
+JSONPacket.prototype.toString = function () {
+ return JSON.stringify(this._object, null, 2);
+};
+
+exports.JSONPacket = JSONPacket;
+
+/**
+ * With a bulk packet, data is transferred by temporarily handing over the
+ * transport's input or output stream to the application layer for writing data
+ * directly. This can be much faster for large data sets, and avoids various
+ * stages of copies and data duplication inherent in the JSON packet type. The
+ * bulk packet looks like:
+ *
+ * bulk [actor] [type] [length]:[data]
+ *
+ * The interpretation of the data portion depends on the kind of actor and the
+ * packet's type. See the Remote Debugging Protocol Stream Transport spec for
+ * more details.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ */
+function BulkPacket(transport) {
+ Packet.call(this, transport);
+ this._done = false;
+ this._readyForWriting = defer();
+}
+
+/**
+ * Attempt to initialize a new BulkPacket based on the incoming packet header
+ * we've received so far.
+ * @param header string
+ * The packet header string to attempt parsing.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @return BulkPacket
+ * The parsed packet, or null if it's not a match.
+ */
+BulkPacket.fromHeader = function (header, transport) {
+ let match = this.HEADER_PATTERN.exec(header);
+
+ if (!match) {
+ return null;
+ }
+
+ dumpv("Header matches bulk packet");
+ let packet = new BulkPacket(transport);
+ packet.header = {
+ actor: match[1],
+ type: match[2],
+ length: +match[3]
+ };
+ return packet;
+};
+
+BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
+
+BulkPacket.prototype = Object.create(Packet.prototype);
+
+BulkPacket.prototype.read = function (stream) {
+ dumpv("Reading bulk packet, handing off input stream");
+
+ // Temporarily pause monitoring of the input stream
+ this._transport.pauseIncoming();
+
+ let deferred = defer();
+
+ this._transport._onBulkReadReady({
+ actor: this.actor,
+ type: this.type,
+ length: this.length,
+ copyTo: (output) => {
+ dumpv("CT length: " + this.length);
+ let copying = StreamUtils.copyStream(stream, output, this.length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: stream,
+ done: deferred
+ });
+
+ // Await the result of reading from the stream
+ deferred.promise.then(() => {
+ dumpv("onReadDone called, ending bulk mode");
+ this._done = true;
+ this._transport.resumeIncoming();
+ }, this._transport.close);
+
+ // Ensure this is only done once
+ this.read = () => {
+ throw new Error("Tried to read() a BulkPacket's stream multiple times.");
+ };
+};
+
+BulkPacket.prototype.write = function (stream) {
+ dumpv("Writing bulk packet");
+
+ if (this._outgoingHeader === undefined) {
+ dumpv("Serializing bulk packet header");
+ // Format the serialized packet header to a buffer
+ this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
+ this.length + ":";
+ }
+
+ // Write the header, or whatever's left of it to write.
+ if (this._outgoingHeader.length) {
+ dumpv("Writing bulk packet header");
+ let written = stream.write(this._outgoingHeader,
+ this._outgoingHeader.length);
+ this._outgoingHeader = this._outgoingHeader.slice(written);
+ return;
+ }
+
+ dumpv("Handing off output stream");
+
+ // Temporarily pause the monitoring of the output stream
+ this._transport.pauseOutgoing();
+
+ let deferred = defer();
+
+ this._readyForWriting.resolve({
+ copyFrom: (input) => {
+ dumpv("CF length: " + this.length);
+ let copying = StreamUtils.copyStream(input, stream, this.length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: stream,
+ done: deferred
+ });
+
+ // Await the result of writing to the stream
+ deferred.promise.then(() => {
+ dumpv("onWriteDone called, ending bulk mode");
+ this._done = true;
+ this._transport.resumeOutgoing();
+ }, this._transport.close);
+
+ // Ensure this is only done once
+ this.write = () => {
+ throw new Error("Tried to write() a BulkPacket's stream multiple times.");
+ };
+};
+
+Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
+ get: function () {
+ return this._readyForWriting.promise;
+ }
+});
+
+Object.defineProperty(BulkPacket.prototype, "header", {
+ get: function () {
+ return {
+ actor: this.actor,
+ type: this.type,
+ length: this.length
+ };
+ },
+
+ set: function (header) {
+ this.actor = header.actor;
+ this.type = header.type;
+ this.length = header.length;
+ },
+});
+
+Object.defineProperty(BulkPacket.prototype, "done", {
+ get: function () { return this._done; },
+});
+
+
+BulkPacket.prototype.toString = function () {
+ return "Bulk: " + JSON.stringify(this.header, null, 2);
+};
+
+exports.BulkPacket = BulkPacket;
+
+/**
+ * RawPacket is used to test the transport's error handling of malformed
+ * packets, by writing data directly onto the stream.
+ * @param transport DebuggerTransport
+ * The transport instance that will own the packet.
+ * @param data string
+ * The raw string to send out onto the stream.
+ */
+function RawPacket(transport, data) {
+ Packet.call(this, transport);
+ this._data = data;
+ this.length = data.length;
+ this._done = false;
+}
+
+RawPacket.prototype = Object.create(Packet.prototype);
+
+RawPacket.prototype.read = function (stream) {
+ // This hasn't yet been needed for testing.
+ throw Error("Not implmented.");
+};
+
+RawPacket.prototype.write = function (stream) {
+ let written = stream.write(this._data, this._data.length);
+ this._data = this._data.slice(written);
+ this._done = !this._data.length;
+};
+
+Object.defineProperty(RawPacket.prototype, "done", {
+ get: function () { return this._done; }
+});
+
+exports.RawPacket = RawPacket;
diff --git a/devtools/shared/transport/stream-utils.js b/devtools/shared/transport/stream-utils.js
new file mode 100644
index 000000000..23f67a518
--- /dev/null
+++ b/devtools/shared/transport/stream-utils.js
@@ -0,0 +1,249 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { Ci, Cc, Cu, Cr, CC } = require("chrome");
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { dumpv } = DevToolsUtils;
+const EventEmitter = require("devtools/shared/event-emitter");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+
+DevToolsUtils.defineLazyGetter(this, "IOUtil", () => {
+ return Cc["@mozilla.org/io-util;1"].getService(Ci.nsIIOUtil);
+});
+
+DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
+ return CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream", "init");
+});
+
+const BUFFER_SIZE = 0x8000;
+
+/**
+ * This helper function (and its companion object) are used by bulk senders and
+ * receivers to read and write data in and out of other streams. Functions that
+ * make use of this tool are passed to callers when it is time to read or write
+ * bulk data. It is highly recommended to use these copier functions instead of
+ * the stream directly because the copier enforces the agreed upon length.
+ * Since bulk mode reuses an existing stream, the sender and receiver must write
+ * and read exactly the agreed upon amount of data, or else the entire transport
+ * will be left in a invalid state. Additionally, other methods of stream
+ * copying (such as NetUtil.asyncCopy) close the streams involved, which would
+ * terminate the debugging transport, and so it is avoided here.
+ *
+ * Overall, this *works*, but clearly the optimal solution would be able to just
+ * use the streams directly. If it were possible to fully implement
+ * nsIInputStream / nsIOutputStream in JS, wrapper streams could be created to
+ * enforce the length and avoid closing, and consumers could use familiar stream
+ * utilities like NetUtil.asyncCopy.
+ *
+ * The function takes two async streams and copies a precise number of bytes
+ * from one to the other. Copying begins immediately, but may complete at some
+ * future time depending on data size. Use the returned promise to know when
+ * it's complete.
+ *
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @param length Integer
+ * The amount of data that needs to be copied.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected if any
+ * (unexpected) errors occur.
+ */
+function copyStream(input, output, length) {
+ let copier = new StreamCopier(input, output, length);
+ return copier.copy();
+}
+
+function StreamCopier(input, output, length) {
+ EventEmitter.decorate(this);
+ this._id = StreamCopier._nextId++;
+ this.input = input;
+ // Save off the base output stream, since we know it's async as we've required
+ this.baseAsyncOutput = output;
+ if (IOUtil.outputStreamIsBuffered(output)) {
+ this.output = output;
+ } else {
+ this.output = Cc["@mozilla.org/network/buffered-output-stream;1"].
+ createInstance(Ci.nsIBufferedOutputStream);
+ this.output.init(output, BUFFER_SIZE);
+ }
+ this._length = length;
+ this._amountLeft = length;
+ this._deferred = defer();
+
+ this._copy = this._copy.bind(this);
+ this._flush = this._flush.bind(this);
+ this._destroy = this._destroy.bind(this);
+
+ // Copy promise's then method up to this object.
+ // Allows the copier to offer a promise interface for the simple succeed or
+ // fail scenarios, but also emit events (due to the EventEmitter) for other
+ // states, like progress.
+ this.then = this._deferred.promise.then.bind(this._deferred.promise);
+ this.then(this._destroy, this._destroy);
+
+ // Stream ready callback starts as |_copy|, but may switch to |_flush| at end
+ // if flushing would block the output stream.
+ this._streamReadyCallback = this._copy;
+}
+StreamCopier._nextId = 0;
+
+StreamCopier.prototype = {
+
+ copy: function () {
+ // Dispatch to the next tick so that it's possible to attach a progress
+ // event listener, even for extremely fast copies (like when testing).
+ Services.tm.currentThread.dispatch(() => {
+ try {
+ this._copy();
+ } catch (e) {
+ this._deferred.reject(e);
+ }
+ }, 0);
+ return this;
+ },
+
+ _copy: function () {
+ let bytesAvailable = this.input.available();
+ let amountToCopy = Math.min(bytesAvailable, this._amountLeft);
+ this._debug("Trying to copy: " + amountToCopy);
+
+ let bytesCopied;
+ try {
+ bytesCopied = this.output.writeFrom(this.input, amountToCopy);
+ } catch (e) {
+ if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this._debug("Base stream would block, will retry");
+ this._debug("Waiting for output stream");
+ this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ this._amountLeft -= bytesCopied;
+ this._debug("Copied: " + bytesCopied +
+ ", Left: " + this._amountLeft);
+ this._emitProgress();
+
+ if (this._amountLeft === 0) {
+ this._debug("Copy done!");
+ this._flush();
+ return;
+ }
+
+ this._debug("Waiting for input stream");
+ this.input.asyncWait(this, 0, 0, Services.tm.currentThread);
+ },
+
+ _emitProgress: function () {
+ this.emit("progress", {
+ bytesSent: this._length - this._amountLeft,
+ totalBytes: this._length
+ });
+ },
+
+ _flush: function () {
+ try {
+ this.output.flush();
+ } catch (e) {
+ if (e.result == Cr.NS_BASE_STREAM_WOULD_BLOCK ||
+ e.result == Cr.NS_ERROR_FAILURE) {
+ this._debug("Flush would block, will retry");
+ this._streamReadyCallback = this._flush;
+ this._debug("Waiting for output stream");
+ this.baseAsyncOutput.asyncWait(this, 0, 0, Services.tm.currentThread);
+ return;
+ } else {
+ throw e;
+ }
+ }
+ this._deferred.resolve();
+ },
+
+ _destroy: function () {
+ this._destroy = null;
+ this._copy = null;
+ this._flush = null;
+ this.input = null;
+ this.output = null;
+ },
+
+ // nsIInputStreamCallback
+ onInputStreamReady: function () {
+ this._streamReadyCallback();
+ },
+
+ // nsIOutputStreamCallback
+ onOutputStreamReady: function () {
+ this._streamReadyCallback();
+ },
+
+ _debug: function (msg) {
+ // Prefix logs with the copier ID, which makes logs much easier to
+ // understand when several copiers are running simultaneously
+ dumpv("Copier: " + this._id + " " + msg);
+ }
+
+};
+
+/**
+ * Read from a stream, one byte at a time, up to the next |delimiter|
+ * character, but stopping if we've read |count| without finding it. Reading
+ * also terminates early if there are less than |count| bytes available on the
+ * stream. In that case, we only read as many bytes as the stream currently has
+ * to offer.
+ * TODO: This implementation could be removed if bug 984651 is fixed, which
+ * provides a native version of the same idea.
+ * @param stream nsIInputStream
+ * The input stream to read from.
+ * @param delimiter string
+ * The character we're trying to find.
+ * @param count integer
+ * The max number of characters to read while searching.
+ * @return string
+ * The data collected. If the delimiter was found, this string will
+ * end with it.
+ */
+function delimitedRead(stream, delimiter, count) {
+ dumpv("Starting delimited read for " + delimiter + " up to " +
+ count + " bytes");
+
+ let scriptableStream;
+ if (stream instanceof Ci.nsIScriptableInputStream) {
+ scriptableStream = stream;
+ } else {
+ scriptableStream = new ScriptableInputStream(stream);
+ }
+
+ let data = "";
+
+ // Don't exceed what's available on the stream
+ count = Math.min(count, stream.available());
+
+ if (count <= 0) {
+ return data;
+ }
+
+ let char;
+ while (char !== delimiter && count > 0) {
+ char = scriptableStream.readBytes(1);
+ count--;
+ data += char;
+ }
+
+ return data;
+}
+
+module.exports = {
+ copyStream: copyStream,
+ delimitedRead: delimitedRead
+};
diff --git a/devtools/shared/transport/tests/unit/.eslintrc.js b/devtools/shared/transport/tests/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/transport/tests/unit/head_dbg.js b/devtools/shared/transport/tests/unit/head_dbg.js
new file mode 100644
index 000000000..1f96ad440
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/head_dbg.js
@@ -0,0 +1,278 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+var Cr = Components.results;
+var CC = Components.Constructor;
+
+const { require } =
+ Cu.import("resource://devtools/shared/Loader.jsm", {});
+const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const { Task } = require("devtools/shared/task");
+
+const Services = require("Services");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+// We do not want to log packets by default, because in some tests,
+// we can be sending large amounts of data. The test harness has
+// trouble dealing with logging all the data, and we end up with
+// intermittent time outs (e.g. bug 775924).
+// Services.prefs.setBoolPref("devtools.debugger.log", true);
+// Services.prefs.setBoolPref("devtools.debugger.log.verbose", true);
+// Enable remote debugging for the relevant tests.
+Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+
+const { DebuggerServer } = require("devtools/server/main");
+const { DebuggerClient } = require("devtools/shared/client/main");
+
+function testExceptionHook(ex) {
+ try {
+ do_report_unexpected_exception(ex);
+ } catch (ex) {
+ return {throw: ex};
+ }
+ return undefined;
+}
+
+// Convert an nsIScriptError 'aFlags' value into an appropriate string.
+function scriptErrorFlagsToKind(aFlags) {
+ var kind;
+ if (aFlags & Ci.nsIScriptError.warningFlag)
+ kind = "warning";
+ if (aFlags & Ci.nsIScriptError.exceptionFlag)
+ kind = "exception";
+ else
+ kind = "error";
+
+ if (aFlags & Ci.nsIScriptError.strictFlag)
+ kind = "strict " + kind;
+
+ return kind;
+}
+
+// Register a console listener, so console messages don't just disappear
+// into the ether.
+var errorCount = 0;
+var listener = {
+ observe: function (aMessage) {
+ errorCount++;
+ try {
+ // If we've been given an nsIScriptError, then we can print out
+ // something nicely formatted, for tools like Emacs to pick up.
+ var scriptError = aMessage.QueryInterface(Ci.nsIScriptError);
+ dump(aMessage.sourceName + ":" + aMessage.lineNumber + ": " +
+ scriptErrorFlagsToKind(aMessage.flags) + ": " +
+ aMessage.errorMessage + "\n");
+ var string = aMessage.errorMessage;
+ } catch (x) {
+ // Be a little paranoid with message, as the whole goal here is to lose
+ // no information.
+ try {
+ var string = "" + aMessage.message;
+ } catch (x) {
+ var string = "<error converting error message to string>";
+ }
+ }
+
+ // Make sure we exit all nested event loops so that the test can finish.
+ while (DebuggerServer.xpcInspector.eventLoopNestLevel > 0) {
+ DebuggerServer.xpcInspector.exitNestedEventLoop();
+ }
+
+ // Throw in most cases, but ignore the "strict" messages
+ if (!(aMessage.flags & Ci.nsIScriptError.strictFlag)) {
+ do_throw("head_dbg.js got console message: " + string + "\n");
+ }
+ }
+};
+
+var consoleService = Cc["@mozilla.org/consoleservice;1"]
+ .getService(Ci.nsIConsoleService);
+consoleService.registerListener(listener);
+
+function check_except(func) {
+ try {
+ func();
+ } catch (e) {
+ do_check_true(true);
+ return;
+ }
+ dump("Should have thrown an exception: " + func.toString());
+ do_check_true(false);
+}
+
+function testGlobal(aName) {
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"]
+ .createInstance(Ci.nsIPrincipal);
+
+ let sandbox = Cu.Sandbox(systemPrincipal);
+ sandbox.__name = aName;
+ return sandbox;
+}
+
+function addTestGlobal(aName)
+{
+ let global = testGlobal(aName);
+ DebuggerServer.addTestGlobal(global);
+ return global;
+}
+
+// List the DebuggerClient |aClient|'s tabs, look for one whose title is
+// |aTitle|, and apply |aCallback| to the packet's entry for that tab.
+function getTestTab(aClient, aTitle, aCallback) {
+ aClient.listTabs(function (aResponse) {
+ for (let tab of aResponse.tabs) {
+ if (tab.title === aTitle) {
+ aCallback(tab);
+ return;
+ }
+ }
+ aCallback(null);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|; pass |aCallback| the
+// response packet and a TabClient instance referring to that tab.
+function attachTestTab(aClient, aTitle, aCallback) {
+ getTestTab(aClient, aTitle, function (aTab) {
+ aClient.attachTab(aTab.actor, aCallback);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, and then attach to
+// that tab's thread. Pass |aCallback| the thread attach response packet, a
+// TabClient referring to the tab, and a ThreadClient referring to the
+// thread.
+function attachTestThread(aClient, aTitle, aCallback) {
+ attachTestTab(aClient, aTitle, function (aResponse, aTabClient) {
+ function onAttach(aResponse, aThreadClient) {
+ aCallback(aResponse, aTabClient, aThreadClient);
+ }
+ aTabClient.attachThread({ useSourceMaps: true }, onAttach);
+ });
+}
+
+// Attach to |aClient|'s tab whose title is |aTitle|, attach to the tab's
+// thread, and then resume it. Pass |aCallback| the thread's response to
+// the 'resume' packet, a TabClient for the tab, and a ThreadClient for the
+// thread.
+function attachTestTabAndResume(aClient, aTitle, aCallback) {
+ attachTestThread(aClient, aTitle, function (aResponse, aTabClient, aThreadClient) {
+ aThreadClient.resume(function (aResponse) {
+ aCallback(aResponse, aTabClient, aThreadClient);
+ });
+ });
+}
+
+/**
+ * Initialize the testing debugger server.
+ */
+function initTestDebuggerServer() {
+ DebuggerServer.registerModule("devtools/server/actors/script", {
+ prefix: "script",
+ constructor: "ScriptActor",
+ type: { global: true, tab: true }
+ });
+ DebuggerServer.registerModule("xpcshell-test/testactors");
+ // Allow incoming connections.
+ DebuggerServer.init();
+}
+
+function finishClient(aClient) {
+ aClient.close().then(function () {
+ do_test_finished();
+ });
+}
+
+/**
+ * Takes a relative file path and returns the absolute file url for it.
+ */
+function getFileUrl(aName, aAllowMissing = false) {
+ let file = do_get_file(aName, aAllowMissing);
+ return Services.io.newFileURI(file).spec;
+}
+
+/**
+ * Returns the full path of the file with the specified name in a
+ * platform-independent and URL-like form.
+ */
+function getFilePath(aName, aAllowMissing = false) {
+ let file = do_get_file(aName, aAllowMissing);
+ let path = Services.io.newFileURI(file).spec;
+ let filePrePath = "file://";
+ if ("nsILocalFileWin" in Ci &&
+ file instanceof Ci.nsILocalFileWin) {
+ filePrePath += "/";
+ }
+ return path.slice(filePrePath.length);
+}
+
+/**
+ * Wrapper around do_get_file to prefix files with the name of current test to
+ * avoid collisions when running in parallel.
+ */
+function getTestTempFile(fileName, allowMissing) {
+ let thisTest = _TEST_FILE.toString().replace(/\\/g, "/");
+ thisTest = thisTest.substring(thisTest.lastIndexOf("/") + 1);
+ thisTest = thisTest.replace(/\..*$/, "");
+ return do_get_file(fileName + "-" + thisTest, allowMissing);
+}
+
+function writeTestTempFile(aFileName, aContent) {
+ let file = getTestTempFile(aFileName, true);
+ let stream = Cc["@mozilla.org/network/file-output-stream;1"]
+ .createInstance(Ci.nsIFileOutputStream);
+ stream.init(file, -1, -1, 0);
+ try {
+ do {
+ let numWritten = stream.write(aContent, aContent.length);
+ aContent = aContent.slice(numWritten);
+ } while (aContent.length > 0);
+ } finally {
+ stream.close();
+ }
+}
+
+/** * Transport Factories ***/
+
+var socket_transport = Task.async(function* () {
+ if (!DebuggerServer.listeningSockets) {
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ let listener = DebuggerServer.createListener();
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ yield listener.open();
+ }
+ let port = DebuggerServer._listeners[0].port;
+ do_print("Debugger server port is " + port);
+ return DebuggerClient.socketConnect({ host: "127.0.0.1", port });
+});
+
+function local_transport() {
+ return promise.resolve(DebuggerServer.connectPipe());
+}
+
+/** * Sample Data ***/
+
+var gReallyLong;
+function really_long() {
+ if (gReallyLong) {
+ return gReallyLong;
+ }
+ let ret = "0123456789";
+ for (let i = 0; i < 18; i++) {
+ ret += ret;
+ }
+ gReallyLong = ret;
+ return ret;
+}
diff --git a/devtools/shared/transport/tests/unit/test_bulk_error.js b/devtools/shared/transport/tests/unit/test_bulk_error.js
new file mode 100644
index 000000000..954499291
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_bulk_error.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ initTestDebuggerServer();
+ add_test_bulk_actor();
+
+ add_task(function* () {
+ yield test_string_error(socket_transport, json_reply);
+ yield test_string_error(local_transport, json_reply);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+
+function TestBulkActor() {}
+
+TestBulkActor.prototype = {
+
+ actorPrefix: "testBulk",
+
+ jsonReply: function ({length, reader, reply, done}) {
+ do_check_eq(length, really_long().length);
+
+ return {
+ allDone: true
+ };
+ }
+
+};
+
+TestBulkActor.prototype.requestTypes = {
+ "jsonReply": TestBulkActor.prototype.jsonReply
+};
+
+function add_test_bulk_actor() {
+ DebuggerServer.addGlobalActor(TestBulkActor);
+}
+
+/** * Tests ***/
+
+var test_string_error = Task.async(function* (transportFactory, onReady) {
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ return client.connect().then(([app, traits]) => {
+ do_check_eq(traits.bulk, true);
+ return client.listTabs();
+ }).then(response => {
+ return onReady(client, response);
+ }).then(() => {
+ client.close();
+ transport.close();
+ });
+});
+
+/** * Reply Types ***/
+
+function json_reply(client, response) {
+ let reallyLong = really_long();
+
+ let request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: "jsonReply",
+ length: reallyLong.length
+ });
+
+ // Send bulk data to server
+ let copyDeferred = defer();
+ request.on("bulk-send-ready", ({writer, done}) => {
+ let input = Cc["@mozilla.org/io/string-input-stream;1"].
+ createInstance(Ci.nsIStringInputStream);
+ input.setData(reallyLong, reallyLong.length);
+ try {
+ writer.copyFrom(input, () => {
+ input.close();
+ done();
+ });
+ do_throw(new Error("Copying should fail, the stream is not async."));
+ } catch (e) {
+ do_check_true(true);
+ copyDeferred.resolve();
+ }
+ });
+
+ return copyDeferred.promise;
+}
diff --git a/devtools/shared/transport/tests/unit/test_client_server_bulk.js b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
new file mode 100644
index 000000000..e4d17d216
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
@@ -0,0 +1,271 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+var Pipe = CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+
+function run_test() {
+ initTestDebuggerServer();
+ add_test_bulk_actor();
+
+ add_task(function* () {
+ yield test_bulk_request_cs(socket_transport, "jsonReply", "json");
+ yield test_bulk_request_cs(local_transport, "jsonReply", "json");
+ yield test_bulk_request_cs(socket_transport, "bulkEcho", "bulk");
+ yield test_bulk_request_cs(local_transport, "bulkEcho", "bulk");
+ yield test_json_request_cs(socket_transport, "bulkReply", "bulk");
+ yield test_json_request_cs(local_transport, "bulkReply", "bulk");
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Sample Bulk Actor ***/
+
+function TestBulkActor(conn) {
+ this.conn = conn;
+}
+
+TestBulkActor.prototype = {
+
+ actorPrefix: "testBulk",
+
+ bulkEcho: function ({actor, type, length, copyTo}) {
+ do_check_eq(length, really_long().length);
+ this.conn.startBulkSend({
+ actor: actor,
+ type: type,
+ length: length
+ }).then(({copyFrom}) => {
+ // We'll just echo back the same thing
+ let pipe = new Pipe(true, true, 0, 0, null);
+ copyTo(pipe.outputStream).then(() => {
+ pipe.outputStream.close();
+ });
+ copyFrom(pipe.inputStream).then(() => {
+ pipe.inputStream.close();
+ });
+ });
+ },
+
+ bulkReply: function ({to, type}) {
+ this.conn.startBulkSend({
+ actor: to,
+ type: type,
+ length: really_long().length
+ }).then(({copyFrom}) => {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ });
+ },
+
+ jsonReply: function ({length, copyTo}) {
+ do_check_eq(length, really_long().length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ return copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify_files();
+ }).then(() => {
+ return { allDone: true };
+ }, do_throw);
+ }
+
+};
+
+TestBulkActor.prototype.requestTypes = {
+ "bulkEcho": TestBulkActor.prototype.bulkEcho,
+ "bulkReply": TestBulkActor.prototype.bulkReply,
+ "jsonReply": TestBulkActor.prototype.jsonReply
+};
+
+function add_test_bulk_actor() {
+ DebuggerServer.addGlobalActor(TestBulkActor);
+}
+
+/** * Reply Handlers ***/
+
+var replyHandlers = {
+
+ json: function (request) {
+ // Receive JSON reply from server
+ let replyDeferred = defer();
+ request.on("json-reply", (reply) => {
+ do_check_true(reply.allDone);
+ replyDeferred.resolve();
+ });
+ return replyDeferred.promise;
+ },
+
+ bulk: function (request) {
+ // Receive bulk data reply from server
+ let replyDeferred = defer();
+ request.on("bulk-reply", ({length, copyTo}) => {
+ do_check_eq(length, really_long().length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ replyDeferred.resolve(verify_files());
+ });
+ });
+ return replyDeferred.promise;
+ }
+
+};
+
+/** * Tests ***/
+
+var test_bulk_request_cs = Task.async(function* (transportFactory, actorType, replyType) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+ let bulkCopyDeferred = defer();
+
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ client.connect().then(([app, traits]) => {
+ do_check_eq(traits.bulk, true);
+ client.listTabs(clientDeferred.resolve);
+ });
+
+ clientDeferred.promise.then(response => {
+ let request = client.startBulkRequest({
+ actor: response.testBulk,
+ type: actorType,
+ length: really_long().length
+ });
+
+ // Send bulk data to server
+ request.on("bulk-send-ready", ({copyFrom}) => {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ copyFrom(input).then(() => {
+ input.close();
+ bulkCopyDeferred.resolve();
+ });
+ });
+ });
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ }).then(null, do_throw);
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ return promise.all([
+ clientDeferred.promise,
+ bulkCopyDeferred.promise,
+ serverDeferred.promise
+ ]);
+});
+
+var test_json_request_cs = Task.async(function* (transportFactory, actorType, replyType) {
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ writeTestTempFile("bulk-input", really_long());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ client.connect((app, traits) => {
+ do_check_eq(traits.bulk, true);
+ client.listTabs(clientDeferred.resolve);
+ });
+
+ clientDeferred.promise.then(response => {
+ let request = client.request({
+ to: response.testBulk,
+ type: actorType
+ });
+
+ // Set up reply handling for this type
+ replyHandlers[replyType](request).then(() => {
+ client.close();
+ transport.close();
+ });
+ }).then(null, do_throw);
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ return promise.all([
+ clientDeferred.promise,
+ serverDeferred.promise
+ ]);
+});
+
+/** * Test Utils ***/
+
+function verify_files() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_dbgsocket.js b/devtools/shared/transport/tests/unit/test_dbgsocket.js
new file mode 100644
index 000000000..79111f877
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var gPort;
+var gExtraListener;
+
+function run_test()
+{
+ do_print("Starting test at " + new Date().toTimeString());
+ initTestDebuggerServer();
+
+ add_task(test_socket_conn);
+ add_task(test_socket_shutdown);
+ add_test(test_pipe_conn);
+
+ run_next_test();
+}
+
+function* test_socket_conn()
+{
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+ let listener = DebuggerServer.createListener();
+ do_check_true(listener);
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.open();
+ do_check_eq(DebuggerServer.listeningSockets, 1);
+ gPort = DebuggerServer._listeners[0].port;
+ do_print("Debugger server port is " + gPort);
+ // Open a second, separate listener
+ gExtraListener = DebuggerServer.createListener();
+ gExtraListener.portOrPath = -1;
+ gExtraListener.authenticator = authenticator;
+ gExtraListener.open();
+ do_check_eq(DebuggerServer.listeningSockets, 2);
+
+ do_print("Starting long and unicode tests at " + new Date().toTimeString());
+ let unicodeString = "(╯°□°)╯︵ ┻━┻";
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+
+ // Assert that connection settings are available on transport object
+ let settings = transport.connectionSettings;
+ do_check_eq(settings.host, "127.0.0.1");
+ do_check_eq(settings.port, gPort);
+
+ let closedDeferred = defer();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ this.onPacket = function (aPacket) {
+ do_check_eq(aPacket.unicode, unicodeString);
+ transport.close();
+ };
+ // Verify that things work correctly when bigger than the output
+ // transport buffers and when transporting unicode...
+ transport.send({to: "root",
+ type: "echo",
+ reallylong: really_long(),
+ unicode: unicodeString});
+ do_check_eq(aPacket.from, "root");
+ },
+ onClosed: function (aStatus) {
+ closedDeferred.resolve();
+ },
+ };
+ transport.ready();
+ return closedDeferred.promise;
+}
+
+function* test_socket_shutdown()
+{
+ do_check_eq(DebuggerServer.listeningSockets, 2);
+ gExtraListener.close();
+ do_check_eq(DebuggerServer.listeningSockets, 1);
+ do_check_true(DebuggerServer.closeAllListeners());
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+ // Make sure closing the listener twice does nothing.
+ do_check_false(DebuggerServer.closeAllListeners());
+ do_check_eq(DebuggerServer.listeningSockets, 0);
+
+ do_print("Connecting to a server socket at " + new Date().toTimeString());
+ try {
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: gPort
+ });
+ } catch (e) {
+ if (e.result == Cr.NS_ERROR_CONNECTION_REFUSED ||
+ e.result == Cr.NS_ERROR_NET_TIMEOUT) {
+ // The connection should be refused here, but on slow or overloaded
+ // machines it may just time out.
+ do_check_true(true);
+ return;
+ } else {
+ throw e;
+ }
+ }
+
+ // Shouldn't reach this, should never connect.
+ do_check_true(false);
+}
+
+function test_pipe_conn()
+{
+ let transport = DebuggerServer.connectPipe();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ do_check_eq(aPacket.from, "root");
+ transport.close();
+ },
+ onClosed: function (aStatus) {
+ run_next_test();
+ }
+ };
+
+ transport.ready();
+}
diff --git a/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js b/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
new file mode 100644
index 000000000..9221939b1
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_dbgsocket_connection_drop.js
@@ -0,0 +1,81 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+/**
+ * Bug 755412 - checks if the server drops the connection on an improperly
+ * framed packet, i.e. when the length header is invalid.
+ */
+
+const { RawPacket } = require("devtools/shared/transport/packets");
+
+function run_test() {
+ do_print("Starting test at " + new Date().toTimeString());
+ initTestDebuggerServer();
+
+ add_task(test_socket_conn_drops_after_invalid_header);
+ add_task(test_socket_conn_drops_after_invalid_header_2);
+ add_task(test_socket_conn_drops_after_too_large_length);
+ add_task(test_socket_conn_drops_after_too_long_header);
+ run_next_test();
+}
+
+function test_socket_conn_drops_after_invalid_header() {
+ return test_helper('fluff30:27:{"to":"root","type":"echo"}');
+}
+
+function test_socket_conn_drops_after_invalid_header_2() {
+ return test_helper('27asd:{"to":"root","type":"echo"}');
+}
+
+function test_socket_conn_drops_after_too_large_length() {
+ // Packet length is limited (semi-arbitrarily) to 1 TiB (2^40)
+ return test_helper("4305724038957487634549823475894325:");
+}
+
+function test_socket_conn_drops_after_too_long_header() {
+ // The packet header is currently limited to no more than 200 bytes
+ let rawPacket = "4305724038957487634549823475894325";
+ for (let i = 0; i < 8; i++) {
+ rawPacket += rawPacket;
+ }
+ return test_helper(rawPacket + ":");
+}
+
+var test_helper = Task.async(function* (payload) {
+ let AuthenticatorType = DebuggerServer.Authenticators.get("PROMPT");
+ let authenticator = new AuthenticatorType.Server();
+ authenticator.allowConnection = () => {
+ return DebuggerServer.AuthenticationResult.ALLOW;
+ };
+
+ let listener = DebuggerServer.createListener();
+ listener.portOrPath = -1;
+ listener.authenticator = authenticator;
+ listener.open();
+
+ let transport = yield DebuggerClient.socketConnect({
+ host: "127.0.0.1",
+ port: listener.port
+ });
+ let closedDeferred = defer();
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ this.onPacket = function (aPacket) {
+ do_throw(new Error("This connection should be dropped."));
+ transport.close();
+ };
+
+ // Inject the payload directly into the stream.
+ transport._outgoing.push(new RawPacket(transport, payload));
+ transport._flushOutgoing();
+ },
+ onClosed: function (aStatus) {
+ do_check_true(true);
+ closedDeferred.resolve();
+ },
+ };
+ transport.ready();
+ return closedDeferred.promise;
+});
diff --git a/devtools/shared/transport/tests/unit/test_delimited_read.js b/devtools/shared/transport/tests/unit/test_delimited_read.js
new file mode 100644
index 000000000..a5a7baf7b
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_delimited_read.js
@@ -0,0 +1,26 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const StreamUtils = require("devtools/shared/transport/stream-utils");
+
+const StringInputStream = CC("@mozilla.org/io/string-input-stream;1",
+ "nsIStringInputStream", "setData");
+
+function run_test() {
+ add_task(function* () {
+ yield test_delimited_read("0123:", "0123:");
+ yield test_delimited_read("0123:4567:", "0123:");
+ yield test_delimited_read("012345678901:", "0123456789");
+ yield test_delimited_read("0123/0123", "0123/0123");
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+function test_delimited_read(input, expected) {
+ input = new StringInputStream(input, input.length);
+ let result = StreamUtils.delimitedRead(input, ":", 10);
+ do_check_eq(result, expected);
+}
diff --git a/devtools/shared/transport/tests/unit/test_no_bulk.js b/devtools/shared/transport/tests/unit/test_no_bulk.js
new file mode 100644
index 000000000..f621a2a52
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_no_bulk.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ DebuggerServer.registerModule("xpcshell-test/testactors-no-bulk");
+ // Allow incoming connections.
+ DebuggerServer.init();
+
+ add_task(function* () {
+ yield test_bulk_send_error(socket_transport);
+ yield test_bulk_send_error(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+var test_bulk_send_error = Task.async(function* (transportFactory) {
+ let deferred = defer();
+ let transport = yield transportFactory();
+
+ let client = new DebuggerClient(transport);
+ return client.connect().then(([app, traits]) => {
+ do_check_false(traits.bulk);
+
+ try {
+ client.startBulkRequest();
+ do_throw(new Error("Can't use bulk since server doesn't support it"));
+ } catch (e) {
+ do_check_true(true);
+ }
+
+ });
+});
diff --git a/devtools/shared/transport/tests/unit/test_packet.js b/devtools/shared/transport/tests/unit/test_packet.js
new file mode 100644
index 000000000..7e1896555
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_packet.js
@@ -0,0 +1,21 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { JSONPacket, BulkPacket } =
+ require("devtools/shared/transport/packets");
+
+function run_test() {
+ add_test(test_packet_done);
+ run_next_test();
+}
+
+// Ensure done can be checked without getting an error
+function test_packet_done() {
+ let json = new JSONPacket();
+ do_check_false(!!json.done);
+
+ let bulk = new BulkPacket();
+ do_check_false(!!bulk.done);
+
+ run_next_test();
+}
diff --git a/devtools/shared/transport/tests/unit/test_queue.js b/devtools/shared/transport/tests/unit/test_queue.js
new file mode 100644
index 000000000..5de14baee
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_queue.js
@@ -0,0 +1,177 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test verifies that the transport's queue operates correctly when various
+ * packets are scheduled simultaneously.
+ */
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_transport(socket_transport);
+ yield test_transport(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+var test_transport = Task.async(function* (transportFactory) {
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ let reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Sending from client to server
+ function write_data({copyFrom}) {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, function (input, status) {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ }
+
+ // Receiving on server from client
+ function on_bulk_packet({actor, type, length, copyTo}) {
+ do_check_eq(actor, "root");
+ do_check_eq(type, "file-stream");
+ do_check_eq(length, reallyLong.length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ }).then(() => {
+ // It's now safe to close
+ transport.hooks.onClosed = () => {
+ clientDeferred.resolve();
+ };
+ transport.close();
+ });
+ }
+
+ // Client
+
+ function send_packets() {
+ // Specifically, we want to ensure that multiple send()s proceed without
+ // causing the transport to die.
+ transport.send({
+ actor: "root",
+ type: "explode"
+ });
+
+ transport.startBulkSend({
+ actor: "root",
+ type: "file-stream",
+ length: reallyLong.length
+ }).then(write_data);
+ }
+
+ transport.hooks = {
+ onPacket: function (packet) {
+ if (packet.error) {
+ transport.hooks.onError(packet);
+ } else if (packet.applicationType) {
+ transport.hooks.onServerHello(packet);
+ } else {
+ do_throw("Unexpected server reply");
+ }
+ },
+
+ onServerHello: function (packet) {
+ // We've received the initial start up packet
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.applicationType, "xpcshell-tests");
+
+ // Server
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 1);
+ do_print(Object.keys(DebuggerServer._connections));
+ for (let connId in DebuggerServer._connections) {
+ DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ send_packets();
+ },
+
+ onError: function (packet) {
+ // The explode actor doesn't exist
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.error, "noSuchActor");
+ },
+
+ onClosed: function () {
+ do_throw("Transport closed before we expected");
+ }
+ };
+
+ transport.ready();
+
+ return promise.all([clientDeferred.promise, serverDeferred.promise]);
+});
+
+/** * Test Utils ***/
+
+function verify() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_transport_bulk.js b/devtools/shared/transport/tests/unit/test_transport_bulk.js
new file mode 100644
index 000000000..a21216acf
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_transport_bulk.js
@@ -0,0 +1,148 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { FileUtils } = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_bulk_transfer_transport(socket_transport);
+ yield test_bulk_transfer_transport(local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+/** * Tests ***/
+
+/**
+ * This tests a one-way bulk transfer at the transport layer.
+ */
+var test_bulk_transfer_transport = Task.async(function* (transportFactory) {
+ do_print("Starting bulk transfer test at " + new Date().toTimeString());
+
+ let clientDeferred = defer();
+ let serverDeferred = defer();
+
+ // Ensure test files are not present from a failed run
+ cleanup_files();
+ let reallyLong = really_long();
+ writeTestTempFile("bulk-input", reallyLong);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Sending from client to server
+ function write_data({copyFrom}) {
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-input")),
+ loadUsingSystemPrincipal: true
+ }, function (input, status) {
+ copyFrom(input).then(() => {
+ input.close();
+ });
+ });
+ }
+
+ // Receiving on server from client
+ function on_bulk_packet({actor, type, length, copyTo}) {
+ do_check_eq(actor, "root");
+ do_check_eq(type, "file-stream");
+ do_check_eq(length, reallyLong.length);
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ outputFile.create(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8));
+
+ let output = FileUtils.openSafeFileOutputStream(outputFile);
+
+ copyTo(output).then(() => {
+ FileUtils.closeSafeFileOutputStream(output);
+ return verify();
+ }).then(() => {
+ // It's now safe to close
+ transport.hooks.onClosed = () => {
+ clientDeferred.resolve();
+ };
+ transport.close();
+ });
+ }
+
+ // Client
+ transport.hooks = {
+ onPacket: function (aPacket) {
+ // We've received the initial start up packet
+ do_check_eq(aPacket.from, "root");
+
+ // Server
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 1);
+ do_print(Object.keys(DebuggerServer._connections));
+ for (let connId in DebuggerServer._connections) {
+ DebuggerServer._connections[connId].onBulkPacket = on_bulk_packet;
+ }
+
+ DebuggerServer.on("connectionchange", (event, type) => {
+ if (type === "closed") {
+ serverDeferred.resolve();
+ }
+ });
+
+ transport.startBulkSend({
+ actor: "root",
+ type: "file-stream",
+ length: reallyLong.length
+ }).then(write_data);
+ },
+
+ onClosed: function () {
+ do_throw("Transport closed before we expected");
+ }
+ };
+
+ transport.ready();
+
+ return promise.all([clientDeferred.promise, serverDeferred.promise]);
+});
+
+/** * Test Utils ***/
+
+function verify() {
+ let reallyLong = really_long();
+
+ let inputFile = getTestTempFile("bulk-input");
+ let outputFile = getTestTempFile("bulk-output");
+
+ do_check_eq(inputFile.fileSize, reallyLong.length);
+ do_check_eq(outputFile.fileSize, reallyLong.length);
+
+ // Ensure output file contents actually match
+ let compareDeferred = defer();
+ NetUtil.asyncFetch({
+ uri: NetUtil.newURI(getTestTempFile("bulk-output")),
+ loadUsingSystemPrincipal: true
+ }, input => {
+ let outputData = NetUtil.readInputStreamToString(input, reallyLong.length);
+ // Avoid do_check_eq here so we don't log the contents
+ do_check_true(outputData === reallyLong);
+ input.close();
+ compareDeferred.resolve();
+ });
+
+ return compareDeferred.promise.then(cleanup_files);
+}
+
+function cleanup_files() {
+ let inputFile = getTestTempFile("bulk-input", true);
+ if (inputFile.exists()) {
+ inputFile.remove(false);
+ }
+
+ let outputFile = getTestTempFile("bulk-output", true);
+ if (outputFile.exists()) {
+ outputFile.remove(false);
+ }
+}
diff --git a/devtools/shared/transport/tests/unit/test_transport_events.js b/devtools/shared/transport/tests/unit/test_transport_events.js
new file mode 100644
index 000000000..ae20f6cf8
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/test_transport_events.js
@@ -0,0 +1,75 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function run_test() {
+ initTestDebuggerServer();
+
+ add_task(function* () {
+ yield test_transport_events("socket", socket_transport);
+ yield test_transport_events("local", local_transport);
+ DebuggerServer.destroy();
+ });
+
+ run_next_test();
+}
+
+function* test_transport_events(name, transportFactory) {
+ do_print(`Started testing of transport: ${name}`);
+
+ do_check_eq(Object.keys(DebuggerServer._connections).length, 0);
+
+ let transport = yield transportFactory();
+
+ // Transport expects the hooks to be not null
+ transport.hooks = {
+ onPacket: () => {},
+ onClosed: () => {},
+ };
+
+ let rootReceived = transport.once("packet", (event, packet) => {
+ do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "packet");
+ do_check_eq(packet.from, "root");
+ });
+
+ transport.ready();
+ yield rootReceived;
+
+ let echoSent = transport.once("send", (event, packet) => {
+ do_print(`Send event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "send");
+ do_check_eq(packet.to, "root");
+ do_check_eq(packet.type, "echo");
+ });
+
+ let echoReceived = transport.once("packet", (event, packet) => {
+ do_print(`Packet event: ${event} ${JSON.stringify(packet)}`);
+ do_check_eq(event, "packet");
+ do_check_eq(packet.from, "root");
+ do_check_eq(packet.type, "echo");
+ });
+
+ transport.send({ to: "root", type: "echo" });
+ yield echoSent;
+ yield echoReceived;
+
+ let clientClosed = transport.once("close", (event) => {
+ do_print(`Close event: ${event}`);
+ do_check_eq(event, "close");
+ });
+
+ let serverClosed = DebuggerServer.once("connectionchange", (event, type) => {
+ do_print(`Server closed`);
+ do_check_eq(event, "connectionchange");
+ do_check_eq(type, "closed");
+ });
+
+ transport.close();
+
+ yield clientClosed;
+ yield serverClosed;
+
+ do_print(`Finished testing of transport: ${name}`);
+}
diff --git a/devtools/shared/transport/tests/unit/testactors-no-bulk.js b/devtools/shared/transport/tests/unit/testactors-no-bulk.js
new file mode 100644
index 000000000..d2c8193fe
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/testactors-no-bulk.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { RootActor } = require("devtools/server/actors/root");
+const { DebuggerServer } = require("devtools/server/main");
+
+/**
+ * Root actor that doesn't have the bulk trait.
+ */
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ root.traits = {
+ bulk: false
+ };
+ return root;
+}
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/transport/tests/unit/testactors.js b/devtools/shared/transport/tests/unit/testactors.js
new file mode 100644
index 000000000..80d5d4e18
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/testactors.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const { ActorPool, appendExtraActors, createExtraActors } =
+ require("devtools/server/actors/common");
+const { RootActor } = require("devtools/server/actors/root");
+const { ThreadActor } = require("devtools/server/actors/script");
+const { DebuggerServer } = require("devtools/server/main");
+const promise = require("promise");
+
+var gTestGlobals = [];
+DebuggerServer.addTestGlobal = function (aGlobal) {
+ gTestGlobals.push(aGlobal);
+};
+
+// A mock tab list, for use by tests. This simply presents each global in
+// gTestGlobals as a tab, and the list is fixed: it never calls its
+// onListChanged handler.
+//
+// As implemented now, we consult gTestGlobals when we're constructed, not
+// when we're iterated over, so tests have to add their globals before the
+// root actor is created.
+function TestTabList(aConnection) {
+ this.conn = aConnection;
+
+ // An array of actors for each global added with
+ // DebuggerServer.addTestGlobal.
+ this._tabActors = [];
+
+ // A pool mapping those actors' names to the actors.
+ this._tabActorPool = new ActorPool(aConnection);
+
+ for (let global of gTestGlobals) {
+ let actor = new TestTabActor(aConnection, global);
+ actor.selected = false;
+ this._tabActors.push(actor);
+ this._tabActorPool.addActor(actor);
+ }
+ if (this._tabActors.length > 0) {
+ this._tabActors[0].selected = true;
+ }
+
+ aConnection.addActorPool(this._tabActorPool);
+}
+
+TestTabList.prototype = {
+ constructor: TestTabList,
+ getList: function () {
+ return promise.resolve([...this._tabActors]);
+ }
+};
+
+function createRootActor(aConnection) {
+ let root = new RootActor(aConnection, {
+ tabList: new TestTabList(aConnection),
+ globalActorFactories: DebuggerServer.globalActorFactories
+ });
+ root.applicationType = "xpcshell-tests";
+ return root;
+}
+
+function TestTabActor(aConnection, aGlobal) {
+ this.conn = aConnection;
+ this._global = aGlobal;
+ this._threadActor = new ThreadActor(this, this._global);
+ this.conn.addActor(this._threadActor);
+ this._attached = false;
+ this._extraActors = {};
+}
+
+TestTabActor.prototype = {
+ constructor: TestTabActor,
+ actorPrefix: "TestTabActor",
+
+ get window() {
+ return { wrappedJSObject: this._global };
+ },
+
+ get url() {
+ return this._global.__name;
+ },
+
+ form: function () {
+ let response = { actor: this.actorID, title: this._global.__name };
+
+ // Walk over tab actors added by extensions and add them to a new ActorPool.
+ let actorPool = new ActorPool(this.conn);
+ this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
+ if (!actorPool.isEmpty()) {
+ this._tabActorPool = actorPool;
+ this.conn.addActorPool(this._tabActorPool);
+ }
+
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onAttach: function (aRequest) {
+ this._attached = true;
+
+ let response = { type: "tabAttached", threadActor: this._threadActor.actorID };
+ this._appendExtraActors(response);
+
+ return response;
+ },
+
+ onDetach: function (aRequest) {
+ if (!this._attached) {
+ return { "error":"wrongState" };
+ }
+ return { type: "detached" };
+ },
+
+ /* Support for DebuggerServer.addTabActor. */
+ _createExtraActors: createExtraActors,
+ _appendExtraActors: appendExtraActors
+};
+
+TestTabActor.prototype.requestTypes = {
+ "attach": TestTabActor.prototype.onAttach,
+ "detach": TestTabActor.prototype.onDetach
+};
+
+exports.register = function (handle) {
+ handle.setRootActor(createRootActor);
+};
+
+exports.unregister = function (handle) {
+ handle.setRootActor(null);
+};
diff --git a/devtools/shared/transport/tests/unit/xpcshell.ini b/devtools/shared/transport/tests/unit/xpcshell.ini
new file mode 100644
index 000000000..b79906e82
--- /dev/null
+++ b/devtools/shared/transport/tests/unit/xpcshell.ini
@@ -0,0 +1,21 @@
+[DEFAULT]
+tags = devtools
+head = head_dbg.js
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+
+support-files =
+ testactors.js
+ testactors-no-bulk.js
+
+[test_bulk_error.js]
+[test_client_server_bulk.js]
+[test_dbgsocket.js]
+[test_dbgsocket_connection_drop.js]
+[test_delimited_read.js]
+[test_no_bulk.js]
+[test_packet.js]
+[test_queue.js]
+[test_transport_bulk.js]
+[test_transport_events.js]
diff --git a/devtools/shared/transport/transport.js b/devtools/shared/transport/transport.js
new file mode 100644
index 000000000..e83bf8de6
--- /dev/null
+++ b/devtools/shared/transport/transport.js
@@ -0,0 +1,908 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global Pipe, ScriptableInputStream, uneval */
+
+// TODO: Get rid of this code once the marionette server loads transport.js as
+// an SDK module (see bug 1000814)
+(function (factory) {
+ if (this.module && module.id.indexOf("transport") >= 0) {
+ // require
+ factory.call(this, require, exports);
+ } else if (this.require) {
+ // loadSubScript
+ factory.call(this, require, this);
+ } else {
+ // Cu.import
+ const Cu = Components.utils;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ factory.call(this, require, this);
+ }
+}).call(this, function (require, exports) {
+ const { Cc, Cr, CC } = require("chrome");
+ const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+ const { dumpn, dumpv } = DevToolsUtils;
+ const flags = require("devtools/shared/flags");
+ const StreamUtils = require("devtools/shared/transport/stream-utils");
+ const { Packet, JSONPacket, BulkPacket } =
+ require("devtools/shared/transport/packets");
+ const promise = require("promise");
+ const defer = require("devtools/shared/defer");
+ const EventEmitter = require("devtools/shared/event-emitter");
+
+ DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
+ return CC("@mozilla.org/pipe;1", "nsIPipe", "init");
+ });
+
+ DevToolsUtils.defineLazyGetter(this, "ScriptableInputStream", () => {
+ return CC("@mozilla.org/scriptableinputstream;1",
+ "nsIScriptableInputStream", "init");
+ });
+
+ const PACKET_HEADER_MAX = 200;
+
+ /**
+ * An adapter that handles data transfers between the debugger client and
+ * server. It can work with both nsIPipe and nsIServerSocket transports so
+ * long as the properly created input and output streams are specified.
+ * (However, for intra-process connections, LocalDebuggerTransport, below,
+ * is more efficient than using an nsIPipe pair with DebuggerTransport.)
+ *
+ * @param input nsIAsyncInputStream
+ * The input stream.
+ * @param output nsIAsyncOutputStream
+ * The output stream.
+ *
+ * Given a DebuggerTransport instance dt:
+ * 1) Set dt.hooks to a packet handler object (described below).
+ * 2) Call dt.ready() to begin watching for input packets.
+ * 3) Call dt.send() / dt.startBulkSend() to send packets.
+ * 4) Call dt.close() to close the connection, and disengage from the event
+ * loop.
+ *
+ * A packet handler is an object with the following methods:
+ *
+ * - onPacket(packet) - called when we have received a complete packet.
+ * |packet| is the parsed form of the packet --- a JavaScript value, not
+ * a JSON-syntax string.
+ *
+ * - onBulkPacket(packet) - called when we have switched to bulk packet
+ * receiving mode. |packet| is an object containing:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be read
+ * * stream: This input stream should only be used directly if you can ensure
+ * that you will read exactly |length| bytes and will not close the
+ * stream when reading is complete
+ * * done: If you use the stream directly (instead of |copyTo| below), you
+ * must signal completion by resolving / rejecting this deferred.
+ * If it's rejected, the transport will be closed. If an Error is
+ * supplied as a rejection value, it will be logged via |dumpn|.
+ * If you do use |copyTo|, resolving is taken care of for you when
+ * copying completes.
+ * * copyTo: A helper function for getting your data out of the stream that
+ * meets the stream handling requirements above, and has the
+ * following signature:
+ * @param output nsIAsyncOutputStream
+ * The stream to copy to.
+ * @return Promise
+ * The promise is resolved when copying completes or rejected if any
+ * (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk that is
+ * copied. See stream-utils.js.
+ *
+ * - onClosed(reason) - called when the connection is closed. |reason| is
+ * an optional nsresult or object, typically passed when the transport is
+ * closed due to some error in a underlying stream.
+ *
+ * See ./packets.js and the Remote Debugging Protocol specification for more
+ * details on the format of these packets.
+ */
+ function DebuggerTransport(input, output) {
+ EventEmitter.decorate(this);
+
+ this._input = input;
+ this._scriptableInput = new ScriptableInputStream(input);
+ this._output = output;
+
+ // The current incoming (possibly partial) header, which will determine which
+ // type of Packet |_incoming| below will become.
+ this._incomingHeader = "";
+ // The current incoming Packet object
+ this._incoming = null;
+ // A queue of outgoing Packet objects
+ this._outgoing = [];
+
+ this.hooks = null;
+ this.active = false;
+
+ this._incomingEnabled = true;
+ this._outgoingEnabled = true;
+
+ this.close = this.close.bind(this);
+ }
+
+ DebuggerTransport.prototype = {
+ /**
+ * Transmit an object as a JSON packet.
+ *
+ * This method returns immediately, without waiting for the entire
+ * packet to be transmitted, registering event handlers as needed to
+ * transmit the entire packet. Packets are transmitted in the order
+ * they are passed to this method.
+ */
+ send: function (object) {
+ this.emit("send", object);
+
+ let packet = new JSONPacket(this);
+ packet.object = object;
+ this._outgoing.push(packet);
+ this._flushOutgoing();
+ },
+
+ /**
+ * Transmit streaming data via a bulk packet.
+ *
+ * This method initiates the bulk send process by queuing up the header data.
+ * The caller receives eventual access to a stream for writing.
+ *
+ * N.B.: Do *not* attempt to close the stream handed to you, as it will
+ * continue to be used by this transport afterwards. Most users should
+ * instead use the provided |copyFrom| function instead.
+ *
+ * @param header Object
+ * This is modeled after the format of JSON packets above, but does not
+ * actually contain the data, but is instead just a routing header:
+ * * actor: Name of actor that will receive the packet
+ * * type: Name of actor's method that should be called on receipt
+ * * length: Size of the data to be sent
+ * @return Promise
+ * The promise will be resolved when you are allowed to write to the
+ * stream with an object containing:
+ * * stream: This output stream should only be used directly if
+ * you can ensure that you will write exactly |length|
+ * bytes and will not close the stream when writing is
+ * complete
+ * * done: If you use the stream directly (instead of |copyFrom|
+ * below), you must signal completion by resolving /
+ * rejecting this deferred. If it's rejected, the
+ * transport will be closed. If an Error is supplied as
+ * a rejection value, it will be logged via |dumpn|. If
+ * you do use |copyFrom|, resolving is taken care of for
+ * you when copying completes.
+ * * copyFrom: A helper function for getting your data onto the
+ * stream that meets the stream handling requirements
+ * above, and has the following signature:
+ * @param input nsIAsyncInputStream
+ * The stream to copy from.
+ * @return Promise
+ * The promise is resolved when copying completes or
+ * rejected if any (unexpected) errors occur.
+ * This object also emits "progress" events for each chunk
+ * that is copied. See stream-utils.js.
+ */
+ startBulkSend: function (header) {
+ this.emit("startbulksend", header);
+
+ let packet = new BulkPacket(this);
+ packet.header = header;
+ this._outgoing.push(packet);
+ this._flushOutgoing();
+ return packet.streamReadyForWriting;
+ },
+
+ /**
+ * Close the transport.
+ * @param reason nsresult / object (optional)
+ * The status code or error message that corresponds to the reason for
+ * closing the transport (likely because a stream closed or failed).
+ */
+ close: function (reason) {
+ this.emit("close", reason);
+
+ this.active = false;
+ this._input.close();
+ this._scriptableInput.close();
+ this._output.close();
+ this._destroyIncoming();
+ this._destroyAllOutgoing();
+ if (this.hooks) {
+ this.hooks.onClosed(reason);
+ this.hooks = null;
+ }
+ if (reason) {
+ dumpn("Transport closed: " + DevToolsUtils.safeErrorString(reason));
+ } else {
+ dumpn("Transport closed.");
+ }
+ },
+
+ /**
+ * The currently outgoing packet (at the top of the queue).
+ */
+ get _currentOutgoing() {
+ return this._outgoing[0];
+ },
+
+ /**
+ * Flush data to the outgoing stream. Waits until the output stream notifies
+ * us that it is ready to be written to (via onOutputStreamReady).
+ */
+ _flushOutgoing: function () {
+ if (!this._outgoingEnabled || this._outgoing.length === 0) {
+ return;
+ }
+
+ // If the top of the packet queue has nothing more to send, remove it.
+ if (this._currentOutgoing.done) {
+ this._finishCurrentOutgoing();
+ }
+
+ if (this._outgoing.length > 0) {
+ let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+ this._output.asyncWait(this, 0, 0, threadManager.currentThread);
+ }
+ },
+
+ /**
+ * Pause this transport's attempts to write to the output stream. This is
+ * used when we've temporarily handed off our output stream for writing bulk
+ * data.
+ */
+ pauseOutgoing: function () {
+ this._outgoingEnabled = false;
+ },
+
+ /**
+ * Resume this transport's attempts to write to the output stream.
+ */
+ resumeOutgoing: function () {
+ this._outgoingEnabled = true;
+ this._flushOutgoing();
+ },
+
+ // nsIOutputStreamCallback
+ /**
+ * This is called when the output stream is ready for more data to be written.
+ * The current outgoing packet will attempt to write some amount of data, but
+ * may not complete.
+ */
+ onOutputStreamReady: DevToolsUtils.makeInfallible(function (stream) {
+ if (!this._outgoingEnabled || this._outgoing.length === 0) {
+ return;
+ }
+
+ try {
+ this._currentOutgoing.write(stream);
+ } catch (e) {
+ if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this.close(e.result);
+ return;
+ }
+ throw e;
+ }
+
+ this._flushOutgoing();
+ }, "DebuggerTransport.prototype.onOutputStreamReady"),
+
+ /**
+ * Remove the current outgoing packet from the queue upon completion.
+ */
+ _finishCurrentOutgoing: function () {
+ if (this._currentOutgoing) {
+ this._currentOutgoing.destroy();
+ this._outgoing.shift();
+ }
+ },
+
+ /**
+ * Clear the entire outgoing queue.
+ */
+ _destroyAllOutgoing: function () {
+ for (let packet of this._outgoing) {
+ packet.destroy();
+ }
+ this._outgoing = [];
+ },
+
+ /**
+ * Initialize the input stream for reading. Once this method has been called,
+ * we watch for packets on the input stream, and pass them to the appropriate
+ * handlers via this.hooks.
+ */
+ ready: function () {
+ this.active = true;
+ this._waitForIncoming();
+ },
+
+ /**
+ * Asks the input stream to notify us (via onInputStreamReady) when it is
+ * ready for reading.
+ */
+ _waitForIncoming: function () {
+ if (this._incomingEnabled) {
+ let threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+ this._input.asyncWait(this, 0, 0, threadManager.currentThread);
+ }
+ },
+
+ /**
+ * Pause this transport's attempts to read from the input stream. This is
+ * used when we've temporarily handed off our input stream for reading bulk
+ * data.
+ */
+ pauseIncoming: function () {
+ this._incomingEnabled = false;
+ },
+
+ /**
+ * Resume this transport's attempts to read from the input stream.
+ */
+ resumeIncoming: function () {
+ this._incomingEnabled = true;
+ this._flushIncoming();
+ this._waitForIncoming();
+ },
+
+ // nsIInputStreamCallback
+ /**
+ * Called when the stream is either readable or closed.
+ */
+ onInputStreamReady: DevToolsUtils.makeInfallible(function (stream) {
+ try {
+ while (stream.available() && this._incomingEnabled &&
+ this._processIncoming(stream, stream.available())) {
+ // Loop until there is nothing more to process
+ }
+ this._waitForIncoming();
+ } catch (e) {
+ if (e.result != Cr.NS_BASE_STREAM_WOULD_BLOCK) {
+ this.close(e.result);
+ } else {
+ throw e;
+ }
+ }
+ }, "DebuggerTransport.prototype.onInputStreamReady"),
+
+ /**
+ * Process the incoming data. Will create a new currently incoming Packet if
+ * needed. Tells the incoming Packet to read as much data as it can, but
+ * reading may not complete. The Packet signals that its data is ready for
+ * delivery by calling one of this transport's _on*Ready methods (see
+ * ./packets.js and the _on*Ready methods below).
+ * @return boolean
+ * Whether incoming stream processing should continue for any
+ * remaining data.
+ */
+ _processIncoming: function (stream, count) {
+ dumpv("Data available: " + count);
+
+ if (!count) {
+ dumpv("Nothing to read, skipping");
+ return false;
+ }
+
+ try {
+ if (!this._incoming) {
+ dumpv("Creating a new packet from incoming");
+
+ if (!this._readHeader(stream)) {
+ // Not enough data to read packet type
+ return false;
+ }
+
+ // Attempt to create a new Packet by trying to parse each possible
+ // header pattern.
+ this._incoming = Packet.fromHeader(this._incomingHeader, this);
+ if (!this._incoming) {
+ throw new Error("No packet types for header: " +
+ this._incomingHeader);
+ }
+ }
+
+ if (!this._incoming.done) {
+ // We have an incomplete packet, keep reading it.
+ dumpv("Existing packet incomplete, keep reading");
+ this._incoming.read(stream, this._scriptableInput);
+ }
+ } catch (e) {
+ let msg = "Error reading incoming packet: (" + e + " - " + e.stack + ")";
+ dumpn(msg);
+
+ // Now in an invalid state, shut down the transport.
+ this.close();
+ return false;
+ }
+
+ if (!this._incoming.done) {
+ // Still not complete, we'll wait for more data.
+ dumpv("Packet not done, wait for more");
+ return true;
+ }
+
+ // Ready for next packet
+ this._flushIncoming();
+ return true;
+ },
+
+ /**
+ * Read as far as we can into the incoming data, attempting to build up a
+ * complete packet header (which terminates with ":"). We'll only read up to
+ * PACKET_HEADER_MAX characters.
+ * @return boolean
+ * True if we now have a complete header.
+ */
+ _readHeader: function () {
+ let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
+ this._incomingHeader +=
+ StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
+ if (flags.wantVerbose) {
+ dumpv("Header read: " + this._incomingHeader);
+ }
+
+ if (this._incomingHeader.endsWith(":")) {
+ if (flags.wantVerbose) {
+ dumpv("Found packet header successfully: " + this._incomingHeader);
+ }
+ return true;
+ }
+
+ if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
+ throw new Error("Failed to parse packet header!");
+ }
+
+ // Not enough data yet.
+ return false;
+ },
+
+ /**
+ * If the incoming packet is done, log it as needed and clear the buffer.
+ */
+ _flushIncoming: function () {
+ if (!this._incoming.done) {
+ return;
+ }
+ if (flags.wantLogging) {
+ dumpn("Got: " + this._incoming);
+ }
+ this._destroyIncoming();
+ },
+
+ /**
+ * Handler triggered by an incoming JSONPacket completing it's |read| method.
+ * Delivers the packet to this.hooks.onPacket.
+ */
+ _onJSONObjectReady: function (object) {
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Ensure the transport is still alive by the time this runs.
+ if (this.active) {
+ this.emit("packet", object);
+ this.hooks.onPacket(object);
+ }
+ }, "DebuggerTransport instance's this.hooks.onPacket"));
+ },
+
+ /**
+ * Handler triggered by an incoming BulkPacket entering the |read| phase for
+ * the stream portion of the packet. Delivers info about the incoming
+ * streaming data to this.hooks.onBulkPacket. See the main comment on the
+ * transport at the top of this file for more details.
+ */
+ _onBulkReadReady: function (...args) {
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Ensure the transport is still alive by the time this runs.
+ if (this.active) {
+ this.emit("bulkpacket", ...args);
+ this.hooks.onBulkPacket(...args);
+ }
+ }, "DebuggerTransport instance's this.hooks.onBulkPacket"));
+ },
+
+ /**
+ * Remove all handlers and references related to the current incoming packet,
+ * either because it is now complete or because the transport is closing.
+ */
+ _destroyIncoming: function () {
+ if (this._incoming) {
+ this._incoming.destroy();
+ }
+ this._incomingHeader = "";
+ this._incoming = null;
+ }
+
+ };
+
+ exports.DebuggerTransport = DebuggerTransport;
+
+ /**
+ * An adapter that handles data transfers between the debugger client and
+ * server when they both run in the same process. It presents the same API as
+ * DebuggerTransport, but instead of transmitting serialized messages across a
+ * connection it merely calls the packet dispatcher of the other side.
+ *
+ * @param other LocalDebuggerTransport
+ * The other endpoint for this debugger connection.
+ *
+ * @see DebuggerTransport
+ */
+ function LocalDebuggerTransport(other) {
+ EventEmitter.decorate(this);
+
+ this.other = other;
+ this.hooks = null;
+
+ // A packet number, shared between this and this.other. This isn't used by the
+ // protocol at all, but it makes the packet traces a lot easier to follow.
+ this._serial = this.other ? this.other._serial : { count: 0 };
+ this.close = this.close.bind(this);
+ }
+
+ LocalDebuggerTransport.prototype = {
+ /**
+ * Transmit a message by directly calling the onPacket handler of the other
+ * endpoint.
+ */
+ send: function (packet) {
+ this.emit("send", packet);
+
+ let serial = this._serial.count++;
+ if (flags.wantLogging) {
+ // Check 'from' first, as 'echo' packets have both.
+ if (packet.from) {
+ dumpn("Packet " + serial + " sent from " + uneval(packet.from));
+ } else if (packet.to) {
+ dumpn("Packet " + serial + " sent to " + uneval(packet.to));
+ }
+ }
+ this._deepFreeze(packet);
+ let other = this.other;
+ if (other) {
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ // Avoid the cost of JSON.stringify() when logging is disabled.
+ if (flags.wantLogging) {
+ dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
+ }
+ if (other.hooks) {
+ other.emit("packet", packet);
+ other.hooks.onPacket(packet);
+ }
+ }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
+ }
+ },
+
+ /**
+ * Send a streaming bulk packet directly to the onBulkPacket handler of the
+ * other endpoint.
+ *
+ * This case is much simpler than the full DebuggerTransport, since there is
+ * no primary stream we have to worry about managing while we hand it off to
+ * others temporarily. Instead, we can just make a single use pipe and be
+ * done with it.
+ */
+ startBulkSend: function ({actor, type, length}) {
+ this.emit("startbulksend", {actor, type, length});
+
+ let serial = this._serial.count++;
+
+ dumpn("Sent bulk packet " + serial + " for actor " + actor);
+ if (!this.other) {
+ let error = new Error("startBulkSend: other side of transport missing");
+ return promise.reject(error);
+ }
+
+ let pipe = new Pipe(true, true, 0, 0, null);
+
+ DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
+ dumpn("Received bulk packet " + serial);
+ if (!this.other.hooks) {
+ return;
+ }
+
+ // Receiver
+ let deferred = defer();
+ let packet = {
+ actor: actor,
+ type: type,
+ length: length,
+ copyTo: (output) => {
+ let copying =
+ StreamUtils.copyStream(pipe.inputStream, output, length);
+ deferred.resolve(copying);
+ return copying;
+ },
+ stream: pipe.inputStream,
+ done: deferred
+ };
+
+ this.other.emit("bulkpacket", packet);
+ this.other.hooks.onBulkPacket(packet);
+
+ // Await the result of reading from the stream
+ deferred.promise.then(() => pipe.inputStream.close(), this.close);
+ }, "LocalDebuggerTransport instance's this.other.hooks.onBulkPacket"));
+
+ // Sender
+ let sendDeferred = defer();
+
+ // The remote transport is not capable of resolving immediately here, so we
+ // shouldn't be able to either.
+ DevToolsUtils.executeSoon(() => {
+ let copyDeferred = defer();
+
+ sendDeferred.resolve({
+ copyFrom: (input) => {
+ let copying =
+ StreamUtils.copyStream(input, pipe.outputStream, length);
+ copyDeferred.resolve(copying);
+ return copying;
+ },
+ stream: pipe.outputStream,
+ done: copyDeferred
+ });
+
+ // Await the result of writing to the stream
+ copyDeferred.promise.then(() => pipe.outputStream.close(), this.close);
+ });
+
+ return sendDeferred.promise;
+ },
+
+ /**
+ * Close the transport.
+ */
+ close: function () {
+ this.emit("close");
+
+ if (this.other) {
+ // Remove the reference to the other endpoint before calling close(), to
+ // avoid infinite recursion.
+ let other = this.other;
+ this.other = null;
+ other.close();
+ }
+ if (this.hooks) {
+ try {
+ this.hooks.onClosed();
+ } catch (ex) {
+ console.error(ex);
+ }
+ this.hooks = null;
+ }
+ },
+
+ /**
+ * An empty method for emulating the DebuggerTransport API.
+ */
+ ready: function () {},
+
+ /**
+ * Helper function that makes an object fully immutable.
+ */
+ _deepFreeze: function (object) {
+ Object.freeze(object);
+ for (let prop in object) {
+ // Freeze the properties that are objects, not on the prototype, and not
+ // already frozen. Note that this might leave an unfrozen reference
+ // somewhere in the object if there is an already frozen object containing
+ // an unfrozen object.
+ if (object.hasOwnProperty(prop) && typeof object === "object" &&
+ !Object.isFrozen(object)) {
+ this._deepFreeze(object[prop]);
+ }
+ }
+ }
+ };
+
+ exports.LocalDebuggerTransport = LocalDebuggerTransport;
+
+ /**
+ * A transport for the debugging protocol that uses nsIMessageManagers to
+ * exchange packets with servers running in child processes.
+ *
+ * In the parent process, |mm| should be the nsIMessageSender for the
+ * child process. In a child process, |mm| should be the child process
+ * message manager, which sends packets to the parent.
+ *
+ * |prefix| is a string included in the message names, to distinguish
+ * multiple servers running in the same child process.
+ *
+ * This transport exchanges messages named 'debug:<prefix>:packet', where
+ * <prefix> is |prefix|, whose data is the protocol packet.
+ */
+ function ChildDebuggerTransport(mm, prefix) {
+ EventEmitter.decorate(this);
+
+ this._mm = mm;
+ this._messageName = "debug:" + prefix + ":packet";
+ }
+
+ /*
+ * To avoid confusion, we use 'message' to mean something that
+ * nsIMessageSender conveys, and 'packet' to mean a remote debugging
+ * protocol packet.
+ */
+ ChildDebuggerTransport.prototype = {
+ constructor: ChildDebuggerTransport,
+
+ hooks: null,
+
+ _addListener() {
+ this._mm.addMessageListener(this._messageName, this);
+ },
+
+ _removeListener() {
+ try {
+ this._mm.removeMessageListener(this._messageName, this);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+ throw e;
+ }
+ // In some cases, especially when using messageManagers in non-e10s mode, we reach
+ // this point with a dead messageManager which only throws errors but does not
+ // seem to indicate in any other way that it is dead.
+ }
+ },
+
+ ready: function () {
+ this._addListener();
+ },
+
+ close: function () {
+ this._removeListener();
+ this.emit("close");
+ this.hooks.onClosed();
+ },
+
+ receiveMessage: function ({data}) {
+ this.emit("packet", data);
+ this.hooks.onPacket(data);
+ },
+
+ send: function (packet) {
+ this.emit("send", packet);
+ try {
+ this._mm.sendAsyncMessage(this._messageName, packet);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_NULL_POINTER) {
+ throw e;
+ }
+ // In some cases, especially when using messageManagers in non-e10s mode, we reach
+ // this point with a dead messageManager which only throws errors but does not
+ // seem to indicate in any other way that it is dead.
+ }
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data to child processes.");
+ },
+
+ swapBrowser(mm) {
+ this._removeListener();
+ this._mm = mm;
+ this._addListener();
+ },
+ };
+
+ exports.ChildDebuggerTransport = ChildDebuggerTransport;
+
+ // WorkerDebuggerTransport is defined differently depending on whether we are
+ // on the main thread or a worker thread. In the former case, we are required
+ // by the devtools loader, and isWorker will be false. Otherwise, we are
+ // required by the worker loader, and isWorker will be true.
+ //
+ // Each worker debugger supports only a single connection to the main thread.
+ // However, its theoretically possible for multiple servers to connect to the
+ // same worker. Consequently, each transport has a connection id, to allow
+ // messages from multiple connections to be multiplexed on a single channel.
+
+ if (!this.isWorker) {
+ // Main thread
+ (function () {
+ /**
+ * A transport that uses a WorkerDebugger to send packets from the main
+ * thread to a worker thread.
+ */
+ function WorkerDebuggerTransport(dbg, id) {
+ this._dbg = dbg;
+ this._id = id;
+ this.onMessage = this._onMessage.bind(this);
+ }
+
+ WorkerDebuggerTransport.prototype = {
+ constructor: WorkerDebuggerTransport,
+
+ ready: function () {
+ this._dbg.addListener(this);
+ },
+
+ close: function () {
+ this._dbg.removeListener(this);
+ if (this.hooks) {
+ this.hooks.onClosed();
+ }
+ },
+
+ send: function (packet) {
+ this._dbg.postMessage(JSON.stringify({
+ type: "message",
+ id: this._id,
+ message: packet
+ }));
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data from worker threads!");
+ },
+
+ _onMessage: function (message) {
+ let packet = JSON.parse(message);
+ if (packet.type !== "message" || packet.id !== this._id) {
+ return;
+ }
+
+ if (this.hooks) {
+ this.hooks.onPacket(packet.message);
+ }
+ }
+ };
+
+ exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+ }).call(this);
+ } else {
+ // Worker thread
+ (function () {
+ /**
+ * A transport that uses a WorkerDebuggerGlobalScope to send packets from a
+ * worker thread to the main thread.
+ */
+ function WorkerDebuggerTransport(scope, id) {
+ this._scope = scope;
+ this._id = id;
+ this._onMessage = this._onMessage.bind(this);
+ }
+
+ WorkerDebuggerTransport.prototype = {
+ constructor: WorkerDebuggerTransport,
+
+ ready: function () {
+ this._scope.addEventListener("message", this._onMessage);
+ },
+
+ close: function () {
+ this._scope.removeEventListener("message", this._onMessage);
+ if (this.hooks) {
+ this.hooks.onClosed();
+ }
+ },
+
+ send: function (packet) {
+ this._scope.postMessage(JSON.stringify({
+ type: "message",
+ id: this._id,
+ message: packet
+ }));
+ },
+
+ startBulkSend: function () {
+ throw new Error("Can't send bulk data from worker threads!");
+ },
+
+ _onMessage: function (event) {
+ let packet = JSON.parse(event.data);
+ if (packet.type !== "message" || packet.id !== this._id) {
+ return;
+ }
+
+ if (this.hooks) {
+ this.hooks.onPacket(packet.message);
+ }
+ }
+ };
+
+ exports.WorkerDebuggerTransport = WorkerDebuggerTransport;
+ }).call(this);
+ }
+});
diff --git a/devtools/shared/transport/websocket-transport.js b/devtools/shared/transport/websocket-transport.js
new file mode 100644
index 000000000..6ba474106
--- /dev/null
+++ b/devtools/shared/transport/websocket-transport.js
@@ -0,0 +1,79 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+
+function WebSocketDebuggerTransport(socket) {
+ EventEmitter.decorate(this);
+
+ this.active = false;
+ this.hooks = null;
+ this.socket = socket;
+}
+
+WebSocketDebuggerTransport.prototype = {
+ ready() {
+ if (this.active) {
+ return;
+ }
+
+ this.socket.addEventListener("message", this);
+ this.socket.addEventListener("close", this);
+
+ this.active = true;
+ },
+
+ send(object) {
+ this.emit("send", object);
+ if (this.socket) {
+ this.socket.send(JSON.stringify(object));
+ }
+ },
+
+ startBulkSend() {
+ throw new Error("Bulk send is not supported by WebSocket transport");
+ },
+
+ close() {
+ this.emit("close");
+ this.active = false;
+
+ this.socket.removeEventListener("message", this);
+ this.socket.removeEventListener("close", this);
+ this.socket.close();
+ this.socket = null;
+
+ if (this.hooks) {
+ this.hooks.onClosed();
+ this.hooks = null;
+ }
+ },
+
+ handleEvent(event) {
+ switch (event.type) {
+ case "message":
+ this.onMessage(event);
+ break;
+ case "close":
+ this.close();
+ break;
+ }
+ },
+
+ onMessage({ data }) {
+ if (typeof data !== "string") {
+ throw new Error("Binary messages are not supported by WebSocket transport");
+ }
+
+ let object = JSON.parse(data);
+ this.emit("packet", object);
+ if (this.hooks) {
+ this.hooks.onPacket(object);
+ }
+ },
+};
+
+module.exports = WebSocketDebuggerTransport;
diff --git a/devtools/shared/webconsole/client.js b/devtools/shared/webconsole/client.js
new file mode 100644
index 000000000..4cc5deedf
--- /dev/null
+++ b/devtools/shared/webconsole/client.js
@@ -0,0 +1,652 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const EventEmitter = require("devtools/shared/event-emitter");
+const promise = require("promise");
+const defer = require("devtools/shared/defer");
+const {LongStringClient} = require("devtools/shared/client/main");
+
+/**
+ * A WebConsoleClient is used as a front end for the WebConsoleActor that is
+ * created on the server, hiding implementation details.
+ *
+ * @param object debuggerClient
+ * The DebuggerClient instance we live for.
+ * @param object response
+ * The response packet received from the "startListeners" request sent to
+ * the WebConsoleActor.
+ */
+function WebConsoleClient(debuggerClient, response) {
+ this._actor = response.from;
+ this._client = debuggerClient;
+ this._longStrings = {};
+ this.traits = response.traits || {};
+ this.events = [];
+ this._networkRequests = new Map();
+
+ this.pendingEvaluationResults = new Map();
+ this.onEvaluationResult = this.onEvaluationResult.bind(this);
+ this.onNetworkEvent = this._onNetworkEvent.bind(this);
+ this.onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
+
+ this._client.addListener("evaluationResult", this.onEvaluationResult);
+ this._client.addListener("networkEvent", this.onNetworkEvent);
+ this._client.addListener("networkEventUpdate", this.onNetworkEventUpdate);
+ EventEmitter.decorate(this);
+}
+
+exports.WebConsoleClient = WebConsoleClient;
+
+WebConsoleClient.prototype = {
+ _longStrings: null,
+ traits: null,
+
+ /**
+ * Holds the network requests currently displayed by the Web Console. Each key
+ * represents the connection ID and the value is network request information.
+ * @private
+ * @type object
+ */
+ _networkRequests: null,
+
+ getNetworkRequest(actorId) {
+ return this._networkRequests.get(actorId);
+ },
+
+ hasNetworkRequest(actorId) {
+ return this._networkRequests.has(actorId);
+ },
+
+ removeNetworkRequest(actorId) {
+ this._networkRequests.delete(actorId);
+ },
+
+ getNetworkEvents() {
+ return this._networkRequests.values();
+ },
+
+ get actor() {
+ return this._actor;
+ },
+
+ /**
+ * The "networkEvent" message type handler. We redirect any message to
+ * the UI for displaying.
+ *
+ * @private
+ * @param string type
+ * Message type.
+ * @param object packet
+ * The message received from the server.
+ */
+ _onNetworkEvent: function (type, packet) {
+ if (packet.from == this._actor) {
+ let actor = packet.eventActor;
+ let networkInfo = {
+ _type: "NetworkEvent",
+ timeStamp: actor.timeStamp,
+ node: null,
+ actor: actor.actor,
+ discardRequestBody: true,
+ discardResponseBody: true,
+ startedDateTime: actor.startedDateTime,
+ request: {
+ url: actor.url,
+ method: actor.method,
+ },
+ isXHR: actor.isXHR,
+ cause: actor.cause,
+ response: {},
+ timings: {},
+ // track the list of network event updates
+ updates: [],
+ private: actor.private,
+ fromCache: actor.fromCache,
+ fromServiceWorker: actor.fromServiceWorker
+ };
+ this._networkRequests.set(actor.actor, networkInfo);
+
+ this.emit("networkEvent", networkInfo);
+ }
+ },
+
+ /**
+ * The "networkEventUpdate" message type handler. We redirect any message to
+ * the UI for displaying.
+ *
+ * @private
+ * @param string type
+ * Message type.
+ * @param object packet
+ * The message received from the server.
+ */
+ _onNetworkEventUpdate: function (type, packet) {
+ let networkInfo = this.getNetworkRequest(packet.from);
+ if (!networkInfo) {
+ return;
+ }
+
+ networkInfo.updates.push(packet.updateType);
+
+ switch (packet.updateType) {
+ case "requestHeaders":
+ networkInfo.request.headersSize = packet.headersSize;
+ break;
+ case "requestPostData":
+ networkInfo.discardRequestBody = packet.discardRequestBody;
+ networkInfo.request.bodySize = packet.dataSize;
+ break;
+ case "responseStart":
+ networkInfo.response.httpVersion = packet.response.httpVersion;
+ networkInfo.response.status = packet.response.status;
+ networkInfo.response.statusText = packet.response.statusText;
+ networkInfo.response.headersSize = packet.response.headersSize;
+ networkInfo.response.remoteAddress = packet.response.remoteAddress;
+ networkInfo.response.remotePort = packet.response.remotePort;
+ networkInfo.discardResponseBody = packet.response.discardResponseBody;
+ break;
+ case "responseContent":
+ networkInfo.response.content = {
+ mimeType: packet.mimeType,
+ };
+ networkInfo.response.bodySize = packet.contentSize;
+ networkInfo.response.transferredSize = packet.transferredSize;
+ networkInfo.discardResponseBody = packet.discardResponseBody;
+ break;
+ case "eventTimings":
+ networkInfo.totalTime = packet.totalTime;
+ break;
+ case "securityInfo":
+ networkInfo.securityInfo = packet.state;
+ break;
+ }
+
+ this.emit("networkEventUpdate", {
+ packet: packet,
+ networkInfo
+ });
+ },
+
+ /**
+ * Retrieve the cached messages from the server.
+ *
+ * @see this.CACHED_MESSAGES
+ * @param array types
+ * The array of message types you want from the server. See
+ * this.CACHED_MESSAGES for known types.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getCachedMessages: function (types, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "getCachedMessages",
+ messageTypes: types,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Inspect the properties of an object.
+ *
+ * @param string actor
+ * The WebConsoleObjectActor ID to send the request to.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ inspectObjectProperties: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "inspectProperties",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Evaluate a JavaScript expression.
+ *
+ * @param string string
+ * The code you want to evaluate.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ * @param object [options={}]
+ * Options for evaluation:
+ *
+ * - bindObjectActor: an ObjectActor ID. The OA holds a reference to
+ * a Debugger.Object that wraps a content object. This option allows
+ * you to bind |_self| to the D.O of the given OA, during string
+ * evaluation.
+ *
+ * See: Debugger.Object.executeInGlobalWithBindings() for information
+ * about bindings.
+ *
+ * Use case: the variable view needs to update objects and it does so
+ * by knowing the ObjectActor it inspects and binding |_self| to the
+ * D.O of the OA. As such, variable view sends strings like these for
+ * eval:
+ * _self["prop"] = value;
+ *
+ * - frameActor: a FrameActor ID. The FA holds a reference to
+ * a Debugger.Frame. This option allows you to evaluate the string in
+ * the frame of the given FA.
+ *
+ * - url: the url to evaluate the script as. Defaults to
+ * "debugger eval code".
+ *
+ * - selectedNodeActor: the NodeActor ID of the current
+ * selection in the Inspector, if such a selection
+ * exists. This is used by helper functions that can
+ * reference the currently selected node in the Inspector,
+ * like $0.
+ */
+ evaluateJS: function (string, onResponse, options = {}) {
+ let packet = {
+ to: this._actor,
+ type: "evaluateJS",
+ text: string,
+ bindObjectActor: options.bindObjectActor,
+ frameActor: options.frameActor,
+ url: options.url,
+ selectedNodeActor: options.selectedNodeActor,
+ selectedObjectActor: options.selectedObjectActor,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Evaluate a JavaScript expression asynchronously.
+ * See evaluateJS for parameter and response information.
+ */
+ evaluateJSAsync: function (string, onResponse, options = {}) {
+ // Pre-37 servers don't support async evaluation.
+ if (!this.traits.evaluateJSAsync) {
+ this.evaluateJS(string, onResponse, options);
+ return;
+ }
+
+ let packet = {
+ to: this._actor,
+ type: "evaluateJSAsync",
+ text: string,
+ bindObjectActor: options.bindObjectActor,
+ frameActor: options.frameActor,
+ url: options.url,
+ selectedNodeActor: options.selectedNodeActor,
+ selectedObjectActor: options.selectedObjectActor,
+ };
+
+ this._client.request(packet, response => {
+ // Null check this in case the client has been detached while waiting
+ // for a response.
+ if (this.pendingEvaluationResults) {
+ this.pendingEvaluationResults.set(response.resultID, onResponse);
+ }
+ });
+ },
+
+ /**
+ * Handler for the actors's unsolicited evaluationResult packet.
+ */
+ onEvaluationResult: function (notification, packet) {
+ // The client on the main thread can receive notification packets from
+ // multiple webconsole actors: the one on the main thread and the ones
+ // on worker threads. So make sure we should be handling this request.
+ if (packet.from !== this._actor) {
+ return;
+ }
+
+ // Find the associated callback based on this ID, and fire it.
+ // In a sync evaluation, this would have already been called in
+ // direct response to the client.request function.
+ let onResponse = this.pendingEvaluationResults.get(packet.resultID);
+ if (onResponse) {
+ onResponse(packet);
+ this.pendingEvaluationResults.delete(packet.resultID);
+ } else {
+ DevToolsUtils.reportException("onEvaluationResult",
+ "No response handler for an evaluateJSAsync result (resultID: " +
+ packet.resultID + ")");
+ }
+ },
+
+ /**
+ * Autocomplete a JavaScript expression.
+ *
+ * @param string string
+ * The code you want to autocomplete.
+ * @param number cursor
+ * Cursor location inside the string. Index starts from 0.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ * @param string frameActor
+ * The id of the frame actor that made the call.
+ */
+ autocomplete: function (string, cursor, onResponse, frameActor) {
+ let packet = {
+ to: this._actor,
+ type: "autocomplete",
+ text: string,
+ cursor: cursor,
+ frameActor: frameActor,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Clear the cache of messages (page errors and console API calls).
+ */
+ clearMessagesCache: function () {
+ let packet = {
+ to: this._actor,
+ type: "clearMessagesCache",
+ };
+ this._client.request(packet);
+ },
+
+ /**
+ * Get Web Console-related preferences on the server.
+ *
+ * @param array preferences
+ * An array with the preferences you want to retrieve.
+ * @param function [onResponse]
+ * Optional function to invoke when the response is received.
+ */
+ getPreferences: function (preferences, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "getPreferences",
+ preferences: preferences,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Set Web Console-related preferences on the server.
+ *
+ * @param object preferences
+ * An object with the preferences you want to change.
+ * @param function [onResponse]
+ * Optional function to invoke when the response is received.
+ */
+ setPreferences: function (preferences, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "setPreferences",
+ preferences: preferences,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the request headers from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getRequestHeaders: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getRequestHeaders",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the request cookies from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getRequestCookies: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getRequestCookies",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the request post data from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getRequestPostData: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getRequestPostData",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the response headers from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getResponseHeaders: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getResponseHeaders",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the response cookies from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getResponseCookies: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getResponseCookies",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the response content from the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getResponseContent: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getResponseContent",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the timing information for the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getEventTimings: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getEventTimings",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Retrieve the security information for the given NetworkEventActor.
+ *
+ * @param string actor
+ * The NetworkEventActor ID.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ getSecurityInfo: function (actor, onResponse) {
+ let packet = {
+ to: actor,
+ type: "getSecurityInfo",
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Send a HTTP request with the given data.
+ *
+ * @param string data
+ * The details of the HTTP request.
+ * @param function onResponse
+ * The function invoked when the response is received.
+ */
+ sendHTTPRequest: function (data, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "sendHTTPRequest",
+ request: data
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Start the given Web Console listeners.
+ *
+ * @see this.LISTENERS
+ * @param array listeners
+ * Array of listeners you want to start. See this.LISTENERS for
+ * known listeners.
+ * @param function onResponse
+ * Function to invoke when the server response is received.
+ */
+ startListeners: function (listeners, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "startListeners",
+ listeners: listeners,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Stop the given Web Console listeners.
+ *
+ * @see this.LISTENERS
+ * @param array listeners
+ * Array of listeners you want to stop. See this.LISTENERS for
+ * known listeners.
+ * @param function onResponse
+ * Function to invoke when the server response is received.
+ */
+ stopListeners: function (listeners, onResponse) {
+ let packet = {
+ to: this._actor,
+ type: "stopListeners",
+ listeners: listeners,
+ };
+ this._client.request(packet, onResponse);
+ },
+
+ /**
+ * Return an instance of LongStringClient for the given long string grip.
+ *
+ * @param object grip
+ * The long string grip returned by the protocol.
+ * @return object
+ * The LongStringClient for the given long string grip.
+ */
+ longString: function (grip) {
+ if (grip.actor in this._longStrings) {
+ return this._longStrings[grip.actor];
+ }
+
+ let client = new LongStringClient(this._client, grip);
+ this._longStrings[grip.actor] = client;
+ return client;
+ },
+
+ /**
+ * Close the WebConsoleClient. This stops all the listeners on the server and
+ * detaches from the console actor.
+ *
+ * @param function onResponse
+ * Function to invoke when the server response is received.
+ */
+ detach: function (onResponse) {
+ this._client.removeListener("evaluationResult", this.onEvaluationResult);
+ this._client.removeListener("networkEvent", this.onNetworkEvent);
+ this._client.removeListener("networkEventUpdate",
+ this.onNetworkEventUpdate);
+ this.stopListeners(null, onResponse);
+ this._longStrings = null;
+ this._client = null;
+ this.pendingEvaluationResults.clear();
+ this.pendingEvaluationResults = null;
+ this.clearNetworkRequests();
+ this._networkRequests = null;
+ },
+
+ clearNetworkRequests: function () {
+ this._networkRequests.clear();
+ },
+
+ /**
+ * Fetches the full text of a LongString.
+ *
+ * @param object | string stringGrip
+ * The long string grip containing the corresponding actor.
+ * If you pass in a plain string (by accident or because you're lazy),
+ * then a promise of the same string is simply returned.
+ * @return object Promise
+ * A promise that is resolved when the full string contents
+ * are available, or rejected if something goes wrong.
+ */
+ getString: function (stringGrip) {
+ // Make sure this is a long string.
+ if (typeof stringGrip != "object" || stringGrip.type != "longString") {
+ // Go home string, you're drunk.
+ return promise.resolve(stringGrip);
+ }
+
+ // Fetch the long string only once.
+ if (stringGrip._fullText) {
+ return stringGrip._fullText.promise;
+ }
+
+ let deferred = stringGrip._fullText = defer();
+ let { initial, length } = stringGrip;
+ let longStringClient = this.longString(stringGrip);
+
+ longStringClient.substring(initial.length, length, response => {
+ if (response.error) {
+ DevToolsUtils.reportException("getString",
+ response.error + ": " + response.message);
+
+ deferred.reject(response);
+ return;
+ }
+ deferred.resolve(initial + response.substring);
+ });
+
+ return deferred.promise;
+ }
+};
diff --git a/devtools/shared/webconsole/js-property-provider.js b/devtools/shared/webconsole/js-property-provider.js
new file mode 100644
index 000000000..9ada46732
--- /dev/null
+++ b/devtools/shared/webconsole/js-property-provider.js
@@ -0,0 +1,538 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+if (!isWorker) {
+ loader.lazyImporter(this, "Parser", "resource://devtools/shared/Parser.jsm");
+}
+
+// Provide an easy way to bail out of even attempting an autocompletion
+// if an object has way too many properties. Protects against large objects
+// with numeric values that wouldn't be tallied towards MAX_AUTOCOMPLETIONS.
+const MAX_AUTOCOMPLETE_ATTEMPTS = exports.MAX_AUTOCOMPLETE_ATTEMPTS = 100000;
+// Prevent iterating over too many properties during autocomplete suggestions.
+const MAX_AUTOCOMPLETIONS = exports.MAX_AUTOCOMPLETIONS = 1500;
+
+const STATE_NORMAL = 0;
+const STATE_QUOTE = 2;
+const STATE_DQUOTE = 3;
+
+const OPEN_BODY = "{[(".split("");
+const CLOSE_BODY = "}])".split("");
+const OPEN_CLOSE_BODY = {
+ "{": "}",
+ "[": "]",
+ "(": ")",
+};
+
+function hasArrayIndex(str) {
+ return /\[\d+\]$/.test(str);
+}
+
+/**
+ * Analyses a given string to find the last statement that is interesting for
+ * later completion.
+ *
+ * @param string str
+ * A string to analyse.
+ *
+ * @returns object
+ * If there was an error in the string detected, then a object like
+ *
+ * { err: "ErrorMesssage" }
+ *
+ * is returned, otherwise a object like
+ *
+ * {
+ * state: STATE_NORMAL|STATE_QUOTE|STATE_DQUOTE,
+ * startPos: index of where the last statement begins
+ * }
+ */
+function findCompletionBeginning(str) {
+ let bodyStack = [];
+
+ let state = STATE_NORMAL;
+ let start = 0;
+ let c;
+ for (let i = 0; i < str.length; i++) {
+ c = str[i];
+
+ switch (state) {
+ // Normal JS state.
+ case STATE_NORMAL:
+ if (c == '"') {
+ state = STATE_DQUOTE;
+ } else if (c == "'") {
+ state = STATE_QUOTE;
+ } else if (c == ";") {
+ start = i + 1;
+ } else if (c == " ") {
+ start = i + 1;
+ } else if (OPEN_BODY.indexOf(c) != -1) {
+ bodyStack.push({
+ token: c,
+ start: start
+ });
+ start = i + 1;
+ } else if (CLOSE_BODY.indexOf(c) != -1) {
+ let last = bodyStack.pop();
+ if (!last || OPEN_CLOSE_BODY[last.token] != c) {
+ return {
+ err: "syntax error"
+ };
+ }
+ if (c == "}") {
+ start = i + 1;
+ } else {
+ start = last.start;
+ }
+ }
+ break;
+
+ // Double quote state > " <
+ case STATE_DQUOTE:
+ if (c == "\\") {
+ i++;
+ } else if (c == "\n") {
+ return {
+ err: "unterminated string literal"
+ };
+ } else if (c == '"') {
+ state = STATE_NORMAL;
+ }
+ break;
+
+ // Single quote state > ' <
+ case STATE_QUOTE:
+ if (c == "\\") {
+ i++;
+ } else if (c == "\n") {
+ return {
+ err: "unterminated string literal"
+ };
+ } else if (c == "'") {
+ state = STATE_NORMAL;
+ }
+ break;
+ }
+ }
+
+ return {
+ state: state,
+ startPos: start
+ };
+}
+
+/**
+ * Provides a list of properties, that are possible matches based on the passed
+ * Debugger.Environment/Debugger.Object and inputValue.
+ *
+ * @param object dbgObject
+ * When the debugger is not paused this Debugger.Object wraps
+ * the scope for autocompletion.
+ * It is null if the debugger is paused.
+ * @param object anEnvironment
+ * When the debugger is paused this Debugger.Environment is the
+ * scope for autocompletion.
+ * It is null if the debugger is not paused.
+ * @param string inputValue
+ * Value that should be completed.
+ * @param number [cursor=inputValue.length]
+ * Optional offset in the input where the cursor is located. If this is
+ * omitted then the cursor is assumed to be at the end of the input
+ * value.
+ * @returns null or object
+ * If no completion valued could be computed, null is returned,
+ * otherwise a object with the following form is returned:
+ * {
+ * matches: [ string, string, string ],
+ * matchProp: Last part of the inputValue that was used to find
+ * the matches-strings.
+ * }
+ */
+function JSPropertyProvider(dbgObject, anEnvironment, inputValue, cursor) {
+ if (cursor === undefined) {
+ cursor = inputValue.length;
+ }
+
+ inputValue = inputValue.substring(0, cursor);
+
+ // Analyse the inputValue and find the beginning of the last part that
+ // should be completed.
+ let beginning = findCompletionBeginning(inputValue);
+
+ // There was an error analysing the string.
+ if (beginning.err) {
+ return null;
+ }
+
+ // If the current state is not STATE_NORMAL, then we are inside of an string
+ // which means that no completion is possible.
+ if (beginning.state != STATE_NORMAL) {
+ return null;
+ }
+
+ let completionPart = inputValue.substring(beginning.startPos);
+ let lastDot = completionPart.lastIndexOf(".");
+
+ // Don't complete on just an empty string.
+ if (completionPart.trim() == "") {
+ return null;
+ }
+
+ // Catch literals like [1,2,3] or "foo" and return the matches from
+ // their prototypes.
+ // Don't run this is a worker, migrating to acorn should allow this
+ // to run in a worker - Bug 1217198.
+ if (!isWorker && lastDot > 0) {
+ let parser = new Parser();
+ parser.logExceptions = false;
+ let syntaxTree = parser.get(completionPart.slice(0, lastDot));
+ let lastTree = syntaxTree.getLastSyntaxTree();
+ let lastBody = lastTree && lastTree.AST.body[lastTree.AST.body.length - 1];
+
+ // Finding the last expression since we've sliced up until the dot.
+ // If there were parse errors this won't exist.
+ if (lastBody) {
+ let expression = lastBody.expression;
+ let matchProp = completionPart.slice(lastDot + 1);
+ if (expression.type === "ArrayExpression") {
+ return getMatchedProps(Array.prototype, matchProp);
+ } else if (expression.type === "Literal" &&
+ (typeof expression.value === "string")) {
+ return getMatchedProps(String.prototype, matchProp);
+ }
+ }
+ }
+
+ // We are completing a variable / a property lookup.
+ let properties = completionPart.split(".");
+ let matchProp = properties.pop().trimLeft();
+ let obj = dbgObject;
+
+ // The first property must be found in the environment of the paused debugger
+ // or of the global lexical scope.
+ let env = anEnvironment || obj.asEnvironment();
+
+ if (properties.length === 0) {
+ return getMatchedPropsInEnvironment(env, matchProp);
+ }
+
+ let firstProp = properties.shift().trim();
+ if (firstProp === "this") {
+ // Special case for 'this' - try to get the Object from the Environment.
+ // No problem if it throws, we will just not autocomplete.
+ try {
+ obj = env.object;
+ } catch (e) {
+ // Ignore.
+ }
+ } else if (hasArrayIndex(firstProp)) {
+ obj = getArrayMemberProperty(null, env, firstProp);
+ } else {
+ obj = getVariableInEnvironment(env, firstProp);
+ }
+
+ if (!isObjectUsable(obj)) {
+ return null;
+ }
+
+ // We get the rest of the properties recursively starting from the
+ // Debugger.Object that wraps the first property
+ for (let i = 0; i < properties.length; i++) {
+ let prop = properties[i].trim();
+ if (!prop) {
+ return null;
+ }
+
+ if (hasArrayIndex(prop)) {
+ // The property to autocomplete is a member of array. For example
+ // list[i][j]..[n]. Traverse the array to get the actual element.
+ obj = getArrayMemberProperty(obj, null, prop);
+ } else {
+ obj = DevToolsUtils.getProperty(obj, prop);
+ }
+
+ if (!isObjectUsable(obj)) {
+ return null;
+ }
+ }
+
+ // If the final property is a primitive
+ if (typeof obj != "object") {
+ return getMatchedProps(obj, matchProp);
+ }
+
+ return getMatchedPropsInDbgObject(obj, matchProp);
+}
+
+/**
+ * Get the array member of obj for the given prop. For example, given
+ * prop='list[0][1]' the element at [0][1] of obj.list is returned.
+ *
+ * @param object obj
+ * The object to operate on. Should be null if env is passed.
+ * @param object env
+ * The Environment to operate in. Should be null if obj is passed.
+ * @param string prop
+ * The property to return.
+ * @return null or Object
+ * Returns null if the property couldn't be located. Otherwise the array
+ * member identified by prop.
+ */
+function getArrayMemberProperty(obj, env, prop) {
+ // First get the array.
+ let propWithoutIndices = prop.substr(0, prop.indexOf("["));
+
+ if (env) {
+ obj = getVariableInEnvironment(env, propWithoutIndices);
+ } else {
+ obj = DevToolsUtils.getProperty(obj, propWithoutIndices);
+ }
+
+ if (!isObjectUsable(obj)) {
+ return null;
+ }
+
+ // Then traverse the list of indices to get the actual element.
+ let result;
+ let arrayIndicesRegex = /\[[^\]]*\]/g;
+ while ((result = arrayIndicesRegex.exec(prop)) !== null) {
+ let indexWithBrackets = result[0];
+ let indexAsText = indexWithBrackets.substr(1, indexWithBrackets.length - 2);
+ let index = parseInt(indexAsText, 10);
+
+ if (isNaN(index)) {
+ return null;
+ }
+
+ obj = DevToolsUtils.getProperty(obj, index);
+
+ if (!isObjectUsable(obj)) {
+ return null;
+ }
+ }
+
+ return obj;
+}
+
+/**
+ * Check if the given Debugger.Object can be used for autocomplete.
+ *
+ * @param Debugger.Object object
+ * The Debugger.Object to check.
+ * @return boolean
+ * True if further inspection into the object is possible, or false
+ * otherwise.
+ */
+function isObjectUsable(object) {
+ if (object == null) {
+ return false;
+ }
+
+ if (typeof object == "object" && object.class == "DeadObject") {
+ return false;
+ }
+
+ return true;
+}
+
+/**
+ * @see getExactMatchImpl()
+ */
+function getVariableInEnvironment(anEnvironment, name) {
+ return getExactMatchImpl(anEnvironment, name, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedPropsImpl()
+ */
+function getMatchedPropsInEnvironment(anEnvironment, match) {
+ return getMatchedPropsImpl(anEnvironment, match, DebuggerEnvironmentSupport);
+}
+
+/**
+ * @see getMatchedPropsImpl()
+ */
+function getMatchedPropsInDbgObject(dbgObject, match) {
+ return getMatchedPropsImpl(dbgObject, match, DebuggerObjectSupport);
+}
+
+/**
+ * @see getMatchedPropsImpl()
+ */
+function getMatchedProps(obj, match) {
+ if (typeof obj != "object") {
+ obj = obj.constructor.prototype;
+ }
+ return getMatchedPropsImpl(obj, match, JSObjectSupport);
+}
+
+/**
+ * Get all properties in the given object (and its parent prototype chain) that
+ * match a given prefix.
+ *
+ * @param mixed obj
+ * Object whose properties we want to filter.
+ * @param string match
+ * Filter for properties that match this string.
+ * @return object
+ * Object that contains the matchProp and the list of names.
+ */
+function getMatchedPropsImpl(obj, match, {chainIterator, getProperties}) {
+ let matches = new Set();
+ let numProps = 0;
+
+ // We need to go up the prototype chain.
+ let iter = chainIterator(obj);
+ for (obj of iter) {
+ let props = getProperties(obj);
+ numProps += props.length;
+
+ // If there are too many properties to event attempt autocompletion,
+ // or if we have already added the max number, then stop looping
+ // and return the partial set that has already been discovered.
+ if (numProps >= MAX_AUTOCOMPLETE_ATTEMPTS ||
+ matches.size >= MAX_AUTOCOMPLETIONS) {
+ break;
+ }
+
+ for (let i = 0; i < props.length; i++) {
+ let prop = props[i];
+ if (prop.indexOf(match) != 0) {
+ continue;
+ }
+ if (prop.indexOf("-") > -1) {
+ continue;
+ }
+ // If it is an array index, we can't take it.
+ // This uses a trick: converting a string to a number yields NaN if
+ // the operation failed, and NaN is not equal to itself.
+ if (+prop != +prop) {
+ matches.add(prop);
+ }
+
+ if (matches.size >= MAX_AUTOCOMPLETIONS) {
+ break;
+ }
+ }
+ }
+
+ return {
+ matchProp: match,
+ matches: [...matches],
+ };
+}
+
+/**
+ * Returns a property value based on its name from the given object, by
+ * recursively checking the object's prototype.
+ *
+ * @param object obj
+ * An object to look the property into.
+ * @param string name
+ * The property that is looked up.
+ * @returns object|undefined
+ * A Debugger.Object if the property exists in the object's prototype
+ * chain, undefined otherwise.
+ */
+function getExactMatchImpl(obj, name, {chainIterator, getProperty}) {
+ // We need to go up the prototype chain.
+ let iter = chainIterator(obj);
+ for (obj of iter) {
+ let prop = getProperty(obj, name, obj);
+ if (prop) {
+ return prop.value;
+ }
+ }
+ return undefined;
+}
+
+var JSObjectSupport = {
+ chainIterator: function* (obj) {
+ while (obj) {
+ yield obj;
+ obj = Object.getPrototypeOf(obj);
+ }
+ },
+
+ getProperties: function (obj) {
+ return Object.getOwnPropertyNames(obj);
+ },
+
+ getProperty: function () {
+ // getProperty is unsafe with raw JS objects.
+ throw new Error("Unimplemented!");
+ },
+};
+
+var DebuggerObjectSupport = {
+ chainIterator: function* (obj) {
+ while (obj) {
+ yield obj;
+ obj = obj.proto;
+ }
+ },
+
+ getProperties: function (obj) {
+ return obj.getOwnPropertyNames();
+ },
+
+ getProperty: function (obj, name, rootObj) {
+ // This is left unimplemented in favor to DevToolsUtils.getProperty().
+ throw new Error("Unimplemented!");
+ },
+};
+
+var DebuggerEnvironmentSupport = {
+ chainIterator: function* (obj) {
+ while (obj) {
+ yield obj;
+ obj = obj.parent;
+ }
+ },
+
+ getProperties: function (obj) {
+ let names = obj.names();
+
+ // Include 'this' in results (in sorted order)
+ for (let i = 0; i < names.length; i++) {
+ if (i === names.length - 1 || names[i + 1] > "this") {
+ names.splice(i + 1, 0, "this");
+ break;
+ }
+ }
+
+ return names;
+ },
+
+ getProperty: function (obj, name) {
+ let result;
+ // Try/catch since name can be anything, and getVariable throws if
+ // it's not a valid ECMAScript identifier name
+ try {
+ // TODO: we should use getVariableDescriptor() here - bug 725815.
+ result = obj.getVariable(name);
+ } catch (e) {
+ // Ignore.
+ }
+
+ // FIXME: Need actual UI, bug 941287.
+ if (result === undefined || result.optimizedOut ||
+ result.missingArguments) {
+ return null;
+ }
+ return { value: result };
+ },
+};
+
+exports.JSPropertyProvider = DevToolsUtils.makeInfallible(JSPropertyProvider);
+
+// Export a version that will throw (for tests)
+exports.FallibleJSPropertyProvider = JSPropertyProvider;
diff --git a/devtools/shared/webconsole/moz.build b/devtools/shared/webconsole/moz.build
new file mode 100644
index 000000000..2ff6ed57f
--- /dev/null
+++ b/devtools/shared/webconsole/moz.build
@@ -0,0 +1,19 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['OS_TARGET'] != 'Android':
+ MOCHITEST_CHROME_MANIFESTS += ['test/chrome.ini']
+ XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
+
+DevToolsModules(
+ 'client.js',
+ 'js-property-provider.js',
+ 'network-helper.js',
+ 'network-monitor.js',
+ 'server-logger-monitor.js',
+ 'server-logger.js',
+ 'throttle.js',
+)
diff --git a/devtools/shared/webconsole/network-helper.js b/devtools/shared/webconsole/network-helper.js
new file mode 100644
index 000000000..af6a2e55b
--- /dev/null
+++ b/devtools/shared/webconsole/network-helper.js
@@ -0,0 +1,814 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/*
+ * Software License Agreement (BSD License)
+ *
+ * Copyright (c) 2007, Parakey Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use of this software in source and binary forms,
+ * with or without modification, are permitted provided that the
+ * following conditions are met:
+ *
+ * * Redistributions of source code must retain the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the
+ * following disclaimer in the documentation and/or other
+ * materials provided with the distribution.
+ *
+ * * Neither the name of Parakey Inc. nor the names of its
+ * contributors may be used to endorse or promote products
+ * derived from this software without specific prior
+ * written permission of Parakey Inc.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/*
+ * Creator:
+ * Joe Hewitt
+ * Contributors
+ * John J. Barton (IBM Almaden)
+ * Jan Odvarko (Mozilla Corp.)
+ * Max Stepanov (Aptana Inc.)
+ * Rob Campbell (Mozilla Corp.)
+ * Hans Hillen (Paciello Group, Mozilla)
+ * Curtis Bartley (Mozilla Corp.)
+ * Mike Collins (IBM Almaden)
+ * Kevin Decker
+ * Mike Ratcliffe (Comartis AG)
+ * Hernan Rodríguez Colmeiro
+ * Austin Andrews
+ * Christoph Dorn
+ * Steven Roussey (AppCenter Inc, Network54)
+ * Mihai Sucan (Mozilla Corp.)
+ */
+
+"use strict";
+
+const {components, Cc, Ci} = require("chrome");
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const Services = require("Services");
+
+// The cache used in the `nsIURL` function.
+const gNSURLStore = new Map();
+
+/**
+ * Helper object for networking stuff.
+ *
+ * Most of the following functions have been taken from the Firebug source. They
+ * have been modified to match the Firefox coding rules.
+ */
+var NetworkHelper = {
+ /**
+ * Converts text with a given charset to unicode.
+ *
+ * @param string text
+ * Text to convert.
+ * @param string charset
+ * Charset to convert the text to.
+ * @returns string
+ * Converted text.
+ */
+ convertToUnicode: function (text, charset) {
+ let conv = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
+ .createInstance(Ci.nsIScriptableUnicodeConverter);
+ try {
+ conv.charset = charset || "UTF-8";
+ return conv.ConvertToUnicode(text);
+ } catch (ex) {
+ return text;
+ }
+ },
+
+ /**
+ * Reads all available bytes from stream and converts them to charset.
+ *
+ * @param nsIInputStream stream
+ * @param string charset
+ * @returns string
+ * UTF-16 encoded string based on the content of stream and charset.
+ */
+ readAndConvertFromStream: function (stream, charset) {
+ let text = null;
+ try {
+ text = NetUtil.readInputStreamToString(stream, stream.available());
+ return this.convertToUnicode(text, charset);
+ } catch (err) {
+ return text;
+ }
+ },
+
+ /**
+ * Reads the posted text from request.
+ *
+ * @param nsIHttpChannel request
+ * @param string charset
+ * The content document charset, used when reading the POSTed data.
+ * @returns string or null
+ * Returns the posted string if it was possible to read from request
+ * otherwise null.
+ */
+ readPostTextFromRequest: function (request, charset) {
+ if (request instanceof Ci.nsIUploadChannel) {
+ let iStream = request.uploadStream;
+
+ let isSeekableStream = false;
+ if (iStream instanceof Ci.nsISeekableStream) {
+ isSeekableStream = true;
+ }
+
+ let prevOffset;
+ if (isSeekableStream) {
+ prevOffset = iStream.tell();
+ iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ }
+
+ // Read data from the stream.
+ let text = this.readAndConvertFromStream(iStream, charset);
+
+ // Seek locks the file, so seek to the beginning only if necko hasn't
+ // read it yet, since necko doesn't seek to 0 before reading (at lest
+ // not till 459384 is fixed).
+ if (isSeekableStream && prevOffset == 0) {
+ iStream.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+ }
+ return text;
+ }
+ return null;
+ },
+
+ /**
+ * Reads the posted text from the page's cache.
+ *
+ * @param nsIDocShell docShell
+ * @param string charset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * docShell otherwise null.
+ */
+ readPostTextFromPage: function (docShell, charset) {
+ let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
+ return this.readPostTextFromPageViaWebNav(webNav, charset);
+ },
+
+ /**
+ * Reads the posted text from the page's cache, given an nsIWebNavigation
+ * object.
+ *
+ * @param nsIWebNavigation webNav
+ * @param string charset
+ * @returns string or null
+ * Returns the posted string if it was possible to read from
+ * webNav, otherwise null.
+ */
+ readPostTextFromPageViaWebNav: function (webNav, charset) {
+ if (webNav instanceof Ci.nsIWebPageDescriptor) {
+ let descriptor = webNav.currentDescriptor;
+
+ if (descriptor instanceof Ci.nsISHEntry && descriptor.postData &&
+ descriptor instanceof Ci.nsISeekableStream) {
+ descriptor.seek(Ci.nsISeekableStream.NS_SEEK_SET, 0);
+
+ return this.readAndConvertFromStream(descriptor, charset);
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Gets the web appId that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns number|null
+ * The appId for the given request, if available.
+ */
+ getAppIdForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).appId;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the topFrameElement that is associated with request. This
+ * works in single-process and multiprocess contexts. It may cross
+ * the content/chrome boundary.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsIDOMElement|null
+ * The top frame element for the given request.
+ */
+ getTopFrameForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).topFrameElement;
+ } catch (ex) {
+ // request loadContent is not always available.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsIDOMWindow that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsIDOMWindow or null
+ */
+ getWindowForRequest: function (request) {
+ try {
+ return this.getRequestLoadContext(request).associatedWindow;
+ } catch (ex) {
+ // TODO: bug 802246 - getWindowForRequest() throws on b2g: there is no
+ // associatedWindow property.
+ }
+ return null;
+ },
+
+ /**
+ * Gets the nsILoadContext that is associated with request.
+ *
+ * @param nsIHttpChannel request
+ * @returns nsILoadContext or null
+ */
+ getRequestLoadContext: function (request) {
+ try {
+ return request.notificationCallbacks.getInterface(Ci.nsILoadContext);
+ } catch (ex) {
+ // Ignore.
+ }
+
+ try {
+ return request.loadGroup.notificationCallbacks
+ .getInterface(Ci.nsILoadContext);
+ } catch (ex) {
+ // Ignore.
+ }
+
+ return null;
+ },
+
+ /**
+ * Determines whether the request has been made for the top level document.
+ *
+ * @param nsIHttpChannel request
+ * @returns Boolean True if the request represents the top level document.
+ */
+ isTopLevelLoad: function (request) {
+ if (request instanceof Ci.nsIChannel) {
+ let loadInfo = request.loadInfo;
+ if (loadInfo && loadInfo.isTopLevelLoad) {
+ return (request.loadFlags & Ci.nsIChannel.LOAD_DOCUMENT_URI);
+ }
+ }
+
+ return false;
+ },
+
+ /**
+ * Loads the content of url from the cache.
+ *
+ * @param string url
+ * URL to load the cached content for.
+ * @param string charset
+ * Assumed charset of the cached content. Used if there is no charset
+ * on the channel directly.
+ * @param function callback
+ * Callback that is called with the loaded cached content if available
+ * or null if something failed while getting the cached content.
+ */
+ loadFromCache: function (url, charset, callback) {
+ let channel = NetUtil.newChannel({uri: url,
+ loadUsingSystemPrincipal: true});
+
+ // Ensure that we only read from the cache and not the server.
+ channel.loadFlags = Ci.nsIRequest.LOAD_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_ONLY_FROM_CACHE |
+ Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
+
+ NetUtil.asyncFetch(
+ channel,
+ (inputStream, statusCode, request) => {
+ if (!components.isSuccessCode(statusCode)) {
+ callback(null);
+ return;
+ }
+
+ // Try to get the encoding from the channel. If there is none, then use
+ // the passed assumed charset.
+ let requestChannel = request.QueryInterface(Ci.nsIChannel);
+ let contentCharset = requestChannel.contentCharset || charset;
+
+ // Read the content of the stream using contentCharset as encoding.
+ callback(this.readAndConvertFromStream(inputStream, contentCharset));
+ });
+ },
+
+ /**
+ * Parse a raw Cookie header value.
+ *
+ * @param string header
+ * The raw Cookie header value.
+ * @return array
+ * Array holding an object for each cookie. Each object holds the
+ * following properties: name and value.
+ */
+ parseCookieHeader: function (header) {
+ let cookies = header.split(";");
+ let result = [];
+
+ cookies.forEach(function (cookie) {
+ let equal = cookie.indexOf("=");
+ let name = cookie.substr(0, equal);
+ let value = cookie.substr(equal + 1);
+ result.push({name: unescape(name.trim()),
+ value: unescape(value.trim())});
+ });
+
+ return result;
+ },
+
+ /**
+ * Parse a raw Set-Cookie header value.
+ *
+ * @param string header
+ * The raw Set-Cookie header value.
+ * @return array
+ * Array holding an object for each cookie. Each object holds the
+ * following properties: name, value, secure (boolean), httpOnly
+ * (boolean), path, domain and expires (ISO date string).
+ */
+ parseSetCookieHeader: function (header) {
+ let rawCookies = header.split(/\r\n|\n|\r/);
+ let cookies = [];
+
+ rawCookies.forEach(function (cookie) {
+ let equal = cookie.indexOf("=");
+ let name = unescape(cookie.substr(0, equal).trim());
+ let parts = cookie.substr(equal + 1).split(";");
+ let value = unescape(parts.shift().trim());
+
+ cookie = {name: name, value: value};
+
+ parts.forEach(function (part) {
+ part = part.trim();
+ if (part.toLowerCase() == "secure") {
+ cookie.secure = true;
+ } else if (part.toLowerCase() == "httponly") {
+ cookie.httpOnly = true;
+ } else if (part.indexOf("=") > -1) {
+ let pair = part.split("=");
+ pair[0] = pair[0].toLowerCase();
+ if (pair[0] == "path" || pair[0] == "domain") {
+ cookie[pair[0]] = pair[1];
+ } else if (pair[0] == "expires") {
+ try {
+ pair[1] = pair[1].replace(/-/g, " ");
+ cookie.expires = new Date(pair[1]).toISOString();
+ } catch (ex) {
+ // Ignore.
+ }
+ }
+ }
+ });
+
+ cookies.push(cookie);
+ });
+
+ return cookies;
+ },
+
+ // This is a list of all the mime category maps jviereck could find in the
+ // firebug code base.
+ mimeCategoryMap: {
+ "text/plain": "txt",
+ "text/html": "html",
+ "text/xml": "xml",
+ "text/xsl": "txt",
+ "text/xul": "txt",
+ "text/css": "css",
+ "text/sgml": "txt",
+ "text/rtf": "txt",
+ "text/x-setext": "txt",
+ "text/richtext": "txt",
+ "text/javascript": "js",
+ "text/jscript": "txt",
+ "text/tab-separated-values": "txt",
+ "text/rdf": "txt",
+ "text/xif": "txt",
+ "text/ecmascript": "js",
+ "text/vnd.curl": "txt",
+ "text/x-json": "json",
+ "text/x-js": "txt",
+ "text/js": "txt",
+ "text/vbscript": "txt",
+ "view-source": "txt",
+ "view-fragment": "txt",
+ "application/xml": "xml",
+ "application/xhtml+xml": "xml",
+ "application/atom+xml": "xml",
+ "application/rss+xml": "xml",
+ "application/vnd.mozilla.maybe.feed": "xml",
+ "application/vnd.mozilla.xul+xml": "xml",
+ "application/javascript": "js",
+ "application/x-javascript": "js",
+ "application/x-httpd-php": "txt",
+ "application/rdf+xml": "xml",
+ "application/ecmascript": "js",
+ "application/http-index-format": "txt",
+ "application/json": "json",
+ "application/x-js": "txt",
+ "application/x-mpegurl": "txt",
+ "application/vnd.apple.mpegurl": "txt",
+ "multipart/mixed": "txt",
+ "multipart/x-mixed-replace": "txt",
+ "image/svg+xml": "svg",
+ "application/octet-stream": "bin",
+ "image/jpeg": "image",
+ "image/jpg": "image",
+ "image/gif": "image",
+ "image/png": "image",
+ "image/bmp": "image",
+ "application/x-shockwave-flash": "flash",
+ "video/x-flv": "flash",
+ "audio/mpeg3": "media",
+ "audio/x-mpeg-3": "media",
+ "video/mpeg": "media",
+ "video/x-mpeg": "media",
+ "video/vnd.mpeg.dash.mpd": "xml",
+ "audio/ogg": "media",
+ "application/ogg": "media",
+ "application/x-ogg": "media",
+ "application/x-midi": "media",
+ "audio/midi": "media",
+ "audio/x-mid": "media",
+ "audio/x-midi": "media",
+ "music/crescendo": "media",
+ "audio/wav": "media",
+ "audio/x-wav": "media",
+ "text/json": "json",
+ "application/x-json": "json",
+ "application/json-rpc": "json",
+ "application/x-web-app-manifest+json": "json",
+ "application/manifest+json": "json"
+ },
+
+ /**
+ * Check if the given MIME type is a text-only MIME type.
+ *
+ * @param string mimeType
+ * @return boolean
+ */
+ isTextMimeType: function (mimeType) {
+ if (mimeType.indexOf("text/") == 0) {
+ return true;
+ }
+
+ // XML and JSON often come with custom MIME types, so in addition to the
+ // standard "application/xml" and "application/json", we also look for
+ // variants like "application/x-bigcorp+xml". For JSON we allow "+json" and
+ // "-json" as suffixes.
+ if (/^application\/\w+(?:[\.-]\w+)*(?:\+xml|[-+]json)$/.test(mimeType)) {
+ return true;
+ }
+
+ let category = this.mimeCategoryMap[mimeType] || null;
+ switch (category) {
+ case "txt":
+ case "js":
+ case "json":
+ case "css":
+ case "html":
+ case "svg":
+ case "xml":
+ return true;
+
+ default:
+ return false;
+ }
+ },
+
+ /**
+ * Takes a securityInfo object of nsIRequest, the nsIRequest itself and
+ * extracts security information from them.
+ *
+ * @param object securityInfo
+ * The securityInfo object of a request. If null channel is assumed
+ * to be insecure.
+ * @param object httpActivity
+ * The httpActivity object for the request with at least members
+ * { private, hostname }.
+ *
+ * @return object
+ * Returns an object containing following members:
+ * - state: The security of the connection used to fetch this
+ * request. Has one of following string values:
+ * * "insecure": the connection was not secure (only http)
+ * * "weak": the connection has minor security issues
+ * * "broken": secure connection failed (e.g. expired cert)
+ * * "secure": the connection was properly secured.
+ * If state == broken:
+ * - errorMessage: full error message from
+ * nsITransportSecurityInfo.
+ * If state == secure:
+ * - protocolVersion: one of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3.
+ * - cipherSuite: the cipher suite used in this connection.
+ * - cert: information about certificate used in this connection.
+ * See parseCertificateInfo for the contents.
+ * - hsts: true if host uses Strict Transport Security,
+ * false otherwise
+ * - hpkp: true if host uses Public Key Pinning, false otherwise
+ * If state == weak: Same as state == secure and
+ * - weaknessReasons: list of reasons that cause the request to be
+ * considered weak. See getReasonsForWeakness.
+ */
+ parseSecurityInfo: function (securityInfo, httpActivity) {
+ const info = {
+ state: "insecure",
+ };
+
+ // The request did not contain any security info.
+ if (!securityInfo) {
+ return info;
+ }
+
+ /**
+ * Different scenarios to consider here and how they are handled:
+ * - request is HTTP, the connection is not secure
+ * => securityInfo is null
+ * => state === "insecure"
+ *
+ * - request is HTTPS, the connection is secure
+ * => .securityState has STATE_IS_SECURE flag
+ * => state === "secure"
+ *
+ * - request is HTTPS, the connection has security issues
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is an NSS error code.
+ * => state === "broken"
+ *
+ * - request is HTTPS, the connection was terminated before the security
+ * could be validated
+ * => .securityState has STATE_IS_INSECURE flag
+ * => .errorCode is NOT an NSS error code.
+ * => .errorMessage is not available.
+ * => state === "insecure"
+ *
+ * - request is HTTPS but it uses a weak cipher or old protocol, see
+ * http://hg.mozilla.org/mozilla-central/annotate/def6ed9d1c1a/
+ * security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ * - request is mixed content (which makes no sense whatsoever)
+ * => .securityState has STATE_IS_BROKEN flag
+ * => .errorCode is NOT an NSS error code
+ * => .errorMessage is not available
+ * => state === "weak"
+ */
+
+ securityInfo.QueryInterface(Ci.nsITransportSecurityInfo);
+ securityInfo.QueryInterface(Ci.nsISSLStatusProvider);
+
+ const wpl = Ci.nsIWebProgressListener;
+ const NSSErrorsService = Cc["@mozilla.org/nss_errors_service;1"]
+ .getService(Ci.nsINSSErrorsService);
+ const SSLStatus = securityInfo.SSLStatus;
+ if (!NSSErrorsService.isNSSErrorCode(securityInfo.errorCode)) {
+ const state = securityInfo.securityState;
+
+ let uri = null;
+ if (httpActivity.channel && httpActivity.channel.URI) {
+ uri = httpActivity.channel.URI;
+ }
+ if (uri && !uri.schemeIs("https") && !uri.schemeIs("wss")) {
+ // it is not enough to look at the transport security info -
+ // schemes other than https and wss are subject to
+ // downgrade/etc at the scheme level and should always be
+ // considered insecure
+ info.state = "insecure";
+ } else if (state & wpl.STATE_IS_SECURE) {
+ // The connection is secure if the scheme is sufficient
+ info.state = "secure";
+ } else if (state & wpl.STATE_IS_BROKEN) {
+ // The connection is not secure, there was no error but there's some
+ // minor security issues.
+ info.state = "weak";
+ info.weaknessReasons = this.getReasonsForWeakness(state);
+ } else if (state & wpl.STATE_IS_INSECURE) {
+ // This was most likely an https request that was aborted before
+ // validation. Return info as info.state = insecure.
+ return info;
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo",
+ "Security state " + state + " has no known STATE_IS_* flags.");
+ return info;
+ }
+
+ // Cipher suite.
+ info.cipherSuite = SSLStatus.cipherName;
+
+ // Protocol version.
+ info.protocolVersion =
+ this.formatSecurityProtocol(SSLStatus.protocolVersion);
+
+ // Certificate.
+ info.cert = this.parseCertificateInfo(SSLStatus.serverCert);
+
+ // HSTS and HPKP if available.
+ if (httpActivity.hostname) {
+ const sss = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+
+ // SiteSecurityService uses different storage if the channel is
+ // private. Thus we must give isSecureHost correct flags or we
+ // might get incorrect results.
+ let flags = (httpActivity.private) ?
+ Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
+
+ let host = httpActivity.hostname;
+
+ info.hsts = sss.isSecureHost(sss.HEADER_HSTS, host, flags);
+ info.hpkp = sss.isSecureHost(sss.HEADER_HPKP, host, flags);
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseSecurityInfo",
+ "Could not get HSTS/HPKP status as hostname is not available.");
+ info.hsts = false;
+ info.hpkp = false;
+ }
+ } else {
+ // The connection failed.
+ info.state = "broken";
+ info.errorMessage = securityInfo.errorMessage;
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes an nsIX509Cert and returns an object with certificate information.
+ *
+ * @param nsIX509Cert cert
+ * The certificate to extract the information from.
+ * @return object
+ * An object with following format:
+ * {
+ * subject: { commonName, organization, organizationalUnit },
+ * issuer: { commonName, organization, organizationUnit },
+ * validity: { start, end },
+ * fingerprint: { sha1, sha256 }
+ * }
+ */
+ parseCertificateInfo: function (cert) {
+ let info = {};
+ if (cert) {
+ info.subject = {
+ commonName: cert.commonName,
+ organization: cert.organization,
+ organizationalUnit: cert.organizationalUnit,
+ };
+
+ info.issuer = {
+ commonName: cert.issuerCommonName,
+ organization: cert.issuerOrganization,
+ organizationUnit: cert.issuerOrganizationUnit,
+ };
+
+ info.validity = {
+ start: cert.validity.notBeforeLocalDay,
+ end: cert.validity.notAfterLocalDay,
+ };
+
+ info.fingerprint = {
+ sha1: cert.sha1Fingerprint,
+ sha256: cert.sha256Fingerprint,
+ };
+ } else {
+ DevToolsUtils.reportException("NetworkHelper.parseCertificateInfo",
+ "Secure connection established without certificate.");
+ }
+
+ return info;
+ },
+
+ /**
+ * Takes protocolVersion of SSLStatus object and returns human readable
+ * description.
+ *
+ * @param Number version
+ * One of nsISSLStatus version constants.
+ * @return string
+ * One of TLSv1, TLSv1.1, TLSv1.2, TLSv1.3 if @param version
+ * is valid, Unknown otherwise.
+ */
+ formatSecurityProtocol: function (version) {
+ switch (version) {
+ case Ci.nsISSLStatus.TLS_VERSION_1:
+ return "TLSv1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_1:
+ return "TLSv1.1";
+ case Ci.nsISSLStatus.TLS_VERSION_1_2:
+ return "TLSv1.2";
+ case Ci.nsISSLStatus.TLS_VERSION_1_3:
+ return "TLSv1.3";
+ default:
+ DevToolsUtils.reportException("NetworkHelper.formatSecurityProtocol",
+ "protocolVersion " + version + " is unknown.");
+ return "Unknown";
+ }
+ },
+
+ /**
+ * Takes the securityState bitfield and returns reasons for weak connection
+ * as an array of strings.
+ *
+ * @param Number state
+ * nsITransportSecurityInfo.securityState.
+ *
+ * @return Array[String]
+ * List of weakness reasons. A subset of { cipher } where
+ * * cipher: The cipher suite is consireded to be weak (RC4).
+ */
+ getReasonsForWeakness: function (state) {
+ const wpl = Ci.nsIWebProgressListener;
+
+ // If there's non-fatal security issues the request has STATE_IS_BROKEN
+ // flag set. See http://hg.mozilla.org/mozilla-central/file/44344099d119
+ // /security/manager/ssl/nsNSSCallbacks.cpp#l1233
+ let reasons = [];
+
+ if (state & wpl.STATE_IS_BROKEN) {
+ let isCipher = state & wpl.STATE_USES_WEAK_CRYPTO;
+
+ if (isCipher) {
+ reasons.push("cipher");
+ }
+
+ if (!isCipher) {
+ DevToolsUtils.reportException("NetworkHelper.getReasonsForWeakness",
+ "STATE_IS_BROKEN without a known reason. Full state was: " + state);
+ }
+ }
+
+ return reasons;
+ },
+
+ /**
+ * Parse a url's query string into its components
+ *
+ * @param string queryString
+ * The query part of a url
+ * @return array
+ * Array of query params {name, value}
+ */
+ parseQueryString: function (queryString) {
+ // Make sure there's at least one param available.
+ // Be careful here, params don't necessarily need to have values, so
+ // no need to verify the existence of a "=".
+ if (!queryString) {
+ return null;
+ }
+
+ // Turn the params string into an array containing { name: value } tuples.
+ let paramsArray = queryString.replace(/^[?&]/, "").split("&").map(e => {
+ let param = e.split("=");
+ return {
+ name: param[0] ?
+ NetworkHelper.convertToUnicode(unescape(param[0])) : "",
+ value: param[1] ?
+ NetworkHelper.convertToUnicode(unescape(param[1])) : ""
+ };
+ });
+
+ return paramsArray;
+ },
+
+ /**
+ * Helper for getting an nsIURL instance out of a string.
+ */
+ nsIURL: function (url, store = gNSURLStore) {
+ if (store.has(url)) {
+ return store.get(url);
+ }
+
+ let uri = Services.io.newURI(url, null, null).QueryInterface(Ci.nsIURL);
+ store.set(url, uri);
+ return uri;
+ }
+};
+
+for (let prop of Object.getOwnPropertyNames(NetworkHelper)) {
+ exports[prop] = NetworkHelper[prop];
+}
diff --git a/devtools/shared/webconsole/network-monitor.js b/devtools/shared/webconsole/network-monitor.js
new file mode 100644
index 000000000..084493432
--- /dev/null
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -0,0 +1,2044 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Cc, Ci, Cm, Cu, Cr, components} = require("chrome");
+const Services = require("Services");
+const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
+
+loader.lazyRequireGetter(this, "NetworkHelper",
+ "devtools/shared/webconsole/network-helper");
+loader.lazyRequireGetter(this, "DevToolsUtils",
+ "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "flags",
+ "devtools/shared/flags");
+loader.lazyRequireGetter(this, "DebuggerServer",
+ "devtools/server/main", true);
+loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
+loader.lazyServiceGetter(this, "gActivityDistributor",
+ "@mozilla.org/network/http-activity-distributor;1",
+ "nsIHttpActivityDistributor");
+const {NetworkThrottleManager} = require("devtools/shared/webconsole/throttle");
+
+// Network logging
+
+// The maximum uint32 value.
+const PR_UINT32_MAX = 4294967295;
+
+// HTTP status codes.
+const HTTP_MOVED_PERMANENTLY = 301;
+const HTTP_FOUND = 302;
+const HTTP_SEE_OTHER = 303;
+const HTTP_TEMPORARY_REDIRECT = 307;
+
+// The maximum number of bytes a NetworkResponseListener can hold: 1 MB
+const RESPONSE_BODY_LIMIT = 1048576;
+// Exported for testing.
+exports.RESPONSE_BODY_LIMIT = RESPONSE_BODY_LIMIT;
+
+/**
+ * Check if a given network request should be logged by a network monitor
+ * based on the specified filters.
+ *
+ * @param nsIHttpChannel channel
+ * Request to check.
+ * @param filters
+ * NetworkMonitor filters to match against.
+ * @return boolean
+ * True if the network request should be logged, false otherwise.
+ */
+function matchRequest(channel, filters) {
+ // Log everything if no filter is specified
+ if (!filters.outerWindowID && !filters.window && !filters.appId) {
+ return true;
+ }
+
+ // Ignore requests from chrome or add-on code when we are monitoring
+ // content.
+ // TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs
+ // the flags.testing check. We will move to a better way to serve
+ // its needs in bug 1167188, where this check should be removed.
+ if (!flags.testing && channel.loadInfo &&
+ channel.loadInfo.loadingDocument === null &&
+ channel.loadInfo.loadingPrincipal ===
+ Services.scriptSecurityManager.getSystemPrincipal()) {
+ return false;
+ }
+
+ if (filters.window) {
+ // Since frames support, this.window may not be the top level content
+ // frame, so that we can't only compare with win.top.
+ let win = NetworkHelper.getWindowForRequest(channel);
+ while (win) {
+ if (win == filters.window) {
+ return true;
+ }
+ if (win.parent == win) {
+ break;
+ }
+ win = win.parent;
+ }
+ }
+
+ if (filters.outerWindowID) {
+ let topFrame = NetworkHelper.getTopFrameForRequest(channel);
+ if (topFrame && topFrame.outerWindowID &&
+ topFrame.outerWindowID == filters.outerWindowID) {
+ return true;
+ }
+ }
+
+ if (filters.appId) {
+ let appId = NetworkHelper.getAppIdForRequest(channel);
+ if (appId && appId == filters.appId) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+/**
+ * This is a nsIChannelEventSink implementation that monitors channel redirects and
+ * informs the registered StackTraceCollector about the old and new channels.
+ */
+const SINK_CLASS_DESCRIPTION = "NetworkMonitor Channel Event Sink";
+const SINK_CLASS_ID = components.ID("{e89fa076-c845-48a8-8c45-2604729eba1d}");
+const SINK_CONTRACT_ID = "@mozilla.org/network/monitor/channeleventsink;1";
+const SINK_CATEGORY_NAME = "net-channel-event-sinks";
+
+function ChannelEventSink() {
+ this.wrappedJSObject = this;
+ this.collectors = new Set();
+}
+
+ChannelEventSink.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIChannelEventSink]),
+
+ registerCollector(collector) {
+ this.collectors.add(collector);
+ },
+
+ unregisterCollector(collector) {
+ this.collectors.delete(collector);
+
+ if (this.collectors.size == 0) {
+ ChannelEventSinkFactory.unregister();
+ }
+ },
+
+ asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
+ for (let collector of this.collectors) {
+ try {
+ collector.onChannelRedirect(oldChannel, newChannel, flags);
+ } catch (ex) {
+ console.error("StackTraceCollector.onChannelRedirect threw an exception", ex);
+ }
+ }
+ callback.onRedirectVerifyCallback(Cr.NS_OK);
+ }
+};
+
+const ChannelEventSinkFactory = XPCOMUtils.generateSingletonFactory(ChannelEventSink);
+
+ChannelEventSinkFactory.register = function () {
+ const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ if (registrar.isCIDRegistered(SINK_CLASS_ID)) {
+ return;
+ }
+
+ registrar.registerFactory(SINK_CLASS_ID,
+ SINK_CLASS_DESCRIPTION,
+ SINK_CONTRACT_ID,
+ ChannelEventSinkFactory);
+
+ XPCOMUtils.categoryManager.addCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
+ SINK_CONTRACT_ID, false, true);
+};
+
+ChannelEventSinkFactory.unregister = function () {
+ const registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar);
+ registrar.unregisterFactory(SINK_CLASS_ID, ChannelEventSinkFactory);
+
+ XPCOMUtils.categoryManager.deleteCategoryEntry(SINK_CATEGORY_NAME, SINK_CONTRACT_ID,
+ false);
+};
+
+ChannelEventSinkFactory.getService = function () {
+ // Make sure the ChannelEventSink service is registered before accessing it
+ ChannelEventSinkFactory.register();
+
+ return Cc[SINK_CONTRACT_ID].getService(Ci.nsIChannelEventSink).wrappedJSObject;
+};
+
+function StackTraceCollector(filters) {
+ this.filters = filters;
+ this.stacktracesById = new Map();
+}
+
+StackTraceCollector.prototype = {
+ init() {
+ Services.obs.addObserver(this, "http-on-opening-request", false);
+ ChannelEventSinkFactory.getService().registerCollector(this);
+ },
+
+ destroy() {
+ Services.obs.removeObserver(this, "http-on-opening-request");
+ ChannelEventSinkFactory.getService().unregisterCollector(this);
+ },
+
+ _saveStackTrace(channel, stacktrace) {
+ this.stacktracesById.set(channel.channelId, stacktrace);
+ },
+
+ observe(subject) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ if (!matchRequest(channel, this.filters)) {
+ return;
+ }
+
+ // Convert the nsIStackFrame XPCOM objects to a nice JSON that can be
+ // passed around through message managers etc.
+ let frame = components.stack;
+ let stacktrace = [];
+ if (frame && frame.caller) {
+ frame = frame.caller;
+ while (frame) {
+ stacktrace.push({
+ filename: frame.filename,
+ lineNumber: frame.lineNumber,
+ columnNumber: frame.columnNumber,
+ functionName: frame.name,
+ asyncCause: frame.asyncCause,
+ });
+ frame = frame.caller || frame.asyncCaller;
+ }
+ }
+
+ this._saveStackTrace(channel, stacktrace);
+ },
+
+ onChannelRedirect(oldChannel, newChannel, flags) {
+ // We can be called with any nsIChannel, but are interested only in HTTP channels
+ try {
+ oldChannel.QueryInterface(Ci.nsIHttpChannel);
+ newChannel.QueryInterface(Ci.nsIHttpChannel);
+ } catch (ex) {
+ return;
+ }
+
+ let oldId = oldChannel.channelId;
+ let stacktrace = this.stacktracesById.get(oldId);
+ if (stacktrace) {
+ this.stacktracesById.delete(oldId);
+ this._saveStackTrace(newChannel, stacktrace);
+ }
+ },
+
+ getStackTrace(channelId) {
+ let trace = this.stacktracesById.get(channelId);
+ this.stacktracesById.delete(channelId);
+ return trace;
+ }
+};
+
+exports.StackTraceCollector = StackTraceCollector;
+
+/**
+ * The network response listener implements the nsIStreamListener and
+ * nsIRequestObserver interfaces. This is used within the NetworkMonitor feature
+ * to get the response body of the request.
+ *
+ * The code is mostly based on code listings from:
+ *
+ * http://www.softwareishard.com/blog/firebug/
+ * nsitraceablechannel-intercept-http-traffic/
+ *
+ * @constructor
+ * @param object owner
+ * The response listener owner. This object needs to hold the
+ * |openResponses| object.
+ * @param object httpActivity
+ * HttpActivity object associated with this request. See NetworkMonitor
+ * for more information.
+ */
+function NetworkResponseListener(owner, httpActivity) {
+ this.owner = owner;
+ this.receivedData = "";
+ this.httpActivity = httpActivity;
+ this.bodySize = 0;
+ // Note that this is really only needed for the non-e10s case.
+ // See bug 1309523.
+ let channel = this.httpActivity.channel;
+ this._wrappedNotificationCallbacks = channel.notificationCallbacks;
+ channel.notificationCallbacks = this;
+}
+
+NetworkResponseListener.prototype = {
+ QueryInterface:
+ XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInputStreamCallback,
+ Ci.nsIRequestObserver, Ci.nsIInterfaceRequestor,
+ Ci.nsISupports]),
+
+ // nsIInterfaceRequestor implementation
+
+ /**
+ * This object implements nsIProgressEventSink, but also needs to forward
+ * interface requests to the notification callbacks of other objects.
+ */
+ getInterface(iid) {
+ if (iid.equals(Ci.nsIProgressEventSink)) {
+ return this;
+ }
+ if (this._wrappedNotificationCallbacks) {
+ return this._wrappedNotificationCallbacks.getInterface(iid);
+ }
+ throw Cr.NS_ERROR_NO_INTERFACE;
+ },
+
+ /**
+ * Forward notifications for interfaces this object implements, in case other
+ * objects also implemented them.
+ */
+ _forwardNotification(iid, method, args) {
+ if (!this._wrappedNotificationCallbacks) {
+ return;
+ }
+ try {
+ let impl = this._wrappedNotificationCallbacks.getInterface(iid);
+ impl[method].apply(impl, args);
+ } catch (e) {
+ if (e.result != Cr.NS_ERROR_NO_INTERFACE) {
+ throw e;
+ }
+ }
+ },
+
+ /**
+ * This NetworkResponseListener tracks the NetworkMonitor.openResponses object
+ * to find the associated uncached headers.
+ * @private
+ */
+ _foundOpenResponse: false,
+
+ /**
+ * If the channel already had notificationCallbacks, hold them here internally
+ * so that we can forward getInterface requests to that object.
+ */
+ _wrappedNotificationCallbacks: null,
+
+ /**
+ * The response listener owner.
+ */
+ owner: null,
+
+ /**
+ * The response will be written into the outputStream of this nsIPipe.
+ * Both ends of the pipe must be blocking.
+ */
+ sink: null,
+
+ /**
+ * The HttpActivity object associated with this response.
+ */
+ httpActivity: null,
+
+ /**
+ * Stores the received data as a string.
+ */
+ receivedData: null,
+
+ /**
+ * The uncompressed, decoded response body size.
+ */
+ bodySize: null,
+
+ /**
+ * Response body size on the wire, potentially compressed / encoded.
+ */
+ transferredSize: null,
+
+ /**
+ * The nsIRequest we are started for.
+ */
+ request: null,
+
+ /**
+ * Set the async listener for the given nsIAsyncInputStream. This allows us to
+ * wait asynchronously for any data coming from the stream.
+ *
+ * @param nsIAsyncInputStream stream
+ * The input stream from where we are waiting for data to come in.
+ * @param nsIInputStreamCallback listener
+ * The input stream callback you want. This is an object that must have
+ * the onInputStreamReady() method. If the argument is null, then the
+ * current callback is removed.
+ * @return void
+ */
+ setAsyncListener: function (stream, listener) {
+ // Asynchronously wait for the stream to be readable or closed.
+ stream.asyncWait(listener, 0, 0, Services.tm.mainThread);
+ },
+
+ /**
+ * Stores the received data, if request/response body logging is enabled. It
+ * also does limit the number of stored bytes, based on the
+ * RESPONSE_BODY_LIMIT constant.
+ *
+ * Learn more about nsIStreamListener at:
+ * https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIStreamListener
+ *
+ * @param nsIRequest request
+ * @param nsISupports context
+ * @param nsIInputStream inputStream
+ * @param unsigned long offset
+ * @param unsigned long count
+ */
+ onDataAvailable: function (request, context, inputStream, offset, count) {
+ this._findOpenResponse();
+ let data = NetUtil.readInputStreamToString(inputStream, count);
+
+ this.bodySize += count;
+
+ if (!this.httpActivity.discardResponseBody &&
+ this.receivedData.length < RESPONSE_BODY_LIMIT) {
+ this.receivedData +=
+ NetworkHelper.convertToUnicode(data, request.contentCharset);
+ }
+ },
+
+ /**
+ * See documentation at
+ * https://developer.mozilla.org/En/NsIRequestObserver
+ *
+ * @param nsIRequest request
+ * @param nsISupports context
+ */
+ onStartRequest: function (request) {
+ // Converter will call this again, we should just ignore that.
+ if (this.request) {
+ return;
+ }
+
+ this.request = request;
+ this._getSecurityInfo();
+ this._findOpenResponse();
+ // We need to track the offset for the onDataAvailable calls where
+ // we pass the data from our pipe to the converter.
+ this.offset = 0;
+
+ // In the multi-process mode, the conversion happens on the child
+ // side while we can only monitor the channel on the parent
+ // side. If the content is gzipped, we have to unzip it
+ // ourself. For that we use the stream converter services. Do not
+ // do that for Service workers as they are run in the child
+ // process.
+ let channel = this.request;
+ if (!this.httpActivity.fromServiceWorker &&
+ channel instanceof Ci.nsIEncodedChannel &&
+ channel.contentEncodings &&
+ !channel.applyConversion) {
+ let encodingHeader = channel.getResponseHeader("Content-Encoding");
+ let scs = Cc["@mozilla.org/streamConverters;1"]
+ .getService(Ci.nsIStreamConverterService);
+ let encodings = encodingHeader.split(/\s*\t*,\s*\t*/);
+ let nextListener = this;
+ let acceptedEncodings = ["gzip", "deflate", "br", "x-gzip", "x-deflate"];
+ for (let i in encodings) {
+ // There can be multiple conversions applied
+ let enc = encodings[i].toLowerCase();
+ if (acceptedEncodings.indexOf(enc) > -1) {
+ this.converter = scs.asyncConvertData(enc, "uncompressed",
+ nextListener, null);
+ nextListener = this.converter;
+ }
+ }
+ if (this.converter) {
+ this.converter.onStartRequest(this.request, null);
+ }
+ }
+ // Asynchronously wait for the data coming from the request.
+ this.setAsyncListener(this.sink.inputStream, this);
+ },
+
+ /**
+ * Parse security state of this request and report it to the client.
+ */
+ _getSecurityInfo: DevToolsUtils.makeInfallible(function () {
+ // Many properties of the securityInfo (e.g., the server certificate or HPKP
+ // status) are not available in the content process and can't be even touched safely,
+ // because their C++ getters trigger assertions. This function is called in content
+ // process for synthesized responses from service workers, in the parent otherwise.
+ if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+ return;
+ }
+
+ // Take the security information from the original nsIHTTPChannel instead of
+ // the nsIRequest received in onStartRequest. If response to this request
+ // was a redirect from http to https, the request object seems to contain
+ // security info for the https request after redirect.
+ let secinfo = this.httpActivity.channel.securityInfo;
+ let info = NetworkHelper.parseSecurityInfo(secinfo, this.httpActivity);
+
+ this.httpActivity.owner.addSecurityInfo(info);
+ }),
+
+ /**
+ * Handle the onStopRequest by closing the sink output stream.
+ *
+ * For more documentation about nsIRequestObserver go to:
+ * https://developer.mozilla.org/En/NsIRequestObserver
+ */
+ onStopRequest: function () {
+ this._findOpenResponse();
+ this.sink.outputStream.close();
+ },
+
+ // nsIProgressEventSink implementation
+
+ /**
+ * Handle progress event as data is transferred. This is used to record the
+ * size on the wire, which may be compressed / encoded.
+ */
+ onProgress: function (request, context, progress, progressMax) {
+ this.transferredSize = progress;
+ // Need to forward as well to keep things like Download Manager's progress
+ // bar working properly.
+ this._forwardNotification(Ci.nsIProgressEventSink, "onProgress", arguments);
+ },
+
+ onStatus: function () {
+ this._forwardNotification(Ci.nsIProgressEventSink, "onStatus", arguments);
+ },
+
+ /**
+ * Find the open response object associated to the current request. The
+ * NetworkMonitor._httpResponseExaminer() method saves the response headers in
+ * NetworkMonitor.openResponses. This method takes the data from the open
+ * response object and puts it into the HTTP activity object, then sends it to
+ * the remote Web Console instance.
+ *
+ * @private
+ */
+ _findOpenResponse: function () {
+ if (!this.owner || this._foundOpenResponse) {
+ return;
+ }
+
+ let openResponse = null;
+
+ for (let id in this.owner.openResponses) {
+ let item = this.owner.openResponses[id];
+ if (item.channel === this.httpActivity.channel) {
+ openResponse = item;
+ break;
+ }
+ }
+
+ if (!openResponse) {
+ return;
+ }
+ this._foundOpenResponse = true;
+
+ delete this.owner.openResponses[openResponse.id];
+
+ this.httpActivity.owner.addResponseHeaders(openResponse.headers);
+ this.httpActivity.owner.addResponseCookies(openResponse.cookies);
+ },
+
+ /**
+ * Clean up the response listener once the response input stream is closed.
+ * This is called from onStopRequest() or from onInputStreamReady() when the
+ * stream is closed.
+ * @return void
+ */
+ onStreamClose: function () {
+ if (!this.httpActivity) {
+ return;
+ }
+ // Remove our listener from the request input stream.
+ this.setAsyncListener(this.sink.inputStream, null);
+
+ this._findOpenResponse();
+
+ if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
+ this._onComplete(this.receivedData);
+ } else if (!this.httpActivity.discardResponseBody &&
+ this.httpActivity.responseStatus == 304) {
+ // Response is cached, so we load it from cache.
+ let charset = this.request.contentCharset || this.httpActivity.charset;
+ NetworkHelper.loadFromCache(this.httpActivity.url, charset,
+ this._onComplete.bind(this));
+ } else {
+ this._onComplete();
+ }
+ },
+
+ /**
+ * Handler for when the response completes. This function cleans up the
+ * response listener.
+ *
+ * @param string [data]
+ * Optional, the received data coming from the response listener or
+ * from the cache.
+ */
+ _onComplete: function (data) {
+ let response = {
+ mimeType: "",
+ text: data || "",
+ };
+
+ response.size = this.bodySize;
+ response.transferredSize = this.transferredSize;
+
+ try {
+ response.mimeType = this.request.contentType;
+ } catch (ex) {
+ // Ignore.
+ }
+
+ if (!response.mimeType ||
+ !NetworkHelper.isTextMimeType(response.mimeType)) {
+ response.encoding = "base64";
+ try {
+ response.text = btoa(response.text);
+ } catch (err) {
+ // Ignore.
+ }
+ }
+
+ if (response.mimeType && this.request.contentCharset) {
+ response.mimeType += "; charset=" + this.request.contentCharset;
+ }
+
+ this.receivedData = "";
+
+ this.httpActivity.owner.addResponseContent(
+ response,
+ this.httpActivity.discardResponseBody
+ );
+
+ this._wrappedNotificationCallbacks = null;
+ this.httpActivity = null;
+ this.sink = null;
+ this.inputStream = null;
+ this.converter = null;
+ this.request = null;
+ this.owner = null;
+ },
+
+ /**
+ * The nsIInputStreamCallback for when the request input stream is ready -
+ * either it has more data or it is closed.
+ *
+ * @param nsIAsyncInputStream stream
+ * The sink input stream from which data is coming.
+ * @returns void
+ */
+ onInputStreamReady: function (stream) {
+ if (!(stream instanceof Ci.nsIAsyncInputStream) || !this.httpActivity) {
+ return;
+ }
+
+ let available = -1;
+ try {
+ // This may throw if the stream is closed normally or due to an error.
+ available = stream.available();
+ } catch (ex) {
+ // Ignore.
+ }
+
+ if (available != -1) {
+ if (available != 0) {
+ if (this.converter) {
+ this.converter.onDataAvailable(this.request, null, stream,
+ this.offset, available);
+ } else {
+ this.onDataAvailable(this.request, null, stream, this.offset,
+ available);
+ }
+ }
+ this.offset += available;
+ this.setAsyncListener(stream, this);
+ } else {
+ this.onStreamClose();
+ this.offset = 0;
+ }
+ },
+};
+
+/**
+ * The network monitor uses the nsIHttpActivityDistributor to monitor network
+ * requests. The nsIObserverService is also used for monitoring
+ * http-on-examine-response notifications. All network request information is
+ * routed to the remote Web Console.
+ *
+ * @constructor
+ * @param object filters
+ * Object with the filters to use for network requests:
+ * - window (nsIDOMWindow): filter network requests by the associated
+ * window object.
+ * - appId (number): filter requests by the appId.
+ * - outerWindowID (number): filter requests by their top frame's outerWindowID.
+ * Filters are optional. If any of these filters match the request is
+ * logged (OR is applied). If no filter is provided then all requests are
+ * logged.
+ * @param object owner
+ * The network monitor owner. This object needs to hold:
+ * - onNetworkEvent(requestInfo)
+ * This method is invoked once for every new network request and it is
+ * given the initial network request information as an argument.
+ * onNetworkEvent() must return an object which holds several add*()
+ * methods which are used to add further network request/response information.
+ * - stackTraceCollector
+ * If the owner has this optional property, it will be used as a
+ * StackTraceCollector by the NetworkMonitor.
+ */
+function NetworkMonitor(filters, owner) {
+ this.filters = filters;
+ this.owner = owner;
+ this.openRequests = {};
+ this.openResponses = {};
+ this._httpResponseExaminer =
+ DevToolsUtils.makeInfallible(this._httpResponseExaminer).bind(this);
+ this._httpModifyExaminer =
+ DevToolsUtils.makeInfallible(this._httpModifyExaminer).bind(this);
+ this._serviceWorkerRequest = this._serviceWorkerRequest.bind(this);
+ this._throttleData = null;
+ this._throttler = null;
+}
+
+exports.NetworkMonitor = NetworkMonitor;
+
+NetworkMonitor.prototype = {
+ filters: null,
+
+ httpTransactionCodes: {
+ 0x5001: "REQUEST_HEADER",
+ 0x5002: "REQUEST_BODY_SENT",
+ 0x5003: "RESPONSE_START",
+ 0x5004: "RESPONSE_HEADER",
+ 0x5005: "RESPONSE_COMPLETE",
+ 0x5006: "TRANSACTION_CLOSE",
+
+ 0x804b0003: "STATUS_RESOLVING",
+ 0x804b000b: "STATUS_RESOLVED",
+ 0x804b0007: "STATUS_CONNECTING_TO",
+ 0x804b0004: "STATUS_CONNECTED_TO",
+ 0x804b0005: "STATUS_SENDING_TO",
+ 0x804b000a: "STATUS_WAITING_FOR",
+ 0x804b0006: "STATUS_RECEIVING_FROM"
+ },
+
+ httpDownloadActivities: [
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START,
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER,
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE
+ ],
+
+ // Network response bodies are piped through a buffer of the given size (in
+ // bytes).
+ responsePipeSegmentSize: null,
+
+ owner: null,
+
+ /**
+ * Whether to save the bodies of network requests and responses.
+ * @type boolean
+ */
+ saveRequestAndResponseBodies: true,
+
+ /**
+ * Object that holds the HTTP activity objects for ongoing requests.
+ */
+ openRequests: null,
+
+ /**
+ * Object that holds response headers coming from this._httpResponseExaminer.
+ */
+ openResponses: null,
+
+ /**
+ * The network monitor initializer.
+ */
+ init: function () {
+ this.responsePipeSegmentSize = Services.prefs
+ .getIntPref("network.buffer.cache.size");
+ this.interceptedChannels = new Set();
+
+ if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+ gActivityDistributor.addObserver(this);
+ Services.obs.addObserver(this._httpResponseExaminer,
+ "http-on-examine-response", false);
+ Services.obs.addObserver(this._httpResponseExaminer,
+ "http-on-examine-cached-response", false);
+ Services.obs.addObserver(this._httpModifyExaminer,
+ "http-on-modify-request", false);
+ }
+ // In child processes, only watch for service worker requests
+ // everything else only happens in the parent process
+ Services.obs.addObserver(this._serviceWorkerRequest,
+ "service-worker-synthesized-response", false);
+ },
+
+ get throttleData() {
+ return this._throttleData;
+ },
+
+ set throttleData(value) {
+ this._throttleData = value;
+ // Clear out any existing throttlers
+ this._throttler = null;
+ },
+
+ _getThrottler: function () {
+ if (this.throttleData !== null && this._throttler === null) {
+ this._throttler = new NetworkThrottleManager(this.throttleData);
+ }
+ return this._throttler;
+ },
+
+ _serviceWorkerRequest: function (subject, topic, data) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ if (!matchRequest(channel, this.filters)) {
+ return;
+ }
+
+ this.interceptedChannels.add(subject);
+
+ // On e10s, we never receive http-on-examine-cached-response, so fake one.
+ if (Services.appinfo.processType == Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+ this._httpResponseExaminer(channel, "http-on-examine-cached-response");
+ }
+ },
+
+ /**
+ * Observe notifications for the http-on-examine-response topic, coming from
+ * the nsIObserverService.
+ *
+ * @private
+ * @param nsIHttpChannel subject
+ * @param string topic
+ * @returns void
+ */
+ _httpResponseExaminer: function (subject, topic) {
+ // The httpResponseExaminer is used to retrieve the uncached response
+ // headers. The data retrieved is stored in openResponses. The
+ // NetworkResponseListener is responsible with updating the httpActivity
+ // object with the data from the new object in openResponses.
+
+ if (!this.owner ||
+ (topic != "http-on-examine-response" &&
+ topic != "http-on-examine-cached-response") ||
+ !(subject instanceof Ci.nsIHttpChannel)) {
+ return;
+ }
+
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ if (!matchRequest(channel, this.filters)) {
+ return;
+ }
+
+ let response = {
+ id: gSequenceId(),
+ channel: channel,
+ headers: [],
+ cookies: [],
+ };
+
+ let setCookieHeader = null;
+
+ channel.visitResponseHeaders({
+ visitHeader: function (name, value) {
+ let lowerName = name.toLowerCase();
+ if (lowerName == "set-cookie") {
+ setCookieHeader = value;
+ }
+ response.headers.push({ name: name, value: value });
+ }
+ });
+
+ if (!response.headers.length) {
+ // No need to continue.
+ return;
+ }
+
+ if (setCookieHeader) {
+ response.cookies = NetworkHelper.parseSetCookieHeader(setCookieHeader);
+ }
+
+ // Determine the HTTP version.
+ let httpVersionMaj = {};
+ let httpVersionMin = {};
+
+ channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ channel.getResponseVersion(httpVersionMaj, httpVersionMin);
+
+ response.status = channel.responseStatus;
+ response.statusText = channel.responseStatusText;
+ response.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
+ httpVersionMin.value;
+
+ this.openResponses[response.id] = response;
+
+ if (topic === "http-on-examine-cached-response") {
+ // Service worker requests emits cached-reponse notification on non-e10s,
+ // and we fake one on e10s.
+ let fromServiceWorker = this.interceptedChannels.has(channel);
+ this.interceptedChannels.delete(channel);
+
+ // If this is a cached response, there never was a request event
+ // so we need to construct one here so the frontend gets all the
+ // expected events.
+ let httpActivity = this._createNetworkEvent(channel, {
+ fromCache: !fromServiceWorker,
+ fromServiceWorker: fromServiceWorker
+ });
+ httpActivity.owner.addResponseStart({
+ httpVersion: response.httpVersion,
+ remoteAddress: "",
+ remotePort: "",
+ status: response.status,
+ statusText: response.statusText,
+ headersSize: 0,
+ }, "", true);
+
+ // There also is never any timing events, so we can fire this
+ // event with zeroed out values.
+ let timings = this._setupHarTimings(httpActivity, true);
+ httpActivity.owner.addEventTimings(timings.total, timings.timings);
+ }
+ },
+
+ /**
+ * Observe notifications for the http-on-modify-request topic, coming from
+ * the nsIObserverService.
+ *
+ * @private
+ * @param nsIHttpChannel aSubject
+ * @returns void
+ */
+ _httpModifyExaminer: function (subject) {
+ let throttler = this._getThrottler();
+ if (throttler) {
+ let channel = subject.QueryInterface(Ci.nsIHttpChannel);
+ if (matchRequest(channel, this.filters)) {
+ // Read any request body here, before it is throttled.
+ let httpActivity = this.createOrGetActivityObject(channel);
+ this._onRequestBodySent(httpActivity);
+ throttler.manageUpload(channel);
+ }
+ }
+ },
+
+ /**
+ * A helper function for observeActivity. This does whatever work
+ * is required by a particular http activity event. Arguments are
+ * the same as for observeActivity.
+ */
+ _dispatchActivity: function (httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData) {
+ let transCodes = this.httpTransactionCodes;
+
+ // Store the time information for this activity subtype.
+ if (activitySubtype in transCodes) {
+ let stage = transCodes[activitySubtype];
+ if (stage in httpActivity.timings) {
+ httpActivity.timings[stage].last = timestamp;
+ } else {
+ httpActivity.timings[stage] = {
+ first: timestamp,
+ last: timestamp,
+ };
+ }
+ }
+
+ switch (activitySubtype) {
+ case gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_BODY_SENT:
+ this._onRequestBodySent(httpActivity);
+ if (httpActivity.sentBody !== null) {
+ httpActivity.owner.addRequestPostData({ text: httpActivity.sentBody });
+ httpActivity.sentBody = null;
+ }
+ break;
+ case gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER:
+ this._onResponseHeader(httpActivity, extraStringData);
+ break;
+ case gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE:
+ this._onTransactionClose(httpActivity);
+ break;
+ default:
+ break;
+ }
+ },
+
+ /**
+ * Begin observing HTTP traffic that originates inside the current tab.
+ *
+ * @see https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIHttpActivityObserver
+ *
+ * @param nsIHttpChannel channel
+ * @param number activityType
+ * @param number activitySubtype
+ * @param number timestamp
+ * @param number extraSizeData
+ * @param string extraStringData
+ */
+ observeActivity:
+ DevToolsUtils.makeInfallible(function (channel, activityType, activitySubtype,
+ timestamp, extraSizeData,
+ extraStringData) {
+ if (!this.owner ||
+ activityType != gActivityDistributor.ACTIVITY_TYPE_HTTP_TRANSACTION &&
+ activityType != gActivityDistributor.ACTIVITY_TYPE_SOCKET_TRANSPORT) {
+ return;
+ }
+
+ if (!(channel instanceof Ci.nsIHttpChannel)) {
+ return;
+ }
+
+ channel = channel.QueryInterface(Ci.nsIHttpChannel);
+
+ if (activitySubtype ==
+ gActivityDistributor.ACTIVITY_SUBTYPE_REQUEST_HEADER) {
+ this._onRequestHeader(channel, timestamp, extraStringData);
+ return;
+ }
+
+ // Iterate over all currently ongoing requests. If channel can't
+ // be found within them, then exit this function.
+ let httpActivity = this._findActivityObject(channel);
+ if (!httpActivity) {
+ return;
+ }
+
+ // If we're throttling, we must not report events as they arrive
+ // from platform, but instead let the throttler emit the events
+ // after some time has elapsed.
+ if (httpActivity.downloadThrottle &&
+ this.httpDownloadActivities.indexOf(activitySubtype) >= 0) {
+ let callback = this._dispatchActivity.bind(this);
+ httpActivity.downloadThrottle
+ .addActivityCallback(callback, httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData);
+ } else {
+ this._dispatchActivity(httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData);
+ }
+ }),
+
+ /**
+ *
+ */
+ _createNetworkEvent: function (channel, { timestamp, extraStringData,
+ fromCache, fromServiceWorker }) {
+ let httpActivity = this.createOrGetActivityObject(channel);
+
+ channel.QueryInterface(Ci.nsIPrivateBrowsingChannel);
+ httpActivity.private = channel.isChannelPrivate;
+
+ if (timestamp) {
+ httpActivity.timings.REQUEST_HEADER = {
+ first: timestamp,
+ last: timestamp
+ };
+ }
+
+ let event = {};
+ event.method = channel.requestMethod;
+ event.channelId = channel.channelId;
+ event.url = channel.URI.spec;
+ event.private = httpActivity.private;
+ event.headersSize = 0;
+ event.startedDateTime =
+ (timestamp ? new Date(Math.round(timestamp / 1000)) : new Date())
+ .toISOString();
+ event.fromCache = fromCache;
+ event.fromServiceWorker = fromServiceWorker;
+ httpActivity.fromServiceWorker = fromServiceWorker;
+
+ if (extraStringData) {
+ event.headersSize = extraStringData.length;
+ }
+
+ // Determine the cause and if this is an XHR request.
+ let causeType = channel.loadInfo.externalContentPolicyType;
+ let loadingPrincipal = channel.loadInfo.loadingPrincipal;
+ let causeUri = loadingPrincipal ? loadingPrincipal.URI : null;
+ let stacktrace;
+ // If this is the parent process, there is no stackTraceCollector - the stack
+ // trace will be added in NetworkMonitorChild._onNewEvent.
+ if (this.owner.stackTraceCollector) {
+ stacktrace = this.owner.stackTraceCollector.getStackTrace(event.channelId);
+ }
+
+ event.cause = {
+ type: causeType,
+ loadingDocumentUri: causeUri ? causeUri.spec : null,
+ stacktrace
+ };
+
+ httpActivity.isXHR = event.isXHR =
+ (causeType === Ci.nsIContentPolicy.TYPE_XMLHTTPREQUEST ||
+ causeType === Ci.nsIContentPolicy.TYPE_FETCH);
+
+ // Determine the HTTP version.
+ let httpVersionMaj = {};
+ let httpVersionMin = {};
+ channel.QueryInterface(Ci.nsIHttpChannelInternal);
+ channel.getRequestVersion(httpVersionMaj, httpVersionMin);
+
+ event.httpVersion = "HTTP/" + httpVersionMaj.value + "." +
+ httpVersionMin.value;
+
+ event.discardRequestBody = !this.saveRequestAndResponseBodies;
+ event.discardResponseBody = !this.saveRequestAndResponseBodies;
+
+ let headers = [];
+ let cookies = [];
+ let cookieHeader = null;
+
+ // Copy the request header data.
+ channel.visitRequestHeaders({
+ visitHeader: function (name, value) {
+ if (name == "Cookie") {
+ cookieHeader = value;
+ }
+ headers.push({ name: name, value: value });
+ }
+ });
+
+ if (cookieHeader) {
+ cookies = NetworkHelper.parseCookieHeader(cookieHeader);
+ }
+
+ httpActivity.owner = this.owner.onNetworkEvent(event);
+
+ this._setupResponseListener(httpActivity, fromCache);
+
+ httpActivity.owner.addRequestHeaders(headers, extraStringData);
+ httpActivity.owner.addRequestCookies(cookies);
+
+ return httpActivity;
+ },
+
+ /**
+ * Handler for ACTIVITY_SUBTYPE_REQUEST_HEADER. When a request starts the
+ * headers are sent to the server. This method creates the |httpActivity|
+ * object where we store the request and response information that is
+ * collected through its lifetime.
+ *
+ * @private
+ * @param nsIHttpChannel channel
+ * @param number timestamp
+ * @param string extraStringData
+ * @return void
+ */
+ _onRequestHeader: function (channel, timestamp, extraStringData) {
+ if (!matchRequest(channel, this.filters)) {
+ return;
+ }
+
+ this._createNetworkEvent(channel, { timestamp, extraStringData });
+ },
+
+ /**
+ * Find an HTTP activity object for the channel.
+ *
+ * @param nsIHttpChannel channel
+ * The HTTP channel whose activity object we want to find.
+ * @return object
+ * The HTTP activity object, or null if it is not found.
+ */
+ _findActivityObject: function (channel) {
+ for (let id in this.openRequests) {
+ let item = this.openRequests[id];
+ if (item.channel === channel) {
+ return item;
+ }
+ }
+ return null;
+ },
+
+ /**
+ * Find an existing HTTP activity object, or create a new one. This
+ * object is used for storing all the request and response
+ * information.
+ *
+ * This is a HAR-like object. Conformance to the spec is not guaranteed at
+ * this point.
+ *
+ * @see http://www.softwareishard.com/blog/har-12-spec
+ * @param nsIHttpChannel channel
+ * The HTTP channel for which the HTTP activity object is created.
+ * @return object
+ * The new HTTP activity object.
+ */
+ createOrGetActivityObject: function (channel) {
+ let httpActivity = this._findActivityObject(channel);
+ if (!httpActivity) {
+ let win = NetworkHelper.getWindowForRequest(channel);
+ let charset = win ? win.document.characterSet : null;
+
+ httpActivity = {
+ id: gSequenceId(),
+ channel: channel,
+ // see _onRequestBodySent()
+ charset: charset,
+ sentBody: null,
+ url: channel.URI.spec,
+ // needed for host specific security info
+ hostname: channel.URI.host,
+ discardRequestBody: !this.saveRequestAndResponseBodies,
+ discardResponseBody: !this.saveRequestAndResponseBodies,
+ // internal timing information, see observeActivity()
+ timings: {},
+ // see _onResponseHeader()
+ responseStatus: null,
+ // the activity owner which is notified when changes happen
+ owner: null,
+ };
+
+ this.openRequests[httpActivity.id] = httpActivity;
+ }
+
+ return httpActivity;
+ },
+
+ /**
+ * Setup the network response listener for the given HTTP activity. The
+ * NetworkResponseListener is responsible for storing the response body.
+ *
+ * @private
+ * @param object httpActivity
+ * The HTTP activity object we are tracking.
+ */
+ _setupResponseListener: function (httpActivity, fromCache) {
+ let channel = httpActivity.channel;
+ channel.QueryInterface(Ci.nsITraceableChannel);
+
+ if (!fromCache) {
+ let throttler = this._getThrottler();
+ if (throttler) {
+ httpActivity.downloadThrottle = throttler.manage(channel);
+ }
+ }
+
+ // The response will be written into the outputStream of this pipe.
+ // This allows us to buffer the data we are receiving and read it
+ // asynchronously.
+ // Both ends of the pipe must be blocking.
+ let sink = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+
+ // The streams need to be blocking because this is required by the
+ // stream tee.
+ sink.init(false, false, this.responsePipeSegmentSize, PR_UINT32_MAX, null);
+
+ // Add listener for the response body.
+ let newListener = new NetworkResponseListener(this, httpActivity);
+
+ // Remember the input stream, so it isn't released by GC.
+ newListener.inputStream = sink.inputStream;
+ newListener.sink = sink;
+
+ let tee = Cc["@mozilla.org/network/stream-listener-tee;1"]
+ .createInstance(Ci.nsIStreamListenerTee);
+
+ let originalListener = channel.setNewListener(tee);
+
+ tee.init(originalListener, sink.outputStream, newListener);
+ },
+
+ /**
+ * Handler for ACTIVITY_SUBTYPE_REQUEST_BODY_SENT. The request body is logged
+ * here.
+ *
+ * @private
+ * @param object httpActivity
+ * The HTTP activity object we are working with.
+ */
+ _onRequestBodySent: function (httpActivity) {
+ // Return early if we don't need the request body, or if we've
+ // already found it.
+ if (httpActivity.discardRequestBody || httpActivity.sentBody !== null) {
+ return;
+ }
+
+ let sentBody = NetworkHelper.readPostTextFromRequest(httpActivity.channel,
+ httpActivity.charset);
+
+ if (sentBody !== null && this.window &&
+ httpActivity.url == this.window.location.href) {
+ // If the request URL is the same as the current page URL, then
+ // we can try to get the posted text from the page directly.
+ // This check is necessary as otherwise the
+ // NetworkHelper.readPostTextFromPageViaWebNav()
+ // function is called for image requests as well but these
+ // are not web pages and as such don't store the posted text
+ // in the cache of the webpage.
+ let webNav = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation);
+ sentBody = NetworkHelper
+ .readPostTextFromPageViaWebNav(webNav, httpActivity.charset);
+ }
+
+ if (sentBody !== null) {
+ httpActivity.sentBody = sentBody;
+ }
+ },
+
+ /**
+ * Handler for ACTIVITY_SUBTYPE_RESPONSE_HEADER. This method stores
+ * information about the response headers.
+ *
+ * @private
+ * @param object httpActivity
+ * The HTTP activity object we are working with.
+ * @param string extraStringData
+ * The uncached response headers.
+ */
+ _onResponseHeader: function (httpActivity, extraStringData) {
+ // extraStringData contains the uncached response headers. The first line
+ // contains the response status (e.g. HTTP/1.1 200 OK).
+ //
+ // Note: The response header is not saved here. Calling the
+ // channel.visitResponseHeaders() method at this point sometimes causes an
+ // NS_ERROR_NOT_AVAILABLE exception.
+ //
+ // We could parse extraStringData to get the headers and their values, but
+ // that is not trivial to do in an accurate manner. Hence, we save the
+ // response headers in this._httpResponseExaminer().
+
+ let headers = extraStringData.split(/\r\n|\n|\r/);
+ let statusLine = headers.shift();
+ let statusLineArray = statusLine.split(" ");
+
+ let response = {};
+ response.httpVersion = statusLineArray.shift();
+ response.remoteAddress = httpActivity.channel.remoteAddress;
+ response.remotePort = httpActivity.channel.remotePort;
+ response.status = statusLineArray.shift();
+ response.statusText = statusLineArray.join(" ");
+ response.headersSize = extraStringData.length;
+
+ httpActivity.responseStatus = response.status;
+
+ // Discard the response body for known response statuses.
+ switch (parseInt(response.status, 10)) {
+ case HTTP_MOVED_PERMANENTLY:
+ case HTTP_FOUND:
+ case HTTP_SEE_OTHER:
+ case HTTP_TEMPORARY_REDIRECT:
+ httpActivity.discardResponseBody = true;
+ break;
+ }
+
+ response.discardResponseBody = httpActivity.discardResponseBody;
+
+ httpActivity.owner.addResponseStart(response, extraStringData);
+ },
+
+ /**
+ * Handler for ACTIVITY_SUBTYPE_TRANSACTION_CLOSE. This method updates the HAR
+ * timing information on the HTTP activity object and clears the request
+ * from the list of known open requests.
+ *
+ * @private
+ * @param object httpActivity
+ * The HTTP activity object we work with.
+ */
+ _onTransactionClose: function (httpActivity) {
+ let result = this._setupHarTimings(httpActivity);
+ httpActivity.owner.addEventTimings(result.total, result.timings);
+ delete this.openRequests[httpActivity.id];
+ },
+
+ /**
+ * Update the HTTP activity object to include timing information as in the HAR
+ * spec. The HTTP activity object holds the raw timing information in
+ * |timings| - these are timings stored for each activity notification. The
+ * HAR timing information is constructed based on these lower level
+ * data.
+ *
+ * @param object httpActivity
+ * The HTTP activity object we are working with.
+ * @param boolean fromCache
+ * Indicates that the result was returned from the browser cache
+ * @return object
+ * This object holds two properties:
+ * - total - the total time for all of the request and response.
+ * - timings - the HAR timings object.
+ */
+ _setupHarTimings: function (httpActivity, fromCache) {
+ if (fromCache) {
+ // If it came from the browser cache, we have no timing
+ // information and these should all be 0
+ return {
+ total: 0,
+ timings: {
+ blocked: 0,
+ dns: 0,
+ connect: 0,
+ send: 0,
+ wait: 0,
+ receive: 0
+ }
+ };
+ }
+
+ let timings = httpActivity.timings;
+ let harTimings = {};
+
+ if (timings.STATUS_RESOLVING && timings.STATUS_CONNECTING_TO) {
+ harTimings.blocked = timings.STATUS_RESOLVING.first -
+ timings.REQUEST_HEADER.first;
+ } else if (timings.STATUS_SENDING_TO) {
+ harTimings.blocked = timings.STATUS_SENDING_TO.first -
+ timings.REQUEST_HEADER.first;
+ } else {
+ harTimings.blocked = -1;
+ }
+
+ // DNS timing information is available only in when the DNS record is not
+ // cached.
+ harTimings.dns = timings.STATUS_RESOLVING && timings.STATUS_RESOLVED ?
+ timings.STATUS_RESOLVED.last -
+ timings.STATUS_RESOLVING.first : -1;
+
+ if (timings.STATUS_CONNECTING_TO && timings.STATUS_CONNECTED_TO) {
+ harTimings.connect = timings.STATUS_CONNECTED_TO.last -
+ timings.STATUS_CONNECTING_TO.first;
+ } else {
+ harTimings.connect = -1;
+ }
+
+ if (timings.STATUS_SENDING_TO) {
+ harTimings.send = timings.STATUS_SENDING_TO.last - timings.STATUS_SENDING_TO.first;
+ } else if (timings.REQUEST_HEADER && timings.REQUEST_BODY_SENT) {
+ harTimings.send = timings.REQUEST_BODY_SENT.last - timings.REQUEST_HEADER.first;
+ } else {
+ harTimings.send = -1;
+ }
+
+ if (timings.RESPONSE_START) {
+ harTimings.wait = timings.RESPONSE_START.first -
+ (timings.REQUEST_BODY_SENT ||
+ timings.STATUS_SENDING_TO).last;
+ } else {
+ harTimings.wait = -1;
+ }
+
+ if (timings.RESPONSE_START && timings.RESPONSE_COMPLETE) {
+ harTimings.receive = timings.RESPONSE_COMPLETE.last -
+ timings.RESPONSE_START.first;
+ } else {
+ harTimings.receive = -1;
+ }
+
+ let totalTime = 0;
+ for (let timing in harTimings) {
+ let time = Math.max(Math.round(harTimings[timing] / 1000), -1);
+ harTimings[timing] = time;
+ if (time > -1) {
+ totalTime += time;
+ }
+ }
+
+ return {
+ total: totalTime,
+ timings: harTimings,
+ };
+ },
+
+ /**
+ * Suspend Web Console activity. This is called when all Web Consoles are
+ * closed.
+ */
+ destroy: function () {
+ if (Services.appinfo.processType != Ci.nsIXULRuntime.PROCESS_TYPE_CONTENT) {
+ gActivityDistributor.removeObserver(this);
+ Services.obs.removeObserver(this._httpResponseExaminer,
+ "http-on-examine-response");
+ Services.obs.removeObserver(this._httpResponseExaminer,
+ "http-on-examine-cached-response");
+ Services.obs.removeObserver(this._httpModifyExaminer,
+ "http-on-modify-request", false);
+ }
+
+ Services.obs.removeObserver(this._serviceWorkerRequest,
+ "service-worker-synthesized-response");
+
+ this.interceptedChannels.clear();
+ this.openRequests = {};
+ this.openResponses = {};
+ this.owner = null;
+ this.filters = null;
+ this._throttler = null;
+ },
+};
+
+/**
+ * The NetworkMonitorChild is used to proxy all of the network activity of the
+ * child app process from the main process. The child WebConsoleActor creates an
+ * instance of this object.
+ *
+ * Network requests for apps happen in the main process. As such,
+ * a NetworkMonitor instance is used by the WebappsActor in the main process to
+ * log the network requests for this child process.
+ *
+ * The main process creates NetworkEventActorProxy instances per request. These
+ * send the data to this object using the nsIMessageManager. Here we proxy the
+ * data to the WebConsoleActor or to a NetworkEventActor.
+ *
+ * @constructor
+ * @param number appId
+ * The web appId of the child process.
+ * @param number outerWindowID
+ * The outerWindowID of the TabActor's main window.
+ * @param nsIMessageManager messageManager
+ * The nsIMessageManager to use to communicate with the parent process.
+ * @param object DebuggerServerConnection
+ * The RDP connection to the client.
+ * @param object owner
+ * The WebConsoleActor that is listening for the network requests.
+ */
+function NetworkMonitorChild(appId, outerWindowID, messageManager, conn, owner) {
+ this.appId = appId;
+ this.outerWindowID = outerWindowID;
+ this.conn = conn;
+ this.owner = owner;
+ this._messageManager = messageManager;
+ this._onNewEvent = this._onNewEvent.bind(this);
+ this._onUpdateEvent = this._onUpdateEvent.bind(this);
+ this._netEvents = new Map();
+ this._msgName = `debug:${this.conn.prefix}netmonitor`;
+}
+
+exports.NetworkMonitorChild = NetworkMonitorChild;
+
+NetworkMonitorChild.prototype = {
+ appId: null,
+ owner: null,
+ _netEvents: null,
+ _saveRequestAndResponseBodies: true,
+ _throttleData: null,
+
+ get saveRequestAndResponseBodies() {
+ return this._saveRequestAndResponseBodies;
+ },
+
+ set saveRequestAndResponseBodies(val) {
+ this._saveRequestAndResponseBodies = val;
+
+ this._messageManager.sendAsyncMessage(this._msgName, {
+ action: "setPreferences",
+ preferences: {
+ saveRequestAndResponseBodies: this._saveRequestAndResponseBodies,
+ },
+ });
+ },
+
+ get throttleData() {
+ return this._throttleData;
+ },
+
+ set throttleData(val) {
+ this._throttleData = val;
+
+ this._messageManager.sendAsyncMessage(this._msgName, {
+ action: "setPreferences",
+ preferences: {
+ throttleData: this._throttleData,
+ },
+ });
+ },
+
+ init: function () {
+ this.conn.setupInParent({
+ module: "devtools/shared/webconsole/network-monitor",
+ setupParent: "setupParentProcess"
+ });
+
+ let mm = this._messageManager;
+ mm.addMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
+ mm.addMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
+ mm.sendAsyncMessage(this._msgName, {
+ appId: this.appId,
+ outerWindowID: this.outerWindowID,
+ action: "start",
+ });
+ },
+
+ _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
+ let {id, event} = msg.data;
+
+ // Try to add stack trace to the event data received from parent
+ if (this.owner.stackTraceCollector) {
+ event.cause.stacktrace =
+ this.owner.stackTraceCollector.getStackTrace(event.channelId);
+ }
+
+ let actor = this.owner.onNetworkEvent(event);
+ this._netEvents.set(id, Cu.getWeakReference(actor));
+ }),
+
+ _onUpdateEvent: DevToolsUtils.makeInfallible(function _onUpdateEvent(msg) {
+ let {id, method, args} = msg.data;
+ let weakActor = this._netEvents.get(id);
+ let actor = weakActor ? weakActor.get() : null;
+ if (!actor) {
+ console.error(`Received ${this._msgName}:updateEvent for unknown event ID: ${id}`);
+ return;
+ }
+ if (!(method in actor)) {
+ console.error(`Received ${this._msgName}:updateEvent unsupported ` +
+ `method: ${method}`);
+ return;
+ }
+ actor[method].apply(actor, args);
+ }),
+
+ destroy: function () {
+ let mm = this._messageManager;
+ try {
+ mm.removeMessageListener(`${this._msgName}:newEvent`, this._onNewEvent);
+ mm.removeMessageListener(`${this._msgName}:updateEvent`, this._onUpdateEvent);
+ } catch (e) {
+ // On b2g, when registered to a new root docshell,
+ // all message manager functions throw when trying to call them during
+ // message-manager-disconnect event.
+ // As there is no attribute/method on message manager to know
+ // if they are still usable or not, we can only catch the exception...
+ }
+ this._netEvents.clear();
+ this._messageManager = null;
+ this.conn = null;
+ this.owner = null;
+ },
+};
+
+/**
+ * The NetworkEventActorProxy is used to send network request information from
+ * the main process to the child app process. One proxy is used per request.
+ * Similarly, one NetworkEventActor in the child app process is used per
+ * request. The client receives all network logs from the child actors.
+ *
+ * The child process has a NetworkMonitorChild instance that is listening for
+ * all network logging from the main process. The net monitor shim is used to
+ * proxy the data to the WebConsoleActor instance of the child process.
+ *
+ * @constructor
+ * @param nsIMessageManager messageManager
+ * The message manager for the child app process. This is used for
+ * communication with the NetworkMonitorChild instance of the process.
+ * @param string msgName
+ * The message name to be used for this connection.
+ */
+function NetworkEventActorProxy(messageManager, msgName) {
+ this.id = gSequenceId();
+ this.messageManager = messageManager;
+ this._msgName = msgName;
+}
+exports.NetworkEventActorProxy = NetworkEventActorProxy;
+
+NetworkEventActorProxy.methodFactory = function (method) {
+ return DevToolsUtils.makeInfallible(function () {
+ let args = Array.slice(arguments);
+ let mm = this.messageManager;
+ mm.sendAsyncMessage(`${this._msgName}:updateEvent`, {
+ id: this.id,
+ method: method,
+ args: args,
+ });
+ }, "NetworkEventActorProxy." + method);
+};
+
+NetworkEventActorProxy.prototype = {
+ /**
+ * Initialize the network event. This method sends the network request event
+ * to the content process.
+ *
+ * @param object event
+ * Object describing the network request.
+ * @return object
+ * This object.
+ */
+ init: DevToolsUtils.makeInfallible(function (event) {
+ let mm = this.messageManager;
+ mm.sendAsyncMessage(`${this._msgName}:newEvent`, {
+ id: this.id,
+ event: event,
+ });
+ return this;
+ }),
+};
+
+(function () {
+ // Listeners for new network event data coming from the NetworkMonitor.
+ let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
+ "addResponseStart", "addSecurityInfo", "addResponseHeaders",
+ "addResponseCookies", "addResponseContent", "addEventTimings"];
+ let factory = NetworkEventActorProxy.methodFactory;
+ for (let method of methods) {
+ NetworkEventActorProxy.prototype[method] = factory(method);
+ }
+})();
+
+/**
+ * This is triggered by the child calling `setupInParent` when the child's network monitor
+ * is starting up. This initializes the parent process side of the monitoring.
+ */
+function setupParentProcess({ mm, prefix }) {
+ let networkMonitor = new NetworkMonitorParent(mm, prefix);
+ return {
+ onBrowserSwap: newMM => networkMonitor.setMessageManager(newMM),
+ onDisconnected: () => {
+ networkMonitor.destroy();
+ networkMonitor = null;
+ }
+ };
+}
+
+exports.setupParentProcess = setupParentProcess;
+
+/**
+ * The NetworkMonitorParent runs in the parent process and uses the message manager to
+ * listen for requests from the child process to start/stop the network monitor. Most
+ * request data is only available from the parent process, so that's why the network
+ * monitor needs to run there when debugging tabs that are in the child.
+ *
+ * @param nsIMessageManager mm
+ * The message manager for the browser we're filtering on.
+ * @param string prefix
+ * The RDP connection prefix that uniquely identifies the connection.
+ */
+function NetworkMonitorParent(mm, prefix) {
+ this._msgName = `debug:${prefix}netmonitor`;
+ this.onNetMonitorMessage = this.onNetMonitorMessage.bind(this);
+ this.onNetworkEvent = this.onNetworkEvent.bind(this);
+ this.setMessageManager(mm);
+}
+
+NetworkMonitorParent.prototype = {
+ netMonitor: null,
+ messageManager: null,
+
+ setMessageManager(mm) {
+ if (this.messageManager) {
+ let oldMM = this.messageManager;
+ oldMM.removeMessageListener(this._msgName, this.onNetMonitorMessage);
+ }
+ this.messageManager = mm;
+ if (mm) {
+ mm.addMessageListener(this._msgName, this.onNetMonitorMessage);
+ }
+ },
+
+ /**
+ * Handler for `debug:${prefix}netmonitor` messages received through the message manager
+ * from the content process.
+ *
+ * @param object msg
+ * Message from the content.
+ */
+ onNetMonitorMessage: DevToolsUtils.makeInfallible(function (msg) {
+ let {action} = msg.json;
+ // Pipe network monitor data from parent to child via the message manager.
+ switch (action) {
+ case "start":
+ if (!this.netMonitor) {
+ let {appId, outerWindowID} = msg.json;
+ this.netMonitor = new NetworkMonitor({
+ outerWindowID,
+ appId,
+ }, this);
+ this.netMonitor.init();
+ }
+ break;
+ case "setPreferences": {
+ let {preferences} = msg.json;
+ for (let key of Object.keys(preferences)) {
+ if ((key == "saveRequestAndResponseBodies" ||
+ key == "throttleData") && this.netMonitor) {
+ this.netMonitor[key] = preferences[key];
+ }
+ }
+ break;
+ }
+
+ case "stop":
+ if (this.netMonitor) {
+ this.netMonitor.destroy();
+ this.netMonitor = null;
+ }
+ break;
+
+ case "disconnect":
+ this.destroy();
+ break;
+ }
+ }),
+
+ /**
+ * Handler for new network requests. This method is invoked by the current
+ * NetworkMonitor instance.
+ *
+ * @param object event
+ * Object describing the network request.
+ * @return object
+ * A NetworkEventActorProxy instance which is notified when further
+ * data about the request is available.
+ */
+ onNetworkEvent: DevToolsUtils.makeInfallible(function (event) {
+ return new NetworkEventActorProxy(this.messageManager, this._msgName).init(event);
+ }),
+
+ destroy: function () {
+ this.setMessageManager(null);
+
+ if (this.netMonitor) {
+ this.netMonitor.destroy();
+ this.netMonitor = null;
+ }
+ },
+};
+
+/**
+ * A WebProgressListener that listens for location changes.
+ *
+ * This progress listener is used to track file loads and other kinds of
+ * location changes.
+ *
+ * @constructor
+ * @param object window
+ * The window for which we need to track location changes.
+ * @param object owner
+ * The listener owner which needs to implement two methods:
+ * - onFileActivity(aFileURI)
+ * - onLocationChange(aState, aTabURI, aPageTitle)
+ */
+function ConsoleProgressListener(window, owner) {
+ this.window = window;
+ this.owner = owner;
+}
+exports.ConsoleProgressListener = ConsoleProgressListener;
+
+ConsoleProgressListener.prototype = {
+ /**
+ * Constant used for startMonitor()/stopMonitor() that tells you want to
+ * monitor file loads.
+ */
+ MONITOR_FILE_ACTIVITY: 1,
+
+ /**
+ * Constant used for startMonitor()/stopMonitor() that tells you want to
+ * monitor page location changes.
+ */
+ MONITOR_LOCATION_CHANGE: 2,
+
+ /**
+ * Tells if you want to monitor file activity.
+ * @private
+ * @type boolean
+ */
+ _fileActivity: false,
+
+ /**
+ * Tells if you want to monitor location changes.
+ * @private
+ * @type boolean
+ */
+ _locationChange: false,
+
+ /**
+ * Tells if the console progress listener is initialized or not.
+ * @private
+ * @type boolean
+ */
+ _initialized: false,
+
+ _webProgress: null,
+
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ /**
+ * Initialize the ConsoleProgressListener.
+ * @private
+ */
+ _init: function () {
+ if (this._initialized) {
+ return;
+ }
+
+ this._webProgress = this.window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIWebNavigation)
+ .QueryInterface(Ci.nsIWebProgress);
+ this._webProgress.addProgressListener(this,
+ Ci.nsIWebProgress.NOTIFY_STATE_ALL);
+
+ this._initialized = true;
+ },
+
+ /**
+ * Start a monitor/tracker related to the current nsIWebProgressListener
+ * instance.
+ *
+ * @param number monitor
+ * Tells what you want to track. Available constants:
+ * - this.MONITOR_FILE_ACTIVITY
+ * Track file loads.
+ * - this.MONITOR_LOCATION_CHANGE
+ * Track location changes for the top window.
+ */
+ startMonitor: function (monitor) {
+ switch (monitor) {
+ case this.MONITOR_FILE_ACTIVITY:
+ this._fileActivity = true;
+ break;
+ case this.MONITOR_LOCATION_CHANGE:
+ this._locationChange = true;
+ break;
+ default:
+ throw new Error("ConsoleProgressListener: unknown monitor type " +
+ monitor + "!");
+ }
+ this._init();
+ },
+
+ /**
+ * Stop a monitor.
+ *
+ * @param number monitor
+ * Tells what you want to stop tracking. See this.startMonitor() for
+ * the list of constants.
+ */
+ stopMonitor: function (monitor) {
+ switch (monitor) {
+ case this.MONITOR_FILE_ACTIVITY:
+ this._fileActivity = false;
+ break;
+ case this.MONITOR_LOCATION_CHANGE:
+ this._locationChange = false;
+ break;
+ default:
+ throw new Error("ConsoleProgressListener: unknown monitor type " +
+ monitor + "!");
+ }
+
+ if (!this._fileActivity && !this._locationChange) {
+ this.destroy();
+ }
+ },
+
+ onStateChange: function (progress, request, state, status) {
+ if (!this.owner) {
+ return;
+ }
+
+ if (this._fileActivity) {
+ this._checkFileActivity(progress, request, state, status);
+ }
+
+ if (this._locationChange) {
+ this._checkLocationChange(progress, request, state, status);
+ }
+ },
+
+ /**
+ * Check if there is any file load, given the arguments of
+ * nsIWebProgressListener.onStateChange. If the state change tells that a file
+ * URI has been loaded, then the remote Web Console instance is notified.
+ * @private
+ */
+ _checkFileActivity: function (progress, request, state, status) {
+ if (!(state & Ci.nsIWebProgressListener.STATE_START)) {
+ return;
+ }
+
+ let uri = null;
+ if (request instanceof Ci.imgIRequest) {
+ let imgIRequest = request.QueryInterface(Ci.imgIRequest);
+ uri = imgIRequest.URI;
+ } else if (request instanceof Ci.nsIChannel) {
+ let nsIChannel = request.QueryInterface(Ci.nsIChannel);
+ uri = nsIChannel.URI;
+ }
+
+ if (!uri || !uri.schemeIs("file") && !uri.schemeIs("ftp")) {
+ return;
+ }
+
+ this.owner.onFileActivity(uri.spec);
+ },
+
+ /**
+ * Check if the current window.top location is changing, given the arguments
+ * of nsIWebProgressListener.onStateChange. If that is the case, the remote
+ * Web Console instance is notified.
+ * @private
+ */
+ _checkLocationChange: function (progress, request, state) {
+ let isStart = state & Ci.nsIWebProgressListener.STATE_START;
+ let isStop = state & Ci.nsIWebProgressListener.STATE_STOP;
+ let isNetwork = state & Ci.nsIWebProgressListener.STATE_IS_NETWORK;
+ let isWindow = state & Ci.nsIWebProgressListener.STATE_IS_WINDOW;
+
+ // Skip non-interesting states.
+ if (!isNetwork || !isWindow || progress.DOMWindow != this.window) {
+ return;
+ }
+
+ if (isStart && request instanceof Ci.nsIChannel) {
+ this.owner.onLocationChange("start", request.URI.spec, "");
+ } else if (isStop) {
+ this.owner.onLocationChange("stop", this.window.location.href,
+ this.window.document.title);
+ }
+ },
+
+ onLocationChange: function () {},
+ onStatusChange: function () {},
+ onProgressChange: function () {},
+ onSecurityChange: function () {},
+
+ /**
+ * Destroy the ConsoleProgressListener.
+ */
+ destroy: function () {
+ if (!this._initialized) {
+ return;
+ }
+
+ this._initialized = false;
+ this._fileActivity = false;
+ this._locationChange = false;
+
+ try {
+ this._webProgress.removeProgressListener(this);
+ } catch (ex) {
+ // This can throw during browser shutdown.
+ }
+
+ this._webProgress = null;
+ this.window = null;
+ this.owner = null;
+ },
+};
+
+function gSequenceId() {
+ return gSequenceId.n++;
+}
+gSequenceId.n = 1;
diff --git a/devtools/shared/webconsole/server-logger-monitor.js b/devtools/shared/webconsole/server-logger-monitor.js
new file mode 100644
index 000000000..9cc2682ea
--- /dev/null
+++ b/devtools/shared/webconsole/server-logger-monitor.js
@@ -0,0 +1,191 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Ci} = require("chrome");
+const Services = require("Services");
+
+const {makeInfallible} = require("devtools/shared/DevToolsUtils");
+
+loader.lazyGetter(this, "NetworkHelper", () => require("devtools/shared/webconsole/network-helper"));
+
+// Helper tracer. Should be generic sharable by other modules (bug 1171927)
+const trace = {
+ log: function (...args) {
+ }
+};
+
+const acceptableHeaders = ["x-chromelogger-data"];
+
+/**
+ * This object represents HTTP events observer. It's intended to be
+ * used in e10s enabled browser only.
+ *
+ * Since child processes can't register HTTP event observer they use
+ * this module to do the observing in the parent process. This monitor
+ * is loaded through DebuggerServerConnection.setupInParent() that is
+ * executed from within the child process. The execution is done by
+ * {@ServerLoggingListener}. The monitor listens to HTTP events and
+ * forwards it into the right child process.
+ *
+ * Read more about the architecture:
+ * https://github.com/mozilla/gecko-dev/blob/fx-team/devtools/server/docs/actor-e10s-handling.md
+ */
+var ServerLoggerMonitor = {
+ // Initialization
+
+ initialize: function () {
+ this.onChildMessage = this.onChildMessage.bind(this);
+ this.onExamineResponse = this.onExamineResponse.bind(this);
+
+ // Set of registered child frames (loggers).
+ this.targets = new Set();
+ },
+
+ // Parent Child Relationship
+
+ attach: makeInfallible(function ({ mm, prefix }) {
+ trace.log("ServerLoggerMonitor.attach; ", arguments);
+
+ let setMessageManager = newMM => {
+ if (mm) {
+ mm.removeMessageListener("debug:server-logger", this.onChildMessage);
+ }
+ mm = newMM;
+ if (mm) {
+ mm.addMessageListener("debug:server-logger", this.onChildMessage);
+ }
+ };
+
+ // Start listening for messages from the {@ServerLogger} actor
+ // living in the child process.
+ setMessageManager(mm);
+
+ return {
+ onBrowserSwap: setMessageManager,
+ onDisconnected: () => {
+ trace.log("ServerLoggerMonitor.onDisconnectChild; ", arguments);
+ setMessageManager(null);
+ }
+ };
+ }),
+
+ // Child Message Handling
+
+ onChildMessage: function (msg) {
+ let method = msg.data.method;
+
+ trace.log("ServerLoggerMonitor.onChildMessage; ", method, msg);
+
+ switch (method) {
+ case "attachChild":
+ return this.onAttachChild(msg);
+ case "detachChild":
+ return this.onDetachChild(msg);
+ default:
+ trace.log("Unknown method name: ", method);
+ return undefined;
+ }
+ },
+
+ onAttachChild: function (event) {
+ let target = event.target;
+ let size = this.targets.size;
+
+ trace.log("ServerLoggerMonitor.onAttachChild; size: ", size, target);
+
+ // If this is the first child attached, register global HTTP observer.
+ if (!size) {
+ trace.log("ServerLoggerMonitor.onAttatchChild; Add HTTP Observer");
+ Services.obs.addObserver(this.onExamineResponse,
+ "http-on-examine-response", false);
+ }
+
+ // Collect child loggers. The frame element where the
+ // window/document lives.
+ this.targets.add(target);
+ },
+
+ onDetachChild: function (event) {
+ let target = event.target;
+ this.targets.delete(target);
+
+ let size = this.targets.size;
+ trace.log("ServerLoggerMonitor.onDetachChild; size: ", size, target);
+
+ // If this is the last child process attached, unregister
+ // the global HTTP observer.
+ if (!size) {
+ trace.log("ServerLoggerMonitor.onDetachChild; Remove HTTP Observer");
+ Services.obs.removeObserver(this.onExamineResponse,
+ "http-on-examine-response", false);
+ }
+ },
+
+ // HTTP Observer
+
+ onExamineResponse: makeInfallible(function (subject, topic) {
+ let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ trace.log("ServerLoggerMonitor.onExamineResponse; ", httpChannel.name,
+ this.targets);
+
+ // Ignore requests from chrome or add-on code when we are monitoring
+ // content.
+ if (!httpChannel.loadInfo &&
+ httpChannel.loadInfo.loadingDocument === null &&
+ httpChannel.loadInfo.loadingPrincipal ===
+ Services.scriptSecurityManager.getSystemPrincipal()) {
+ return;
+ }
+
+ let requestFrame = NetworkHelper.getTopFrameForRequest(httpChannel);
+ if (!requestFrame) {
+ return;
+ }
+
+ // Ignore requests from parent frames that aren't registered.
+ if (!this.targets.has(requestFrame)) {
+ return;
+ }
+
+ let headers = [];
+
+ httpChannel.visitResponseHeaders((header, value) => {
+ header = header.toLowerCase();
+ if (acceptableHeaders.indexOf(header) !== -1) {
+ headers.push({header: header, value: value});
+ }
+ });
+
+ if (!headers.length) {
+ return;
+ }
+
+ let { messageManager } = requestFrame;
+ messageManager.sendAsyncMessage("debug:server-logger", {
+ method: "examineHeaders",
+ headers: headers,
+ });
+
+ trace.log("ServerLoggerMonitor.onExamineResponse; headers ",
+ headers.length, ", ", headers);
+ }),
+};
+
+/**
+ * Executed automatically by the framework.
+ */
+function setupParentProcess(event) {
+ return ServerLoggerMonitor.attach(event);
+}
+
+// Monitor initialization.
+ServerLoggerMonitor.initialize();
+
+// Exports from this module
+exports.setupParentProcess = setupParentProcess;
diff --git a/devtools/shared/webconsole/server-logger.js b/devtools/shared/webconsole/server-logger.js
new file mode 100644
index 000000000..58a2f216a
--- /dev/null
+++ b/devtools/shared/webconsole/server-logger.js
@@ -0,0 +1,514 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {Ci} = require("chrome");
+const {Class} = require("sdk/core/heritage");
+const Services = require("Services");
+
+const {DebuggerServer} = require("devtools/server/main");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+
+loader.lazyGetter(this, "NetworkHelper", () => require("devtools/shared/webconsole/network-helper"));
+
+// Helper tracer. Should be generic sharable by other modules (bug 1171927)
+const trace = {
+ log: function () {
+ }
+};
+
+// Constants
+const makeInfallible = DevToolsUtils.makeInfallible;
+const acceptableHeaders = ["x-chromelogger-data"];
+
+/**
+ * The listener is responsible for detecting server side logs
+ * within HTTP headers and sending them to the client.
+ *
+ * The logic is based on "http-on-examine-response" event that is
+ * sent when a response from the server is received. Consequently HTTP
+ * headers are parsed to find server side logs.
+ *
+ * A listeners for "http-on-examine-response" is registered when
+ * the listener starts and removed when destroy is executed.
+ */
+var ServerLoggingListener = Class({
+ /**
+ * Initialization of the listener. The main step during the initialization
+ * process is registering a listener for "http-on-examine-response" event.
+ *
+ * @param {Object} win (nsIDOMWindow):
+ * filter network requests by the associated window object.
+ * If null (i.e. in the browser context) log everything
+ * @param {Object} owner
+ * The {@WebConsoleActor} instance
+ */
+ initialize: function (win, owner) {
+ trace.log("ServerLoggingListener.initialize; ", owner.actorID,
+ ", child process: ", DebuggerServer.isInChildProcess);
+
+ this.owner = owner;
+ this.window = win;
+
+ this.onExamineResponse = this.onExamineResponse.bind(this);
+ this.onExamineHeaders = this.onExamineHeaders.bind(this);
+ this.onParentMessage = this.onParentMessage.bind(this);
+
+ this.attach();
+ },
+
+ /**
+ * The destroy is called by the parent WebConsoleActor actor.
+ */
+ destroy: function () {
+ trace.log("ServerLoggingListener.destroy; ", this.owner.actorID,
+ ", child process: ", DebuggerServer.isInChildProcess);
+
+ this.detach();
+ },
+
+ /**
+ * The main responsibility of this method is registering a listener for
+ * "http-on-examine-response" events.
+ */
+ attach: makeInfallible(function () {
+ trace.log("ServerLoggingListener.attach; child process: ",
+ DebuggerServer.isInChildProcess);
+
+ // Setup the child <-> parent communication if this actor module
+ // is running in a child process. If e10s is disabled (this actor
+ // running in the same process as everything else) register observer
+ // listener just like in good old pre e10s days.
+ if (DebuggerServer.isInChildProcess) {
+ this.attachParentProcess();
+ } else {
+ Services.obs.addObserver(this.onExamineResponse,
+ "http-on-examine-response", false);
+ }
+ }),
+
+ /**
+ * Remove the "http-on-examine-response" listener.
+ */
+ detach: makeInfallible(function () {
+ trace.log("ServerLoggingListener.detach; ", this.owner.actorID);
+
+ if (DebuggerServer.isInChildProcess) {
+ this.detachParentProcess();
+ } else {
+ Services.obs.removeObserver(this.onExamineResponse,
+ "http-on-examine-response", false);
+ }
+ }),
+
+ // Parent Child Relationship
+
+ attachParentProcess: function () {
+ trace.log("ServerLoggingListener.attachParentProcess;");
+
+ this.owner.conn.setupInParent({
+ module: "devtools/shared/webconsole/server-logger-monitor",
+ setupParent: "setupParentProcess"
+ });
+
+ let mm = this.owner.conn.parentMessageManager;
+ let { addMessageListener, sendSyncMessage } = mm;
+
+ // It isn't possible to register HTTP-* event observer inside
+ // a child process (in case of e10s), so listen for messages
+ // coming from the {@ServerLoggerMonitor} that lives inside
+ // the parent process.
+ addMessageListener("debug:server-logger", this.onParentMessage);
+
+ // Attach to the {@ServerLoggerMonitor} object to subscribe events.
+ sendSyncMessage("debug:server-logger", {
+ method: "attachChild"
+ });
+ },
+
+ detachParentProcess: makeInfallible(function () {
+ trace.log("ServerLoggingListener.detachParentProcess;");
+
+ let mm = this.owner.conn.parentMessageManager;
+ let { removeMessageListener, sendSyncMessage } = mm;
+
+ sendSyncMessage("debug:server-logger", {
+ method: "detachChild",
+ });
+
+ removeMessageListener("debug:server-logger", this.onParentMessage);
+ }),
+
+ onParentMessage: makeInfallible(function (msg) {
+ if (!msg.data) {
+ return;
+ }
+
+ let method = msg.data.method;
+ trace.log("ServerLogger.onParentMessage; ", method, msg.data);
+
+ switch (method) {
+ case "examineHeaders":
+ this.onExamineHeaders(msg);
+ break;
+ default:
+ trace.log("Unknown method name: ", method);
+ }
+ }),
+
+ // HTTP Observer
+
+ onExamineHeaders: function (event) {
+ let headers = event.data.headers;
+
+ trace.log("ServerLoggingListener.onExamineHeaders;", headers);
+
+ let parsedMessages = [];
+
+ for (let item of headers) {
+ let header = item.header;
+ let value = item.value;
+
+ let messages = this.parse(header, value);
+ if (messages) {
+ parsedMessages.push(...messages);
+ }
+ }
+
+ if (!parsedMessages.length) {
+ return;
+ }
+
+ for (let message of parsedMessages) {
+ this.sendMessage(message);
+ }
+ },
+
+ onExamineResponse: makeInfallible(function (subject) {
+ let httpChannel = subject.QueryInterface(Ci.nsIHttpChannel);
+
+ trace.log("ServerLoggingListener.onExamineResponse; ", httpChannel.name,
+ ", ", this.owner.actorID, httpChannel);
+
+ if (!this._matchRequest(httpChannel)) {
+ trace.log("ServerLoggerMonitor.onExamineResponse; No matching request!");
+ return;
+ }
+
+ let headers = [];
+
+ httpChannel.visitResponseHeaders((header, value) => {
+ header = header.toLowerCase();
+ if (acceptableHeaders.indexOf(header) !== -1) {
+ headers.push({header: header, value: value});
+ }
+ });
+
+ this.onExamineHeaders({
+ data: {
+ headers: headers,
+ }
+ });
+ }),
+
+ /**
+ * Check if a given network request should be logged by this network monitor
+ * instance based on the current filters.
+ *
+ * @private
+ * @param nsIHttpChannel channel
+ * Request to check.
+ * @return boolean
+ * True if the network request should be logged, false otherwise.
+ */
+ _matchRequest: function (channel) {
+ trace.log("_matchRequest ", this.window, ", ", this.topFrame);
+
+ // Log everything if the window is null (it's null in the browser context)
+ if (!this.window) {
+ return true;
+ }
+
+ // Ignore requests from chrome or add-on code when we are monitoring
+ // content.
+ if (!channel.loadInfo &&
+ channel.loadInfo.loadingDocument === null &&
+ channel.loadInfo.loadingPrincipal ===
+ Services.scriptSecurityManager.getSystemPrincipal()) {
+ return false;
+ }
+
+ // Since frames support, this.window may not be the top level content
+ // frame, so that we can't only compare with win.top.
+ let win = NetworkHelper.getWindowForRequest(channel);
+ while (win) {
+ if (win == this.window) {
+ return true;
+ }
+ if (win.parent == win) {
+ break;
+ }
+ win = win.parent;
+ }
+
+ return false;
+ },
+
+ // Server Logs
+
+ /**
+ * Search through HTTP headers to catch all server side logs.
+ * Learn more about the data structure:
+ * https://craig.is/writing/chrome-logger/techspecs
+ */
+ parse: function (header, value) {
+ let data;
+
+ try {
+ let result = decodeURIComponent(escape(atob(value)));
+ data = JSON.parse(result);
+ } catch (err) {
+ console.error("Failed to parse HTTP log data! " + err);
+ return null;
+ }
+
+ let parsedMessage = [];
+ let columnMap = this.getColumnMap(data);
+
+ trace.log("ServerLoggingListener.parse; ColumnMap", columnMap);
+ trace.log("ServerLoggingListener.parse; data", data);
+
+ let lastLocation;
+
+ for (let row of data.rows) {
+ let backtrace = row[columnMap.get("backtrace")];
+ let rawLogs = row[columnMap.get("log")];
+ let type = row[columnMap.get("type")] || "log";
+
+ // Old version of the protocol includes a label.
+ // If this is the old version do some converting.
+ if (data.columns.indexOf("label") != -1) {
+ let label = row[columnMap.get("label")];
+ let showLabel = label && typeof label === "string";
+
+ rawLogs = [rawLogs];
+
+ if (showLabel) {
+ rawLogs.unshift(label);
+ }
+ }
+
+ // If multiple logs come from the same line only the first log
+ // has info about the backtrace. So, remember the last valid
+ // location and use it for those that not set.
+ let location = this.parseBacktrace(backtrace);
+ if (location) {
+ lastLocation = location;
+ } else {
+ location = lastLocation;
+ }
+
+ parsedMessage.push({
+ logs: rawLogs,
+ location: location,
+ type: type
+ });
+ }
+
+ return parsedMessage;
+ },
+
+ parseBacktrace: function (backtrace) {
+ if (!backtrace) {
+ return null;
+ }
+
+ let result = backtrace.match(/\s*(\d+)$/);
+ if (!result || result.length < 2) {
+ return backtrace;
+ }
+
+ return {
+ url: backtrace.slice(0, -result[0].length),
+ line: result[1]
+ };
+ },
+
+ getColumnMap: function (data) {
+ let columnMap = new Map();
+ let columnName;
+
+ for (let key in data.columns) {
+ columnName = data.columns[key];
+ columnMap.set(columnName, key);
+ }
+
+ return columnMap;
+ },
+
+ sendMessage: function (msg) {
+ trace.log("ServerLoggingListener.sendMessage; message", msg);
+
+ let formatted = format(msg);
+ trace.log("ServerLoggingListener.sendMessage; formatted", formatted);
+
+ let win = this.window;
+ let innerID = win ? getInnerId(win) : null;
+ let location = msg.location;
+
+ let message = {
+ category: "server",
+ innerID: innerID,
+ level: msg.type,
+ filename: location ? location.url : null,
+ lineNumber: location ? location.line : null,
+ columnNumber: 0,
+ private: false,
+ timeStamp: Date.now(),
+ arguments: formatted ? formatted.logs : null,
+ styles: formatted ? formatted.styles : null,
+ };
+
+ // Make sure to set the group name.
+ if (msg.type == "group" && formatted && formatted.logs) {
+ message.groupName = formatted ? formatted.logs[0] : null;
+ }
+
+ // A message for console.table() method (passed in as the first
+ // argument) isn't supported. But, it's passed in by some server
+ // side libraries that implement console.* API - let's just remove it.
+ let args = message.arguments;
+ if (msg.type == "table" && args) {
+ if (typeof args[0] == "string") {
+ args.shift();
+ }
+ }
+
+ trace.log("ServerLoggingListener.sendMessage; raw: ",
+ msg.logs.join(", "), message);
+
+ this.owner.onServerLogCall(message);
+ },
+});
+
+// Helpers
+
+/**
+ * Parse printf-like specifiers ("%f", "%d", ...) and
+ * format the logs according to them.
+ */
+function format(msg) {
+ if (!msg.logs || !msg.logs[0]) {
+ return null;
+ }
+
+ // Initialize the styles array (used for the "%c" specifier).
+ msg.styles = [];
+
+ // Remove and get the first log (in which the specifiers are).
+ // Note that the first string doesn't have to be specified.
+ // An example of a log on the server side:
+ // ChromePhp::log("server info: ", $_SERVER);
+ // ChromePhp::log($_SERVER);
+ let firstString = "";
+ if (typeof msg.logs[0] == "string") {
+ firstString = msg.logs.shift();
+ }
+
+ // All the specifiers present in the first string.
+ let splitLogRegExp = /(.*?)(%[oOcsdif]|$)/g;
+ let splitLogRegExpRes;
+ let concatString = "";
+ let pushConcatString = () => {
+ if (concatString) {
+ rebuiltLogArray.push(concatString);
+ }
+ concatString = "";
+ };
+
+ // This array represents the string of the log, in which the specifiers
+ // are replaced. It alternates strings and objects (%o;%O).
+ let rebuiltLogArray = [];
+
+ // Get the strings before the specifiers (or the last chunk before the end
+ // of the string).
+ while ((splitLogRegExpRes = splitLogRegExp.exec(firstString)) !== null) {
+ let [, log, specifier] = splitLogRegExpRes;
+
+ // We may start with a specifier or add consecutively several ones. In such
+ // a case, there is no log.
+ // Example: "%ctest" => first iteration: log = "", specifier = "%c".
+ // => second iteration: log = "test", specifier = "".
+ if (log) {
+ concatString += log;
+ }
+
+ // Break now if there is no specifier anymore
+ // (means that we have reached the end of the string).
+ if (!specifier) {
+ break;
+ }
+
+ let argument = msg.logs.shift();
+ switch (specifier) {
+ case "%i":
+ case "%d":
+ // Parse into integer.
+ concatString += (argument | 0);
+ break;
+ case "%f":
+ // Parse into float.
+ concatString += (+argument);
+ break;
+ case "%o":
+ case "%O":
+ // Push the concatenated string and reinitialize concatString.
+ pushConcatString();
+ // Push the object.
+ rebuiltLogArray.push(argument);
+ break;
+ case "%s":
+ concatString += argument;
+ break;
+ case "%c":
+ pushConcatString();
+ let fillNullArrayLength = rebuiltLogArray.length - msg.styles.length;
+ let fillNullArray = Array(fillNullArrayLength).fill(null);
+ msg.styles.push(...fillNullArray, argument);
+ break;
+ }
+ }
+
+ if (concatString) {
+ rebuiltLogArray.push(concatString);
+ }
+
+ // Append the rest of arguments that don't have corresponding
+ // specifiers to the message logs.
+ msg.logs.unshift(...rebuiltLogArray);
+
+ // Remove special ___class_name property that isn't supported
+ // by the current implementation. This property represents object class
+ // allowing custom rendering in the console panel.
+ for (let log of msg.logs) {
+ if (typeof log == "object") {
+ delete log.___class_name;
+ }
+ }
+
+ return msg;
+}
+
+// These helper are cloned from SDK to avoid loading to
+// much SDK modules just because of two functions.
+function getInnerId(win) {
+ return win.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils).currentInnerWindowID;
+}
+
+// Exports from this module
+exports.ServerLoggingListener = ServerLoggingListener;
diff --git a/devtools/shared/webconsole/test/chrome.ini b/devtools/shared/webconsole/test/chrome.ini
new file mode 100644
index 000000000..ae867b821
--- /dev/null
+++ b/devtools/shared/webconsole/test/chrome.ini
@@ -0,0 +1,41 @@
+[DEFAULT]
+tags = devtools
+support-files =
+ common.js
+ data.json
+ data.json^headers^
+ helper_serviceworker.js
+ network_requests_iframe.html
+ sandboxed_iframe.html
+ console-test-worker.js
+ !/browser/base/content/test/general/browser_star_hsts.sjs
+ !/browser/base/content/test/general/pinning_headers.sjs
+
+[test_basics.html]
+[test_bug819670_getter_throws.html]
+[test_cached_messages.html]
+[test_commands_other.html]
+[test_commands_registration.html]
+[test_consoleapi.html]
+[test_consoleapi_innerID.html]
+[test_console_serviceworker.html]
+[test_console_serviceworker_cached.html]
+[test_console_styling.html]
+[test_file_uri.html]
+[test_reflow.html]
+[test_jsterm.html]
+[test_jsterm_autocomplete.html]
+[test_jsterm_cd_iframe.html]
+[test_jsterm_last_result.html]
+[test_jsterm_queryselector.html]
+[test_network_get.html]
+[test_network_longstring.html]
+[test_network_post.html]
+[test_network_security-hpkp.html]
+[test_network_security-hsts.html]
+[test_nsiconsolemessage.html]
+[test_object_actor.html]
+[test_object_actor_native_getters.html]
+[test_object_actor_native_getters_lenient_this.html]
+[test_page_errors.html]
+[test_throw.html]
diff --git a/devtools/shared/webconsole/test/common.js b/devtools/shared/webconsole/test/common.js
new file mode 100644
index 000000000..b6649bc44
--- /dev/null
+++ b/devtools/shared/webconsole/test/common.js
@@ -0,0 +1,345 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
+
+const XHTML_NS = "http://www.w3.org/1999/xhtml";
+
+// This gives logging to stdout for tests
+var {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+var Services = require("Services");
+var WebConsoleUtils = require("devtools/client/webconsole/utils").Utils;
+var {Task} = require("devtools/shared/task");
+
+var ConsoleAPIStorage = Cc["@mozilla.org/consoleAPI-storage;1"]
+ .getService(Ci.nsIConsoleAPIStorage);
+var {DebuggerServer} = require("devtools/server/main");
+var {DebuggerClient, ObjectClient} = require("devtools/shared/client/main");
+
+var {ConsoleServiceListener, ConsoleAPIListener} =
+ require("devtools/server/actors/utils/webconsole-utils");
+
+function initCommon()
+{
+ // Services.prefs.setBoolPref("devtools.debugger.log", true);
+}
+
+function initDebuggerServer()
+{
+ if (!DebuggerServer.initialized) {
+ DebuggerServer.init();
+ DebuggerServer.addBrowserActors();
+ }
+ DebuggerServer.allowChromeProcess = true;
+}
+
+function connectToDebugger(aCallback)
+{
+ initCommon();
+ initDebuggerServer();
+
+ let transport = DebuggerServer.connectPipe();
+ let client = new DebuggerClient(transport);
+
+ let dbgState = { dbgClient: client };
+ client.connect().then(response => aCallback(dbgState, response));
+}
+
+function attachConsole(aListeners, aCallback) {
+ _attachConsole(aListeners, aCallback);
+}
+function attachConsoleToTab(aListeners, aCallback) {
+ _attachConsole(aListeners, aCallback, true);
+}
+function attachConsoleToWorker(aListeners, aCallback) {
+ _attachConsole(aListeners, aCallback, true, true);
+}
+
+function _attachConsole(aListeners, aCallback, aAttachToTab, aAttachToWorker)
+{
+ function _onAttachConsole(aState, aResponse, aWebConsoleClient)
+ {
+ if (aResponse.error) {
+ console.error("attachConsole failed: " + aResponse.error + " " +
+ aResponse.message);
+ }
+
+ aState.client = aWebConsoleClient;
+
+ aCallback(aState, aResponse);
+ }
+
+ connectToDebugger(function _onConnect(aState, aResponse) {
+ if (aResponse.error) {
+ console.error("client.connect() failed: " + aResponse.error + " " +
+ aResponse.message);
+ aCallback(aState, aResponse);
+ return;
+ }
+
+ if (aAttachToTab) {
+ aState.dbgClient.listTabs(function _onListTabs(aResponse) {
+ if (aResponse.error) {
+ console.error("listTabs failed: " + aResponse.error + " " +
+ aResponse.message);
+ aCallback(aState, aResponse);
+ return;
+ }
+ let tab = aResponse.tabs[aResponse.selected];
+ aState.dbgClient.attachTab(tab.actor, function (response, tabClient) {
+ if (aAttachToWorker) {
+ let workerName = "console-test-worker.js#" + new Date().getTime();
+ var worker = new Worker(workerName);
+ // Keep a strong reference to the Worker to avoid it being
+ // GCd during the test (bug 1237492).
+ aState._worker_ref = worker;
+ worker.addEventListener("message", function listener() {
+ worker.removeEventListener("message", listener);
+ tabClient.listWorkers(function (response) {
+ let worker = response.workers.filter(w => w.url == workerName)[0];
+ if (!worker) {
+ console.error("listWorkers failed. Unable to find the " +
+ "worker actor\n");
+ return;
+ }
+ tabClient.attachWorker(worker.actor, function (response, workerClient) {
+ if (!workerClient || response.error) {
+ console.error("attachWorker failed. No worker client or " +
+ " error: " + response.error);
+ return;
+ }
+ workerClient.attachThread({}, function (aResponse) {
+ aState.actor = workerClient.consoleActor;
+ aState.dbgClient.attachConsole(workerClient.consoleActor, aListeners,
+ _onAttachConsole.bind(null, aState));
+ });
+ });
+ });
+ });
+ } else {
+ aState.actor = tab.consoleActor;
+ aState.dbgClient.attachConsole(tab.consoleActor, aListeners,
+ _onAttachConsole.bind(null, aState));
+ }
+ });
+ });
+ } else {
+ aState.dbgClient.getProcess().then(response => {
+ aState.dbgClient.attachTab(response.form.actor, function () {
+ let consoleActor = response.form.consoleActor;
+ aState.actor = consoleActor;
+ aState.dbgClient.attachConsole(consoleActor, aListeners,
+ _onAttachConsole.bind(null, aState));
+ });
+ });
+ }
+ });
+}
+
+function closeDebugger(aState, aCallback)
+{
+ aState.dbgClient.close().then(aCallback);
+ aState.dbgClient = null;
+ aState.client = null;
+}
+
+function checkConsoleAPICalls(consoleCalls, expectedConsoleCalls)
+{
+ is(consoleCalls.length, expectedConsoleCalls.length,
+ "received correct number of console calls");
+ expectedConsoleCalls.forEach(function (aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+}
+
+function checkConsoleAPICall(aCall, aExpected)
+{
+ if (aExpected.level != "trace" && aExpected.arguments) {
+ is(aCall.arguments.length, aExpected.arguments.length,
+ "number of arguments");
+ }
+
+ checkObject(aCall, aExpected);
+}
+
+function checkObject(aObject, aExpected)
+{
+ for (let name of Object.keys(aExpected))
+ {
+ let expected = aExpected[name];
+ let value = aObject[name];
+ checkValue(name, value, expected);
+ }
+}
+
+function checkValue(aName, aValue, aExpected)
+{
+ if (aExpected === null) {
+ ok(!aValue, "'" + aName + "' is null");
+ }
+ else if (aValue === undefined) {
+ ok(false, "'" + aName + "' is undefined");
+ }
+ else if (aValue === null) {
+ ok(false, "'" + aName + "' is null");
+ }
+ else if (typeof aExpected == "string" || typeof aExpected == "number" ||
+ typeof aExpected == "boolean") {
+ is(aValue, aExpected, "property '" + aName + "'");
+ }
+ else if (aExpected instanceof RegExp) {
+ ok(aExpected.test(aValue), aName + ": " + aExpected + " matched " + aValue);
+ }
+ else if (Array.isArray(aExpected)) {
+ info("checking array for property '" + aName + "'");
+ checkObject(aValue, aExpected);
+ }
+ else if (typeof aExpected == "object") {
+ info("checking object for property '" + aName + "'");
+ checkObject(aValue, aExpected);
+ }
+}
+
+function checkHeadersOrCookies(aArray, aExpected)
+{
+ let foundHeaders = {};
+
+ for (let elem of aArray) {
+ if (!(elem.name in aExpected)) {
+ continue;
+ }
+ foundHeaders[elem.name] = true;
+ info("checking value of header " + elem.name);
+ checkValue(elem.name, elem.value, aExpected[elem.name]);
+ }
+
+ for (let header in aExpected) {
+ if (!(header in foundHeaders)) {
+ ok(false, header + " was not found");
+ }
+ }
+}
+
+function checkRawHeaders(aText, aExpected)
+{
+ let headers = aText.split(/\r\n|\n|\r/);
+ let arr = [];
+ for (let header of headers) {
+ let index = header.indexOf(": ");
+ if (index < 0) {
+ continue;
+ }
+ arr.push({
+ name: header.substr(0, index),
+ value: header.substr(index + 2)
+ });
+ }
+
+ checkHeadersOrCookies(arr, aExpected);
+}
+
+var gTestState = {};
+
+function runTests(aTests, aEndCallback)
+{
+ function* driver()
+ {
+ let lastResult, sendToNext;
+ for (let i = 0; i < aTests.length; i++) {
+ gTestState.index = i;
+ let fn = aTests[i];
+ info("will run test #" + i + ": " + fn.name);
+ lastResult = fn(sendToNext, lastResult);
+ sendToNext = yield lastResult;
+ }
+ yield aEndCallback(sendToNext, lastResult);
+ }
+ gTestState.driver = driver();
+ return gTestState.driver.next();
+}
+
+function nextTest(aMessage)
+{
+ return gTestState.driver.next(aMessage);
+}
+
+function withFrame(url) {
+ return new Promise(resolve => {
+ let iframe = document.createElement("iframe");
+ iframe.onload = function () {
+ resolve(iframe);
+ };
+ iframe.src = url;
+ document.body.appendChild(iframe);
+ });
+}
+
+function navigateFrame(iframe, url) {
+ return new Promise(resolve => {
+ iframe.onload = function () {
+ resolve(iframe);
+ };
+ iframe.src = url;
+ });
+}
+
+function forceReloadFrame(iframe) {
+ return new Promise(resolve => {
+ iframe.onload = function () {
+ resolve(iframe);
+ };
+ iframe.contentWindow.location.reload(true);
+ });
+}
+
+function withActiveServiceWorker(win, url, scope) {
+ let opts = {};
+ if (scope) {
+ opts.scope = scope;
+ }
+ return win.navigator.serviceWorker.register(url, opts).then(swr => {
+ if (swr.active) {
+ return swr;
+ }
+
+ // Unfortunately we can't just use navigator.serviceWorker.ready promise
+ // here. If the service worker is for a scope that does not cover the window
+ // then the ready promise will never resolve. Instead monitor the service
+ // workers state change events to determine when its activated.
+ return new Promise(resolve => {
+ let sw = swr.waiting || swr.installing;
+ sw.addEventListener("statechange", function stateHandler(evt) {
+ if (sw.state === "activated") {
+ sw.removeEventListener("statechange", stateHandler);
+ resolve(swr);
+ }
+ });
+ });
+ });
+}
+
+function messageServiceWorker(win, scope, message) {
+ return win.navigator.serviceWorker.getRegistration(scope).then(swr => {
+ return new Promise(resolve => {
+ win.navigator.serviceWorker.onmessage = evt => {
+ resolve();
+ };
+ let sw = swr.active || swr.waiting || swr.installing;
+ sw.postMessage({ type: "PING", message: message });
+ });
+ });
+}
+
+function unregisterServiceWorker(win) {
+ return win.navigator.serviceWorker.ready.then(swr => {
+ return swr.unregister();
+ });
+}
diff --git a/devtools/shared/webconsole/test/console-test-worker.js b/devtools/shared/webconsole/test/console-test-worker.js
new file mode 100644
index 000000000..881eab0b8
--- /dev/null
+++ b/devtools/shared/webconsole/test/console-test-worker.js
@@ -0,0 +1,16 @@
+"use strict";
+
+function f() {
+ var a = 1;
+ var b = 2;
+ var c = 3;
+}
+
+self.onmessage = function (event) {
+ if (event.data == "ping") {
+ f();
+ postMessage("pong");
+ }
+};
+
+postMessage("load");
diff --git a/devtools/shared/webconsole/test/data.json b/devtools/shared/webconsole/test/data.json
new file mode 100644
index 000000000..d46085c12
--- /dev/null
+++ b/devtools/shared/webconsole/test/data.json
@@ -0,0 +1,3 @@
+{ id: "test JSON data", myArray: [ "foo", "bar", "baz", "biff" ],
+ veryLong: "foo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo barfoo bar"
+}
diff --git a/devtools/shared/webconsole/test/data.json^headers^ b/devtools/shared/webconsole/test/data.json^headers^
new file mode 100644
index 000000000..bb6b45500
--- /dev/null
+++ b/devtools/shared/webconsole/test/data.json^headers^
@@ -0,0 +1,3 @@
+Content-Type: application/json
+x-very-long: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse a ipsum massa. Phasellus at elit dictum libero laoreet sagittis. Phasellus condimentum ultricies imperdiet. Nam eu ligula justo, ut tincidunt quam. Etiam sollicitudin, tortor sed egestas blandit, sapien sem tincidunt nulla, eu luctus libero odio quis leo. Nam elit massa, mattis quis blandit ac, facilisis vitae arcu. Donec vitae dictum neque. Proin ornare nisl at lectus commodo iaculis eget eget est. Quisque scelerisque vestibulum quam sed interdum.
+x-very-short: hello world
diff --git a/devtools/shared/webconsole/test/helper_serviceworker.js b/devtools/shared/webconsole/test/helper_serviceworker.js
new file mode 100644
index 000000000..633f3e09b
--- /dev/null
+++ b/devtools/shared/webconsole/test/helper_serviceworker.js
@@ -0,0 +1,19 @@
+console.log("script evaluation");
+
+addEventListener("install", function (evt) {
+ console.log("install event");
+});
+
+addEventListener("activate", function (evt) {
+ console.log("activate event");
+});
+
+addEventListener("fetch", function (evt) {
+ console.log("fetch event: " + evt.request.url);
+ evt.respondWith(new Response("Hello world"));
+});
+
+addEventListener("message", function (evt) {
+ console.log("message event: " + evt.data.message);
+ evt.source.postMessage({ type: "PONG" });
+});
diff --git a/devtools/shared/webconsole/test/network_requests_iframe.html b/devtools/shared/webconsole/test/network_requests_iframe.html
new file mode 100644
index 000000000..9147b6720
--- /dev/null
+++ b/devtools/shared/webconsole/test/network_requests_iframe.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <title>Console HTTP test page</title>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+ <script type="text/javascript"><!--
+ var setAllowAllCookies = false;
+
+ function makeXhr(aMethod, aUrl, aRequestBody, aCallback) {
+ // On the first call, allow all cookies and set cookies, then resume the actual test
+ if (!setAllowAllCookies)
+ SpecialPowers.pushPrefEnv({"set": [["network.cookie.cookieBehavior", 0]]}, function () {
+ setAllowAllCookies = true;
+ setCookies();
+ makeXhrCallback(aMethod, aUrl, aRequestBody, aCallback);
+ });
+ else
+ makeXhrCallback(aMethod, aUrl, aRequestBody, aCallback);
+ }
+
+ function makeXhrCallback(aMethod, aUrl, aRequestBody, aCallback) {
+ var xmlhttp = new XMLHttpRequest();
+ xmlhttp.open(aMethod, aUrl, true);
+ if (aCallback) {
+ xmlhttp.onreadystatechange = function() {
+ if (xmlhttp.readyState == 4) {
+ aCallback();
+ }
+ };
+ }
+ xmlhttp.send(aRequestBody);
+ }
+
+ function testXhrGet(aCallback) {
+ makeXhr('get', 'data.json', null, aCallback);
+ }
+
+ function testXhrPost(aCallback) {
+ var body = "Hello world! " + (new Array(50)).join("foobaz barr");
+ makeXhr('post', 'data.json', body, aCallback);
+ }
+
+ function setCookies() {
+ document.cookie = "foobar=fooval";
+ document.cookie = "omgfoo=bug768096";
+ document.cookie = "badcookie=bug826798=st3fan";
+ }
+ // --></script>
+ </head>
+ <body>
+ <h1>Web Console HTTP Logging Testpage</h1>
+ <h2>This page is used to test the HTTP logging.</h2>
+
+ <form action="?" method="post">
+ <input name="name" type="text" value="foo bar"><br>
+ <input name="age" type="text" value="144"><br>
+ </form>
+ </body>
+</html>
diff --git a/devtools/shared/webconsole/test/sandboxed_iframe.html b/devtools/shared/webconsole/test/sandboxed_iframe.html
new file mode 100644
index 000000000..55a6224b5
--- /dev/null
+++ b/devtools/shared/webconsole/test/sandboxed_iframe.html
@@ -0,0 +1,8 @@
+<html>
+<head><title>Sandboxed iframe</title></head>
+<body>
+ <iframe id="sandboxed-iframe"
+ sandbox="allow-scripts"
+ srcdoc="<script>var foobarObject = {bug1051224: 'sandboxed'};</script>"></iframe>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_basics.html b/devtools/shared/webconsole/test/test_basics.html
new file mode 100644
index 000000000..fa54557ae
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_basics.html
@@ -0,0 +1,80 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Basic Web Console Actor tests</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Basic Web Console Actor tests</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["PageError"], onStartPageError);
+}
+
+function onStartPageError(aState, aResponse)
+{
+ is(aResponse.startedListeners.length, 1, "startedListeners.length");
+ is(aResponse.startedListeners[0], "PageError", "startedListeners: PageError");
+ ok(aResponse.nativeConsoleAPI, "nativeConsoleAPI");
+
+ closeDebugger(aState, function() {
+ top.console_ = top.console;
+ top.console = { lolz: "foo" };
+ attachConsoleToTab(["PageError", "ConsoleAPI", "foo"],
+ onStartPageErrorAndConsoleAPI);
+ });
+}
+
+function onStartPageErrorAndConsoleAPI(aState, aResponse)
+{
+ let startedListeners = aResponse.startedListeners;
+ is(startedListeners.length, 2, "startedListeners.length");
+ isnot(startedListeners.indexOf("PageError"), -1, "startedListeners: PageError");
+ isnot(startedListeners.indexOf("ConsoleAPI"), -1,
+ "startedListeners: ConsoleAPI");
+ is(startedListeners.indexOf("foo"), -1, "startedListeners: no foo");
+ ok(!aResponse.nativeConsoleAPI, "!nativeConsoleAPI");
+
+ top.console = top.console_;
+ aState.client.stopListeners(["ConsoleAPI", "foo"],
+ onStopConsoleAPI.bind(null, aState));
+}
+
+function onStopConsoleAPI(aState, aResponse)
+{
+ is(aResponse.stoppedListeners.length, 1, "stoppedListeners.length");
+ is(aResponse.stoppedListeners[0], "ConsoleAPI", "stoppedListeners: ConsoleAPI");
+
+ closeDebugger(aState, function() {
+ attachConsoleToTab(["ConsoleAPI"], onStartConsoleAPI);
+ });
+}
+
+function onStartConsoleAPI(aState, aResponse)
+{
+ is(aResponse.startedListeners.length, 1, "startedListeners.length");
+ is(aResponse.startedListeners[0], "ConsoleAPI", "startedListeners: ConsoleAPI");
+ ok(aResponse.nativeConsoleAPI, "nativeConsoleAPI");
+
+ top.console = top.console_;
+ delete top.console_;
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_bug819670_getter_throws.html b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
new file mode 100644
index 000000000..1b45c2d88
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_bug819670_getter_throws.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for Bug 819670 - Web console object inspection does not handle native getters throwing very well</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsoleToTab([], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onEvaluate = onEvaluate.bind(null, aState);
+ aState.client.evaluateJS("document.__proto__", onEvaluate);
+}
+
+function onEvaluate(aState, aResponse)
+{
+ checkObject(aResponse, {
+ from: aState.actor,
+ input: "document.__proto__",
+ result: {
+ type: "object",
+ actor: /[a-z]/,
+ },
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+ ok(!aResponse.helperResult, "no helper result");
+
+ onInspect = onInspect.bind(null, aState);
+ let client = new ObjectClient(aState.dbgClient, aResponse.result);
+ client.getPrototypeAndProperties(onInspect);
+}
+
+function onInspect(aState, aResponse)
+{
+ ok(!aResponse.error, "no response error");
+
+ let expectedProps = {
+ "addBroadcastListenerFor": { value: { type: "object" } },
+ "commandDispatcher": { get: { type: "object" } },
+ "getBoxObjectFor": { value: { type: "object" } },
+ "getElementsByAttribute": { value: { type: "object" } },
+ };
+
+ let props = aResponse.ownProperties;
+ ok(props, "response properties available");
+
+ if (props) {
+ ok(Object.keys(props).length > Object.keys(expectedProps).length,
+ "number of enumerable properties");
+ checkObject(props, expectedProps);
+ }
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_cached_messages.html b/devtools/shared/webconsole/test/test_cached_messages.html
new file mode 100644
index 000000000..210543ca3
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_cached_messages.html
@@ -0,0 +1,230 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for cached messages</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for cached messages</p>
+
+<script class="testbody" type="application/javascript;version=1.8">
+let expectedConsoleCalls = [];
+let expectedPageErrors = [];
+
+function doPageErrors()
+{
+ Services.console.reset();
+
+ expectedPageErrors = [
+ {
+ _type: "PageError",
+ errorMessage: /fooColor/,
+ sourceName: /.+/,
+ category: "CSS Parser",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: true,
+ exception: false,
+ strict: false,
+ },
+ {
+ _type: "PageError",
+ errorMessage: /doTheImpossible/,
+ sourceName: /.+/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ strict: false,
+ },
+ ];
+
+ let container = document.createElement("script");
+ document.body.appendChild(container);
+ container.textContent = "document.body.style.color = 'fooColor';";
+ document.body.removeChild(container);
+
+ SimpleTest.expectUncaughtException();
+
+ container = document.createElement("script");
+ document.body.appendChild(container);
+ container.textContent = "document.doTheImpossible();";
+ document.body.removeChild(container);
+}
+
+function doConsoleCalls()
+{
+ ConsoleAPIStorage.clearEvents();
+
+ top.console.log("foobarBaz-log", undefined);
+ top.console.info("foobarBaz-info", null);
+ top.console.warn("foobarBaz-warn", document.body);
+
+ expectedConsoleCalls = [
+ {
+ _type: "ConsoleAPI",
+ level: "log",
+ filename: /test_cached_messages/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-log", { type: "undefined" }],
+ },
+ {
+ _type: "ConsoleAPI",
+ level: "info",
+ filename: /test_cached_messages/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-info", { type: "null" }],
+ },
+ {
+ _type: "ConsoleAPI",
+ level: "warn",
+ filename: /test_cached_messages/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
+ },
+ ];
+}
+</script>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let consoleAPIListener, consoleServiceListener;
+let consoleAPICalls = 0;
+let pageErrors = 0;
+
+let handlers = {
+ onConsoleAPICall: function onConsoleAPICall(aMessage)
+ {
+ for (let msg of expectedConsoleCalls) {
+ if (msg.functionName == aMessage.functionName &&
+ msg.filename.test(aMessage.filename)) {
+ consoleAPICalls++;
+ break;
+ }
+ }
+ if (consoleAPICalls == expectedConsoleCalls.length) {
+ checkConsoleAPICache();
+ }
+ },
+
+ onConsoleServiceMessage: function onConsoleServiceMessage(aMessage)
+ {
+ if (!(aMessage instanceof Ci.nsIScriptError)) {
+ return;
+ }
+ for (let msg of expectedPageErrors) {
+ if (msg.category == aMessage.category &&
+ msg.errorMessage.test(aMessage.errorMessage)) {
+ pageErrors++;
+ break;
+ }
+ }
+ if (pageErrors == expectedPageErrors.length) {
+ testPageErrors();
+ }
+ },
+};
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ consoleAPIListener = new ConsoleAPIListener(top, handlers);
+ consoleAPIListener.init();
+
+ doConsoleCalls();
+}
+
+function checkConsoleAPICache()
+{
+ consoleAPIListener.destroy();
+ consoleAPIListener = null;
+ attachConsole(["ConsoleAPI"], onAttach1);
+}
+
+function onAttach1(aState, aResponse)
+{
+ aState.client.getCachedMessages(["ConsoleAPI"],
+ onCachedConsoleAPI.bind(null, aState));
+}
+
+function onCachedConsoleAPI(aState, aResponse)
+{
+ let msgs = aResponse.messages;
+ info("cached console messages: " + msgs.length);
+
+ ok(msgs.length >= expectedConsoleCalls.length,
+ "number of cached console messages");
+
+ for (let msg of msgs) {
+ for (let expected of expectedConsoleCalls) {
+ if (expected.functionName == msg.functionName &&
+ expected.filename.test(msg.filename)) {
+ expectedConsoleCalls.splice(expectedConsoleCalls.indexOf(expected));
+ checkConsoleAPICall(msg, expected);
+ break;
+ }
+ }
+ }
+
+ is(expectedConsoleCalls.length, 0, "all expected messages have been found");
+
+ closeDebugger(aState, function() {
+ consoleServiceListener = new ConsoleServiceListener(null, handlers);
+ consoleServiceListener.init();
+ doPageErrors();
+ });
+}
+
+function testPageErrors()
+{
+ consoleServiceListener.destroy();
+ consoleServiceListener = null;
+ attachConsole(["PageError"], onAttach2);
+}
+
+function onAttach2(aState, aResponse)
+{
+ aState.client.getCachedMessages(["PageError"],
+ onCachedPageErrors.bind(null, aState));
+}
+
+function onCachedPageErrors(aState, aResponse)
+{
+ let msgs = aResponse.messages;
+ info("cached page errors: " + msgs.length);
+
+ ok(msgs.length >= expectedPageErrors.length,
+ "number of cached page errors");
+
+ for (let msg of msgs) {
+ for (let expected of expectedPageErrors) {
+ if (expected.category == msg.category &&
+ expected.errorMessage.test(msg.errorMessage)) {
+ expectedPageErrors.splice(expectedPageErrors.indexOf(expected));
+ checkObject(msg, expected);
+ break;
+ }
+ }
+ }
+
+ is(expectedPageErrors.length, 0, "all expected messages have been found");
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_commands_other.html b/devtools/shared/webconsole/test/test_commands_other.html
new file mode 100644
index 000000000..47d1142c9
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_commands_other.html
@@ -0,0 +1,83 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the other command helpers</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the querySelector / querySelectorAll helpers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+let gState;
+let gWin;
+let tests;
+
+function evaluateJS(input) {
+ return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
+}
+
+function startTest() {
+ info ("Content window opened, attaching console to it");
+
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+ ok (!gWin.document.nodePrincipal.equals(systemPrincipal),
+ "The test document is not using the system principal");
+
+ attachConsoleToTab([], state => {
+ gState = state;
+ runTests(tests, testEnd);
+ });
+}
+
+tests = [
+ Task.async(function* keys() {
+ let response = yield evaluateJS("keys({foo: 'bar'})");
+ checkObject(response, {
+ from: gState.actor,
+ result: {
+ class: "Array",
+ preview: {
+ items: ["foo"]
+ }
+ }
+ });
+ nextTest();
+ }),
+ Task.async(function* values() {
+ let response = yield evaluateJS("values({foo: 'bar'})");
+ checkObject(response, {
+ from: gState.actor,
+ result: {
+ class: "Array",
+ preview: {
+ items: ["bar"]
+ }
+ }
+ });
+ nextTest();
+ }),
+];
+
+function testEnd() {
+ gWin.close();
+ gWin = null;
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+}
+
+window.onload = function() {
+ // Open a content window to test XRay functionality on built in functions.
+ gWin = window.open("data:text/html,");
+ info ("Waiting for content window to load");
+ gWin.onload = startTest;
+}
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_commands_registration.html b/devtools/shared/webconsole/test/test_commands_registration.html
new file mode 100644
index 000000000..2a68f7cdc
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_commands_registration.html
@@ -0,0 +1,191 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for Web Console commands registration.</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for Web Console commands registration.</p>
+<p id="quack"></p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gState;
+let tests;
+
+let {WebConsoleCommands} = require("devtools/server/actors/utils/webconsole-utils");
+
+function evaluateJS(input) {
+ return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
+}
+
+function* evaluateJSAndCheckResult(input, result) {
+ let response = yield evaluateJS(input);
+ checkObject(response, {result});
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ gState = aState;
+
+ runTests(tests, testEnd);
+}
+
+tests = [
+ Task.async(function* registerNewCommand() {
+ let win;
+ WebConsoleCommands.register("setFoo", (owner, value) => {
+ owner.window.foo = value;
+ return "ok";
+ });
+
+ ok(WebConsoleCommands.hasCommand("setFoo"),
+ "The command should be registered");
+
+ let command = "setFoo('bar')";
+ let response = yield evaluateJS(command);
+
+ checkObject(response, {
+ from: gState.actor,
+ input: command,
+ result: "ok"
+ });
+ is(top.foo, "bar", "top.foo should equal to 'bar'");
+ nextTest();
+ }),
+
+ Task.async(function* wrapCommand() {
+ let origKeys = WebConsoleCommands.getCommand("keys");
+
+ let newKeys = (...args) => {
+ let [owner, arg0] = args;
+ if (arg0 === ">o_/") {
+ return "bang!";
+ }
+ else {
+ return origKeys(...args);
+ }
+ };
+
+ WebConsoleCommands.register("keys", newKeys);
+ is(WebConsoleCommands.getCommand("keys"), newKeys,
+ "the keys() command should have been replaced");
+
+ let response = yield evaluateJS("keys('>o_/')");
+ checkObject(response, {
+ from: gState.actor,
+ result: "bang!"
+ });
+
+ response = yield evaluateJS("keys({foo: 'bar'})");
+ checkObject(response, {
+ from: gState.actor,
+ result: {
+ class: "Array",
+ preview: {
+ items: ["foo"]
+ }
+ }
+ });
+
+ WebConsoleCommands.register("keys", origKeys);
+ is(WebConsoleCommands.getCommand("keys"), origKeys,
+ "the keys() command should be restored");
+ nextTest();
+ }),
+
+ Task.async(function* unregisterCommand() {
+ WebConsoleCommands.unregister("setFoo");
+
+ let response = yield evaluateJS("setFoo");
+
+ checkObject(response, {
+ from: gState.actor,
+ input: "setFoo",
+ result: {
+ type: "undefined"
+ },
+ exceptionMessage: /setFoo is not defined/
+ });
+ nextTest();
+ }),
+
+ Task.async(function* registerAccessor() {
+ WebConsoleCommands.register("$foo", {
+ get(owner) {
+ let foo = owner.window.frames[0].window.document.getElementById("quack");
+ return owner.makeDebuggeeValue(foo);
+ }
+ });
+ let command = "$foo.textContent = '>o_/'";
+ let response = yield evaluateJS(command);
+
+ checkObject(response, {
+ from: gState.actor,
+ input: command,
+ result: ">o_/"
+ });
+ is(document.getElementById("quack").textContent, ">o_/",
+ "#foo textContent should equal to \">o_/\"");
+ WebConsoleCommands.unregister("$foo");
+ ok(!WebConsoleCommands.hasCommand("$foo"), "$foo should be unregistered");
+ nextTest();
+ }),
+
+ Task.async(function* unregisterAfterOverridingTwice() {
+ WebConsoleCommands.register("keys", (owner, obj) => "command 1");
+ info("checking the value of the first override");
+ yield evaluateJSAndCheckResult("keys('foo');", "command 1");
+
+ let orig = WebConsoleCommands.getCommand("keys");
+ WebConsoleCommands.register("keys", (owner, obj) => {
+ if (obj === "quack")
+ return "bang!";
+ return orig(owner, obj);
+ });
+
+ info("checking the values after the second override");
+ yield evaluateJSAndCheckResult("keys({});", "command 1");
+ yield evaluateJSAndCheckResult("keys('quack');", "bang!");
+
+ WebConsoleCommands.unregister("keys");
+
+ info("checking the value after unregistration (should restore " +
+ "the original command)");
+ yield evaluateJSAndCheckResult("keys({});", {
+ class: "Array",
+ preview: {items: []}
+ });
+ nextTest();
+
+ })
+];
+
+function testEnd()
+{
+ // If this is the first run, reload the page and do it again.
+ // Otherwise, end the test.
+ delete top.foo;
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
+
diff --git a/devtools/shared/webconsole/test/test_console_serviceworker.html b/devtools/shared/webconsole/test/test_console_serviceworker.html
new file mode 100644
index 000000000..83d728d92
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_serviceworker.html
@@ -0,0 +1,157 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the Console API and Service Workers</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the Console API and Service Workers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/";
+let SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js";
+let SCOPE = BASE_URL + "foo/";
+let NONSCOPE_FRAME_URL = BASE_URL + "sandboxed_iframe.html";
+let SCOPE_FRAME_URL = SCOPE + "fake.html";
+let SCOPE_FRAME_URL2 = SCOPE + "whatsit.html";
+let MESSAGE = 'Tic Tock';
+
+let expectedConsoleCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['script evaluation'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['install event'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['activate event'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['fetch event: ' + SCOPE_FRAME_URL],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['fetch event: ' + SCOPE_FRAME_URL2],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['message event: ' + MESSAGE],
+ },
+];
+let consoleCalls = [];
+
+let startTest = Task.async(function*() {
+ removeEventListener("load", startTest);
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["devtools.webconsole.filter.serviceworkers", true]
+ ]}, resolve);
+ });
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+});
+addEventListener("load", startTest);
+
+let onAttach = Task.async(function*(state, response) {
+ onConsoleAPICall = onConsoleAPICall.bind(null, state);
+ state.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+
+ let currentFrame;
+ try {
+ // First, we need a frame from which to register our script. This
+ // will not trigger any console calls.
+ info("Loading a non-scope frame from which to register a service worker.");
+ currentFrame = yield withFrame(NONSCOPE_FRAME_URL);
+
+ // Now register the service worker and wait for it to become
+ // activate. This should trigger 3 console calls; 1 for script
+ // evaluation, 1 for the install event, and 1 for the activate
+ // event. These console calls are received because we called
+ // register(), not because we are in scope for the worker.
+ info("Registering the service worker");
+ yield withActiveServiceWorker(currentFrame.contentWindow,
+ SERVICE_WORKER_URL, SCOPE);
+ ok(!currentFrame.contentWindow.navigator.serviceWorker.controller,
+ 'current frame should not be controlled');
+
+ // Now that the service worker is activate, lets navigate our frame.
+ // This will trigger 1 more console call for the fetch event.
+ info("Service worker registered. Navigating frame.");
+ yield navigateFrame(currentFrame, SCOPE_FRAME_URL);
+ ok(currentFrame.contentWindow.navigator.serviceWorker.controller,
+ 'navigated frame should be controlled');
+
+ // We now have a controlled frame. Lets perform a non-navigation fetch.
+ // This should produce another console call for the fetch event.
+ info("Frame navigated. Calling fetch().");
+ yield currentFrame.contentWindow.fetch(SCOPE_FRAME_URL2);
+
+ // Now force refresh our controlled frame. This will cause the frame
+ // to bypass the service worker and become an uncontrolled frame. It
+ // also happens to make the frame display a 404 message because the URL
+ // does not resolve to a real resource. This is ok, as we really only
+ // care about the frame being non-controlled, but still having a location
+ // that matches our service worker scope so we can provide its not
+ // incorrectly getting console calls.
+ info("Completed fetch(). Force refreshing to get uncontrolled frame.");
+ yield forceReloadFrame(currentFrame);
+ ok(!currentFrame.contentWindow.navigator.serviceWorker.controller,
+ 'current frame should not be controlled after force refresh');
+ is(currentFrame.contentWindow.location.toString(), SCOPE_FRAME_URL,
+ 'current frame should still have in-scope location URL even though it got 404');
+
+ // Now postMessage() the service worker to trigger its message event
+ // handler. This will generate 1 or 2 to console.log() statements
+ // depending on if the worker thread needs to spin up again. Although we
+ // don't have a controlled or registering document in both cases, we still
+ // could get console calls since we only flush reports when the channel is
+ // finally destroyed.
+ info("Completed force refresh. Messaging service worker.");
+ yield messageServiceWorker(currentFrame.contentWindow, SCOPE, MESSAGE);
+
+ info("Done messaging service worker. Unregistering service worker.");
+ yield unregisterServiceWorker(currentFrame.contentWindow);
+
+ info('Service worker unregistered. Checking console calls.');
+ state.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+ checkConsoleAPICalls(consoleCalls, expectedConsoleCalls);
+ } catch(error) {
+ ok(false, 'unexpected error: ' + error);
+ } finally {
+ if (currentFrame) {
+ currentFrame.remove();
+ currentFrame = null;
+ }
+ consoleCalls = [];
+ closeDebugger(state, function() {
+ SimpleTest.finish();
+ });
+ }
+});
+
+function onConsoleAPICall(state, type, packet) {
+ info("received message level: " + packet.message.level);
+ is(packet.from, state.actor, "console API call actor");
+ consoleCalls.push(packet.message);
+}
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_console_serviceworker_cached.html b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html
new file mode 100644
index 000000000..5aab64d7f
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html
@@ -0,0 +1,117 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for getCachedMessages and Service Workers</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for getCachedMessages and Service Workers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let BASE_URL = "https://example.com/chrome/devtools/shared/webconsole/test/";
+let SERVICE_WORKER_URL = BASE_URL + "helper_serviceworker.js";
+let FRAME_URL = BASE_URL + "sandboxed_iframe.html";
+
+let firstTabExpectedCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['script evaluation'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['install event'],
+ },
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['activate event'],
+ },
+];
+
+let secondTabExpectedCalls = [
+ {
+ level: "log",
+ filename: /helper_serviceworker/,
+ arguments: ['fetch event: ' + FRAME_URL],
+ }
+];
+
+let startTest = Task.async(function*() {
+ removeEventListener("load", startTest);
+
+ yield new Promise(resolve => {
+ SpecialPowers.pushPrefEnv({"set": [
+ ["dom.serviceWorkers.enabled", true],
+ ["devtools.webconsole.filter.serviceworkers", true]
+ ]}, resolve);
+ });
+
+ info("Adding a tab and attaching a service worker");
+ let tab1 = yield addTab(FRAME_URL);
+ let swr = yield withActiveServiceWorker(tab1.linkedBrowser.contentWindow,
+ SERVICE_WORKER_URL);
+
+ yield new Promise(resolve => {
+ info("Attaching console to tab 1");
+ attachConsoleToTab(["ConsoleAPI"], function(state) {
+ state.client.getCachedMessages(["ConsoleAPI"], function(calls) {
+ checkConsoleAPICalls(calls.messages, firstTabExpectedCalls);
+ closeDebugger(state, resolve);
+ });
+ });
+ });
+
+ // Because this tab is being added after the original messages happened,
+ // they shouldn't show up in a call to getCachedMessages.
+ // However, there is a fetch event which is logged due to loading the tab.
+ info("Adding a new tab at the same URL");
+ let tab2 = yield addTab(FRAME_URL);
+ yield new Promise(resolve => {
+ info("Attaching console to tab 2");
+ attachConsoleToTab(["ConsoleAPI"], function(state) {
+ state.client.getCachedMessages(["ConsoleAPI"], function(calls) {
+ checkConsoleAPICalls(calls.messages, secondTabExpectedCalls);
+ closeDebugger(state, resolve);
+ });
+ });
+ });
+
+ yield swr.unregister();
+
+ SimpleTest.finish();
+});
+addEventListener("load", startTest);
+
+// This test needs to add tabs that are controlled by a service worker
+// so use some special powers to dig around and find gBrowser
+let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window.get());
+
+SimpleTest.registerCleanupFunction(() => {
+ while (gBrowser.tabs.length > 1) {
+ gBrowser.removeCurrentTab();
+ }
+});
+
+function addTab(url) {
+ info("Adding a new tab with URL: '" + url + "'");
+ return new Promise(resolve => {
+ let tab = gBrowser.selectedTab = gBrowser.addTab(url);
+ gBrowser.selectedBrowser.addEventListener("load", function onload() {
+ gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+ info("URL '" + url + "' loading complete");
+ resolve(tab);
+ }, true);
+ });
+}
+
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_console_styling.html b/devtools/shared/webconsole/test/test_console_styling.html
new file mode 100644
index 000000000..97d21bcb9
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_console_styling.html
@@ -0,0 +1,126 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for console.log styling with %c</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for console.log styling with %c</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedConsoleCalls = [];
+
+function doConsoleCalls(aState)
+{
+ top.console.log("%cOne formatter with no styles");
+ top.console.log("%cOne formatter", "color: red");
+ top.console.log("%cTwo formatters%cEach with an arg",
+ "color: red", "background: red");
+ top.console.log("%c%cTwo formatters next to each other",
+ "color: red", "background: red");
+ top.console.log("%c%c%cThree formatters next to each other",
+ "color: red", "background: red", "font-size: 150%");
+ top.console.log("%c%cTwo formatters%cWith a third separated",
+ "color: red", "background: red", "font-size: 150%");
+ top.console.log("%cOne formatter", "color: red",
+ "Second arg with no styles");
+ top.console.log("%cOne formatter", "color: red",
+ "%cSecond formatter is ignored", "background: blue")
+
+ expectedConsoleCalls = [
+ {
+ level: "log",
+ styles: /^$/,
+ arguments: ["%cOne formatter with no styles"],
+ },
+ {
+ level: "log",
+ styles: /^color: red$/,
+ arguments: ["One formatter"],
+ },
+ {
+ level: "log",
+ styles: /^color: red,background: red$/,
+ arguments: ["Two formatters", "Each with an arg"],
+ },
+ {
+ level: "log",
+ styles: /^background: red$/,
+ arguments: ["Two formatters next to each other"],
+ },
+ {
+ level: "log",
+ styles: /^font-size: 150%$/,
+ arguments: ["Three formatters next to each other"],
+ },
+ {
+ level: "log",
+ styles: /^background: red,font-size: 150%$/,
+ arguments: ["Two formatters", "With a third separated"],
+ },
+ {
+ level: "log",
+ styles: /^color: red$/,
+ arguments: ["One formatter", "Second arg with no styles"],
+ },
+ {
+ level: "log",
+ styles: /^color: red$/,
+ arguments: ["One formatter",
+ "%cSecond formatter is ignored",
+ "background: blue"],
+ },
+ ];
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleAPICall = onConsoleAPICall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+ doConsoleCalls(aState.actor);
+}
+
+let consoleCalls = [];
+
+function onConsoleAPICall(aState, aType, aPacket)
+{
+ info("received message level: " + aPacket.message.level);
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ consoleCalls.push(aPacket.message);
+ if (consoleCalls.length != expectedConsoleCalls.length) {
+ return;
+ }
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+
+ expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+
+
+ consoleCalls = [];
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_consoleapi.html b/devtools/shared/webconsole/test/test_consoleapi.html
new file mode 100644
index 000000000..848db9cb6
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_consoleapi.html
@@ -0,0 +1,233 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the Console API</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the Console API</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedConsoleCalls = [];
+
+function doConsoleCalls(aState)
+{
+ let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
+
+ top.console.log("foobarBaz-log", undefined);
+
+ top.console.log("Float from not a number: %f", "foo");
+ top.console.log("Float from string: %f", "1.2");
+ top.console.log("Float from number: %f", 1.3);
+
+ top.console.info("foobarBaz-info", null);
+ top.console.warn("foobarBaz-warn", top.document.documentElement);
+ top.console.debug(null);
+ top.console.trace();
+ top.console.dir(top.document, top.location);
+ top.console.log("foo", longString);
+
+ let sandbox = new Cu.Sandbox(null, { invisibleToDebugger: true });
+ let sandboxObj = sandbox.eval("new Object");
+ top.console.log(sandboxObj);
+
+ function fromAsmJS() {
+ top.console.error("foobarBaz-asmjs-error", undefined);
+ }
+
+ (function(global, foreign) {
+ "use asm";
+ var fromAsmJS = foreign.fromAsmJS;
+ function inAsmJS2() { fromAsmJS() }
+ function inAsmJS1() { inAsmJS2() }
+ return inAsmJS1
+ })(null, { fromAsmJS:fromAsmJS })();
+
+ expectedConsoleCalls = [
+ {
+ level: "log",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-log", { type: "undefined" }],
+ },
+ {
+ level: "log",
+ arguments: ["Float from not a number: NaN"],
+ },
+ {
+ level: "log",
+ arguments: ["Float from string: 1.200000"],
+ },
+ {
+ level: "log",
+ arguments: ["Float from number: 1.300000"],
+ },
+ {
+ level: "info",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-info", { type: "null" }],
+ },
+ {
+ level: "warn",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
+ },
+ {
+ level: "debug",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [{ type: "null" }],
+ },
+ {
+ level: "trace",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ stacktrace: [
+ {
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "onAttach",
+ },
+ ],
+ },
+ {
+ level: "dir",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [
+ {
+ type: "object",
+ actor: /[a-z]/,
+ class: "XULDocument",
+ },
+ {
+ type: "object",
+ actor: /[a-z]/,
+ class: "Location",
+ }
+ ],
+ },
+ {
+ level: "log",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [
+ "foo",
+ {
+ type: "longString",
+ initial: longString.substring(0,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+ length: longString.length,
+ actor: /[a-z]/,
+ },
+ ],
+ },
+ {
+ level: "log",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [
+ {
+ type: "object",
+ actor: /[a-z]/,
+ class: "Object",
+ },
+ ],
+ },
+ {
+ level: "error",
+ filename: /test_consoleapi/,
+ functionName: "fromAsmJS",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-asmjs-error", { type: "undefined" }],
+
+ stacktrace: [
+ {
+ filename: /test_consoleapi/,
+ functionName: "fromAsmJS",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "inAsmJS2",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "inAsmJS1",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "onAttach",
+ },
+ ],
+ },
+ ];
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleAPICall = onConsoleAPICall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+ doConsoleCalls(aState.actor);
+}
+
+let consoleCalls = [];
+
+function onConsoleAPICall(aState, aType, aPacket)
+{
+ info("received message level: " + aPacket.message.level);
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ consoleCalls.push(aPacket.message);
+ if (consoleCalls.length != expectedConsoleCalls.length) {
+ return;
+ }
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+
+ expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+
+
+ consoleCalls = [];
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_consoleapi_innerID.html b/devtools/shared/webconsole/test/test_consoleapi_innerID.html
new file mode 100644
index 000000000..b477a36be
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_consoleapi_innerID.html
@@ -0,0 +1,164 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the innerID property of the Console API</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the Console API</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedConsoleCalls = [];
+
+function doConsoleCalls(aState)
+{
+ let { ConsoleAPI } = Cu.import("resource://gre/modules/Console.jsm", {});
+ let console = new ConsoleAPI({
+ innerID: window.QueryInterface(Ci.nsIInterfaceRequestor)
+ .getInterface(Ci.nsIDOMWindowUtils)
+ .currentInnerWindowID
+ });
+
+ let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 2)).join("a");
+
+ console.log("foobarBaz-log", undefined);
+ console.info("foobarBaz-info", null);
+ console.warn("foobarBaz-warn", top.document.documentElement);
+ console.debug(null);
+ console.trace();
+ console.dir(top.document, top.location);
+ console.log("foo", longString);
+
+ expectedConsoleCalls = [
+ {
+ level: "log",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-log", { type: "undefined" }],
+ },
+ {
+ level: "info",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-info", { type: "null" }],
+ },
+ {
+ level: "warn",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: ["foobarBaz-warn", { type: "object", actor: /[a-z]/ }],
+ },
+ {
+ level: "debug",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [{ type: "null" }],
+ },
+ {
+ level: "trace",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ stacktrace: [
+ {
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ },
+ {
+ filename: /test_consoleapi/,
+ functionName: "onAttach",
+ },
+ ],
+ },
+ {
+ level: "dir",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [
+ {
+ type: "object",
+ actor: /[a-z]/,
+ class: "XULDocument",
+ },
+ {
+ type: "object",
+ actor: /[a-z]/,
+ class: "Location",
+ }
+ ],
+ },
+ {
+ level: "log",
+ filename: /test_consoleapi/,
+ functionName: "doConsoleCalls",
+ timeStamp: /^\d+$/,
+ arguments: [
+ "foo",
+ {
+ type: "longString",
+ initial: longString.substring(0,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+ length: longString.length,
+ actor: /[a-z]/,
+ },
+ ],
+ },
+ ];
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleAPICall = onConsoleAPICall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleAPICall);
+ doConsoleCalls(aState.actor);
+}
+
+let consoleCalls = [];
+
+function onConsoleAPICall(aState, aType, aPacket)
+{
+ info("received message level: " + aPacket.message.level);
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ consoleCalls.push(aPacket.message);
+ if (consoleCalls.length != expectedConsoleCalls.length) {
+ return;
+ }
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleAPICall);
+
+ expectedConsoleCalls.forEach(function(aMessage, aIndex) {
+ info("checking received console call #" + aIndex);
+ checkConsoleAPICall(consoleCalls[aIndex], expectedConsoleCalls[aIndex]);
+ });
+
+
+ consoleCalls = [];
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_file_uri.html b/devtools/shared/webconsole/test/test_file_uri.html
new file mode 100644
index 000000000..f5aada5b1
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_file_uri.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for file activity tracking</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for file activity tracking</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
+
+let gState;
+let gTmpFile;
+
+function doFileActivity()
+{
+ info("doFileActivity");
+ let fileContent = "<p>hello world from bug 798764";
+
+ gTmpFile = FileUtils.getFile("TmpD", ["bug798764.html"]);
+ gTmpFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
+
+ let fout = FileUtils.openSafeFileOutputStream(gTmpFile,
+ FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE | FileUtils.MODE_TRUNCATE);
+
+ let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
+ createInstance(Ci.nsIScriptableUnicodeConverter);
+ converter.charset = "UTF-8";
+ let fileContentStream = converter.convertToInputStream(fileContent);
+
+ NetUtil.asyncCopy(fileContentStream, fout, addIframe);
+}
+
+function addIframe(aStatus)
+{
+ ok(Components.isSuccessCode(aStatus),
+ "the temporary file was saved successfully");
+
+ let iframe = document.createElement("iframe");
+ iframe.src = NetUtil.newURI(gTmpFile).spec;
+ document.body.appendChild(iframe);
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsole(["FileActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ gState = aState;
+ gState.dbgClient.addListener("fileActivity", onFileActivity);
+ doFileActivity();
+}
+
+function onFileActivity(aType, aPacket)
+{
+ is(aPacket.from, gState.actor, "fileActivity actor");
+
+ gState.dbgClient.removeListener("fileActivity", onFileActivity);
+
+ info("aPacket.uri: " + aPacket.uri);
+ ok(/\bbug798764\b.*\.html$/.test(aPacket.uri), "file URI match");
+
+ testEnd();
+}
+
+function testEnd()
+{
+ if (gTmpFile) {
+ SimpleTest.executeSoon(function() {
+ try {
+ gTmpFile.remove(false);
+ }
+ catch (ex if (ex.name == "NS_ERROR_FILE_IS_LOCKED")) {
+ // Sometimes remove() throws because the file is not unlocked soon
+ // enough.
+ }
+ gTmpFile = null;
+ });
+ }
+
+ if (gState) {
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+ } else {
+ SimpleTest.finish();
+ }
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_jsterm.html b/devtools/shared/webconsole/test/test_jsterm.html
new file mode 100644
index 000000000..b6eefad4b
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_jsterm.html
@@ -0,0 +1,309 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for JavaScript terminal functionality</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for JavaScript terminal functionality</p>
+
+<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gState;
+
+let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");
+
+// This test runs all of its assertions twice - once with
+// evaluateJS and once with evaluateJSAsync.
+let evaluatingSync = true;
+function evaluateJS(input, options = {}) {
+ return new Promise((resolve, reject) => {
+ if (evaluatingSync) {
+ gState.client.evaluateJS(input, resolve, options);
+ } else {
+ gState.client.evaluateJSAsync(input, resolve, options);
+ }
+ });
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ top.foobarObject = Object.create(null);
+ top.foobarObject.foo = 1;
+ top.foobarObject.foobar = 2;
+ top.foobarObject.foobaz = 3;
+ top.foobarObject.omg = 4;
+ top.foobarObject.omgfoo = 5;
+ top.foobarObject.strfoo = "foobarz";
+ top.foobarObject.omgstr = "foobarz" +
+ (new Array(DebuggerServer.LONG_STRING_LENGTH * 2)).join("abb");
+
+ top.largeObject1 = Object.create(null);
+ for (let i = 0; i < MAX_AUTOCOMPLETE_ATTEMPTS + 1; i++) {
+ top.largeObject1['a' + i] = i;
+ }
+
+ top.largeObject2 = Object.create(null);
+ for (let i = 0; i < MAX_AUTOCOMPLETIONS * 2; i++) {
+ top.largeObject2['a' + i] = i;
+ }
+
+ gState = aState;
+
+ let tests = [doSimpleEval, doWindowEval, doEvalWithException,
+ doEvalWithHelper, doEvalString, doEvalLongString,
+ doEvalWithBinding, doEvalWithBindingFrame,
+ forceLexicalInit].map(t => {
+ return Task.async(t);
+ });
+
+ runTests(tests, testEnd);
+}
+
+function* doSimpleEval() {
+ info("test eval '2+2'");
+ let response = yield evaluateJS("2+2");
+ checkObject(response, {
+ from: gState.actor,
+ input: "2+2",
+ result: 4,
+ });
+
+ ok(!response.exception, "no eval exception");
+ ok(!response.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function* doWindowEval() {
+ info("test eval 'document'");
+ let response = yield evaluateJS("document");
+ checkObject(response, {
+ from: gState.actor,
+ input: "document",
+ result: {
+ type: "object",
+ class: "XULDocument",
+ actor: /[a-z]/,
+ },
+ });
+
+ ok(!response.exception, "no eval exception");
+ ok(!response.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function* doEvalWithException() {
+ info("test eval with exception");
+ let response = yield evaluateJS("window.doTheImpossible()");
+ checkObject(response, {
+ from: gState.actor,
+ input: "window.doTheImpossible()",
+ result: {
+ type: "undefined",
+ },
+ exceptionMessage: /doTheImpossible/,
+ });
+
+ ok(response.exception, "js eval exception");
+ ok(!response.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function* doEvalWithHelper() {
+ info("test eval with helper");
+ let response = yield evaluateJS("clear()");
+ checkObject(response, {
+ from: gState.actor,
+ input: "clear()",
+ result: {
+ type: "undefined",
+ },
+ helperResult: { type: "clearOutput" },
+ });
+
+ ok(!response.exception, "no eval exception");
+
+ nextTest();
+}
+
+function* doEvalString() {
+ let response = yield evaluateJS("window.foobarObject.strfoo");
+ checkObject(response, {
+ from: gState.actor,
+ input: "window.foobarObject.strfoo",
+ result: "foobarz",
+ });
+
+ nextTest();
+}
+
+function* doEvalLongString() {
+ let response = yield evaluateJS("window.foobarObject.omgstr");
+ let str = top.foobarObject.omgstr;
+ let initial = str.substring(0, DebuggerServer.LONG_STRING_INITIAL_LENGTH);
+
+ checkObject(response, {
+ from: gState.actor,
+ input: "window.foobarObject.omgstr",
+ result: {
+ type: "longString",
+ initial: initial,
+ length: str.length,
+ },
+ });
+
+ nextTest();
+}
+
+function* doEvalWithBinding() {
+ let response = yield evaluateJS("document;");
+ let documentActor = response.result.actor;
+
+ info("running a command with _self as document using bindObjectActor");
+ let bindObjectSame = yield evaluateJS("_self === document", {
+ bindObjectActor: documentActor
+ });
+ checkObject(bindObjectSame, {
+ result: true
+ });
+
+ info("running a command with _self as document using selectedObjectActor");
+ let selectedObjectSame = yield evaluateJS("_self === document", {
+ selectedObjectActor: documentActor
+ });
+ checkObject(selectedObjectSame, {
+ result: true
+ });
+
+ nextTest();
+}
+
+function* doEvalWithBindingFrame() {
+ let frameWin = top.document.querySelector("iframe").contentWindow;
+ frameWin.fooFrame = { bar: 1 };
+
+ let response = yield evaluateJS(
+ "document.querySelector('iframe').contentWindow.fooFrame"
+ );
+ let iframeObjectActor = response.result.actor;
+ ok(iframeObjectActor, "There is an actor associated with the response");
+
+ let bindObjectGlobal = yield evaluateJS("this.temp0 = _self;", {
+ bindObjectActor: iframeObjectActor
+ });
+ ok(!top.temp0,
+ "Global doesn't match the top global with bindObjectActor");
+ ok(frameWin.temp0 && frameWin.temp0.bar === 1,
+ "Global matches the object's global with bindObjectActor");
+
+ let selectedObjectGlobal = yield evaluateJS("this.temp1 = _self;", {
+ selectedObjectActor: iframeObjectActor
+ });
+ ok(top.temp1 && top.temp1.bar === 1,
+ "Global matches the top global with bindObjectActor");
+ ok(!frameWin.temp1,
+ "Global doesn't match the object's global with bindObjectActor");
+
+ nextTest()
+}
+
+function* forceLexicalInit() {
+ info("test that failed let/const bindings are initialized to undefined");
+
+ const testData = [
+ {
+ stmt: "let foopie = wubbalubadubdub",
+ vars: ["foopie"]
+ },
+ {
+ stmt: "let {z, w={n}=null} = {}",
+ vars: ["z", "w"]
+ },
+ {
+ stmt: "let [a, b, c] = null",
+ vars: ["a", "b", "c"]
+ },
+ {
+ stmt: "const nein1 = rofl, nein2 = copter",
+ vars: ["nein1", "nein2"]
+ },
+ {
+ stmt: "const {ha} = null",
+ vars: ["ha"]
+ },
+ {
+ stmt: "const [haw=[lame]=null] = []",
+ vars: ["haw"]
+ },
+ {
+ stmt: "const [rawr, wat=[lame]=null] = []",
+ vars: ["rawr", "haw"]
+ },
+ {
+ stmt: "let {zzz: xyz=99, zwz: wb} = nexistepas()",
+ vars: ["xyz", "wb"]
+ },
+ {
+ stmt: "let {c3pdoh=101} = null",
+ vars: ["c3pdoh"]
+ }
+ ];
+
+ for (let data of testData) {
+ let response = yield evaluateJS(data.stmt);
+ checkObject(response, {
+ from: gState.actor,
+ input: data.stmt,
+ result: undefined,
+ });
+ ok(response.exception, "expected exception");
+ for (let varName of data.vars) {
+ let response2 = yield evaluateJS(varName);
+ checkObject(response2, {
+ from: gState.actor,
+ input: varName,
+ result: undefined,
+ });
+ ok(!response2.exception, "unexpected exception");
+ }
+ }
+
+ nextTest();
+}
+
+function testEnd()
+{
+ // If this is the first run, reload the page and do it again.
+ // Otherwise, end the test.
+ closeDebugger(gState, function() {
+ gState = null;
+ if (evaluatingSync) {
+ evaluatingSync = false;
+ startTest();
+ } else {
+ SimpleTest.finish();
+ }
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_jsterm_autocomplete.html b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
new file mode 100644
index 000000000..0e32e1a63
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_jsterm_autocomplete.html
@@ -0,0 +1,183 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for JavaScript terminal functionality</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for JavaScript terminal autocomplete functionality</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gState;
+let {MAX_AUTOCOMPLETE_ATTEMPTS,MAX_AUTOCOMPLETIONS} = require("devtools/shared/webconsole/js-property-provider");
+
+function evaluateJS(input, options = {}) {
+ return new Promise((resolve, reject) => {
+ gState.client.evaluateJSAsync(input, resolve, options);
+ });
+}
+
+function autocompletePromise(str, cursor, frameActor) {
+ return new Promise(resolve => {
+ gState.client.autocomplete(str, cursor, resolve, frameActor);
+ });
+}
+
+// This test runs all of its assertions twice - once with
+// the tab as a target and once with a worker
+let runningInTab = true;
+function startTest({worker}) {
+ if (worker) {
+ attachConsoleToWorker(["PageError"], onAttach);
+ } else {
+ attachConsoleToTab(["PageError"], onAttach);
+ }
+};
+
+let onAttach = Task.async(function*(aState, response) {
+ gState = aState;
+
+ let longStrLength = DebuggerServer.LONG_STRING_LENGTH;
+
+ // Set up the global variables needed to test autocompletion
+ // in the target.
+ let script = `
+ // This is for workers so autocomplete acts the same
+ if (!this.window) {
+ window = this;
+ }
+
+ window.foobarObject = Object.create(null);
+ window.foobarObject.foo = 1;
+ window.foobarObject.foobar = 2;
+ window.foobarObject.foobaz = 3;
+ window.foobarObject.omg = 4;
+ window.foobarObject.omgfoo = 5;
+ window.foobarObject.strfoo = "foobarz";
+ window.foobarObject.omgstr = "foobarz" +
+ (new Array(${longStrLength})).join("abb");
+ window.largeObject1 = Object.create(null);
+ for (let i = 0; i < ${MAX_AUTOCOMPLETE_ATTEMPTS + 1}; i++) {
+ window.largeObject1['a' + i] = i;
+ }
+
+ window.largeObject2 = Object.create(null);
+ for (let i = 0; i < ${MAX_AUTOCOMPLETIONS * 2}; i++) {
+ window.largeObject2['a' + i] = i;
+ }
+ `;
+
+ yield evaluateJS(script);
+
+ let tests = [doAutocomplete1, doAutocomplete2, doAutocomplete3,
+ doAutocomplete4, doAutocompleteLarge1,
+ doAutocompleteLarge2].map(t => {
+ return Task.async(t);
+ });
+
+ runTests(tests, testEnd);
+});
+
+function* doAutocomplete1() {
+ info("test autocomplete for 'window.foo'");
+ let response = yield autocompletePromise("window.foo", 10);
+ let matches = response.matches;
+
+ is(response.matchProp, "foo", "matchProp");
+ is(matches.length, 1, "matches.length");
+ is(matches[0], "foobarObject", "matches[0]");
+
+ nextTest();
+}
+
+function* doAutocomplete2() {
+ info("test autocomplete for 'window.foobarObject.'");
+ let response = yield autocompletePromise("window.foobarObject.", 20);
+ let matches = response.matches;
+
+ ok(!response.matchProp, "matchProp");
+ is(matches.length, 7, "matches.length");
+ checkObject(matches,
+ ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
+
+ nextTest();
+}
+
+function* doAutocomplete3() {
+ // Check that completion suggestions are offered inside the string.
+ info("test autocomplete for 'dump(window.foobarObject.)'");
+ let response = yield autocompletePromise("dump(window.foobarObject.)", 25);
+ let matches = response.matches;
+
+ ok(!response.matchProp, "matchProp");
+ is(matches.length, 7, "matches.length");
+ checkObject(matches,
+ ["foo", "foobar", "foobaz", "omg", "omgfoo", "omgstr", "strfoo"]);
+
+ nextTest();
+}
+
+function* doAutocomplete4() {
+ // Check that completion requests can have no suggestions.
+ info("test autocomplete for 'dump(window.foobarObject.)'");
+ let response = yield autocompletePromise("dump(window.foobarObject.)", 26);
+ ok(!response.matchProp, "matchProp");
+ is(response.matches.length, 0, "matches.length");
+
+ nextTest();
+}
+
+function* doAutocompleteLarge1() {
+ // Check that completion requests with too large objects will
+ // have no suggestions.
+ info("test autocomplete for 'window.largeObject1.'");
+ let response = yield autocompletePromise("window.largeObject1.", 20);
+ ok(!response.matchProp, "matchProp");
+ info (response.matches.join("|"));
+ is(response.matches.length, 0, "Bailed out with too many properties");
+
+ nextTest();
+}
+
+function* doAutocompleteLarge2() {
+ // Check that completion requests with pretty large objects will
+ // have MAX_AUTOCOMPLETIONS suggestions
+ info("test autocomplete for 'window.largeObject2.'");
+ let response = yield autocompletePromise("window.largeObject2.", 20);
+ ok(!response.matchProp, "matchProp");
+ is(response.matches.length, MAX_AUTOCOMPLETIONS, "matches.length is MAX_AUTOCOMPLETIONS");
+
+ nextTest();
+}
+
+function testEnd()
+{
+ // If this is the first run, reload the page and do it again
+ // in a worker. Otherwise, end the test.
+ closeDebugger(gState, function() {
+ gState = null;
+ if (runningInTab) {
+ runningInTab = false;
+ startTest({
+ worker: true
+ });
+ } else {
+ SimpleTest.finish();
+ }
+ });
+}
+
+addEventListener("load", () => {
+ startTest({
+ worker: false
+ });
+});
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html b/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html
new file mode 100644
index 000000000..7207a00a1
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_jsterm_cd_iframe.html
@@ -0,0 +1,223 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the cd() function</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the cd() function</p>
+
+<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gState;
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab([], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ top.foobarObject = Object.create(null);
+ top.foobarObject.bug609872 = "parent";
+
+ window.foobarObject = Object.create(null);
+ window.foobarObject.bug609872 = "child";
+
+ gState = aState;
+
+ let tests = [doCheckParent, doCdIframe, doCheckIframe,
+ doCdContentIframe,
+ doCdSandboxedIframe, doCheckSandboxedIframe,
+ doCdParent,
+ doCdParent,
+ doCheckParent2];
+ runTests(tests, testEnd);
+}
+
+function doCheckParent()
+{
+ info("check parent window");
+ gState.client.evaluateJS("window.foobarObject.bug609872",
+ onFooObjectFromParent);
+}
+
+function onFooObjectFromParent(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "window.foobarObject.bug609872",
+ result: "parent",
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+ ok(!aResponse.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function doCdIframe()
+{
+ info("test cd('iframe')");
+ gState.client.evaluateJS("cd('iframe')", onCdIframe);
+}
+
+function onCdIframe(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "cd('iframe')",
+ result: { type: "undefined" },
+ helperResult: { type: "cd" },
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+
+ nextTest();
+}
+
+function doCheckIframe()
+{
+ info("check foobarObject from the iframe");
+ gState.client.evaluateJS("window.foobarObject.bug609872",
+ onFooObjectFromIframe);
+}
+
+function onFooObjectFromIframe(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "window.foobarObject.bug609872",
+ result: "child",
+ });
+
+ ok(!aResponse.exception, "no js eval exception");
+ ok(!aResponse.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function doCdContentIframe()
+{
+ info("test cd('#content-iframe')");
+ gState.client.evaluateJS("cd('#content-iframe')", onCdContentIframe);
+}
+
+function onCdContentIframe(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "cd('#content-iframe')",
+ result: { type: "undefined" },
+ helperResult: { type: "cd" },
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+
+ nextTest();
+}
+function doCdSandboxedIframe()
+{
+ // Don't use string to ensure we don't get security exception
+ // when passing a content window reference.
+ let cmd = "cd(document.getElementById('sandboxed-iframe').contentWindow)";
+ info("test " + cmd);
+ gState.client.evaluateJS(cmd, onCdSandboxedIframe.bind(null, cmd));
+}
+
+function onCdSandboxedIframe(cmd, aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: cmd,
+ result: { type: "undefined" },
+ helperResult: { type: "cd" },
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+
+ nextTest();
+}
+
+function doCheckSandboxedIframe()
+{
+ info("check foobarObject from the sandboxed iframe");
+ gState.client.evaluateJS("window.foobarObject.bug1051224",
+ onFooObjectFromSandboxedIframe);
+}
+
+function onFooObjectFromSandboxedIframe(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "window.foobarObject.bug1051224",
+ result: "sandboxed",
+ });
+
+ ok(!aResponse.exception, "no js eval exception");
+ ok(!aResponse.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function doCdParent()
+{
+ info("test cd() back to parent");
+ gState.client.evaluateJS("cd()", onCdParent);
+}
+
+function onCdParent(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "cd()",
+ result: { type: "undefined" },
+ helperResult: { type: "cd" },
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+
+ nextTest();
+}
+
+function doCheckParent2()
+{
+ gState.client.evaluateJS("window.foobarObject.bug609872",
+ onFooObjectFromParent2);
+}
+
+function onFooObjectFromParent2(aResponse)
+{
+ checkObject(aResponse, {
+ from: gState.actor,
+ input: "window.foobarObject.bug609872",
+ result: "parent",
+ });
+
+ ok(!aResponse.exception, "no eval exception");
+ ok(!aResponse.helperResult, "no helper result");
+
+ nextTest();
+}
+
+function testEnd()
+{
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_jsterm_last_result.html b/devtools/shared/webconsole/test/test_jsterm_last_result.html
new file mode 100644
index 000000000..e90ce14ed
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_jsterm_last_result.html
@@ -0,0 +1,130 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the $_ getter</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the $_ getter</p>
+
+<iframe id="content-iframe" src="http://example.com/chrome/devtools/shared/webconsole/test/sandboxed_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+let gState;
+
+function evaluateJS(input, callback) {
+ return new Promise((resolve, reject) => {
+ gState.client.evaluateJSAsync(input, response => {
+ if (callback) {
+ callback(response);
+ }
+ resolve(response);
+ });
+ });
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsoleToTab([], state => {
+ gState = state;
+ let tests = [checkUndefinedResult,checkAdditionResult,checkObjectResult];
+ runTests(tests, testEnd);
+ });
+}
+
+let checkUndefinedResult = Task.async(function*() {
+ info ("$_ returns undefined if nothing has evaluated yet");
+ let response = yield evaluateJS("$_");
+ basicResultCheck(response, "$_", undefined);
+ nextTest();
+});
+
+let checkAdditionResult = Task.async(function*() {
+ info ("$_ returns last value and performs basic arithmetic");
+ let response = yield evaluateJS("2+2");
+ basicResultCheck(response, "2+2", 4);
+
+ response = yield evaluateJS("$_");
+ basicResultCheck(response, "$_", 4);
+
+ response = yield evaluateJS("$_ + 2");
+ basicResultCheck(response, "$_ + 2", 6);
+
+ response = yield evaluateJS("$_ + 4");
+ basicResultCheck(response, "$_ + 4", 10);
+
+ nextTest();
+});
+
+let checkObjectResult = Task.async(function*() {
+ info ("$_ has correct references to objects");
+
+ let response = yield evaluateJS("var foo = {bar:1}; foo;");
+ basicResultCheck(response, "var foo = {bar:1}; foo;", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.preview.ownProperties, {
+ bar: {
+ value: 1
+ }
+ });
+
+ response = yield evaluateJS("$_");
+ basicResultCheck(response, "$_", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.preview.ownProperties, {
+ bar: {
+ value: 1
+ }
+ });
+
+ top.foo.bar = 2;
+
+ response = yield evaluateJS("$_");
+ basicResultCheck(response, "$_", {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ });
+ checkObject(response.result.preview.ownProperties, {
+ bar: {
+ value: 2
+ }
+ });
+
+ nextTest();
+});
+
+function basicResultCheck(response, input, output) {
+ checkObject(response, {
+ from: gState.actor,
+ input: input,
+ result: output,
+ });
+ ok(!response.exception, "no eval exception");
+ ok(!response.helperResult, "no helper result");
+}
+
+function testEnd()
+{
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_jsterm_queryselector.html b/devtools/shared/webconsole/test/test_jsterm_queryselector.html
new file mode 100644
index 000000000..b75ee399c
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_jsterm_queryselector.html
@@ -0,0 +1,134 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the querySelector / querySelectorAll helpers</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the querySelector / querySelectorAll helpers</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+let gState;
+let gWin;
+
+function evaluateJS(input) {
+ return new Promise((resolve) => gState.client.evaluateJS(input, resolve));
+}
+
+function startTest() {
+ info ("Content window opened, attaching console to it");
+
+ let systemPrincipal = Cc["@mozilla.org/systemprincipal;1"].createInstance(Ci.nsIPrincipal);
+ ok (!gWin.document.nodePrincipal.equals(systemPrincipal),
+ "The test document is not using the system principal");
+
+ attachConsoleToTab([], state => {
+ gState = state;
+ let tests = [
+ setupWindow,
+ checkQuerySelector,
+ checkQuerySelectorAll,
+ checkQuerySelectorAllNotExist,
+ checkQuerySelectorAllException
+ ];
+ runTests(tests, testEnd);
+ });
+}
+
+let setupWindow = Task.async(function*() {
+ info ("Shimming window functions for the content privileged tab");
+ yield evaluateJS("document.querySelector = function() { throw 'should not call qS'; }");
+ yield evaluateJS("document.querySelectorAll = function() { throw 'should not call qSA'; }");
+ nextTest();
+});
+
+let checkQuerySelector = Task.async(function*() {
+ info ("$ returns an DOMNode");
+ let response = yield evaluateJS("$('body')");
+ basicResultCheck(response, "$('body')", {
+ type: "object",
+ class: "HTMLBodyElement",
+ preview: {
+ kind: "DOMNode",
+ nodeName: "body"
+ }
+ });
+ nextTest();
+});
+
+let checkQuerySelectorAll = Task.async(function*() {
+ info ("$$ returns an array");
+ let response = yield evaluateJS("$$('body')");
+ basicResultCheck(response, "$$('body')", {
+ type: "object",
+ class: "Array",
+ preview: {
+ length: 1
+ }
+ });
+ nextTest();
+});
+
+let checkQuerySelectorAllNotExist = Task.async(function*() {
+ info ("$$ returns an array even if query yields no results");
+ let response = yield evaluateJS("$$('foobar')");
+ basicResultCheck(response, "$$('foobar')", {
+ type: "object",
+ class: "Array",
+ preview: {
+ length: 0
+ }
+ });
+ nextTest();
+});
+
+let checkQuerySelectorAllException = Task.async(function*() {
+ info ("$$ returns an exception if an invalid selector was provided");
+ let response = yield evaluateJS("$$(':foo')");
+ checkObject(response, {
+ input: "$$(':foo')",
+ exceptionMessage: "SyntaxError: ':foo' is not a valid selector",
+ exception: {
+ preview: {
+ kind: "DOMException",
+ name: "SyntaxError",
+ message: "':foo' is not a valid selector"
+ }
+ }
+ });
+ nextTest();
+});
+
+function basicResultCheck(response, input, output) {
+ checkObject(response, {
+ from: gState.actor,
+ input: input,
+ result: output,
+ });
+ ok(!response.exception, "no eval exception");
+ ok(!response.helperResult, "no helper result");
+}
+
+function testEnd() {
+ gWin.close();
+ gWin = null;
+ closeDebugger(gState, function() {
+ gState = null;
+ SimpleTest.finish();
+ });
+}
+
+window.onload = function() {
+ // Open a content window to test XRay functionality on built in functions.
+ gWin = window.open("data:text/html,");
+ info ("Waiting for content window to load");
+ gWin.onload = startTest;
+}
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_network_get.html b/devtools/shared/webconsole/test/test_network_get.html
new file mode 100644
index 000000000..710c9b0d7
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_network_get.html
@@ -0,0 +1,260 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the network actor (GET request)</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the network actor (GET request)</p>
+
+<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsoleToTab(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ info("test network GET request");
+
+ onNetworkEvent = onNetworkEvent.bind(null, aState);
+ aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+ onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+ aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+ let iframe = document.querySelector("iframe").contentWindow;
+ iframe.wrappedJSObject.testXhrGet();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "network event actor");
+
+ info("checking the network event packet");
+
+ let netActor = aPacket.eventActor;
+
+ checkObject(netActor, {
+ actor: /[a-z]/,
+ startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+ url: /data\.json/,
+ method: "GET",
+ });
+
+ aState.netActor = netActor.actor;
+
+ aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+ info("received networkEventUpdate " + aPacket.updateType);
+ is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+ updates.push(aPacket.updateType);
+
+ let expectedPacket = null;
+
+ switch (aPacket.updateType) {
+ case "requestHeaders":
+ case "responseHeaders":
+ ok(aPacket.headers > 0, "headers > 0");
+ ok(aPacket.headersSize > 0, "headersSize > 0");
+ break;
+ case "requestCookies":
+ expectedPacket = {
+ cookies: 3,
+ };
+ break;
+ case "requestPostData":
+ ok(false, "got unexpected requestPostData");
+ break;
+ case "responseStart":
+ expectedPacket = {
+ response: {
+ httpVersion: /^HTTP\/\d\.\d$/,
+ status: "200",
+ statusText: "OK",
+ headersSize: /^\d+$/,
+ discardResponseBody: false,
+ },
+ };
+ break;
+ case "securityInfo":
+ expectedPacket = {
+ state: "insecure",
+ };
+ break;
+ case "responseCookies":
+ expectedPacket = {
+ cookies: 0,
+ };
+ break;
+ case "responseContent":
+ expectedPacket = {
+ mimeType: "application/json",
+ contentSize: 1070,
+ discardResponseBody: false,
+ };
+ break;
+ case "eventTimings":
+ expectedPacket = {
+ totalTime: /^\d+$/,
+ };
+ break;
+ default:
+ ok(false, "unknown network event update type: " +
+ aPacket.updateType);
+ return;
+ }
+
+ if (expectedPacket) {
+ info("checking the packet content");
+ checkObject(aPacket, expectedPacket);
+ }
+
+ if (updates.indexOf("responseContent") > -1 &&
+ updates.indexOf("eventTimings") > -1) {
+ aState.dbgClient.removeListener("networkEventUpdate",
+ onNetworkEvent);
+
+ onRequestHeaders = onRequestHeaders.bind(null, aState);
+ aState.client.getRequestHeaders(aState.netActor,
+ onRequestHeaders);
+ }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+ info("checking request headers");
+
+ ok(aResponse.headers.length > 0, "request headers > 0");
+ ok(aResponse.headersSize > 0, "request headersSize > 0");
+ ok(!!aResponse.rawHeaders, "request rawHeaders available");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ Referer: /network_requests_iframe\.html/,
+ Cookie: /bug768096/,
+ });
+
+ checkRawHeaders(aResponse.rawHeaders, {
+ Referer: /network_requests_iframe\.html/,
+ Cookie: /bug768096/,
+ });
+
+ onRequestCookies = onRequestCookies.bind(null, aState);
+ aState.client.getRequestCookies(aState.netActor,
+ onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+ info("checking request cookies");
+
+ is(aResponse.cookies.length, 3, "request cookies length");
+
+ checkHeadersOrCookies(aResponse.cookies, {
+ foobar: "fooval",
+ omgfoo: "bug768096",
+ badcookie: "bug826798=st3fan",
+ });
+
+ onRequestPostData = onRequestPostData.bind(null, aState);
+ aState.client.getRequestPostData(aState.netActor,
+ onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+ info("checking request POST data");
+
+ ok(!aResponse.postData.text, "no request POST data");
+ ok(!aResponse.postDataDiscarded, "request POST data was not discarded");
+
+ onResponseHeaders = onResponseHeaders.bind(null, aState);
+ aState.client.getResponseHeaders(aState.netActor,
+ onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+ info("checking response headers");
+
+ ok(aResponse.headers.length > 0, "response headers > 0");
+ ok(aResponse.headersSize > 0, "response headersSize > 0");
+ ok(!!aResponse.rawHeaders, "response rawHeaders available");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ "Content-Type": /^application\/(json|octet-stream)$/,
+ "Content-Length": /^\d+$/,
+ });
+
+ checkRawHeaders(aResponse.rawHeaders, {
+ "Content-Type": /^application\/(json|octet-stream)$/,
+ "Content-Length": /^\d+$/,
+ });
+
+ onResponseCookies = onResponseCookies.bind(null, aState);
+ aState.client.getResponseCookies(aState.netActor,
+ onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+ info("checking response cookies");
+
+ is(aResponse.cookies.length, 0, "response cookies length");
+
+ onResponseContent = onResponseContent.bind(null, aState);
+ aState.client.getResponseContent(aState.netActor,
+ onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+ info("checking response content");
+
+ ok(aResponse.content.text, "response content text");
+ ok(!aResponse.contentDiscarded, "response content was not discarded");
+
+ onEventTimings = onEventTimings.bind(null, aState);
+ aState.client.getEventTimings(aState.netActor,
+ onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+ info("checking event timings");
+
+ checkObject(aResponse, {
+ timings: {
+ blocked: /^-1|\d+$/,
+ dns: /^-1|\d+$/,
+ connect: /^-1|\d+$/,
+ send: /^-1|\d+$/,
+ wait: /^-1|\d+$/,
+ receive: /^-1|\d+$/,
+ },
+ totalTime: /^\d+$/,
+ });
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_network_longstring.html b/devtools/shared/webconsole/test/test_network_longstring.html
new file mode 100644
index 000000000..d55136896
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_network_longstring.html
@@ -0,0 +1,293 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test that the network actor uses the LongStringActor</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test that the network actor uses the LongStringActor</p>
+
+<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ info("set long string length");
+
+ window.ORIGINAL_LONG_STRING_LENGTH = DebuggerServer.LONG_STRING_LENGTH;
+ window.ORIGINAL_LONG_STRING_INITIAL_LENGTH =
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH;
+
+ DebuggerServer.LONG_STRING_LENGTH = 400;
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH = 400;
+
+ info("test network POST request");
+
+ onNetworkEvent = onNetworkEvent.bind(null, aState);
+ aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+ onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+ aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+ let iframe = document.querySelector("iframe").contentWindow;
+ iframe.wrappedJSObject.testXhrPost();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "network event actor");
+
+ info("checking the network event packet");
+
+ let netActor = aPacket.eventActor;
+
+ checkObject(netActor, {
+ actor: /[a-z]/,
+ startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+ url: /data\.json/,
+ method: "POST",
+ });
+
+ aState.netActor = netActor.actor;
+
+ aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+ info("received networkEventUpdate " + aPacket.updateType);
+ is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+ updates.push(aPacket.updateType);
+
+ let expectedPacket = null;
+
+ switch (aPacket.updateType) {
+ case "requestHeaders":
+ case "responseHeaders":
+ ok(aPacket.headers > 0, "headers > 0");
+ ok(aPacket.headersSize > 0, "headersSize > 0");
+ break;
+ case "requestCookies":
+ expectedPacket = {
+ cookies: 3,
+ };
+ break;
+ case "requestPostData":
+ ok(aPacket.dataSize > 0, "dataSize > 0");
+ ok(!aPacket.discardRequestBody, "discardRequestBody");
+ break;
+ case "responseStart":
+ expectedPacket = {
+ response: {
+ httpVersion: /^HTTP\/\d\.\d$/,
+ status: "200",
+ statusText: "OK",
+ headersSize: /^\d+$/,
+ discardResponseBody: false,
+ },
+ };
+ break;
+ case "securityInfo":
+ expectedPacket = {
+ state: "insecure",
+ };
+ break;
+ case "responseCookies":
+ expectedPacket = {
+ cookies: 0,
+ };
+ break;
+ case "responseContent":
+ expectedPacket = {
+ mimeType: "application/json",
+ contentSize: /^\d+$/,
+ discardResponseBody: false,
+ };
+ break;
+ case "eventTimings":
+ expectedPacket = {
+ totalTime: /^\d+$/,
+ };
+ break;
+ default:
+ ok(false, "unknown network event update type: " +
+ aPacket.updateType);
+ return;
+ }
+
+ if (expectedPacket) {
+ info("checking the packet content");
+ checkObject(aPacket, expectedPacket);
+ }
+
+ if (updates.indexOf("responseContent") > -1 &&
+ updates.indexOf("eventTimings") > -1) {
+ aState.dbgClient.removeListener("networkEventUpdate",
+ onNetworkEvent);
+
+ onRequestHeaders = onRequestHeaders.bind(null, aState);
+ aState.client.getRequestHeaders(aState.netActor,
+ onRequestHeaders);
+ }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+ info("checking request headers");
+
+ ok(aResponse.headers.length > 0, "request headers > 0");
+ ok(aResponse.headersSize > 0, "request headersSize > 0");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ Referer: /network_requests_iframe\.html/,
+ Cookie: /bug768096/,
+ });
+
+ onRequestCookies = onRequestCookies.bind(null, aState);
+ aState.client.getRequestCookies(aState.netActor,
+ onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+ info("checking request cookies");
+
+ is(aResponse.cookies.length, 3, "request cookies length");
+
+ checkHeadersOrCookies(aResponse.cookies, {
+ foobar: "fooval",
+ omgfoo: "bug768096",
+ badcookie: "bug826798=st3fan",
+ });
+
+ onRequestPostData = onRequestPostData.bind(null, aState);
+ aState.client.getRequestPostData(aState.netActor,
+ onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+ info("checking request POST data");
+
+ checkObject(aResponse, {
+ postData: {
+ text: {
+ type: "longString",
+ initial: /^Hello world! foobaz barr.+foobaz barrfo$/,
+ length: 552,
+ actor: /[a-z]/,
+ },
+ },
+ postDataDiscarded: false,
+ });
+
+ is(aResponse.postData.text.initial.length,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH, "postData text initial length");
+
+ onResponseHeaders = onResponseHeaders.bind(null, aState);
+ aState.client.getResponseHeaders(aState.netActor,
+ onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+ info("checking response headers");
+
+ ok(aResponse.headers.length > 0, "response headers > 0");
+ ok(aResponse.headersSize > 0, "response headersSize > 0");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ "Content-Type": /^application\/(json|octet-stream)$/,
+ "Content-Length": /^\d+$/,
+ "x-very-short": "hello world",
+ "x-very-long": {
+ "type": "longString",
+ "length": 521,
+ "initial": /^Lorem ipsum.+\. Donec vitae d$/,
+ "actor": /[a-z]/,
+ },
+ });
+
+ onResponseCookies = onResponseCookies.bind(null, aState);
+ aState.client.getResponseCookies(aState.netActor,
+ onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+ info("checking response cookies");
+
+ is(aResponse.cookies.length, 0, "response cookies length");
+
+ onResponseContent = onResponseContent.bind(null, aState);
+ aState.client.getResponseContent(aState.netActor,
+ onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+ info("checking response content");
+
+ checkObject(aResponse, {
+ content: {
+ text: {
+ type: "longString",
+ initial: /^\{ id: "test JSON data"(.|\r|\n)+ barfoo ba$/g,
+ length: 1070,
+ actor: /[a-z]/,
+ },
+ },
+ contentDiscarded: false,
+ });
+
+ is(aResponse.content.text.initial.length,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH, "content initial length");
+
+ onEventTimings = onEventTimings.bind(null, aState);
+ aState.client.getEventTimings(aState.netActor,
+ onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+ info("checking event timings");
+
+ checkObject(aResponse, {
+ timings: {
+ blocked: /^-1|\d+$/,
+ dns: /^-1|\d+$/,
+ connect: /^-1|\d+$/,
+ send: /^-1|\d+$/,
+ wait: /^-1|\d+$/,
+ receive: /^-1|\d+$/,
+ },
+ totalTime: /^\d+$/,
+ });
+
+ closeDebugger(aState, function() {
+ DebuggerServer.LONG_STRING_LENGTH = ORIGINAL_LONG_STRING_LENGTH;
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH = ORIGINAL_LONG_STRING_INITIAL_LENGTH;
+
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_network_post.html b/devtools/shared/webconsole/test/test_network_post.html
new file mode 100644
index 000000000..d96b9b0b7
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_network_post.html
@@ -0,0 +1,272 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the network actor (POST request)</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the network actor (POST request)</p>
+
+<iframe src="http://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ info("test network POST request");
+
+ onNetworkEvent = onNetworkEvent.bind(null, aState);
+ aState.dbgClient.addListener("networkEvent", onNetworkEvent);
+ onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+ aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+ let iframe = document.querySelector("iframe").contentWindow;
+ iframe.wrappedJSObject.testXhrPost();
+}
+
+function onNetworkEvent(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "network event actor");
+
+ info("checking the network event packet");
+
+ let netActor = aPacket.eventActor;
+
+ checkObject(netActor, {
+ actor: /[a-z]/,
+ startedDateTime: /^\d+\-\d+\-\d+T.+$/,
+ url: /data\.json/,
+ method: "POST",
+ });
+
+ aState.netActor = netActor.actor;
+
+ aState.dbgClient.removeListener("networkEvent", onNetworkEvent);
+}
+
+let updates = [];
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+ info("received networkEventUpdate " + aPacket.updateType);
+ is(aPacket.from, aState.netActor, "networkEventUpdate actor");
+
+ updates.push(aPacket.updateType);
+
+ let expectedPacket = null;
+
+ switch (aPacket.updateType) {
+ case "requestHeaders":
+ case "responseHeaders":
+ ok(aPacket.headers > 0, "headers > 0");
+ ok(aPacket.headersSize > 0, "headersSize > 0");
+ break;
+ case "requestCookies":
+ expectedPacket = {
+ cookies: 3,
+ };
+ break;
+ case "requestPostData":
+ ok(aPacket.dataSize > 0, "dataSize > 0");
+ ok(!aPacket.discardRequestBody, "discardRequestBody");
+ break;
+ case "responseStart":
+ expectedPacket = {
+ response: {
+ httpVersion: /^HTTP\/\d\.\d$/,
+ status: "200",
+ statusText: "OK",
+ headersSize: /^\d+$/,
+ discardResponseBody: false,
+ },
+ };
+ break;
+ case "securityInfo":
+ expectedPacket = {
+ state: "insecure",
+ };
+ break;
+ case "responseCookies":
+ expectedPacket = {
+ cookies: 0,
+ };
+ break;
+ case "responseContent":
+ expectedPacket = {
+ mimeType: "application/json",
+ contentSize: /^\d+$/,
+ discardResponseBody: false,
+ };
+ break;
+ case "eventTimings":
+ expectedPacket = {
+ totalTime: /^\d+$/,
+ };
+ break;
+ default:
+ ok(false, "unknown network event update type: " +
+ aPacket.updateType);
+ return;
+ }
+
+ if (expectedPacket) {
+ info("checking the packet content");
+ checkObject(aPacket, expectedPacket);
+ }
+
+ if (updates.indexOf("responseContent") > -1 &&
+ updates.indexOf("eventTimings") > -1) {
+ aState.dbgClient.removeListener("networkEventUpdate",
+ onNetworkEvent);
+
+ onRequestHeaders = onRequestHeaders.bind(null, aState);
+ aState.client.getRequestHeaders(aState.netActor,
+ onRequestHeaders);
+ }
+}
+
+function onRequestHeaders(aState, aResponse)
+{
+ info("checking request headers");
+
+ ok(aResponse.headers.length > 0, "request headers > 0");
+ ok(aResponse.headersSize > 0, "request headersSize > 0");
+ ok(!!aResponse.rawHeaders.length, "request rawHeaders available");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ Referer: /network_requests_iframe\.html/,
+ Cookie: /bug768096/,
+ });
+
+ checkRawHeaders(aResponse.rawHeaders, {
+ Referer: /network_requests_iframe\.html/,
+ Cookie: /bug768096/,
+ });
+
+ onRequestCookies = onRequestCookies.bind(null, aState);
+ aState.client.getRequestCookies(aState.netActor,
+ onRequestCookies);
+}
+
+function onRequestCookies(aState, aResponse)
+{
+ info("checking request cookies");
+
+ is(aResponse.cookies.length, 3, "request cookies length");
+
+ checkHeadersOrCookies(aResponse.cookies, {
+ foobar: "fooval",
+ omgfoo: "bug768096",
+ badcookie: "bug826798=st3fan",
+ });
+
+ onRequestPostData = onRequestPostData.bind(null, aState);
+ aState.client.getRequestPostData(aState.netActor,
+ onRequestPostData);
+}
+
+function onRequestPostData(aState, aResponse)
+{
+ info("checking request POST data");
+
+ checkObject(aResponse, {
+ postData: {
+ text: /^Hello world! foobaz barr.+foobaz barr$/,
+ },
+ postDataDiscarded: false,
+ });
+
+ is(aResponse.postData.text.length, 552, "postData text length");
+
+ onResponseHeaders = onResponseHeaders.bind(null, aState);
+ aState.client.getResponseHeaders(aState.netActor,
+ onResponseHeaders);
+}
+
+function onResponseHeaders(aState, aResponse)
+{
+ info("checking response headers");
+
+ ok(aResponse.headers.length > 0, "response headers > 0");
+ ok(aResponse.headersSize > 0, "response headersSize > 0");
+ ok(!!aResponse.rawHeaders, "response rawHeaders available");
+
+ checkHeadersOrCookies(aResponse.headers, {
+ "Content-Type": /^application\/(json|octet-stream)$/,
+ "Content-Length": /^\d+$/,
+ });
+
+ checkRawHeaders(aResponse.rawHeaders, {
+ "Content-Type": /^application\/(json|octet-stream)$/,
+ "Content-Length": /^\d+$/,
+ });
+
+ onResponseCookies = onResponseCookies.bind(null, aState);
+ aState.client.getResponseCookies(aState.netActor,
+ onResponseCookies);
+}
+
+function onResponseCookies(aState, aResponse)
+{
+ info("checking response cookies");
+
+ is(aResponse.cookies.length, 0, "response cookies length");
+
+ onResponseContent = onResponseContent.bind(null, aState);
+ aState.client.getResponseContent(aState.netActor,
+ onResponseContent);
+}
+
+function onResponseContent(aState, aResponse)
+{
+ info("checking response content");
+
+ checkObject(aResponse, {
+ content: {
+ text: /"test JSON data"/,
+ },
+ contentDiscarded: false,
+ });
+
+ onEventTimings = onEventTimings.bind(null, aState);
+ aState.client.getEventTimings(aState.netActor,
+ onEventTimings);
+}
+
+function onEventTimings(aState, aResponse)
+{
+ info("checking event timings");
+
+ checkObject(aResponse, {
+ timings: {
+ blocked: /^-1|\d+$/,
+ dns: /^-1|\d+$/,
+ connect: /^-1|\d+$/,
+ send: /^-1|\d+$/,
+ wait: /^-1|\d+$/,
+ receive: /^-1|\d+$/,
+ },
+ totalTime: /^\d+$/,
+ });
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_network_security-hpkp.html b/devtools/shared/webconsole/test/test_network_security-hpkp.html
new file mode 100644
index 000000000..55e2621a8
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_network_security-hpkp.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the network actor (HPKP detection)</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the network actor (HPKP detection)</p>
+
+<iframe src="https://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gCurrentTestCase = -1;
+const HPKP_PREF = "security.cert_pinning.process_headers_from_non_builtin_roots";
+
+// Static pins tested by unit/test_security-info-static-hpkp.js.
+const TEST_CASES = [
+ {
+ desc: "no Public Key Pinning",
+ url: "https://example.com",
+ usesPinning: false,
+ },
+ {
+ desc: "dynamic Public Key Pinning with this request",
+ url: "https://include-subdomains.pinning-dynamic.example.com/" +
+ "browser/browser/base/content/test/general/pinning_headers.sjs",
+ usesPinning: true,
+ },
+ {
+ desc: "dynamic Public Key Pinning with previous request",
+ url: "https://include-subdomains.pinning-dynamic.example.com/",
+ usesPinning: true,
+ }
+];
+
+function startTest()
+{
+ // Need to enable this pref or pinning headers are rejected due test
+ // certificate.
+ Services.prefs.setBoolPref(HPKP_PREF, true);
+ SimpleTest.registerCleanupFunction(() => {
+ Services.prefs.setBoolPref(HPKP_PREF, false);
+
+ // Reset pinning state.
+ let gSSService = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+
+ let gIOService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+ for (let {url} of TEST_CASES) {
+ let uri = gIOService.newURI(url, null, null);
+ gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HPKP, uri, 0);
+ }
+ });
+
+ info("Test detection of Public Key Pinning.");
+ removeEventListener("load", startTest);
+ attachConsoleToTab(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+ aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+ runNextCase(aState);
+}
+
+function runNextCase(aState) {
+ gCurrentTestCase++;
+ if (gCurrentTestCase === TEST_CASES.length) {
+ info("Tests ran. Cleaning up.");
+ closeDebugger(aState, SimpleTest.finish);
+ return;
+ }
+
+ let { desc, url } = TEST_CASES[gCurrentTestCase];
+ info("Testing site with " + desc);
+
+ let iframe = document.querySelector("iframe").contentWindow;
+ iframe.wrappedJSObject.makeXhrCallback("GET", url);
+}
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+ function onSecurityInfo(packet) {
+ let data = TEST_CASES[gCurrentTestCase];
+ is(packet.securityInfo.hpkp, data.usesPinning,
+ "Public Key Pinning detected correctly.");
+
+ runNextCase(aState);
+ }
+
+ if (aPacket.updateType === "securityInfo") {
+ aState.client.getSecurityInfo(aPacket.from, onSecurityInfo);
+ }
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_network_security-hsts.html b/devtools/shared/webconsole/test/test_network_security-hsts.html
new file mode 100644
index 000000000..f69244d8d
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_network_security-hsts.html
@@ -0,0 +1,100 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the network actor (HSTS detection)</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the network actor (HSTS detection)</p>
+
+<iframe src="https://example.com/chrome/devtools/shared/webconsole/test/network_requests_iframe.html"></iframe>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let gCurrentTestCase = -1;
+const TEST_CASES = [
+ {
+ desc: "no HSTS",
+ url: "https://example.com",
+ usesHSTS: false,
+ },
+ {
+ desc: "HSTS from this response",
+ url: "https://example.com/"+
+ "browser/browser/base/content/test/general/browser_star_hsts.sjs",
+ usesHSTS: true,
+ },
+ {
+ desc: "stored HSTS from previous response",
+ url: "https://example.com/",
+ usesHSTS: true,
+ }
+];
+
+function startTest()
+{
+
+ SimpleTest.registerCleanupFunction(() => {
+ // Reset HSTS state.
+ let gSSService = Cc["@mozilla.org/ssservice;1"]
+ .getService(Ci.nsISiteSecurityService);
+
+ let gIOService = Cc["@mozilla.org/network/io-service;1"]
+ .getService(Ci.nsIIOService);
+
+ let uri = gIOService.newURI(TEST_CASES[0].url, null, null);
+ gSSService.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, 0);
+ });
+
+ info("Test detection of HTTP Strict Transport Security.");
+ removeEventListener("load", startTest);
+ attachConsoleToTab(["NetworkActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onNetworkEventUpdate = onNetworkEventUpdate.bind(null, aState);
+ aState.dbgClient.addListener("networkEventUpdate", onNetworkEventUpdate);
+
+ runNextCase(aState);
+}
+
+function runNextCase(aState) {
+ gCurrentTestCase++;
+ if (gCurrentTestCase === TEST_CASES.length) {
+ info("Tests ran. Cleaning up.");
+ closeDebugger(aState, SimpleTest.finish);
+ return;
+ }
+
+ let { desc, url } = TEST_CASES[gCurrentTestCase];
+ info("Testing site with " + desc);
+
+ let iframe = document.querySelector("iframe").contentWindow;
+ iframe.wrappedJSObject.makeXhrCallback("GET", url);
+}
+
+function onNetworkEventUpdate(aState, aType, aPacket)
+{
+ function onSecurityInfo(packet) {
+ let data = TEST_CASES[gCurrentTestCase];
+ is(packet.securityInfo.hsts, data.usesHSTS,
+ "Strict Transport Security detected correctly.");
+
+ runNextCase(aState);
+ }
+
+ if (aPacket.updateType === "securityInfo") {
+ aState.client.getSecurityInfo(aPacket.from, onSecurityInfo);
+ }
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_nsiconsolemessage.html b/devtools/shared/webconsole/test/test_nsiconsolemessage.html
new file mode 100644
index 000000000..ef8b8067e
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_nsiconsolemessage.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for nsIConsoleMessages</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Make sure that nsIConsoleMessages are logged. See bug 859756.</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+"use strict";
+SimpleTest.waitForExplicitFinish();
+
+let expectedMessages = [];
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsole(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onLogMessage = onLogMessage.bind(null, aState);
+ aState.dbgClient.addListener("logMessage", onLogMessage);
+
+ expectedMessages = [{
+ message: "hello world! bug859756",
+ timeStamp: /^\d+$/,
+ }];
+
+ Services.console.logStringMessage("hello world! bug859756");
+
+ info("waiting for messages");
+}
+
+let receivedMessages = [];
+
+function onLogMessage(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "packet actor");
+ info("received message: " + aPacket.message);
+
+ let found = false;
+ for (let expected of expectedMessages) {
+ if (expected.message == aPacket.message) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ return;
+ }
+
+ receivedMessages.push(aPacket);
+ if (receivedMessages.length != expectedMessages.length) {
+ return;
+ }
+
+ aState.dbgClient.removeListener("logMessage", onLogMessage);
+
+ checkObject(receivedMessages, expectedMessages);
+
+ closeDebugger(aState, () => SimpleTest.finish());
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_object_actor.html b/devtools/shared/webconsole/test/test_object_actor.html
new file mode 100644
index 000000000..09176a5aa
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_object_actor.html
@@ -0,0 +1,178 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the object actor</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the object actor</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedProps = [];
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleCall = onConsoleCall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
+
+ let longString = (new Array(DebuggerServer.LONG_STRING_LENGTH + 3)).join("\u0629");
+
+ // Here we put the objects in the correct window, to avoid having them all
+ // wrapped by proxies for cross-compartment access.
+
+ let foobarObject = top.Object.create(null);
+ foobarObject.tamarbuta = longString;
+ foobarObject.foo = 1;
+ foobarObject.foobar = "hello";
+ foobarObject.omg = null;
+ foobarObject.testfoo = false;
+ foobarObject.notInspectable = top.Object.create(null);
+ foobarObject.omgfn = new top.Function("return 'myResult'");
+ foobarObject.abArray = new top.Array("a", "b");
+ foobarObject.foobaz = top.document;
+
+ top.Object.defineProperty(foobarObject, "getterAndSetter", {
+ enumerable: true,
+ get: new top.Function("return 'foo';"),
+ set: new top.Function("1+2"),
+ });
+
+ foobarObject.longStringObj = top.Object.create(null);
+ foobarObject.longStringObj.toSource = new top.Function("'" + longString + "'");
+ foobarObject.longStringObj.toString = new top.Function("'" + longString + "'");
+ foobarObject.longStringObj.boom = "explode";
+
+ top.wrappedJSObject.foobarObject = foobarObject;
+ top.console.log("hello", top.wrappedJSObject.foobarObject);
+
+ expectedProps = {
+ "abArray": {
+ value: {
+ type: "object",
+ class: "Array",
+ actor: /[a-z]/,
+ },
+ },
+ "foo": {
+ configurable: true,
+ enumerable: true,
+ writable: true,
+ value: 1,
+ },
+ "foobar": {
+ value: "hello",
+ },
+ "foobaz": {
+ value: {
+ type: "object",
+ class: "XULDocument",
+ actor: /[a-z]/,
+ },
+ },
+ "getterAndSetter": {
+ get: {
+ type: "object",
+ class: "Function",
+ actor: /[a-z]/,
+ },
+ set: {
+ type: "object",
+ class: "Function",
+ actor: /[a-z]/,
+ },
+ },
+ "longStringObj": {
+ value: {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ },
+ },
+ "notInspectable": {
+ value: {
+ type: "object",
+ class: "Object",
+ actor: /[a-z]/,
+ },
+ },
+ "omg": {
+ value: { type: "null" },
+ },
+ "omgfn": {
+ value: {
+ type: "object",
+ class: "Function",
+ actor: /[a-z]/,
+ },
+ },
+ "tamarbuta": {
+ value: {
+ type: "longString",
+ initial: longString.substring(0,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH),
+ length: longString.length,
+ },
+ },
+ "testfoo": {
+ value: false,
+ },
+ };
+}
+
+function onConsoleCall(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ info("checking the console API call packet");
+
+ checkConsoleAPICall(aPacket.message, {
+ level: "log",
+ filename: /test_object_actor/,
+ functionName: "onAttach",
+ arguments: ["hello", {
+ type: "object",
+ actor: /[a-z]/,
+ }],
+ });
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleCall);
+
+ info("inspecting object properties");
+ let args = aPacket.message.arguments;
+ onProperties = onProperties.bind(null, aState);
+
+ let client = new ObjectClient(aState.dbgClient, args[1]);
+ client.getPrototypeAndProperties(onProperties);
+}
+
+function onProperties(aState, aResponse)
+{
+ let props = aResponse.ownProperties;
+ is(Object.keys(props).length, Object.keys(expectedProps).length,
+ "number of enumerable properties");
+ checkObject(props, expectedProps);
+
+ expectedProps = [];
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_object_actor_native_getters.html b/devtools/shared/webconsole/test/test_object_actor_native_getters.html
new file mode 100644
index 000000000..e22eb8cd5
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters.html
@@ -0,0 +1,106 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the native getters in object actors</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the native getters in object actors</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedProps = [];
+let expectedSafeGetters = [];
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleCall = onConsoleCall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
+
+ top.console.log("hello", document);
+
+ expectedProps = {
+ "location": {
+ get: {
+ type: "object",
+ class: "Function",
+ actor: /[a-z]/,
+ },
+ },
+ };
+
+ expectedSafeGetters = {
+ "title": {
+ getterValue: /native getters in object actors/,
+ getterPrototypeLevel: 2,
+ },
+ "styleSheets": {
+ getterValue: /\[object Object\]/,
+ getterPrototypeLevel: 2,
+ },
+ };
+}
+
+function onConsoleCall(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ info("checking the console API call packet");
+
+ checkConsoleAPICall(aPacket.message, {
+ level: "log",
+ filename: /test_object_actor/,
+ functionName: "onAttach",
+ arguments: ["hello", {
+ type: "object",
+ actor: /[a-z]/,
+ }],
+ });
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleCall);
+
+ info("inspecting object properties");
+ let args = aPacket.message.arguments;
+ onProperties = onProperties.bind(null, aState);
+
+ let client = new ObjectClient(aState.dbgClient, args[1]);
+ client.getPrototypeAndProperties(onProperties);
+}
+
+function onProperties(aState, aResponse)
+{
+ let props = aResponse.ownProperties;
+ let keys = Object.keys(props);
+ info(keys.length + " ownProperties: " + keys);
+
+ ok(keys.length >= Object.keys(expectedProps).length, "number of properties");
+
+ info("check ownProperties");
+ checkObject(props, expectedProps);
+ info("check safeGetterValues");
+ checkObject(aResponse.safeGetterValues, expectedSafeGetters);
+
+ expectedProps = [];
+ expectedSafeGetters = [];
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
new file mode 100644
index 000000000..c4197a5b8
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_object_actor_native_getters_lenient_this.html
@@ -0,0 +1,79 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test that WebIDL attributes with the LenientThis extended attribute
+ do not appear in the wrong objects</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for the native getters in object actors</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsoleToTab(["ConsoleAPI"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onConsoleCall = onConsoleCall.bind(null, aState);
+ aState.dbgClient.addListener("consoleAPICall", onConsoleCall);
+
+ let docAsProto = Object.create(document);
+
+ top.console.log("hello", docAsProto);
+}
+
+function onConsoleCall(aState, aType, aPacket)
+{
+ is(aPacket.from, aState.actor, "console API call actor");
+
+ info("checking the console API call packet");
+
+ checkConsoleAPICall(aPacket.message, {
+ level: "log",
+ filename: /test_object_actor/,
+ functionName: "onAttach",
+ arguments: ["hello", {
+ type: "object",
+ actor: /[a-z]/,
+ }],
+ });
+
+ aState.dbgClient.removeListener("consoleAPICall", onConsoleCall);
+
+ info("inspecting object properties");
+ let args = aPacket.message.arguments;
+ onProperties = onProperties.bind(null, aState);
+
+ let client = new ObjectClient(aState.dbgClient, args[1]);
+ client.getPrototypeAndProperties(onProperties);
+}
+
+function onProperties(aState, aResponse)
+{
+ let props = aResponse.ownProperties;
+ let keys = Object.keys(props);
+ info(keys.length + " ownProperties: " + keys);
+
+ is(keys.length, 0, "number of properties");
+ keys = Object.keys(aResponse.safeGetterValues);
+ is(keys.length, 0, "number of safe getters");
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_page_errors.html b/devtools/shared/webconsole/test/test_page_errors.html
new file mode 100644
index 000000000..19e5ba4b4
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_page_errors.html
@@ -0,0 +1,186 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for page errors</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for page errors</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let expectedPageErrors = [];
+
+function doPageErrors()
+{
+ expectedPageErrors = {
+ "document.body.style.color = 'fooColor';": {
+ errorMessage: /fooColor/,
+ errorMessageName: undefined,
+ sourceName: /test_page_errors/,
+ category: "CSS Parser",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: true,
+ exception: false,
+ strict: false,
+ },
+ "document.doTheImpossible();": {
+ errorMessage: /doTheImpossible/,
+ errorMessageName: undefined,
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ strict: false,
+ },
+ "(42).toString(0);": {
+ errorMessage: /radix/,
+ errorMessageName: "JSMSG_BAD_RADIX",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ strict: false,
+ },
+ "'use strict'; (Object.freeze({name: 'Elsa', score: 157})).score = 0;": {
+ errorMessage: /read.only/,
+ errorMessageName: "JSMSG_READ_ONLY",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "([]).length = -1": {
+ errorMessage: /array length/,
+ errorMessageName: "JSMSG_BAD_ARRAY_LENGTH",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "'abc'.repeat(-1);": {
+ errorMessage: /repeat count.*non-negative/,
+ errorMessageName: "JSMSG_NEGATIVE_REPETITION_COUNT",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "'a'.repeat(2e28);": {
+ errorMessage: /repeat count.*less than infinity/,
+ errorMessageName: "JSMSG_RESULTING_STRING_TOO_LARGE",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "77.1234.toExponential(-1);": {
+ errorMessage: /out of range/,
+ errorMessageName: "JSMSG_PRECISION_RANGE",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "var f = Function('x y', 'return x + y;');": {
+ errorMessage: /malformed formal/,
+ errorMessageName: "JSMSG_BAD_FORMAL",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: false,
+ exception: true,
+ },
+ "function a() { return; 1 + 1; }": {
+ errorMessage: /unreachable code/,
+ errorMessageName: "JSMSG_STMT_AFTER_RETURN",
+ sourceName: /test_page_errors/,
+ category: "chrome javascript",
+ timeStamp: /^\d+$/,
+ error: false,
+ warning: true,
+ exception: false,
+ },
+ };
+
+ let container = document.createElement("script");
+ for (let stmt of Object.keys(expectedPageErrors)) {
+ if (expectedPageErrors[stmt].exception) {
+ SimpleTest.expectUncaughtException();
+ }
+ info("starting stmt: " + stmt);
+ container = document.createElement("script");
+ document.body.appendChild(container);
+ container.textContent = stmt;
+ document.body.removeChild(container);
+ info("ending stmt: " + stmt);
+ }
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+
+ attachConsole(["PageError"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ onPageError = onPageError.bind(null, aState);
+ aState.dbgClient.addListener("pageError", onPageError);
+ doPageErrors();
+}
+
+let pageErrors = [];
+
+function onPageError(aState, aType, aPacket)
+{
+ if (!aPacket.pageError.sourceName.includes("test_page_errors")) {
+ info("Ignoring error from unknown source: " + aPacket.pageError.sourceName);
+ return;
+ }
+
+ is(aPacket.from, aState.actor, "page error actor");
+
+ pageErrors.push(aPacket.pageError);
+ if (pageErrors.length != Object.keys(expectedPageErrors).length) {
+ return;
+ }
+
+ aState.dbgClient.removeListener("pageError", onPageError);
+
+ Object.values(expectedPageErrors).forEach(function(aMessage, aIndex) {
+ info("checking received page error #" + aIndex);
+ checkObject(pageErrors[aIndex], Object.values(expectedPageErrors)[aIndex]);
+ });
+
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_reflow.html b/devtools/shared/webconsole/test/test_reflow.html
new file mode 100644
index 000000000..2ac2ca509
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_reflow.html
@@ -0,0 +1,94 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Test for the Reflow Activity</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Test for reflow events</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+let client;
+
+function generateReflow()
+{
+ top.document.documentElement.style.display = "none";
+ top.document.documentElement.getBoundingClientRect();
+ top.document.documentElement.style.display = "block";
+}
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsoleToTab(["ReflowActivity"], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ client = aState.dbgClient;
+
+ onReflowActivity = onReflowActivity.bind(null, aState);
+ client.addListener("reflowActivity", onReflowActivity);
+ generateReflow();
+}
+
+// We are expecting 3 reflow events.
+let expectedEvents = [
+ {
+ interruptible: false,
+ sourceURL: "chrome://mochitests/content/chrome/devtools/shared/webconsole/test/test_reflow.html",
+ functionName: "generateReflow"
+ },
+ {
+ interruptible: true,
+ sourceURL: null,
+ functionName: null
+ },
+ {
+ interruptible: true,
+ sourceURL: null,
+ functionName: null
+ },
+];
+
+let receivedEvents = [];
+
+
+function onReflowActivity(aState, aType, aPacket)
+{
+ info("packet: " + aPacket.message);
+ receivedEvents.push(aPacket);
+ if (receivedEvents.length == expectedEvents.length) {
+ checkEvents();
+ finish(aState);
+ }
+}
+
+function checkEvents() {
+ for (let i = 0; i < expectedEvents.length; i++) {
+ let a = expectedEvents[i];
+ let b = receivedEvents[i];
+ for (let key in a) {
+ is(a[key], b[key], "field " + key + " is valid");
+ }
+ }
+}
+
+function finish(aState) {
+ client.removeListener("reflowActivity", onReflowActivity);
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/test_throw.html b/devtools/shared/webconsole/test/test_throw.html
new file mode 100644
index 000000000..7d7ea7b31
--- /dev/null
+++ b/devtools/shared/webconsole/test/test_throw.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html lang="en">
+<head>
+ <meta charset="utf8">
+ <title>Web Console throw tests</title>
+ <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript;version=1.8" src="common.js"></script>
+ <!-- Any copyright is dedicated to the Public Domain.
+ - http://creativecommons.org/publicdomain/zero/1.0/ -->
+</head>
+<body>
+<p>Web Console throw tests</p>
+
+<script class="testbody" type="text/javascript;version=1.8">
+SimpleTest.waitForExplicitFinish();
+
+function startTest()
+{
+ removeEventListener("load", startTest);
+ attachConsoleToTab([], onAttach);
+}
+
+function onAttach(aState, aResponse)
+{
+ let tests = [];
+
+ let falsyValues = ["-0", "null", "undefined", "Infinity", "-Infinity", "NaN"];
+ falsyValues.forEach(function(value) {
+ tests.push(function() {
+ aState.client.evaluateJS("throw " + value + ";", function(aResponse) {
+ let type = aResponse.exception.type;
+ is(type, value, "exception.type for throw " + value);
+ nextTest();
+ });
+ });
+ });
+
+ let identityTestValues = [false, 0];
+ identityTestValues.forEach(function(value) {
+ tests.push(function() {
+ aState.client.evaluateJS("throw " + value + ";", function(aResponse) {
+ let exception = aResponse.exception;
+ is(exception, value, "response.exception for throw " + value);
+ nextTest();
+ });
+ });
+ });
+
+ let longString = Array(DebuggerServer.LONG_STRING_LENGTH + 1).join("a"),
+ shortedString = longString.substring(0,
+ DebuggerServer.LONG_STRING_INITIAL_LENGTH
+ );
+ tests.push(function() {
+ aState.client.evaluateJS("throw '" + longString + "';", function(aResponse) {
+ is(aResponse.exception.initial, shortedString,
+ "exception.initial for throw longString"
+ );
+ is(aResponse.exceptionMessage.initial, shortedString,
+ "exceptionMessage.initial for throw longString"
+ );
+ nextTest();
+ });
+ });
+
+ let symbolTestValues = [
+ ["Symbol.iterator", "Symbol(Symbol.iterator)"],
+ ["Symbol('foo')", "Symbol(foo)"],
+ ["Symbol()", "Symbol()"],
+ ];
+ symbolTestValues.forEach(function([expr, message]) {
+ tests.push(function() {
+ aState.client.evaluateJS("throw " + expr + ";", function(aResponse) {
+ is(aResponse.exceptionMessage, message,
+ "response.exception for throw " + expr);
+ nextTest();
+ });
+ });
+ });
+
+ runTests(tests, endTest.bind(null, aState));
+}
+
+function endTest(aState)
+{
+ closeDebugger(aState, function() {
+ SimpleTest.finish();
+ });
+}
+
+addEventListener("load", startTest);
+</script>
+</body>
+</html>
diff --git a/devtools/shared/webconsole/test/unit/.eslintrc.js b/devtools/shared/webconsole/test/unit/.eslintrc.js
new file mode 100644
index 000000000..59adf410a
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the common devtools xpcshell eslintrc config.
+ "extends": "../../../../.eslintrc.xpcshell.js"
+};
diff --git a/devtools/shared/webconsole/test/unit/test_js_property_provider.js b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
new file mode 100644
index 000000000..c360cf96d
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_js_property_provider.js
@@ -0,0 +1,170 @@
+/* -*- js-indent-level: 2; indent-tabs-mode: nil -*- */
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+"use strict";
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+const { FallibleJSPropertyProvider: JSPropertyProvider } =
+ require("devtools/shared/webconsole/js-property-provider");
+
+Components.utils.import("resource://gre/modules/jsdebugger.jsm");
+addDebuggerToGlobal(this);
+
+function run_test() {
+ const testArray = `var testArray = [
+ {propA: "A"},
+ {
+ propB: "B",
+ propC: [
+ "D"
+ ]
+ },
+ [
+ {propE: "E"}
+ ]
+ ]`;
+
+ const testObject = 'var testObject = {"propA": [{"propB": "B"}]}';
+ const testHyphenated = 'var testHyphenated = {"prop-A": "res-A"}';
+ const testLet = "let foobar = {a: ''}; const blargh = {a: 1};";
+
+ let sandbox = Components.utils.Sandbox("http://example.com");
+ let dbg = new Debugger;
+ let dbgObject = dbg.addDebuggee(sandbox);
+ let dbgEnv = dbgObject.asEnvironment();
+ Components.utils.evalInSandbox(testArray, sandbox);
+ Components.utils.evalInSandbox(testObject, sandbox);
+ Components.utils.evalInSandbox(testHyphenated, sandbox);
+ Components.utils.evalInSandbox(testLet, sandbox);
+
+ do_print("Running tests with dbgObject");
+ runChecks(dbgObject, null);
+
+ do_print("Running tests with dbgEnv");
+ runChecks(null, dbgEnv);
+
+}
+
+function runChecks(dbgObject, dbgEnv) {
+ do_print("Test that suggestions are given for 'this'");
+ let results = JSPropertyProvider(dbgObject, dbgEnv, "t");
+ test_has_result(results, "this");
+
+ if (dbgObject != null) {
+ do_print("Test that suggestions are given for 'this.'");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "this.");
+ test_has_result(results, "testObject");
+
+ do_print("Test that no suggestions are given for 'this.this'");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "this.this");
+ test_has_no_results(results);
+ }
+
+ do_print("Testing lexical scope issues (Bug 1207868)");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "foobar");
+ test_has_result(results, "foobar");
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "foobar.");
+ test_has_result(results, "a");
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "blargh");
+ test_has_result(results, "blargh");
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "blargh.");
+ test_has_result(results, "a");
+
+ do_print("Test that suggestions are given for 'foo[n]' where n is an integer.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0].");
+ test_has_result(results, "propA");
+
+ do_print("Test that suggestions are given for multidimensional arrays.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[2][0].");
+ test_has_result(results, "propE");
+
+ do_print("Test that suggestions are given for nested arrays.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[1].propC[0].");
+ test_has_result(results, "indexOf");
+
+ do_print("Test that suggestions are given for literal arrays.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].");
+ test_has_result(results, "indexOf");
+
+ do_print("Test that suggestions are given for literal arrays with newlines.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3,\n4\n].");
+ test_has_result(results, "indexOf");
+
+ do_print("Test that suggestions are given for literal strings.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'.");
+ test_has_result(results, "charAt");
+ results = JSPropertyProvider(dbgObject, dbgEnv, '"foo".');
+ test_has_result(results, "charAt");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "`foo`.");
+ test_has_result(results, "charAt");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2,3]'.");
+ test_has_result(results, "charAt");
+
+ do_print("Test that suggestions are not given for syntax errors.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'foo\"");
+ do_check_null(results);
+ results = JSPropertyProvider(dbgObject, dbgEnv, "[1,',2]");
+ do_check_null(results);
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'[1,2].");
+ do_check_null(results);
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'..");
+ do_check_null(results);
+
+ do_print("Test that suggestions are not given without a dot.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "'foo'");
+ test_has_no_results(results);
+ results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3]");
+ test_has_no_results(results);
+ results = JSPropertyProvider(dbgObject, dbgEnv, "[1,2,3].\n'foo'");
+ test_has_no_results(results);
+
+ do_print("Test that suggestions are not given for numeric literals.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "1.");
+ do_check_null(results);
+
+ do_print("Test that suggestions are not given for index that's out of bounds.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[10].");
+ do_check_null(results);
+
+ do_print("Test that no suggestions are given if an index is not numerical somewhere in the chain.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'][0].");
+ do_check_null(results);
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testObject['propA'][0].");
+ do_check_null(results);
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[0]['propC'].");
+ do_check_null(results);
+
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testArray[][1].");
+ do_check_null(results);
+
+ do_print("Test that suggestions are not given if there is an hyphen in the chain.");
+ results = JSPropertyProvider(dbgObject, dbgEnv, "testHyphenated['prop-A'].");
+ do_check_null(results);
+}
+
+/**
+ * A helper that ensures an empty array of results were found.
+ * @param Object aResults
+ * The results returned by JSPropertyProvider.
+ */
+function test_has_no_results(aResults) {
+ do_check_neq(aResults, null);
+ do_check_eq(aResults.matches.length, 0);
+}
+/**
+ * A helper that ensures (required) results were found.
+ * @param Object aResults
+ * The results returned by JSPropertyProvider.
+ * @param String aRequiredSuggestion
+ * A suggestion that must be found from the results.
+ */
+function test_has_result(aResults, aRequiredSuggestion) {
+ do_check_neq(aResults, null);
+ do_check_true(aResults.matches.length > 0);
+ do_check_true(aResults.matches.indexOf(aRequiredSuggestion) !== -1);
+}
diff --git a/devtools/shared/webconsole/test/unit/test_network_helper.js b/devtools/shared/webconsole/test/unit/test_network_helper.js
new file mode 100644
index 000000000..3a43ff432
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_network_helper.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+var Cu = Components.utils;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+function run_test() {
+ test_isTextMimeType();
+}
+
+function test_isTextMimeType() {
+ do_check_eq(NetworkHelper.isTextMimeType("text/plain"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("application/javascript"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("application/json"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("text/css"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("text/html"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("image/svg+xml"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("application/xml"), true);
+
+ // Test custom JSON subtype
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+json"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-json"), true);
+ // Test custom XML subtype
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0+xml"), true);
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.tent.posts-feed.v0-xml"), false);
+ // Test case-insensitive
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.BIG-CORP+json"), true);
+ // Test non-text type
+ do_check_eq(NetworkHelper.isTextMimeType("image/png"), false);
+ // Test invalid types
+ do_check_eq(NetworkHelper.isTextMimeType("application/foo-+json"), false);
+ do_check_eq(NetworkHelper.isTextMimeType("application/-foo+json"), false);
+ do_check_eq(NetworkHelper.isTextMimeType("application/foo--bar+json"), false);
+
+ // Test we do not cause internal errors with unoptimized regex. Bug 961097
+ do_check_eq(NetworkHelper.isTextMimeType("application/vnd.google.safebrowsing-chunk"), false);
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-certificate.js b/devtools/shared/webconsole/test/unit/test_security-info-certificate.js
new file mode 100644
index 000000000..be223d87e
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-certificate.js
@@ -0,0 +1,68 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.parseCertificateInfo parses certificate information
+// correctly.
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const DUMMY_CERT = {
+ commonName: "cn",
+ organization: "o",
+ organizationalUnit: "ou",
+ issuerCommonName: "issuerCN",
+ issuerOrganization: "issuerO",
+ issuerOrganizationUnit: "issuerOU",
+ sha256Fingerprint: "qwertyuiopoiuytrewq",
+ sha1Fingerprint: "qwertyuiop",
+ validity: {
+ notBeforeLocalDay: "yesterday",
+ notAfterLocalDay: "tomorrow",
+ }
+};
+
+function run_test() {
+ do_print("Testing NetworkHelper.parseCertificateInfo.");
+
+ let result = NetworkHelper.parseCertificateInfo(DUMMY_CERT);
+
+ // Subject
+ equal(result.subject.commonName, DUMMY_CERT.commonName,
+ "Common name is correct.");
+ equal(result.subject.organization, DUMMY_CERT.organization,
+ "Organization is correct.");
+ equal(result.subject.organizationUnit, DUMMY_CERT.organizationUnit,
+ "Organizational unit is correct.");
+
+ // Issuer
+ equal(result.issuer.commonName, DUMMY_CERT.issuerCommonName,
+ "Common name of the issuer is correct.");
+ equal(result.issuer.organization, DUMMY_CERT.issuerOrganization,
+ "Organization of the issuer is correct.");
+ equal(result.issuer.organizationUnit, DUMMY_CERT.issuerOrganizationUnit,
+ "Organizational unit of the issuer is correct.");
+
+ // Validity
+ equal(result.validity.start, DUMMY_CERT.validity.notBeforeLocalDay,
+ "Start of the validity period is correct.");
+ equal(result.validity.end, DUMMY_CERT.validity.notAfterLocalDay,
+ "End of the validity period is correct.");
+
+ // Fingerprints
+ equal(result.fingerprint.sha1, DUMMY_CERT.sha1Fingerprint,
+ "Certificate SHA1 fingerprint is correct.");
+ equal(result.fingerprint.sha256, DUMMY_CERT.sha256Fingerprint,
+ "Certificate SHA256 fingerprint is correct.");
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-parser.js b/devtools/shared/webconsole/test/unit/test_security-info-parser.js
new file mode 100644
index 000000000..a5682e209
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-parser.js
@@ -0,0 +1,64 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that NetworkHelper.parseSecurityInfo returns correctly formatted object.
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const wpl = Ci.nsIWebProgressListener;
+const MockCertificate = {
+ commonName: "cn",
+ organization: "o",
+ organizationalUnit: "ou",
+ issuerCommonName: "issuerCN",
+ issuerOrganization: "issuerO",
+ issuerOrganizationUnit: "issuerOU",
+ sha256Fingerprint: "qwertyuiopoiuytrewq",
+ sha1Fingerprint: "qwertyuiop",
+ validity: {
+ notBeforeLocalDay: "yesterday",
+ notAfterLocalDay: "tomorrow",
+ }
+};
+
+const MockSecurityInfo = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo,
+ Ci.nsISSLStatusProvider]),
+ securityState: wpl.STATE_IS_SECURE,
+ errorCode: 0,
+ SSLStatus: {
+ cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ protocolVersion: 3, // TLS_VERSION_1_2
+ serverCert: MockCertificate,
+ }
+};
+
+function run_test() {
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {});
+
+ equal(result.state, "secure", "State is correct.");
+
+ equal(result.cipherSuite, MockSecurityInfo.cipherSuite,
+ "Cipher suite is correct.");
+
+ equal(result.protocolVersion, "TLSv1.2", "Protocol version is correct.");
+
+ deepEqual(result.cert, NetworkHelper.parseCertificateInfo(MockCertificate),
+ "Certificate information is correct.");
+
+ equal(result.hpkp, false, "HPKP is false when URI is not available.");
+ equal(result.hsts, false, "HSTS is false when URI is not available.");
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js b/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js
new file mode 100644
index 000000000..b84002131
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-protocol-version.js
@@ -0,0 +1,54 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.formatSecurityProtocol returns correct
+// protocol version strings.
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const TEST_CASES = [
+ {
+ description: "TLS_VERSION_1",
+ input: 1,
+ expected: "TLSv1"
+ }, {
+ description: "TLS_VERSION_1.1",
+ input: 2,
+ expected: "TLSv1.1"
+ }, {
+ description: "TLS_VERSION_1.2",
+ input: 3,
+ expected: "TLSv1.2"
+ }, {
+ description: "TLS_VERSION_1.3",
+ input: 4,
+ expected: "TLSv1.3"
+ }, {
+ description: "invalid version",
+ input: -1,
+ expected: "Unknown"
+ },
+];
+
+function run_test() {
+ do_print("Testing NetworkHelper.formatSecurityProtocol.");
+
+ for (let {description, input, expected} of TEST_CASES) {
+ do_print("Testing " + description);
+
+ equal(NetworkHelper.formatSecurityProtocol(input), expected,
+ "Got the expected protocol string.");
+ }
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-state.js b/devtools/shared/webconsole/test/unit/test_security-info-state.js
new file mode 100644
index 000000000..efa493a95
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-state.js
@@ -0,0 +1,100 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that security info parser gives correct general security state for
+// different cases.
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const wpl = Ci.nsIWebProgressListener;
+const MockSecurityInfo = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo,
+ Ci.nsISSLStatusProvider]),
+ securityState: wpl.STATE_IS_BROKEN,
+ errorCode: 0,
+ SSLStatus: {
+ protocolVersion: 3, // nsISSLStatus.TLS_VERSION_1_2
+ cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ }
+};
+
+function run_test() {
+ test_nullSecurityInfo();
+ test_insecureSecurityInfoWithNSSError();
+ test_insecureSecurityInfoWithoutNSSError();
+ test_brokenSecurityInfo();
+ test_secureSecurityInfo();
+}
+
+/**
+ * Test that undefined security information is returns "insecure".
+ */
+function test_nullSecurityInfo() {
+ let result = NetworkHelper.parseSecurityInfo(null, {});
+ equal(result.state, "insecure",
+ "state == 'insecure' when securityInfo was undefined");
+}
+
+/**
+ * Test that STATE_IS_INSECURE with NSSError returns "broken"
+ */
+function test_insecureSecurityInfoWithNSSError() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE;
+
+ // Taken from security/manager/ssl/tests/unit/head_psm.js.
+ MockSecurityInfo.errorCode = -8180;
+
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {});
+ equal(result.state, "broken",
+ "state == 'broken' if securityState contains STATE_IS_INSECURE flag AND " +
+ "errorCode is NSS error.");
+
+ MockSecurityInfo.errorCode = 0;
+}
+
+/**
+ * Test that STATE_IS_INSECURE without NSSError returns "insecure"
+ */
+function test_insecureSecurityInfoWithoutNSSError() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_INSECURE;
+
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {});
+ equal(result.state, "insecure",
+ "state == 'insecure' if securityState contains STATE_IS_INSECURE flag BUT " +
+ "errorCode is not NSS error.");
+}
+
+/**
+ * Test that STATE_IS_SECURE returns "secure"
+ */
+function test_secureSecurityInfo() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_SECURE;
+
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {});
+ equal(result.state, "secure",
+ "state == 'secure' if securityState contains STATE_IS_SECURE flag");
+}
+
+/**
+ * Test that STATE_IS_BROKEN returns "weak"
+ */
+function test_brokenSecurityInfo() {
+ MockSecurityInfo.securityState = wpl.STATE_IS_BROKEN;
+
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, {});
+ equal(result.state, "weak",
+ "state == 'weak' if securityState contains STATE_IS_BROKEN flag");
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js b/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js
new file mode 100644
index 000000000..b76fa141a
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-static-hpkp.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Test that NetworkHelper.parseSecurityInfo correctly detects static hpkp pins
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+const Services = require("Services");
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const wpl = Ci.nsIWebProgressListener;
+
+const MockSecurityInfo = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsITransportSecurityInfo,
+ Ci.nsISSLStatusProvider]),
+ securityState: wpl.STATE_IS_SECURE,
+ errorCode: 0,
+ SSLStatus: {
+ cipherSuite: "TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256",
+ protocolVersion: 3, // TLS_VERSION_1_2
+ serverCert: {
+ validity: {}
+ },
+ }
+};
+
+const MockHttpInfo = {
+ hostname: "include-subdomains.pinning.example.com",
+ private: false,
+};
+
+function run_test() {
+ Services.prefs.setIntPref("security.cert_pinning.enforcement_level", 1);
+ let result = NetworkHelper.parseSecurityInfo(MockSecurityInfo, MockHttpInfo);
+ equal(result.hpkp, true, "Static HPKP detected.");
+}
diff --git a/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js b/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js
new file mode 100644
index 000000000..f91d8049e
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_security-info-weakness-reasons.js
@@ -0,0 +1,47 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+// Tests that NetworkHelper.getReasonsForWeakness returns correct reasons for
+// weak requests.
+
+const { require } = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+
+Object.defineProperty(this, "NetworkHelper", {
+ get: function () {
+ return require("devtools/shared/webconsole/network-helper");
+ },
+ configurable: true,
+ writeable: false,
+ enumerable: true
+});
+
+var Ci = Components.interfaces;
+const wpl = Ci.nsIWebProgressListener;
+const TEST_CASES = [
+ {
+ description: "weak cipher",
+ input: wpl.STATE_IS_BROKEN | wpl.STATE_USES_WEAK_CRYPTO,
+ expected: ["cipher"]
+ }, {
+ description: "only STATE_IS_BROKEN flag",
+ input: wpl.STATE_IS_BROKEN,
+ expected: []
+ }, {
+ description: "only STATE_IS_SECURE flag",
+ input: wpl.STATE_IS_SECURE,
+ expected: []
+ },
+];
+
+function run_test() {
+ do_print("Testing NetworkHelper.getReasonsForWeakness.");
+
+ for (let {description, input, expected} of TEST_CASES) {
+ do_print("Testing " + description);
+
+ deepEqual(NetworkHelper.getReasonsForWeakness(input), expected,
+ "Got the expected reasons for weakness.");
+ }
+}
diff --git a/devtools/shared/webconsole/test/unit/test_throttle.js b/devtools/shared/webconsole/test/unit/test_throttle.js
new file mode 100644
index 000000000..fa8b26b61
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/test_throttle.js
@@ -0,0 +1,140 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const Cu = Components.utils;
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+const promise = require("promise");
+const { NetworkThrottleManager } =
+ require("devtools/shared/webconsole/throttle");
+const nsIScriptableInputStream = Ci.nsIScriptableInputStream;
+
+function TestStreamListener() {
+ this.state = "initial";
+}
+TestStreamListener.prototype = {
+ onStartRequest: function() {
+ this.setState("start");
+ },
+
+ onStopRequest: function() {
+ this.setState("stop");
+ },
+
+ onDataAvailable: function(request, context, inputStream, offset, count) {
+ const sin = Components.classes["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(nsIScriptableInputStream);
+ sin.init(inputStream);
+ this.data = sin.read(count);
+ this.setState("data");
+ },
+
+ setState: function(state) {
+ this.state = state;
+ if (this._deferred) {
+ this._deferred.resolve(state);
+ this._deferred = null;
+ }
+ },
+
+ onStateChanged: function() {
+ if (!this._deferred) {
+ this._deferred = promise.defer();
+ }
+ return this._deferred.promise;
+ }
+};
+
+function TestChannel() {
+ this.state = "initial";
+ this.testListener = new TestStreamListener();
+ this._throttleQueue = null;
+}
+TestChannel.prototype = {
+ QueryInterface: function() {
+ return this;
+ },
+
+ get throttleQueue() {
+ return this._throttleQueue;
+ },
+
+ set throttleQueue(q) {
+ this._throttleQueue = q;
+ this.state = "throttled";
+ },
+
+ setNewListener: function(listener) {
+ this.listener = listener;
+ this.state = "listener";
+ return this.testListener;
+ },
+};
+
+add_task(function*() {
+ let throttler = new NetworkThrottleManager({
+ roundTripTimeMean: 1,
+ roundTripTimeMax: 1,
+ downloadBPSMean: 500,
+ downloadBPSMax: 500,
+ uploadBPSMean: 500,
+ uploadBPSMax: 500,
+ });
+
+ let uploadChannel = new TestChannel();
+ throttler.manageUpload(uploadChannel);
+ equal(uploadChannel.state, "throttled",
+ "NetworkThrottleManager set throttleQueue");
+
+ let downloadChannel = new TestChannel();
+ let testListener = downloadChannel.testListener;
+
+ let listener = throttler.manage(downloadChannel);
+ equal(downloadChannel.state, "listener",
+ "NetworkThrottleManager called setNewListener");
+
+ equal(testListener.state, "initial", "test listener in initial state");
+
+ // This method must be passed through immediately.
+ listener.onStartRequest(null, null);
+ equal(testListener.state, "start", "test listener started");
+
+ const TEST_INPUT = "hi bob";
+
+ let testStream = Cc["@mozilla.org/storagestream;1"]
+ .createInstance(Ci.nsIStorageStream);
+ testStream.init(512, 512);
+ let out = testStream.getOutputStream(0);
+ out.write(TEST_INPUT, TEST_INPUT.length);
+ out.close();
+ let testInputStream = testStream.newInputStream(0);
+
+ let activityDistributor =
+ Cc["@mozilla.org/network/http-activity-distributor;1"]
+ .getService(Ci.nsIHttpActivityDistributor);
+ let activitySeen = false;
+ listener.addActivityCallback(() => activitySeen = true, null, null, null,
+ activityDistributor
+ .ACTIVITY_SUBTYPE_RESPONSE_COMPLETE,
+ null, TEST_INPUT.length, null);
+
+ // onDataAvailable is required to immediately read the data.
+ listener.onDataAvailable(null, null, testInputStream, 0, 6);
+ equal(testInputStream.available(), 0, "no more data should be available");
+ equal(testListener.state, "start",
+ "test listener should not have received data");
+ equal(activitySeen, false, "activity not distributed yet");
+
+ let newState = yield testListener.onStateChanged();
+ equal(newState, "data", "test listener received data");
+ equal(testListener.data, TEST_INPUT, "test listener received all the data");
+ equal(activitySeen, true, "activity has been distributed");
+
+ let onChange = testListener.onStateChanged();
+ listener.onStopRequest(null, null, null);
+ newState = yield onChange;
+ equal(newState, "stop", "onStateChanged reported");
+});
diff --git a/devtools/shared/webconsole/test/unit/xpcshell.ini b/devtools/shared/webconsole/test/unit/xpcshell.ini
new file mode 100644
index 000000000..083950834
--- /dev/null
+++ b/devtools/shared/webconsole/test/unit/xpcshell.ini
@@ -0,0 +1,17 @@
+[DEFAULT]
+tags = devtools
+head =
+tail =
+firefox-appdir = browser
+skip-if = toolkit == 'android'
+support-files =
+
+[test_js_property_provider.js]
+[test_network_helper.js]
+[test_security-info-certificate.js]
+[test_security-info-parser.js]
+[test_security-info-protocol-version.js]
+[test_security-info-state.js]
+[test_security-info-static-hpkp.js]
+[test_security-info-weakness-reasons.js]
+[test_throttle.js]
diff --git a/devtools/shared/webconsole/throttle.js b/devtools/shared/webconsole/throttle.js
new file mode 100644
index 000000000..3a875ee24
--- /dev/null
+++ b/devtools/shared/webconsole/throttle.js
@@ -0,0 +1,418 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {CC, Ci, Cu, Cc} = require("chrome");
+
+const ArrayBufferInputStream = CC("@mozilla.org/io/arraybuffer-input-stream;1",
+ "nsIArrayBufferInputStream");
+const BinaryInputStream = CC("@mozilla.org/binaryinputstream;1",
+ "nsIBinaryInputStream", "setInputStream");
+
+loader.lazyServiceGetter(this, "gActivityDistributor",
+ "@mozilla.org/network/http-activity-distributor;1",
+ "nsIHttpActivityDistributor");
+
+const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
+const {setTimeout} = Cu.import("resource://gre/modules/Timer.jsm", {});
+
+/**
+ * Construct a new nsIStreamListener that buffers data and provides a
+ * method to notify another listener when data is available. This is
+ * used to throttle network data on a per-channel basis.
+ *
+ * After construction, @see setOriginalListener must be called on the
+ * new object.
+ *
+ * @param {NetworkThrottleQueue} queue the NetworkThrottleQueue to
+ * which status changes should be reported
+ */
+function NetworkThrottleListener(queue) {
+ this.queue = queue;
+ this.pendingData = [];
+ this.pendingException = null;
+ this.offset = 0;
+ this.responseStarted = false;
+ this.activities = {};
+}
+
+NetworkThrottleListener.prototype = {
+ QueryInterface:
+ XPCOMUtils.generateQI([Ci.nsIStreamListener, Ci.nsIInterfaceRequestor,
+ Ci.nsISupports]),
+
+ /**
+ * Set the original listener for this object. The original listener
+ * will receive requests from this object when the queue allows data
+ * through.
+ *
+ * @param {nsIStreamListener} originalListener the original listener
+ * for the channel, to which all requests will be sent
+ */
+ setOriginalListener: function (originalListener) {
+ this.originalListener = originalListener;
+ },
+
+ /**
+ * @see nsIStreamListener.onStartRequest.
+ */
+ onStartRequest: function (request, context) {
+ this.originalListener.onStartRequest(request, context);
+ this.queue.start(this);
+ },
+
+ /**
+ * @see nsIStreamListener.onStopRequest.
+ */
+ onStopRequest: function (request, context, statusCode) {
+ this.pendingData.push({request, context, statusCode});
+ this.queue.dataAvailable(this);
+ },
+
+ /**
+ * @see nsIStreamListener.onDataAvailable.
+ */
+ onDataAvailable: function (request, context, inputStream, offset, count) {
+ if (this.pendingException) {
+ throw this.pendingException;
+ }
+
+ const bin = new BinaryInputStream(inputStream);
+ const bytes = new ArrayBuffer(count);
+ bin.readArrayBuffer(count, bytes);
+
+ const stream = new ArrayBufferInputStream();
+ stream.setData(bytes, 0, count);
+
+ this.pendingData.push({request, context, stream, count});
+ this.queue.dataAvailable(this);
+ },
+
+ /**
+ * Allow some buffered data from this object to be forwarded to this
+ * object's originalListener.
+ *
+ * @param {Number} bytesPermitted The maximum number of bytes
+ * permitted to be sent.
+ * @return {Object} an object of the form {length, done}, where
+ * |length| is the number of bytes actually forwarded, and
+ * |done| is a boolean indicating whether this particular
+ * request has been completed. (A NetworkThrottleListener
+ * may be queued multiple times, so this does not mean that
+ * all available data has been sent.)
+ */
+ sendSomeData: function (bytesPermitted) {
+ if (this.pendingData.length === 0) {
+ // Shouldn't happen.
+ return {length: 0, done: true};
+ }
+
+ const {request, context, stream, count, statusCode} = this.pendingData[0];
+
+ if (statusCode !== undefined) {
+ this.pendingData.shift();
+ this.originalListener.onStopRequest(request, context, statusCode);
+ return {length: 0, done: true};
+ }
+
+ if (bytesPermitted > count) {
+ bytesPermitted = count;
+ }
+
+ try {
+ this.originalListener.onDataAvailable(request, context, stream,
+ this.offset, bytesPermitted);
+ } catch (e) {
+ this.pendingException = e;
+ }
+
+ let done = false;
+ if (bytesPermitted === count) {
+ this.pendingData.shift();
+ done = true;
+ } else {
+ this.pendingData[0].count -= bytesPermitted;
+ }
+
+ this.offset += bytesPermitted;
+ // Maybe our state has changed enough to emit an event.
+ this.maybeEmitEvents();
+
+ return {length: bytesPermitted, done};
+ },
+
+ /**
+ * Return the number of pending data requests available for this
+ * listener.
+ */
+ pendingCount: function () {
+ return this.pendingData.length;
+ },
+
+ /**
+ * This is called when an http activity event is delivered. This
+ * object delays the event until the appropriate moment.
+ */
+ addActivityCallback: function (callback, httpActivity, channel, activityType,
+ activitySubtype, timestamp, extraSizeData,
+ extraStringData) {
+ let datum = {callback, httpActivity, channel, activityType,
+ activitySubtype, extraSizeData,
+ extraStringData};
+ this.activities[activitySubtype] = datum;
+
+ if (activitySubtype ===
+ gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE) {
+ this.totalSize = extraSizeData;
+ }
+
+ this.maybeEmitEvents();
+ },
+
+ /**
+ * This is called for a download throttler when the latency timeout
+ * has ended.
+ */
+ responseStart: function () {
+ this.responseStarted = true;
+ this.maybeEmitEvents();
+ },
+
+ /**
+ * Check our internal state and emit any http activity events as
+ * needed. Note that we wait until both our internal state has
+ * changed and we've received the real http activity event from
+ * platform. This approach ensures we can both pass on the correct
+ * data from the original event, and update the reported time to be
+ * consistent with the delay we're introducing.
+ */
+ maybeEmitEvents: function () {
+ if (this.responseStarted) {
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_START);
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_HEADER);
+ }
+
+ if (this.totalSize !== undefined && this.offset >= this.totalSize) {
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_RESPONSE_COMPLETE);
+ this.maybeEmit(gActivityDistributor.ACTIVITY_SUBTYPE_TRANSACTION_CLOSE);
+ }
+ },
+
+ /**
+ * Emit an event for |code|, if the appropriate entry in
+ * |activities| is defined.
+ */
+ maybeEmit: function (code) {
+ if (this.activities[code] !== undefined) {
+ let {callback, httpActivity, channel, activityType,
+ activitySubtype, extraSizeData,
+ extraStringData} = this.activities[code];
+ let now = Date.now() * 1000;
+ callback(httpActivity, channel, activityType, activitySubtype,
+ now, extraSizeData, extraStringData);
+ this.activities[code] = undefined;
+ }
+ },
+};
+
+/**
+ * Construct a new queue that can be used to throttle the network for
+ * a group of related network requests.
+ *
+ * meanBPS {Number} Mean bytes per second.
+ * maxBPS {Number} Maximum bytes per second.
+ * roundTripTimeMean {Number} Mean round trip time in milliseconds.
+ * roundTripTimeMax {Number} Maximum round trip time in milliseconds.
+ */
+function NetworkThrottleQueue(meanBPS, maxBPS,
+ roundTripTimeMean, roundTripTimeMax) {
+ this.meanBPS = meanBPS;
+ this.maxBPS = maxBPS;
+ this.roundTripTimeMean = roundTripTimeMean;
+ this.roundTripTimeMax = roundTripTimeMax;
+
+ this.pendingRequests = new Set();
+ this.downloadQueue = [];
+ this.previousReads = [];
+
+ this.pumping = false;
+}
+
+NetworkThrottleQueue.prototype = {
+ /**
+ * A helper function that, given a mean and a maximum, returns a
+ * random integer between (mean - (max - mean)) and max.
+ */
+ random: function (mean, max) {
+ return mean - (max - mean) + Math.floor(2 * (max - mean) * Math.random());
+ },
+
+ /**
+ * A helper function that lets the indicating listener start sending
+ * data. This is called after the initial round trip time for the
+ * listener has elapsed.
+ */
+ allowDataFrom: function (throttleListener) {
+ throttleListener.responseStart();
+ this.pendingRequests.delete(throttleListener);
+ const count = throttleListener.pendingCount();
+ for (let i = 0; i < count; ++i) {
+ this.downloadQueue.push(throttleListener);
+ }
+ this.pump();
+ },
+
+ /**
+ * Notice a new listener object. This is called by the
+ * NetworkThrottleListener when the request has started. Initially
+ * a new listener object is put into a "pending" state, until the
+ * round-trip time has elapsed. This is used to simulate latency.
+ *
+ * @param {NetworkThrottleListener} throttleListener the new listener
+ */
+ start: function (throttleListener) {
+ this.pendingRequests.add(throttleListener);
+ let delay = this.random(this.roundTripTimeMean, this.roundTripTimeMax);
+ if (delay > 0) {
+ setTimeout(() => this.allowDataFrom(throttleListener), delay);
+ } else {
+ this.allowDataFrom(throttleListener);
+ }
+ },
+
+ /**
+ * Note that new data is available for a given listener. Each time
+ * data is available, the listener will be re-queued.
+ *
+ * @param {NetworkThrottleListener} throttleListener the listener
+ * which has data available.
+ */
+ dataAvailable: function (throttleListener) {
+ if (!this.pendingRequests.has(throttleListener)) {
+ this.downloadQueue.push(throttleListener);
+ this.pump();
+ }
+ },
+
+ /**
+ * An internal function that permits individual listeners to send
+ * data.
+ */
+ pump: function () {
+ // A redirect will cause two NetworkThrottleListeners to be on a
+ // listener chain. In this case, we might recursively call into
+ // this method. Avoid infinite recursion here.
+ if (this.pumping) {
+ return;
+ }
+ this.pumping = true;
+
+ const now = Date.now();
+ const oneSecondAgo = now - 1000;
+
+ while (this.previousReads.length &&
+ this.previousReads[0].when < oneSecondAgo) {
+ this.previousReads.shift();
+ }
+
+ const totalBytes = this.previousReads.reduce((sum, elt) => {
+ return sum + elt.numBytes;
+ }, 0);
+
+ let thisSliceBytes = this.random(this.meanBPS, this.maxBPS);
+ if (totalBytes < thisSliceBytes) {
+ thisSliceBytes -= totalBytes;
+ let readThisTime = 0;
+ while (thisSliceBytes > 0 && this.downloadQueue.length) {
+ let {length, done} = this.downloadQueue[0].sendSomeData(thisSliceBytes);
+ thisSliceBytes -= length;
+ readThisTime += length;
+ if (done) {
+ this.downloadQueue.shift();
+ }
+ }
+ this.previousReads.push({when: now, numBytes: readThisTime});
+ }
+
+ // If there is more data to download, then schedule ourselves for
+ // one second after the oldest previous read.
+ if (this.downloadQueue.length) {
+ const when = this.previousReads[0].when + 1000;
+ setTimeout(this.pump.bind(this), when - now);
+ }
+
+ this.pumping = false;
+ },
+};
+
+/**
+ * Construct a new object that can be used to throttle the network for
+ * a group of related network requests.
+ *
+ * @param {Object} An object with the following attributes:
+ * roundTripTimeMean {Number} Mean round trip time in milliseconds.
+ * roundTripTimeMax {Number} Maximum round trip time in milliseconds.
+ * downloadBPSMean {Number} Mean bytes per second for downloads.
+ * downloadBPSMax {Number} Maximum bytes per second for downloads.
+ * uploadBPSMean {Number} Mean bytes per second for uploads.
+ * uploadBPSMax {Number} Maximum bytes per second for uploads.
+ *
+ * Download throttling will not be done if downloadBPSMean and
+ * downloadBPSMax are <= 0. Upload throttling will not be done if
+ * uploadBPSMean and uploadBPSMax are <= 0.
+ */
+function NetworkThrottleManager({roundTripTimeMean, roundTripTimeMax,
+ downloadBPSMean, downloadBPSMax,
+ uploadBPSMean, uploadBPSMax}) {
+ if (downloadBPSMax <= 0 && downloadBPSMean <= 0) {
+ this.downloadQueue = null;
+ } else {
+ this.downloadQueue =
+ new NetworkThrottleQueue(downloadBPSMean, downloadBPSMax,
+ roundTripTimeMean, roundTripTimeMax);
+ }
+ if (uploadBPSMax <= 0 && uploadBPSMean <= 0) {
+ this.uploadQueue = null;
+ } else {
+ this.uploadQueue = Cc["@mozilla.org/network/throttlequeue;1"]
+ .createInstance(Ci.nsIInputChannelThrottleQueue);
+ this.uploadQueue.init(uploadBPSMean, uploadBPSMax);
+ }
+}
+exports.NetworkThrottleManager = NetworkThrottleManager;
+
+NetworkThrottleManager.prototype = {
+ /**
+ * Create a new NetworkThrottleListener for a given channel and
+ * install it using |setNewListener|.
+ *
+ * @param {nsITraceableChannel} channel the channel to manage
+ * @return {NetworkThrottleListener} the new listener, or null if
+ * download throttling is not being done.
+ */
+ manage: function (channel) {
+ if (this.downloadQueue) {
+ let listener = new NetworkThrottleListener(this.downloadQueue);
+ let originalListener = channel.setNewListener(listener);
+ listener.setOriginalListener(originalListener);
+ return listener;
+ }
+ return null;
+ },
+
+ /**
+ * Throttle uploads taking place on the given channel.
+ *
+ * @param {nsITraceableChannel} channel the channel to manage
+ */
+ manageUpload: function (channel) {
+ if (this.uploadQueue) {
+ channel = channel.QueryInterface(Ci.nsIThrottledInputChannel);
+ channel.throttleQueue = this.uploadQueue;
+ }
+ },
+};
diff --git a/devtools/shared/worker/helper.js b/devtools/shared/worker/helper.js
new file mode 100644
index 000000000..69512550b
--- /dev/null
+++ b/devtools/shared/worker/helper.js
@@ -0,0 +1,133 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+(function (root, factory) {
+ "use strict";
+
+ if (typeof define === "function" && define.amd) {
+ define(factory);
+ } else if (typeof exports === "object") {
+ module.exports = factory();
+ } else {
+ root.workerHelper = factory();
+ }
+}(this, function () {
+ "use strict";
+
+ /**
+ * This file is to only be included by ChromeWorkers. This exposes
+ * a `createTask` function to workers to register tasks for communication
+ * back to `devtools/shared/worker`.
+ *
+ * Tasks can be send their responses via a return value, either a primitive
+ * or a promise.
+ *
+ * createTask(self, "average", function (data) {
+ * return data.reduce((sum, val) => sum + val, 0) / data.length;
+ * });
+ *
+ * createTask(self, "average", function (data) {
+ * return new Promise((resolve, reject) => {
+ * resolve(data.reduce((sum, val) => sum + val, 0) / data.length);
+ * });
+ * });
+ *
+ *
+ * Errors:
+ *
+ * Returning an Error value, or if the returned promise is rejected, this
+ * propagates to the DevToolsWorker as a rejected promise. If an error is
+ * thrown in a synchronous function, that error is also propagated.
+ */
+
+ /**
+ * Takes a worker's `self` object, a task name, and a function to
+ * be called when that task is called. The task is called with the
+ * passed in data as the first argument
+ *
+ * @param {object} self
+ * @param {string} name
+ * @param {function} fn
+ */
+ function createTask(self, name, fn) {
+ // Store a hash of task name to function on the Worker
+ if (!self._tasks) {
+ self._tasks = {};
+ }
+
+ // Create the onmessage handler if not yet created.
+ if (!self.onmessage) {
+ self.onmessage = createHandler(self);
+ }
+
+ // Store the task on the worker.
+ self._tasks[name] = fn;
+ }
+
+ /**
+ * Creates the `self.onmessage` handler for a Worker.
+ *
+ * @param {object} self
+ * @return {function}
+ */
+ function createHandler(self) {
+ return function (e) {
+ let { id, task, data } = e.data;
+ let taskFn = self._tasks[task];
+
+ if (!taskFn) {
+ self.postMessage({ id, error: `Task "${task}" not found in worker.` });
+ return;
+ }
+
+ try {
+ let results;
+ handleResponse(taskFn(data));
+ } catch (e) {
+ handleError(e);
+ }
+
+ function handleResponse(response) {
+ // If a promise
+ if (response && typeof response.then === "function") {
+ response.then(val => self.postMessage({ id, response: val }), handleError);
+ }
+ // If an error object
+ else if (response instanceof Error) {
+ handleError(response);
+ }
+ // If anything else
+ else {
+ self.postMessage({ id, response });
+ }
+ }
+
+ function handleError(error = "Error") {
+ try {
+ // First, try and structured clone the error across directly.
+ self.postMessage({ id, error });
+ } catch (_) {
+ // We could not clone whatever error value was given. Do our best to
+ // stringify it.
+ let errorString = `Error while performing task "${task}": `;
+
+ try {
+ errorString += error.toString();
+ } catch (_) {
+ errorString += "<could not stringify error>";
+ }
+
+ if ("stack" in error) {
+ try {
+ errorString += "\n" + error.stack;
+ } catch (_) { }
+ }
+
+ self.postMessage({ id, error: errorString });
+ }
+ }
+ };
+ }
+
+ return { createTask: createTask };
+}.bind(this)));
diff --git a/devtools/shared/worker/loader.js b/devtools/shared/worker/loader.js
new file mode 100644
index 000000000..1de72963d
--- /dev/null
+++ b/devtools/shared/worker/loader.js
@@ -0,0 +1,517 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+// A CommonJS module loader that is designed to run inside a worker debugger.
+// We can't simply use the SDK module loader, because it relies heavily on
+// Components, which isn't available in workers.
+//
+// In principle, the standard instance of the worker loader should provide the
+// same built-in modules as its devtools counterpart, so that both loaders are
+// interchangable on the main thread, making them easier to test.
+//
+// On the worker thread, some of these modules, in particular those that rely on
+// the use of Components, and for which the worker debugger doesn't provide an
+// alternative API, will be replaced by vacuous objects. Consequently, they can
+// still be required, but any attempts to use them will lead to an exception.
+
+this.EXPORTED_SYMBOLS = ["WorkerDebuggerLoader", "worker"];
+
+// Some notes on module ids and URLs:
+//
+// An id is either a relative id or an absolute id. An id is relative if and
+// only if it starts with a dot. An absolute id is a normalized id if and only
+// if it contains no redundant components.
+//
+// Every normalized id is a URL. A URL is either an absolute URL or a relative
+// URL. A URL is absolute if and only if it starts with a scheme name followed
+// by a colon and 2 or 3 slashes.
+
+/**
+ * Convert the given relative id to an absolute id.
+ *
+ * @param String id
+ * The relative id to be resolved.
+ * @param String baseId
+ * The absolute base id to resolve the relative id against.
+ *
+ * @return String
+ * An absolute id
+ */
+function resolveId(id, baseId) {
+ return baseId + "/../" + id;
+}
+
+/**
+ * Convert the given absolute id to a normalized id.
+ *
+ * @param String id
+ * The absolute id to be normalized.
+ *
+ * @return String
+ * A normalized id.
+ */
+function normalizeId(id) {
+ // An id consists of an optional root and a path. A root consists of either
+ // a scheme name followed by 2 or 3 slashes, or a single slash. Slashes in the
+ // root are not used as separators, so only normalize the path.
+ let [_, root, path] = id.match(/^(\w+:\/\/\/?|\/)?(.*)/);
+
+ let stack = [];
+ path.split("/").forEach(function (component) {
+ switch (component) {
+ case "":
+ case ".":
+ break;
+ case "..":
+ if (stack.length === 0) {
+ if (root !== undefined) {
+ throw new Error("Can't normalize absolute id '" + id + "'!");
+ } else {
+ stack.push("..");
+ }
+ } else {
+ if (stack[stack.length - 1] == "..") {
+ stack.push("..");
+ } else {
+ stack.pop();
+ }
+ }
+ break;
+ default:
+ stack.push(component);
+ break;
+ }
+ });
+
+ return (root ? root : "") + stack.join("/");
+}
+
+/**
+ * Create a module object with the given normalized id.
+ *
+ * @param String
+ * The normalized id of the module to be created.
+ *
+ * @return Object
+ * A module with the given id.
+ */
+function createModule(id) {
+ return Object.create(null, {
+ // CommonJS specifies the id property to be non-configurable and
+ // non-writable.
+ id: {
+ configurable: false,
+ enumerable: true,
+ value: id,
+ writable: false
+ },
+
+ // CommonJS does not specify an exports property, so follow the NodeJS
+ // convention, which is to make it non-configurable and writable.
+ exports: {
+ configurable: false,
+ enumerable: true,
+ value: Object.create(null),
+ writable: true
+ }
+ });
+}
+
+/**
+ * Create a CommonJS loader with the following options:
+ * - createSandbox:
+ * A function that will be used to create sandboxes. It should take the name
+ * and prototype of the sandbox to be created, and return the newly created
+ * sandbox as result. This option is required.
+ * - globals:
+ * A map of names to built-in globals that will be exposed to every module.
+ * Defaults to the empty map.
+ * - loadSubScript:
+ * A function that will be used to load scripts in sandboxes. It should take
+ * the URL from and the sandbox in which the script is to be loaded, and not
+ * return a result. This option is required.
+ * - modules:
+ * A map from normalized ids to built-in modules that will be added to the
+ * module cache. Defaults to the empty map.
+ * - paths:
+ * A map of paths to base URLs that will be used to resolve relative URLs to
+ * absolute URLS. Defaults to the empty map.
+ * - resolve:
+ * A function that will be used to resolve relative ids to absolute ids. It
+ * should take the relative id of a module to be required and the absolute
+ * id of the requiring module as arguments, and return the absolute id of
+ * the module to be required as result. Defaults to resolveId above.
+ */
+function WorkerDebuggerLoader(options) {
+ /**
+ * Convert the given relative URL to an absolute URL, using the map of paths
+ * given below.
+ *
+ * @param String url
+ * The relative URL to be resolved.
+ *
+ * @return String
+ * An absolute URL.
+ */
+ function resolveURL(url) {
+ let found = false;
+ for (let [path, baseURL] of paths) {
+ if (url.startsWith(path)) {
+ found = true;
+ url = url.replace(path, baseURL);
+ break;
+ }
+ }
+ if (!found) {
+ throw new Error("Can't resolve relative URL '" + url + "'!");
+ }
+
+ // If the url has no extension, use ".js" by default.
+ return url.endsWith(".js") ? url : url + ".js";
+ }
+
+ /**
+ * Load the given module with the given url.
+ *
+ * @param Object module
+ * The module object to be loaded.
+ * @param String url
+ * The URL to load the module from.
+ */
+ function loadModule(module, url) {
+ // CommonJS specifies 3 free variables: require, exports, and module. These
+ // must be exposed to every module, so define these as properties on the
+ // sandbox prototype. Additional built-in globals are exposed by making
+ // the map of built-in globals the prototype of the sandbox prototype.
+ let prototype = Object.create(globals);
+ prototype.Components = {};
+ prototype.require = createRequire(module);
+ prototype.exports = module.exports;
+ prototype.module = module;
+
+ let sandbox = createSandbox(url, prototype);
+ try {
+ loadSubScript(url, sandbox);
+ } catch (error) {
+ if (/^Error opening input stream/.test(String(error))) {
+ throw new Error("Can't load module '" + module.id + "' with url '" +
+ url + "'!");
+ }
+ throw error;
+ }
+
+ // The value of exports may have been changed by the module script, so
+ // freeze it if and only if it is still an object.
+ if (typeof module.exports === "object" && module.exports !== null) {
+ Object.freeze(module.exports);
+ }
+ }
+
+ /**
+ * Create a require function for the given module. If no module is given,
+ * create a require function for the top-level module instead.
+ *
+ * @param Object requirer
+ * The module for which the require function is to be created.
+ *
+ * @return Function
+ * A require function for the given module.
+ */
+ function createRequire(requirer) {
+ return function require(id) {
+ // Make sure an id was passed.
+ if (id === undefined) {
+ throw new Error("Can't require module without id!");
+ }
+
+ // Built-in modules are cached by id rather than URL, so try to find the
+ // module to be required by id first.
+ let module = modules[id];
+ if (module === undefined) {
+ // Failed to find the module to be required by id, so convert the id to
+ // a URL and try again.
+
+ // If the id is relative, convert it to an absolute id.
+ if (id.startsWith(".")) {
+ if (requirer === undefined) {
+ throw new Error("Can't require top-level module with relative id " +
+ "'" + id + "'!");
+ }
+ id = resolve(id, requirer.id);
+ }
+
+ // Convert the absolute id to a normalized id.
+ id = normalizeId(id);
+
+ // Convert the normalized id to a URL.
+ let url = id;
+
+ // If the URL is relative, resolve it to an absolute URL.
+ if (url.match(/^\w+:\/\//) === null) {
+ url = resolveURL(id);
+ }
+
+ // Try to find the module to be required by URL.
+ module = modules[url];
+ if (module === undefined) {
+ // Failed to find the module to be required in the cache, so create
+ // a new module, load it from the given URL, and add it to the cache.
+
+ // Add modules to the cache early so that any recursive calls to
+ // require for the same module will return the partially-loaded module
+ // from the cache instead of triggering a new load.
+ module = modules[url] = createModule(id);
+
+ try {
+ loadModule(module, url);
+ } catch (error) {
+ // If the module failed to load, remove it from the cache so that
+ // subsequent calls to require for the same module will trigger a
+ // new load, instead of returning a partially-loaded module from
+ // the cache.
+ delete modules[url];
+ throw error;
+ }
+
+ Object.freeze(module);
+ }
+ }
+
+ return module.exports;
+ };
+ }
+
+ let createSandbox = options.createSandbox;
+ let globals = options.globals || Object.create(null);
+ let loadSubScript = options.loadSubScript;
+
+ // Create the module cache, by converting each entry in the map from
+ // normalized ids to built-in modules to a module object, with the exports
+ // property of each module set to a frozen version of the original entry.
+ let modules = options.modules || {};
+ for (let id in modules) {
+ let module = createModule(id);
+ module.exports = Object.freeze(modules[id]);
+ modules[id] = module;
+ }
+
+ // Convert the map of paths to base URLs into an array for use by resolveURL.
+ // The array is sorted from longest to shortest path to ensure that the
+ // longest path is always the first to be found.
+ let paths = options.paths || Object.create(null);
+ paths = Object.keys(paths)
+ .sort((a, b) => b.length - a.length)
+ .map(path => [path, paths[path]]);
+
+ let resolve = options.resolve || resolveId;
+
+ this.require = createRequire();
+}
+
+this.WorkerDebuggerLoader = WorkerDebuggerLoader;
+
+// The following APIs rely on the use of Components, and the worker debugger
+// does not provide alternative definitions for them. Consequently, they are
+// stubbed out both on the main thread and worker threads.
+
+var chrome = {
+ CC: undefined,
+ Cc: undefined,
+ ChromeWorker: undefined,
+ Cm: undefined,
+ Ci: undefined,
+ Cu: undefined,
+ Cr: undefined,
+ components: undefined
+};
+
+var loader = {
+ lazyGetter: function (object, name, lambda) {
+ Object.defineProperty(object, name, {
+ get: function () {
+ delete object[name];
+ return object[name] = lambda.apply(object);
+ },
+ configurable: true,
+ enumerable: true
+ });
+ },
+ lazyImporter: function () {
+ throw new Error("Can't import JSM from worker thread!");
+ },
+ lazyServiceGetter: function () {
+ throw new Error("Can't import XPCOM service from worker thread!");
+ },
+ lazyRequireGetter: function (obj, property, module, destructure) {
+ Object.defineProperty(obj, property, {
+ get: () => destructure ? worker.require(module)[property]
+ : worker.require(module || property)
+ });
+ }
+};
+
+// The following APIs are defined differently depending on whether we are on the
+// main thread or a worker thread. On the main thread, we use the Components
+// object to implement them. On worker threads, we use the APIs provided by
+// the worker debugger.
+
+var {
+ Debugger,
+ URL,
+ createSandbox,
+ dump,
+ rpc,
+ loadSubScript,
+ reportError,
+ setImmediate,
+ xpcInspector
+} = (function () {
+ if (typeof Components === "object") { // Main thread
+ let {
+ Constructor: CC,
+ classes: Cc,
+ manager: Cm,
+ interfaces: Ci,
+ results: Cr,
+ utils: Cu
+ } = Components;
+
+ let principal = CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")();
+
+ // To ensure that the this passed to addDebuggerToGlobal is a global, the
+ // Debugger object needs to be defined in a sandbox.
+ let sandbox = Cu.Sandbox(principal, {});
+ Cu.evalInSandbox(
+ "Components.utils.import('resource://gre/modules/jsdebugger.jsm');" +
+ "addDebuggerToGlobal(this);",
+ sandbox
+ );
+ let Debugger = sandbox.Debugger;
+
+ let createSandbox = function (name, prototype) {
+ return Cu.Sandbox(principal, {
+ invisibleToDebugger: true,
+ sandboxName: name,
+ sandboxPrototype: prototype,
+ wantComponents: false,
+ wantXrays: false
+ });
+ };
+
+ let rpc = undefined;
+
+ let subScriptLoader = Cc["@mozilla.org/moz/jssubscript-loader;1"].
+ getService(Ci.mozIJSSubScriptLoader);
+
+ let loadSubScript = function (url, sandbox) {
+ subScriptLoader.loadSubScript(url, sandbox, "UTF-8");
+ };
+
+ let reportError = Cu.reportError;
+
+ let Timer = Cu.import("resource://gre/modules/Timer.jsm", {});
+
+ let setImmediate = function (callback) {
+ Timer.setTimeout(callback, 0);
+ };
+
+ let xpcInspector = Cc["@mozilla.org/jsinspector;1"].
+ getService(Ci.nsIJSInspector);
+
+ return {
+ Debugger,
+ URL: this.URL,
+ createSandbox,
+ dump: this.dump,
+ rpc,
+ loadSubScript,
+ reportError,
+ setImmediate,
+ xpcInspector
+ };
+ } else { // Worker thread
+ let requestors = [];
+
+ let scope = this;
+
+ let xpcInspector = {
+ get eventLoopNestLevel() {
+ return requestors.length;
+ },
+
+ get lastNestRequestor() {
+ return requestors.length === 0 ? null : requestors[requestors.length - 1];
+ },
+
+ enterNestedEventLoop: function (requestor) {
+ requestors.push(requestor);
+ scope.enterEventLoop();
+ return requestors.length;
+ },
+
+ exitNestedEventLoop: function () {
+ requestors.pop();
+ scope.leaveEventLoop();
+ return requestors.length;
+ }
+ };
+
+ return {
+ Debugger: this.Debugger,
+ URL: this.URL,
+ createSandbox: this.createSandbox,
+ dump: this.dump,
+ rpc: this.rpc,
+ loadSubScript: this.loadSubScript,
+ reportError: this.reportError,
+ setImmediate: this.setImmediate,
+ xpcInspector: xpcInspector
+ };
+ }
+}).call(this);
+
+// Create the default instance of the worker loader, using the APIs we defined
+// above.
+
+this.worker = new WorkerDebuggerLoader({
+ createSandbox: createSandbox,
+ globals: {
+ "isWorker": true,
+ "dump": dump,
+ "loader": loader,
+ "reportError": reportError,
+ "rpc": rpc,
+ "setImmediate": setImmediate,
+ "URL": URL,
+ },
+ loadSubScript: loadSubScript,
+ modules: {
+ "Debugger": Debugger,
+ "Services": Object.create(null),
+ "chrome": chrome,
+ "xpcInspector": xpcInspector
+ },
+ paths: {
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "": "resource://gre/modules/commonjs/",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ // Modules here are intended to have one implementation for
+ // chrome, and a separate implementation for content. Here we
+ // map the directory to the chrome subdirectory, but the content
+ // loader will map to the content subdirectory. See the
+ // README.md in devtools/shared/platform.
+ "devtools/shared/platform": "resource://devtools/shared/platform/chrome",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "devtools": "resource://devtools",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "promise": "resource://gre/modules/Promise-backend.js",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "source-map": "resource://devtools/shared/sourcemap/source-map.js",
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ "xpcshell-test": "resource://test"
+ // ⚠ DISCUSSION ON DEV-DEVELOPER-TOOLS REQUIRED BEFORE MODIFYING ⚠
+ }
+});
diff --git a/devtools/shared/worker/moz.build b/devtools/shared/worker/moz.build
new file mode 100644
index 000000000..28b26c0a7
--- /dev/null
+++ b/devtools/shared/worker/moz.build
@@ -0,0 +1,13 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
+
+DevToolsModules(
+ 'helper.js',
+ 'loader.js',
+ 'worker.js',
+)
diff --git a/devtools/shared/worker/tests/browser/.eslintrc.js b/devtools/shared/worker/tests/browser/.eslintrc.js
new file mode 100644
index 000000000..698ae9181
--- /dev/null
+++ b/devtools/shared/worker/tests/browser/.eslintrc.js
@@ -0,0 +1,6 @@
+"use strict";
+
+module.exports = {
+ // Extend from the shared list of defined globals for mochitests.
+ "extends": "../../../../.eslintrc.mochitests.js"
+};
diff --git a/devtools/shared/worker/tests/browser/browser.ini b/devtools/shared/worker/tests/browser/browser.ini
new file mode 100644
index 000000000..a64916dff
--- /dev/null
+++ b/devtools/shared/worker/tests/browser/browser.ini
@@ -0,0 +1,9 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+ ../../../../server/tests/browser/head.js
+
+[browser_worker-01.js]
+[browser_worker-02.js]
+[browser_worker-03.js]
diff --git a/devtools/shared/worker/tests/browser/browser_worker-01.js b/devtools/shared/worker/tests/browser/browser_worker-01.js
new file mode 100644
index 000000000..7679e4166
--- /dev/null
+++ b/devtools/shared/worker/tests/browser/browser_worker-01.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the devtools/shared/worker communicates properly
+// as both CommonJS module and as a JSM.
+
+const WORKER_URL =
+ "resource://devtools/client/shared/widgets/GraphsWorker.js";
+
+const count = 100000;
+const WORKER_DATA = (function () {
+ let timestamps = [];
+ for (let i = 0; i < count; i++) {
+ timestamps.push(i);
+ }
+ return timestamps;
+})();
+const INTERVAL = 100;
+const DURATION = 1000;
+
+add_task(function* () {
+ // Test both CJS and JSM versions
+
+ yield testWorker("JSM", () => Cu.import("resource://devtools/shared/worker/worker.js", {}));
+ yield testWorker("CommonJS", () => require("devtools/shared/worker/worker"));
+});
+
+function* testWorker(context, workerFactory) {
+ let { DevToolsWorker, workerify } = workerFactory();
+ let worker = new DevToolsWorker(WORKER_URL);
+ let results = yield worker.performTask("plotTimestampsGraph", {
+ timestamps: WORKER_DATA,
+ interval: INTERVAL,
+ duration: DURATION
+ });
+
+ ok(results.plottedData.length,
+ `worker should have returned an object with array properties in ${context}`);
+
+ let fn = workerify(function (x) { return x * x; });
+ is((yield fn(5)), 25, `workerify works in ${context}`);
+ fn.destroy();
+
+ worker.destroy();
+}
diff --git a/devtools/shared/worker/tests/browser/browser_worker-02.js b/devtools/shared/worker/tests/browser/browser_worker-02.js
new file mode 100644
index 000000000..e6a9a54cf
--- /dev/null
+++ b/devtools/shared/worker/tests/browser/browser_worker-02.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests errors are handled properly by the DevToolsWorker.
+
+const { DevToolsWorker } = require("devtools/shared/worker/worker");
+const WORKER_URL =
+ "resource://devtools/client/shared/widgets/GraphsWorker.js";
+
+add_task(function* () {
+ try {
+ let workerNotFound = new DevToolsWorker("resource://i/dont/exist.js");
+ ok(false, "Creating a DevToolsWorker with an invalid URL throws");
+ } catch (e) {
+ ok(true, "Creating a DevToolsWorker with an invalid URL throws");
+ }
+
+ let worker = new DevToolsWorker(WORKER_URL);
+ try {
+ // plotTimestampsGraph requires timestamp, interval an duration props on the object
+ // passed in so there should be an error thrown in the worker
+ let results = yield worker.performTask("plotTimestampsGraph", {});
+ ok(false, "DevToolsWorker returns a rejected promise when an error occurs in the worker");
+ } catch (e) {
+ ok(true, "DevToolsWorker returns a rejected promise when an error occurs in the worker");
+ }
+
+ try {
+ let results = yield worker.performTask("not a real task");
+ ok(false, "DevToolsWorker returns a rejected promise when task does not exist");
+ } catch (e) {
+ ok(true, "DevToolsWorker returns a rejected promise when task does not exist");
+ }
+
+ worker.destroy();
+ try {
+ let results = yield worker.performTask("plotTimestampsGraph", {
+ timestamps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ interval: 1,
+ duration: 1
+ });
+ ok(false, "DevToolsWorker rejects when performing a task on a destroyed worker");
+ } catch (e) {
+ ok(true, "DevToolsWorker rejects when performing a task on a destroyed worker");
+ }
+});
diff --git a/devtools/shared/worker/tests/browser/browser_worker-03.js b/devtools/shared/worker/tests/browser/browser_worker-03.js
new file mode 100644
index 000000000..053e381b9
--- /dev/null
+++ b/devtools/shared/worker/tests/browser/browser_worker-03.js
@@ -0,0 +1,52 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the devtools/shared/worker can handle:
+// returned primitives (or promise or Error)
+//
+// And tests `workerify` by doing so.
+
+const { DevToolsWorker, workerify } = require("devtools/shared/worker/worker");
+function square(x) {
+ return x * x;
+}
+
+function squarePromise(x) {
+ return new Promise((resolve) => resolve(x * x));
+}
+
+function squareError(x) {
+ return new Error("Nope");
+}
+
+function squarePromiseReject(x) {
+ return new Promise((_, reject) => reject("Nope"));
+}
+
+add_task(function* () {
+ let fn = workerify(square);
+ is((yield fn(5)), 25, "return primitives successful");
+ fn.destroy();
+
+ fn = workerify(squarePromise);
+ is((yield fn(5)), 25, "promise primitives successful");
+ fn.destroy();
+
+ fn = workerify(squareError);
+ try {
+ yield fn(5);
+ ok(false, "return error should reject");
+ } catch (e) {
+ ok(true, "return error should reject");
+ }
+ fn.destroy();
+
+ fn = workerify(squarePromiseReject);
+ try {
+ yield fn(5);
+ ok(false, "returned rejected promise rejects");
+ } catch (e) {
+ ok(true, "returned rejected promise rejects");
+ }
+ fn.destroy();
+});
diff --git a/devtools/shared/worker/worker.js b/devtools/shared/worker/worker.js
new file mode 100644
index 000000000..9ed9afa27
--- /dev/null
+++ b/devtools/shared/worker/worker.js
@@ -0,0 +1,171 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/* global ChromeWorker */
+
+(function (factory) {
+ if (this.module && module.id.indexOf("worker") >= 0) {
+ // require
+ const { Cc, Ci, Cu, ChromeWorker } = require("chrome");
+ const dumpn = require("devtools/shared/DevToolsUtils").dumpn;
+ factory.call(this, require, exports, module, { Cc, Ci, Cu }, ChromeWorker, dumpn);
+ } else {
+ // Cu.import
+ const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+ const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+ this.isWorker = false;
+ this.Promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
+ this.console = Cu.import("resource://gre/modules/Console.jsm", {}).console;
+ factory.call(
+ this, require, this, { exports: this },
+ { Cc, Ci, Cu }, ChromeWorker, null
+ );
+ this.EXPORTED_SYMBOLS = ["DevToolsWorker"];
+ }
+}).call(this, function (require, exports, module, { Ci, Cc }, ChromeWorker, dumpn) {
+ let MESSAGE_COUNTER = 0;
+
+ /**
+ * Creates a wrapper around a ChromeWorker, providing easy
+ * communication to offload demanding tasks. The corresponding URL
+ * must implement the interface provided by `devtools/shared/worker/helper`.
+ *
+ * @see `./devtools/client/shared/widgets/GraphsWorker.js`
+ *
+ * @param {string} url
+ * The URL of the worker.
+ * @param Object opts
+ * An option with the following optional fields:
+ * - name: a name that will be printed with logs
+ * - verbose: log incoming and outgoing messages
+ */
+ function DevToolsWorker(url, opts) {
+ opts = opts || {};
+ this._worker = new ChromeWorker(url);
+ this._verbose = opts.verbose;
+ this._name = opts.name;
+
+ this._worker.addEventListener("error", this.onError, false);
+ }
+ exports.DevToolsWorker = DevToolsWorker;
+
+ /**
+ * Performs the given task in a chrome worker, passing in data.
+ * Returns a promise that resolves when the task is completed, resulting in
+ * the return value of the task.
+ *
+ * @param {string} task
+ * The name of the task to execute in the worker.
+ * @param {any} data
+ * Data to be passed into the task implemented by the worker.
+ * @return {Promise}
+ */
+ DevToolsWorker.prototype.performTask = function (task, data) {
+ if (this._destroyed) {
+ return Promise.reject("Cannot call performTask on a destroyed DevToolsWorker");
+ }
+ let worker = this._worker;
+ let id = ++MESSAGE_COUNTER;
+ let payload = { task, id, data };
+
+ if (this._verbose && dumpn) {
+ dumpn("Sending message to worker" +
+ (this._name ? (" (" + this._name + ")") : "") +
+ ": " +
+ JSON.stringify(payload, null, 2));
+ }
+ worker.postMessage(payload);
+
+ return new Promise((resolve, reject) => {
+ let listener = ({ data: result }) => {
+ if (this._verbose && dumpn) {
+ dumpn("Received message from worker" +
+ (this._name ? (" (" + this._name + ")") : "") +
+ ": " +
+ JSON.stringify(result, null, 2));
+ }
+
+ if (result.id !== id) {
+ return;
+ }
+ worker.removeEventListener("message", listener);
+ if (result.error) {
+ reject(result.error);
+ } else {
+ resolve(result.response);
+ }
+ };
+
+ worker.addEventListener("message", listener);
+ });
+ };
+
+ /**
+ * Terminates the underlying worker. Use when no longer needing the worker.
+ */
+ DevToolsWorker.prototype.destroy = function () {
+ this._worker.terminate();
+ this._worker = null;
+ this._destroyed = true;
+ };
+
+ DevToolsWorker.prototype.onError = function ({ message, filename, lineno }) {
+ dump(new Error(message + " @ " + filename + ":" + lineno) + "\n");
+ };
+
+ /**
+ * Takes a function and returns a Worker-wrapped version of the same function.
+ * Returns a promise upon resolution.
+ * @see `./devtools/shared/shared/tests/browser/browser_devtools-worker-03.js
+ *
+ * ⚠ This should only be used for tests or A/B testing performance ⚠
+ *
+ * The original function must:
+ *
+ * Be a pure function, that is, not use any variables not declared within the
+ * function, or its arguments.
+ *
+ * Return a value or a promise.
+ *
+ * Note any state change in the worker will not affect the callee's context.
+ *
+ * @param {function} fn
+ * @return {function}
+ */
+ function workerify(fn) {
+ console.warn("`workerify` should only be used in tests or measuring performance. " +
+ "This creates an object URL on the browser window, and should not be " +
+ "used in production.");
+ // Fetch via window/utils here as we don't want to include
+ // this module normally.
+ let { getMostRecentBrowserWindow } = require("sdk/window/utils");
+ let { URL, Blob } = getMostRecentBrowserWindow();
+ let stringifiedFn = createWorkerString(fn);
+ let blob = new Blob([stringifiedFn]);
+ let url = URL.createObjectURL(blob);
+ let worker = new DevToolsWorker(url);
+
+ let wrapperFn = data => worker.performTask("workerifiedTask", data);
+
+ wrapperFn.destroy = function () {
+ URL.revokeObjectURL(url);
+ worker.destroy();
+ };
+
+ return wrapperFn;
+ }
+ exports.workerify = workerify;
+
+ /**
+ * Takes a function, and stringifies it, attaching the worker-helper.js
+ * boilerplate hooks.
+ */
+ function createWorkerString(fn) {
+ return `importScripts("resource://gre/modules/workers/require.js");
+ const { createTask } = require("resource://devtools/shared/worker/helper.js");
+ createTask(self, "workerifiedTask", ${fn.toString()});`;
+ }
+});