[
  {
    "path": ".gitignore",
    "content": ".gradle\n/local.properties\n/.idea\n*.iml\n.DS_Store\n/build\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: android\n\njdk:\n  - oraclejdk8\n\nbefore_install:\n  - yes | sdkmanager \"platforms;android-27\"\n\nandroid:\n  components:\n    - tools\n    - android-28\n    - platform-tools\n\nbranches:\n  except:\n    - gh-pages\n\n# Disable Travis container-based infrastructure to correct\n# resource consumption in gradle presumably due to dexing.\nsudo: true\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "Change Log\n==========\n\n## Version 1.6.0\n_2021-03-17_\n\n * Fix #662/665: AndroidX upgrade.\n\n## Version 1.5.1\n_2019-03-17_\n\n * **Fix view inspection crash on Android P+**\n   View highlighting was using a legacy pre-O API which was removed in P that caused\n   a crash when highlighting a view in the Elements tab.  This has now been fixed (#607)\n   and the Elements tab is expected to work normally again.\n\n * Fix #325/295: Add ability to track windows in Elements tab (dialog, popups, etc)\n * Fix #614: Add the ability to manually control Runtime class object tracking (for \n   J2V8 integration)\n * Fix #612/613: Fix issue subclassing Runtime class and other devtools domain modules\n * Fix #602: Make it possible to change welcome banner (see Page class constructor)\n * Fix #600: Actually make .remove() method work properly in DefaultDumperPluginsBuilder\n * Fix #596: Sort SharedPreferences entries\n * Fix #623: dumpapp now supports ADB_SERVER_SOCKET variable\n * Fix #589: Fix parsing issue using dumpapp script causing failure to connect\n * Fix #529: WebSocket session info now displayed correctly on upgrade\n * Fix #541/580: Avoid crash when installing the Stetho interceptor in the \n   wrong okhttp chain (the request chain lacks a connection instance)\n\n## Version 1.5.0\n_2017_04_13_\n\n * **Generic socket support**\n   Added the ability to inspect arbitrary sockets via Chrome devtools'\n   WebSocket APIs.  This support is integrated into stetho-sample\n   as a demo, and in future releases we'd like to make it available\n   generically for at least okhttp customers.\n\n * **Major Elements tab extensibility points**\n   Significant expansion to AndroidDocumentProviderFactory which allows \n   for a much greater degree of customization, including the\n   ability to have editable styles.  Over time we'd like to incorporate\n   this support into native Android UI but also to make it even\n   easier to unlock powerful new customizations for custom view or\n   entire rendering systems.\n\n * Fix #392: Fix database id mapping for multiple database providers\n * Fix #521: Fix hit testing for empty view groups\n * Fix #513: Fix LeakCanary false positive\n * Fix #514: Handle runtime exceptions from database drivers\n * Fix #511: Remove accidental hard dependency on support library\n * Fix #406: Fix stetho-rhino importClass/Package not working properly\n * Fix #499: Treat HttpURLConnection headers as case insensitive\n * Fix #512: Fix severe stdin issue in dumpapp script\n * Fix #486: Fix TextViewDescriptor NPE when text is null\n\n## Version 1.4.2\n_2016_12_14_\n\n * **Bug fixes**\n  Fixes a few bugs in the Elements tab.\n\n * Fix #381: Fixes NPE while rotating the device with retained fragments.\n * Fix #447: Support Instant Run in android studio, by fixing ObjectMapper\n * Fix #456: Support ANDROID_ADB_SERVER_PORT\n * Fix #454: Upgrade to OkHttp 3.4.2\n * Fix #449: Make sure only unfocusable children's descriptions are being\n   co-opted by parents\n\n\n## Version 1.4.1\n_2016_09_13_\n\n  * Fix #432 Have DefaultDatabaseProvider return filename\n\n    v1.4.0 exposed a long standing bug relating to loading databases.\n\n\n## Version 1.4.0\n_2016_09_07_\n\n * **Add UI Accessibility Properties to Styles tab**\n   Added support for accessibility inspection, which allows users to select\n   a View and see whether or not it will be focusable by an Accessibility\n   Service, why it will or won't be focusable, the text description sent to\n   Accessibility Services, and any AccessibilityActions that are currently\n   available on the View.\n\n * Fix #367: Fixed SqliteDatabaseDriver with custom DatabaseFilesProvider\n * Fix #424: Make aar be the default packaging in maven\n\n## Version 1.3.1\n\n_2016-02-25_\n\n * **Major performance improvements in Elements tab**\n   Several performance, correctness, and stability improvements related to\n   how Stetho performs tree diffing.\n\n * Fix #349: Fix dumpapp scripts under various edge cases.\n * Fix #357: Remove static fields from exported view \"styles\".\n\n## Version 1.3.0\n\n_2016-01-20_\n\n * **okhttp3 support!**\n   A new module, stetho-okhttp3, has been added which supports the new\n   okhttp3 APIs.  Note that stetho-okhttp is now deprecated.\n\n * **Removed Apache HttpClient dependency**\n   A new, lightweight HTTP server implementation replaces it in Stetho\n   and the dumpapp protocol has been modified to no longer use HTTP.\n   Old dumpapp scripts will still work with new clients, however the\n   opposite will hang!\n\n * **Custom database drivers**\n   Completely custom or ContentProvider-based database drivers are available\n   which allow greater inspection options with some configuration.  See\n   `DefaultInspectorModulesBuilder#provideDatabaseDriver`.\n\n * New #282: Show view margins in \"Styles\" subtab.\n * New #289: Show SQLite views as tables.\n * New #294: dumpapp now responds to `STETHO_PROCESS` env variable in addition\n   to the `--process` flag.\n * Fix #286: Minor JsConsole improvements.\n * Fix #297: Sort CSS properties by name.\n * Fix #292: Minor JSON serialization fixes.\n * Fix #299: Memory leak fixes in view inspection (still some likely remain).\n * Fix #305: Add proguard rules to stetho-js-rhino aar artifact.\n * Fix #313: Work around issues formatting `Long.MIN_VALUE` and possibly others\n   when showing in the database view.\n * Fix #332: NPE in ShadowDocument.getGarbageElements().\n\n## Version 1.2.0\n\n_2015-09-11_\n\n * **View properties support!**\n   The \"Styles\" and \"Computed\" sub-tabs in \"Elements\" are now implemented,\n   complete with the box model diagram and a summary of the most useful view\n   properties.\n\n * **Screencasting**\n   Click the small screen icon in the upper right to view a live preview of\n   your phone's screen while using Stetho!  Coming soon: mouse/keyboard\n   support.\n\n * **Console tab support**\n   Arbitrary Java/JavaScript support added to the Console with the optional\n   `stetho-js-rhino` dependency.  See\n   [`stetho-sample/build.gradle`](stetho-sample/build.gradle) for details.\n\n * **New simpler initialization and customization API**\n   Most callers can now just use `Stetho.initializeWithDefaults(context)`.\n\n * New #218: Ability to pass pretty printers for binary data in the Network tab.\n * New #248: Implement transparent request decompression.\n * New #225: Ability to search View hierarchy (invoke with CTRL+F on the Elements tab).\n * New #238: Add EXPLAIN support in SQL console.\n * New #222: Add PRAGMA support in SQL console. \n * New #207: Add `dumpapp files` plugin.\n * New #181: Highlight view margins and padding when hovering over DOM entry.\n * New #211: Implement DialogFragment in Elements tab.\n * Fix #231: Sort database and shared preferences entries by name.\n * Fix #206: Fix small memory leak in View hierarchy support.\n * Fix #204: Use DOM tree diffing to fix ListView and a number of other edge\n   case view hierarchies.\n * Fix #183: Fix Fragment support in Elements tab.\n\n## Version 1.1.1\n\n_2015-05-01_\n\n * **Updated patent grant!**\n   See https://code.facebook.com/posts/1639473982937255/updating-our-open-source-patent-grant/\n\n * New: `stetho-timber` added to redirect log messages to the Stetho console.\n * Fix #140: More efficient and simpler Fragment accessor code.\n * Fix #123: All view inspection features are now available for ICS (API 15)\n   and up (some features required JB MR2, API 18).\n * Fix #154: Fix subtle race when a database is removed after the DevTools\n   UI is opened.\n * Fix #151: Crash when rapidly adding/removing SharedPreferences keys.\n * Fix #142: View inspection \"hit testing\" didn't work as intended with its\n   two-pass design.\n * Fix: Ignore extraneous files when WAL is enabled for SQLite databases.\n\n## Version 1.1.0\n\n_2015-04-02_\n\n * **View inspection!**\n   For ICS (API 15) and up, we now have full View inspection support in the\n   \"Elements\" tab!  Lots of goodies such as `<fragment>` instances virtually\n   placed in the hierarchy, view highlighting, and the ability to tap on a view\n   to jump to its position in the hierarchy.  Some features are only available\n   for JB MR2 (API 18).\n\n * New #109: Add `dumpapp hprof` plugin to conveniently extract HPROF memory\n   dumps.\n * New #110: Add `dumpapp crash` plugin to mechanize process death in a variety\n   of ways.\n * New #105: Simplify excluding Stetho from release builds (exercised in\n   `stetho-sample`).\n * New #40: Support SQLite databases in arbitrary directories.\n * Fix #115: Support multiple headers with the same name (most notably, HTTP\n   cookies).\n * Fix #108: Workaround throughput issue in Android's LocalSocket#flush()\n   method.\n * Fix #88: Avoid OOM on huge request/response HTTP payloads.\n * Fix #82: Provide visual feedback for INSERT/UPDATE/DELETE statements.\n * Fix: Javadoc JAR should now be uploaded properly to Maven.\n\n## Version 1.0.1\n\n_2015-02-27_\n\n * **`SharedPreferences` inspection.**\n   It's now possible to inspect SharedPreference files from the \"Resources\"\n   tab.\n\n * New #65: Show non-default process name to chrome://inspect UI.\n * Fix #57: HTTP responses without the Content-Type header do not appear in the\n   DevTools UI.\n * Fix #49: Unconditional \"Could not bind to socket\" error.\n * Fix #37: Duplicate dumpapp endpoints for the same process.\n * Fix: Use raw process name for Stetho sockets to fix an issue formatting\n   choices in `dumpapp`\n\n## Version 1.0.0\n\n_2015-02-18_\n\n * Initial release.\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Code of Conduct\n\nFacebook has adopted a Code of Conduct that we expect project participants to adhere to.\nPlease read the [full text](https://code.fb.com/codeofconduct/)\nso that you can understand what actions will and will not be tolerated.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Stetho\nWe want to make contributing to this project as easy and transparent as\npossible.\n\n## Our Development Process\nWe work directly in the github project and provide versioned releases\nappropriate for major milestones and minor bug fixes or improvements.  GitHub\nis used directly for issues and pull requests and the developers actively\nrespond to requests.\n\n## Pull Requests\nWe actively welcome your pull requests.  \n\n1. Fork the repo and create your branch from `master`.  \n2. If you've added code that should be tested, add tests  \n3. If you've changed APIs, update the documentation.  \n4. Ensure the test suite passes.  \n5. Make sure your code lints.  \n6. If you haven't already, complete the Contributor License Agreement (\"CLA\").\n\n## Contributor License Agreement (\"CLA\")\nIn order to accept your pull request, we need you to submit a CLA. You only need\nto do this once to work on any of Facebook's open source projects.\n\nComplete your CLA here: <https://code.facebook.com/cla>\n\n## Issues  \nWe use GitHub issues to track public bugs. Please ensure your description is\nclear and has sufficient instructions to be able to reproduce the issue.\n\nFacebook has a [bounty program](https://www.facebook.com/whitehat/) for the safe\ndisclosure of security bugs. In those cases, please go through the process\noutlined on that page and do not file a public issue.\n\n## Coding Style  \n* 2 spaces for indentation rather than tabs\n* Line wrapping indents 4 spaces\n* 100 character line length\n* One parameter per line when line wrapping is required\n* Use the `m` member variable prefix for private fields\n* Opening braces to appear on the same line as code\n\n## License\nBy contributing to Stetho, you agree that your contributions will be licensed\nunder its MIT license.  See LICENSE file for details.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) Facebook, Inc. and its affiliates.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# Stetho [![Build Status](https://travis-ci.org/facebook/stetho.svg?branch=master)](https://travis-ci.org/facebook/stetho)\n\n[Stetho](https://facebook.github.io/stetho) is a sophisticated debug bridge for Android applications. When enabled,\ndevelopers have access to the Chrome Developer Tools feature natively part of\nthe Chrome desktop browser. Developers can also choose to enable the optional\n`dumpapp` tool which offers a powerful command-line interface to application\ninternals.\n\nOnce you complete the set-up instructions below, just start your app and point\nyour laptop browser to `chrome://inspect`.  Click the \"Inspect\" button to\nbegin.\n\n## Set-up\n\n### Download\nDownload [the latest JARs](https://github.com/facebook/stetho/releases/latest) or grab via Gradle:\n```groovy\nimplementation 'com.facebook.stetho:stetho:1.6.0'\n```\nor Maven:\n```xml\n<dependency>\n  <groupId>com.facebook.stetho</groupId>\n  <artifactId>stetho</artifactId>\n  <version>1.6.0</version>\n</dependency>\n```\n\nOnly the main `stetho` dependency is strictly required; however, you may also wish to use one of the network helpers:\n\n```groovy\nimplementation 'com.facebook.stetho:stetho-okhttp3:1.6.0'\n```\nor:\n```groovy\nimplementation 'com.facebook.stetho:stetho-urlconnection:1.6.0'\n```\n\nYou can also enable a JavaScript console with:\n\n```groovy\nimplementation 'com.facebook.stetho:stetho-js-rhino:1.6.0'\n```\nFor more details on how to customize the JavaScript runtime see [stetho-js-rhino](stetho-js-rhino/).\n\n### Putting it together\nIntegrating with Stetho is intended to be seamless and straightforward for\nmost existing Android applications.  There is a simple initialization step\nwhich occurs in your `Application` class:\n\n```java\npublic class MyApplication extends Application {\n  public void onCreate() {\n    super.onCreate();\n    Stetho.initializeWithDefaults(this);\n  }\n}\n```\nAlso ensure that your `MyApplication` Java class is registered in your `AndroidManifest.xml` file, otherwise you will not see an \"Inspect\" button in `chrome://inspect/#devices` :\n\n```xml\n<manifest\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        ...>\n        <application\n                android:name=\"MyApplication\"\n                ...>\n         </application>\n</manifest>                \n```\n\nThis brings up most of the default configuration but does not enable some\nadditional hooks (most notably, network inspection).  See below for specific\ndetails on individual subsystems.\n\n### Enable network inspection\nIf you are using the popular [OkHttp](https://github.com/square/okhttp)\nlibrary at the 3.x release, you can use the\n[Interceptors](https://github.com/square/okhttp/wiki/Interceptors) system to\nautomatically hook into your existing stack.  This is currently the simplest\nand most straightforward way to enable network inspection:\n\n```java\nnew OkHttpClient.Builder()\n    .addNetworkInterceptor(new StethoInterceptor())\n    .build()\n```\n\nNote that okhttp 2.x will work as well, but with slightly different syntax and you must use the `stetho-okhttp` artifact (not `stetho-okhttp3`).\n\nAs interceptors can modify the request and response, add the Stetho interceptor after all others to get an accurate view of the network traffic.\n\nIf you are using `HttpURLConnection`, you can use `StethoURLConnectionManager`\nto assist with integration though you should be aware that there are some\ncaveats with this approach.  In particular, you must explicitly add\n`Accept-Encoding: gzip` to the request headers and manually handle compressed\nresponses in order for Stetho to report compressed payload sizes.\n\nSee the [`stetho-sample` project](stetho-sample) for more details.\n\n## Going further\n\n### Custom dumpapp plugins\nCustom plugins are the preferred means of extending the `dumpapp` system and\ncan be added easily during configuration.  Simply replace your configuration\nstep as such:\n\n```java\nStetho.initialize(Stetho.newInitializerBuilder(context)\n    .enableDumpapp(new DumperPluginsProvider() {\n      @Override\n      public Iterable<DumperPlugin> get() {\n        return new Stetho.DefaultDumperPluginsBuilder(context)\n            .provide(new MyDumperPlugin())\n            .finish();\n      }\n    })\n    .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(context))\n    .build())\n```\n\nSee the [`stetho-sample` project](stetho-sample) for more details.\n\n## Improve Stetho!\nSee the [CONTRIBUTING.md](CONTRIBUTING.md) file for how to help out.\n\n## License\nStetho is MIT-licensed. See LICENSE file for more details.\n"
  },
  {
    "path": "build-tools/protocol.json",
    "content": "{\n    \"version\": { \"major\": \"1\", \"minor\": \"1\" },\n    \"domains\": [{\n        \"domain\": \"Inspector\",\n        \"hidden\": true,\n        \"types\": [],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables inspector domain notifications.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables inspector domain notifications.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"evaluateForTestInFrontend\",\n                \"parameters\": [\n                    { \"name\": \"testCallId\", \"type\": \"integer\" },\n                    { \"name\": \"script\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"inspect\",\n                \"parameters\": [\n                    { \"name\": \"object\", \"$ref\": \"Runtime.RemoteObject\" },\n                    { \"name\": \"hints\", \"type\": \"object\" }\n                ]\n            },\n            {\n                \"name\": \"detached\",\n                \"description\": \"Fired when remote debugging connection is about to be terminated. Contains detach reason.\",\n                \"parameters\": [\n                    { \"name\": \"reason\", \"type\": \"string\", \"description\": \"The reason why connection has been terminated.\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"targetCrashed\",\n                \"description\": \"Fired when debugging target has crashed\",\n                \"handlers\": [\"browser\"]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Memory\",\n        \"hidden\": true,\n        \"commands\": [\n            {\n                \"name\": \"getDOMCounters\",\n                \"returns\": [\n                    { \"name\": \"documents\", \"type\": \"integer\" },\n                    { \"name\": \"nodes\", \"type\": \"integer\" },\n                    { \"name\": \"jsEventListeners\", \"type\": \"integer\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Page\",\n        \"description\": \"Actions and events related to the inspected page belong to the page domain.\",\n        \"types\": [\n            {\n                \"id\": \"ResourceType\",\n                \"type\": \"string\",\n                \"enum\": [\"Document\", \"Stylesheet\", \"Image\", \"Media\", \"Font\", \"Script\", \"TextTrack\", \"XHR\", \"WebSocket\", \"Other\"],\n                \"description\": \"Resource type as it was perceived by the rendering engine.\"\n            },\n            {\n              \"id\": \"FrameId\",\n              \"type\": \"string\",\n              \"description\": \"Unique frame identifier.\"\n            },\n            {\n                \"id\": \"Frame\",\n                \"type\": \"object\",\n                \"description\": \"Information about the Frame on the page.\",\n                \"properties\": [\n                    { \"name\": \"id\", \"type\": \"string\", \"description\": \"Frame unique identifier.\" },\n                    { \"name\": \"parentId\", \"type\": \"string\", \"optional\": true, \"description\": \"Parent frame identifier.\" },\n                    { \"name\": \"loaderId\", \"$ref\": \"Network.LoaderId\", \"description\": \"Identifier of the loader associated with this frame.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"optional\": true, \"description\": \"Frame's name as specified in the tag.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Frame document's URL.\" },\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Frame document's security origin.\" },\n                    { \"name\": \"mimeType\", \"type\": \"string\", \"description\": \"Frame document's mimeType as determined by the browser.\" }\n                ]\n            },\n            {\n                \"id\": \"FrameResourceTree\",\n                \"type\": \"object\",\n                \"description\": \"Information about the Frame hierarchy along with their cached resources.\",\n                \"properties\": [\n                    { \"name\": \"frame\", \"$ref\": \"Frame\", \"description\": \"Frame information for this tree item.\" },\n                    { \"name\": \"childFrames\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"FrameResourceTree\" }, \"description\": \"Child frames.\" },\n                    { \"name\": \"resources\", \"type\": \"array\",\n                        \"items\": {\n                            \"type\": \"object\",\n                            \"properties\": [\n                                { \"name\": \"url\", \"type\": \"string\", \"description\": \"Resource URL.\" },\n                                { \"name\": \"type\", \"$ref\": \"ResourceType\", \"description\": \"Type of this resource.\" },\n                                { \"name\": \"mimeType\", \"type\": \"string\", \"description\": \"Resource mimeType as determined by the browser.\" },\n                                { \"name\": \"failed\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the resource failed to load.\" },\n                                { \"name\": \"canceled\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the resource was canceled during loading.\" }\n                            ]\n                        },\n                        \"description\": \"Information about frame resources.\"\n                    }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"ScriptIdentifier\",\n                \"type\": \"string\",\n                \"description\": \"Unique script identifier.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"NavigationEntry\",\n                \"type\": \"object\",\n                \"description\": \"Navigation history entry.\",\n                \"properties\": [\n                  { \"name\": \"id\", \"type\": \"integer\", \"description\": \"Unique id of the navigation history entry.\" },\n                  { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the navigation history entry.\" },\n                  { \"name\": \"title\", \"type\": \"string\", \"description\": \"Title of the navigation history entry.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"ScreencastFrameMetadata\",\n                \"type\": \"object\",\n                \"description\": \"Screencast frame metadata\",\n                \"properties\": [\n                    { \"name\": \"offsetTop\", \"type\": \"number\", \"hidden\": true, \"description\": \"Top offset in DIP.\" },\n                    { \"name\": \"pageScaleFactor\", \"type\": \"number\", \"hidden\": true, \"description\": \"Page scale factor.\" },\n                    { \"name\": \"deviceWidth\", \"type\": \"number\", \"hidden\": true, \"description\": \"Device screen width in DIP.\" },\n                    { \"name\": \"deviceHeight\", \"type\": \"number\", \"hidden\": true, \"description\": \"Device screen height in DIP.\" },\n                    { \"name\": \"scrollOffsetX\", \"type\": \"number\", \"hidden\": true, \"description\": \"Position of horizontal scroll in CSS pixels.\" },\n                    { \"name\": \"scrollOffsetY\", \"type\": \"number\", \"hidden\": true, \"description\": \"Position of vertical scroll in CSS pixels.\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"optional\": true, \"hidden\": true, \"description\": \"Frame swap timestamp.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"DialogType\",\n                \"description\": \"Javascript dialog type\",\n                \"type\": \"string\",\n                \"enum\": [\"alert\", \"confirm\", \"prompt\", \"beforeunload\"],\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables page domain notifications.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables page domain notifications.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"addScriptToEvaluateOnLoad\",\n                \"parameters\": [\n                    { \"name\": \"scriptSource\", \"type\": \"string\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"identifier\", \"$ref\": \"ScriptIdentifier\", \"description\": \"Identifier of the added script.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"removeScriptToEvaluateOnLoad\",\n                \"parameters\": [\n                    { \"name\": \"identifier\", \"$ref\": \"ScriptIdentifier\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"reload\",\n                \"parameters\": [\n                    { \"name\": \"ignoreCache\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true, browser cache is ignored (as if the user pressed Shift+refresh).\" },\n                    { \"name\": \"scriptToEvaluateOnLoad\", \"type\": \"string\", \"optional\": true, \"description\": \"If set, the script will be injected into all frames of the inspected page after reload.\" }\n                ],\n                \"description\": \"Reloads given page optionally ignoring the cache.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"navigate\",\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL to navigate the page to.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"hidden\": true, \"description\": \"Frame id that will be navigated.\" }\n                ],\n                \"description\": \"Navigates current page to the given URL.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n              \"name\": \"getNavigationHistory\",\n              \"parameters\": [],\n              \"returns\": [\n                { \"name\": \"currentIndex\", \"type\": \"integer\", \"description\": \"Index of the current navigation history entry.\" },\n                { \"name\": \"entries\", \"type\": \"array\", \"items\": { \"$ref\": \"NavigationEntry\" }, \"description\": \"Array of navigation history entries.\" }\n              ],\n              \"description\": \"Returns navigation history for the current page.\",\n              \"hidden\": true,\n              \"handlers\": [\"browser\"]\n            },\n            {\n              \"name\": \"navigateToHistoryEntry\",\n              \"parameters\": [\n                  { \"name\": \"entryId\", \"type\": \"integer\", \"description\": \"Unique id of the entry to navigate to.\" }\n              ],\n              \"description\": \"Navigates current page to the given history entry.\",\n              \"hidden\": true,\n              \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"getCookies\",\n                \"returns\": [\n                    { \"name\": \"cookies\", \"type\": \"array\", \"items\": { \"$ref\": \"Network.Cookie\" }, \"description\": \"Array of cookie objects.\" }\n                ],\n                \"description\": \"Returns all browser cookies. Depending on the backend support, will return detailed cookie information in the <code>cookies</code> field.\",\n                \"handlers\": [\"browser\"],\n                \"async\": true,\n                \"hidden\": true,\n                \"redirect\": \"Network\"\n            },\n            {\n                \"name\": \"deleteCookie\",\n                \"parameters\": [\n                    { \"name\": \"cookieName\", \"type\": \"string\", \"description\": \"Name of the cookie to remove.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL to match cooke domain and path.\" }\n                ],\n                \"description\": \"Deletes browser cookie with given name, domain and path.\",\n                \"handlers\": [\"browser\"],\n                \"async\": true,\n                \"hidden\": true,\n                \"redirect\": \"Network\"\n            },\n            {\n                \"name\": \"getResourceTree\",\n                \"description\": \"Returns present frame / resource tree structure.\",\n                \"returns\": [\n                    { \"name\": \"frameTree\", \"$ref\": \"FrameResourceTree\", \"description\": \"Present frame / resource tree structure.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getResourceContent\",\n                \"async\": true,\n                \"description\": \"Returns content of the given resource.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Frame id to get resource for.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the resource to get content for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"content\", \"type\": \"string\", \"description\": \"Resource content.\" },\n                    { \"name\": \"base64Encoded\", \"type\": \"boolean\", \"description\": \"True, if content was served as base64.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"searchInResource\",\n                \"description\": \"Searches for given string in resource content.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Frame id for resource to search in.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the resource to search in.\" },\n                    { \"name\": \"query\", \"type\": \"string\", \"description\": \"String to search for.\"  },\n                    { \"name\": \"caseSensitive\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true, search is case sensitive.\" },\n                    { \"name\": \"isRegex\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true, treats string parameter as regex.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"array\", \"items\": { \"$ref\": \"Debugger.SearchMatch\" }, \"description\": \"List of search matches.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setDocumentContent\",\n                \"description\": \"Sets given markup as the document's HTML.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Frame id to set HTML for.\" },\n                    { \"name\": \"html\", \"type\": \"string\", \"description\": \"HTML content to set.\"  }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setDeviceMetricsOverride\",\n                \"description\": \"Overrides the values of device screen dimensions (window.screen.width, window.screen.height, window.innerWidth, window.innerHeight, and \\\"device-width\\\"/\\\"device-height\\\"-related CSS media query results).\",\n                \"parameters\": [\n                    { \"name\": \"width\", \"type\": \"integer\", \"description\": \"Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.\" },\n                    { \"name\": \"height\", \"type\": \"integer\", \"description\": \"Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.\" },\n                    { \"name\": \"deviceScaleFactor\", \"type\": \"number\", \"description\": \"Overriding device scale factor value. 0 disables the override.\" },\n                    { \"name\": \"mobile\", \"type\": \"boolean\", \"description\": \"Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text autosizing and more.\" },\n                    { \"name\": \"fitWindow\", \"type\": \"boolean\", \"description\": \"Whether a view that exceeds the available browser window area should be scaled down to fit.\" },\n                    { \"name\": \"scale\", \"type\": \"number\", \"optional\": true, \"description\": \"Scale to apply to resulting view image. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"offsetX\", \"type\": \"number\", \"optional\": true, \"description\": \"X offset to shift resulting view image by. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"offsetY\", \"type\": \"number\", \"optional\": true, \"description\": \"Y offset to shift resulting view image by. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"screenWidth\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding screen width value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"screenHeight\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding screen height value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"positionX\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding view X position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"positionY\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" }\n                ],\n                \"handlers\": [\"browser\"],\n                \"redirect\": \"Emulation\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"clearDeviceMetricsOverride\",\n                \"description\": \"Clears the overriden device metrics.\",\n                \"handlers\": [\"browser\"],\n                \"redirect\": \"Emulation\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setGeolocationOverride\",\n                \"description\": \"Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.\",\n                \"parameters\": [\n                    { \"name\": \"latitude\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock latitude\"},\n                    { \"name\": \"longitude\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock longitude\"},\n                    { \"name\": \"accuracy\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock accuracy\"}\n                ],\n                \"redirect\": \"Emulation\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"clearGeolocationOverride\",\n                \"description\": \"Clears the overriden Geolocation Position and Error.\",\n                \"redirect\": \"Emulation\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"setDeviceOrientationOverride\",\n                \"description\": \"Overrides the Device Orientation.\",\n                \"parameters\": [\n                    { \"name\": \"alpha\", \"type\": \"number\", \"description\": \"Mock alpha\"},\n                    { \"name\": \"beta\", \"type\": \"number\", \"description\": \"Mock beta\"},\n                    { \"name\": \"gamma\", \"type\": \"number\", \"description\": \"Mock gamma\"}\n                ],\n                \"redirect\": \"DeviceOrientation\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"clearDeviceOrientationOverride\",\n                \"description\": \"Clears the overridden Device Orientation.\",\n                \"redirect\": \"DeviceOrientation\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setTouchEmulationEnabled\",\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"Whether the touch event emulation should be enabled.\" },\n                    { \"name\": \"configuration\", \"type\": \"string\", \"enum\": [\"mobile\", \"desktop\"], \"optional\": true, \"description\": \"Touch/gesture events configuration. Default: current platform.\" }\n                ],\n                \"description\": \"Toggles mouse event-based touch event emulation.\",\n                \"hidden\": true,\n                \"redirect\": \"Emulation\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"captureScreenshot\",\n                \"async\": true,\n                \"description\": \"Capture page screenshot.\",\n                \"parameters\": [],\n                \"returns\": [\n                    { \"name\": \"data\", \"type\": \"string\", \"description\": \"Base64-encoded image data (PNG).\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"canScreencast\",\n                \"description\": \"Tells whether screencast is supported.\",\n                \"returns\": [\n                  { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if screencast is supported.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"startScreencast\",\n                \"description\": \"Starts sending each frame using the <code>screencastFrame</code> event.\",\n                \"parameters\": [\n                    { \"name\": \"format\", \"type\": \"string\", \"optional\": true, \"enum\": [\"jpeg\", \"png\"], \"description\": \"Image compression format.\" },\n                    { \"name\": \"quality\", \"type\": \"integer\", \"optional\": true, \"description\": \"Compression quality from range [0..100].\" },\n                    { \"name\": \"maxWidth\", \"type\": \"integer\", \"optional\": true, \"description\": \"Maximum screenshot width.\" },\n                    { \"name\": \"maxHeight\", \"type\": \"integer\", \"optional\": true, \"description\": \"Maximum screenshot height.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"stopScreencast\",\n                \"description\": \"Stops sending each frame in the <code>screencastFrame</code>.\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"screencastFrameAck\",\n                \"description\": \"Acknowledges that a screencast frame has been received by the frontend.\",\n                \"parameters\": [\n                    { \"name\": \"frameNumber\", \"type\": \"integer\", \"description\": \"Frame number.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"handleJavaScriptDialog\",\n                \"description\": \"Accepts or dismisses a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload).\",\n                \"parameters\": [\n                    { \"name\": \"accept\", \"type\": \"boolean\", \"description\": \"Whether to accept or dismiss the dialog.\" },\n                    { \"name\": \"promptText\", \"type\": \"string\", \"optional\": true, \"description\": \"The text to enter into the dialog prompt before accepting. Used only if this is a prompt dialog.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"setShowViewportSizeOnResize\",\n                \"description\": \"Paints viewport size upon main frame resize.\",\n                \"parameters\": [\n                    { \"name\": \"show\", \"type\": \"boolean\", \"description\": \"Whether to paint size or not.\" },\n                    { \"name\": \"showGrid\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether to paint grid as well.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setColorPickerEnabled\",\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"Shows / hides color picker\" }\n                ],\n                \"description\": \"Shows / hides color picker\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"setOverlayMessage\",\n                \"parameters\": [\n                    { \"name\": \"message\", \"type\": \"string\", \"optional\": true, \"description\": \"Overlay message to display when paused in debugger.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Sets overlay message.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"domContentEventFired\",\n                \"parameters\": [\n                    { \"name\": \"timestamp\", \"type\": \"number\" }\n                ]\n            },\n            {\n                \"name\": \"loadEventFired\",\n                \"parameters\": [\n                    { \"name\": \"timestamp\", \"type\": \"number\" }\n                ]\n            },\n            {\n                \"name\": \"frameAttached\",\n                \"description\": \"Fired when frame has been attached to its parent.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has been attached.\" },\n                    { \"name\": \"parentFrameId\", \"$ref\": \"FrameId\", \"description\": \"Parent frame identifier.\" }\n                ]\n            },\n            {\n                \"name\": \"frameNavigated\",\n                \"description\": \"Fired once navigation of the frame has completed. Frame is now associated with the new loader.\",\n                \"parameters\": [\n                    { \"name\": \"frame\", \"$ref\": \"Frame\", \"description\": \"Frame object.\" }\n                ]\n            },\n            {\n                \"name\": \"frameDetached\",\n                \"description\": \"Fired when frame has been detached from its parent.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has been detached.\" }\n                ]\n            },\n            {\n                \"name\": \"frameStartedLoading\",\n                \"description\": \"Fired when frame has started loading.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has started loading.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"frameStoppedLoading\",\n                \"description\": \"Fired when frame has stopped loading.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has stopped loading.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"frameScheduledNavigation\",\n                \"description\": \"Fired when frame schedules a potential navigation.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has scheduled a navigation.\" },\n                    { \"name\": \"delay\", \"type\": \"number\", \"description\": \"Delay (in seconds) until the navigation is scheduled to begin. The navigation is not guaranteed to start.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"frameClearedScheduledNavigation\",\n                \"description\": \"Fired when frame no longer has a scheduled navigation.\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"FrameId\", \"description\": \"Id of the frame that has cleared its scheduled navigation.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"frameResized\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"javascriptDialogOpening\",\n                \"description\": \"Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) is about to open.\",\n                \"parameters\": [\n                    { \"name\": \"message\", \"type\": \"string\", \"description\": \"Message that will be displayed by the dialog.\" },\n                    { \"name\": \"type\", \"$ref\": \"DialogType\", \"description\": \"Dialog type.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"javascriptDialogClosed\",\n                \"description\": \"Fired when a JavaScript initiated dialog (alert, confirm, prompt, or onbeforeunload) has been closed.\",\n                \"parameters\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"Whether dialog was confirmed.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"screencastFrame\",\n                \"description\": \"Compressed image data requested by the <code>startScreencast</code>.\",\n                \"parameters\": [\n                    { \"name\": \"data\", \"type\": \"string\", \"description\": \"Base64-encoded compressed image.\" },\n                    { \"name\": \"metadata\", \"$ref\": \"ScreencastFrameMetadata\", \"description\": \"Screencast frame metadata.\"},\n                    { \"name\": \"frameNumber\", \"type\": \"integer\", \"optional\": true, \"description\": \"Frame number.\"}\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"screencastVisibilityChanged\",\n                \"description\": \"Fired when the page with currently enabled screencast was shown or hidden </code>.\",\n                \"parameters\": [\n                    { \"name\": \"visible\", \"type\": \"boolean\", \"description\": \"True if the page is visible.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"colorPicked\",\n                \"description\": \"Fired when a color has been picked.\",\n                \"parameters\": [\n                    { \"name\": \"color\", \"$ref\": \"DOM.RGBA\", \"description\": \"RGBA of the picked color.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"interstitialShown\",\n                \"description\": \"Fired when interstitial page was shown\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"interstitialHidden\",\n                \"description\": \"Fired when interstitial page was hidden\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Rendering\",\n        \"description\": \"This domain allows to control rendering of the page.\",\n        \"hidden\": true,\n        \"commands\": [\n            {\n                \"name\": \"setShowPaintRects\",\n                \"description\": \"Requests that backend shows paint rectangles\",\n                \"parameters\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True for showing paint rectangles\" }\n                ]\n            },\n            {\n                \"name\": \"setShowDebugBorders\",\n                \"description\": \"Requests that backend shows debug borders on layers\",\n                \"parameters\": [\n                    { \"name\": \"show\", \"type\": \"boolean\", \"description\": \"True for showing debug borders\" }\n                ]\n            },\n            {\n                \"name\": \"setShowFPSCounter\",\n                \"description\": \"Requests that backend shows the FPS counter\",\n                \"parameters\": [\n                    { \"name\": \"show\", \"type\": \"boolean\", \"description\": \"True for showing the FPS counter\" }\n                ]\n            },\n            {\n                \"name\": \"setContinuousPaintingEnabled\",\n                \"description\": \"Requests that backend enables continuous painting\",\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"True for enabling cointinuous painting\" }\n                ]\n            },\n            {\n                \"name\": \"setShowScrollBottleneckRects\",\n                \"description\": \"Requests that backend shows scroll bottleneck rects\",\n                \"parameters\": [\n                    { \"name\": \"show\", \"type\": \"boolean\", \"description\": \"True for showing scroll bottleneck rects\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Emulation\",\n        \"description\": \"This domain emulates different environments for the page.\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"Viewport\",\n                \"type\": \"object\",\n                \"description\": \"Visible page viewport\",\n                \"properties\": [\n                    { \"name\": \"scrollX\", \"type\": \"number\", \"description\": \"X scroll offset in CSS pixels.\" },\n                    { \"name\": \"scrollY\", \"type\": \"number\", \"description\": \"Y scroll offset in CSS pixels.\" },\n                    { \"name\": \"contentsWidth\", \"type\": \"number\", \"description\": \"Contents width in CSS pixels.\" },\n                    { \"name\": \"contentsHeight\", \"type\": \"number\", \"description\": \"Contents height in CSS pixels.\" },\n                    { \"name\": \"pageScaleFactor\", \"type\": \"number\", \"description\": \"Page scale factor.\" },\n                    { \"name\": \"minimumPageScaleFactor\", \"type\": \"number\", \"description\": \"Minimum page scale factor.\" },\n                    { \"name\": \"maximumPageScaleFactor\", \"type\": \"number\", \"description\": \"Maximum page scale factor.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"setDeviceMetricsOverride\",\n                \"description\": \"Overrides the values of device screen dimensions (window.screen.width, window.screen.height, window.innerWidth, window.innerHeight, and \\\"device-width\\\"/\\\"device-height\\\"-related CSS media query results).\",\n                \"parameters\": [\n                    { \"name\": \"width\", \"type\": \"integer\", \"description\": \"Overriding width value in pixels (minimum 0, maximum 10000000). 0 disables the override.\" },\n                    { \"name\": \"height\", \"type\": \"integer\", \"description\": \"Overriding height value in pixels (minimum 0, maximum 10000000). 0 disables the override.\" },\n                    { \"name\": \"deviceScaleFactor\", \"type\": \"number\", \"description\": \"Overriding device scale factor value. 0 disables the override.\" },\n                    { \"name\": \"mobile\", \"type\": \"boolean\", \"description\": \"Whether to emulate mobile device. This includes viewport meta tag, overlay scrollbars, text autosizing and more.\" },\n                    { \"name\": \"fitWindow\", \"type\": \"boolean\", \"description\": \"Whether a view that exceeds the available browser window area should be scaled down to fit.\" },\n                    { \"name\": \"scale\", \"type\": \"number\", \"optional\": true, \"description\": \"Scale to apply to resulting view image. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"offsetX\", \"type\": \"number\", \"optional\": true, \"description\": \"X offset to shift resulting view image by. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"offsetY\", \"type\": \"number\", \"optional\": true, \"description\": \"Y offset to shift resulting view image by. Ignored in |fitWindow| mode.\" },\n                    { \"name\": \"screenWidth\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding screen width value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"screenHeight\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding screen height value in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"positionX\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding view X position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" },\n                    { \"name\": \"positionY\", \"type\": \"integer\", \"optional\": true, \"description\": \"Overriding view Y position on screen in pixels (minimum 0, maximum 10000000). Only used for |mobile==true|.\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"clearDeviceMetricsOverride\",\n                \"description\": \"Clears the overriden device metrics.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"resetScrollAndPageScaleFactor\",\n                \"description\": \"Requests that scroll offsets and page scale factor are reset to initial values.\"\n            },\n            {\n                \"name\": \"setPageScaleFactor\",\n                \"description\": \"Sets a specified page scale factor.\",\n                \"parameters\": [\n                    { \"name\": \"pageScaleFactor\", \"type\": \"number\", \"description\": \"Page scale factor.\" }\n                ]\n            },\n            {\n                \"name\": \"setScriptExecutionDisabled\",\n                \"description\": \"Switches script execution in the page.\",\n                \"parameters\": [\n                    { \"name\": \"value\", \"type\": \"boolean\", \"description\": \"Whether script execution should be disabled in the page.\" }\n                ]\n            },\n            {\n                \"name\": \"setGeolocationOverride\",\n                \"description\": \"Overrides the Geolocation Position or Error. Omitting any of the parameters emulates position unavailable.\",\n                \"parameters\": [\n                    { \"name\": \"latitude\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock latitude\"},\n                    { \"name\": \"longitude\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock longitude\"},\n                    { \"name\": \"accuracy\", \"type\": \"number\", \"optional\": true, \"description\": \"Mock accuracy\"}\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"clearGeolocationOverride\",\n                \"description\": \"Clears the overriden Geolocation Position and Error.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"setTouchEmulationEnabled\",\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"Whether the touch event emulation should be enabled.\" },\n                    { \"name\": \"configuration\", \"type\": \"string\", \"enum\": [\"mobile\", \"desktop\"], \"optional\": true, \"description\": \"Touch/gesture events configuration. Default: current platform.\" }\n                ],\n                \"description\": \"Toggles mouse event-based touch event emulation.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"setEmulatedMedia\",\n                \"parameters\": [\n                    { \"name\": \"media\", \"type\": \"string\", \"description\": \"Media type to emulate. Empty string disables the override.\" }\n                ],\n                \"description\": \"Emulates the given media for CSS media queries.\"\n            },\n            {\n                \"name\": \"canEmulate\",\n                \"description\": \"Tells whether emulation is supported.\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if emulation is supported.\" }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"viewportChanged\",\n                \"description\": \"Fired when a visible page viewport has changed. Only fired when device metrics are overridden.\",\n                \"parameters\": [\n                    { \"name\": \"viewport\", \"$ref\": \"Viewport\", \"description\": \"Viewport description.\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Runtime\",\n        \"description\": \"Runtime domain exposes JavaScript runtime by means of remote evaluation and mirror objects. Evaluation results are returned as mirror object that expose object type, string representation and unique identifier that can be used for further object reference. Original objects are maintained in memory unless they are either explicitly released or are released along with the other objects in their object group.\",\n        \"types\": [\n            {\n                \"id\": \"RemoteObjectId\",\n                \"type\": \"string\",\n                \"description\": \"Unique object identifier.\"\n            },\n            {\n                \"id\": \"RemoteObject\",\n                \"type\": \"object\",\n                \"description\": \"Mirror object referencing original JavaScript object.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"object\", \"function\", \"undefined\", \"string\", \"number\", \"boolean\", \"symbol\"], \"description\": \"Object type.\" },\n                    { \"name\": \"subtype\", \"type\": \"string\", \"optional\": true, \"enum\": [\"array\", \"null\", \"node\", \"regexp\", \"date\", \"map\", \"set\", \"iterator\", \"generator\", \"error\"], \"description\": \"Object subtype hint. Specified for <code>object</code> type values only.\" },\n                    { \"name\": \"className\", \"type\": \"string\", \"optional\": true, \"description\": \"Object class (constructor) name. Specified for <code>object</code> type values only.\" },\n                    { \"name\": \"value\", \"type\": \"any\", \"optional\": true, \"description\": \"Remote object value in case of primitive values or JSON values (if it was requested), or description string if the value can not be JSON-stringified (like NaN, Infinity, -Infinity, -0).\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"optional\": true, \"description\": \"String representation of the object.\" },\n                    { \"name\": \"objectId\", \"$ref\": \"RemoteObjectId\", \"optional\": true, \"description\": \"Unique object identifier (for non-primitive values).\" },\n                    { \"name\": \"preview\", \"$ref\": \"ObjectPreview\", \"optional\": true, \"description\": \"Preview containing abbreviated property values. Specified for <code>object</code> type values only.\", \"hidden\": true },\n                    { \"name\": \"customPreview\", \"$ref\": \"CustomPreview\", \"optional\": true, \"hidden\": true}\n                ]\n            },\n            {   \"id\": \"CustomPreview\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"header\", \"type\": \"string\"},\n                    { \"name\": \"hasBody\", \"type\": \"boolean\"},\n                    {\"name\": \"formatterObjectId\", \"$ref\": \"RemoteObjectId\"},\n                    {\"name\": \"configObjectId\", \"$ref\": \"RemoteObjectId\", \"optional\": true}\n                ]\n            },\n            {\n                \"id\": \"ObjectPreview\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"description\": \"Object containing abbreviated remote object value.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"object\", \"function\", \"undefined\", \"string\", \"number\", \"boolean\", \"symbol\"], \"description\": \"Object type.\" },\n                    { \"name\": \"subtype\", \"type\": \"string\", \"optional\": true, \"enum\": [\"array\", \"null\", \"node\", \"regexp\", \"date\", \"map\", \"set\", \"iterator\", \"generator\", \"error\"], \"description\": \"Object subtype hint. Specified for <code>object</code> type values only.\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"optional\": true, \"description\": \"String representation of the object.\" },\n                    { \"name\": \"lossless\", \"type\": \"boolean\", \"description\": \"Determines whether preview is lossless (contains all information of the original object).\" },\n                    { \"name\": \"overflow\", \"type\": \"boolean\", \"description\": \"True iff some of the properties or entries of the original object did not fit.\" },\n                    { \"name\": \"properties\", \"type\": \"array\", \"items\": { \"$ref\": \"PropertyPreview\" }, \"description\": \"List of the properties.\" },\n                    { \"name\": \"entries\", \"type\": \"array\", \"items\": { \"$ref\": \"EntryPreview\" }, \"optional\": true, \"description\": \"List of the entries. Specified for <code>map</code> and <code>set</code> subtype values only.\" }\n                ]\n            },\n            {\n                \"id\": \"PropertyPreview\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Property name.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"object\", \"function\", \"undefined\", \"string\", \"number\", \"boolean\", \"symbol\", \"accessor\"], \"description\": \"Object type. Accessor means that the property itself is an accessor property.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"optional\": true, \"description\": \"User-friendly property value string.\" },\n                    { \"name\": \"valuePreview\", \"$ref\": \"ObjectPreview\", \"optional\": true, \"description\": \"Nested value preview.\" },\n                    { \"name\": \"subtype\", \"type\": \"string\", \"optional\": true, \"enum\": [\"array\", \"null\", \"node\", \"regexp\", \"date\", \"map\", \"set\", \"iterator\", \"generator\", \"error\"], \"description\": \"Object subtype hint. Specified for <code>object</code> type values only.\" }\n                ]\n            },\n            {\n                \"id\": \"EntryPreview\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"key\", \"$ref\": \"ObjectPreview\", \"optional\": true, \"description\": \"Preview of the key. Specified for map-like collection entries.\" },\n                    { \"name\": \"value\", \"$ref\": \"ObjectPreview\", \"description\": \"Preview of the value.\" }\n                ]\n            },\n            {\n                \"id\": \"PropertyDescriptor\",\n                \"type\": \"object\",\n                \"description\": \"Object property descriptor.\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Property name or symbol description.\" },\n                    { \"name\": \"value\", \"$ref\": \"RemoteObject\", \"optional\": true, \"description\": \"The value associated with the property.\" },\n                    { \"name\": \"writable\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the value associated with the property may be changed (data descriptors only).\" },\n                    { \"name\": \"get\", \"$ref\": \"RemoteObject\", \"optional\": true, \"description\": \"A function which serves as a getter for the property, or <code>undefined</code> if there is no getter (accessor descriptors only).\" },\n                    { \"name\": \"set\", \"$ref\": \"RemoteObject\", \"optional\": true, \"description\": \"A function which serves as a setter for the property, or <code>undefined</code> if there is no setter (accessor descriptors only).\" },\n                    { \"name\": \"configurable\", \"type\": \"boolean\", \"description\": \"True if the type of this property descriptor may be changed and if the property may be deleted from the corresponding object.\" },\n                    { \"name\": \"enumerable\", \"type\": \"boolean\", \"description\": \"True if this property shows up during enumeration of the properties on the corresponding object.\" },\n                    { \"name\": \"wasThrown\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the result was thrown during the evaluation.\" },\n                    { \"name\": \"isOwn\", \"optional\": true, \"type\": \"boolean\", \"description\": \"True if the property is owned for the object.\", \"hidden\": true },\n                    { \"name\": \"symbol\", \"$ref\": \"RemoteObject\", \"optional\": true, \"description\": \"Property symbol object, if the property is of the <code>symbol</code> type.\", \"hidden\": true }\n                ]\n            },\n            {\n                \"id\": \"InternalPropertyDescriptor\",\n                \"type\": \"object\",\n                \"description\": \"Object internal property descriptor. This property isn't normally visible in JavaScript code.\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Conventional property name.\" },\n                    { \"name\": \"value\", \"$ref\": \"RemoteObject\", \"optional\": true, \"description\": \"The value associated with the property.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"CallArgument\",\n                \"type\": \"object\",\n                \"description\": \"Represents function call argument. Either remote object id <code>objectId</code> or primitive <code>value</code> or neither of (for undefined) them should be specified.\",\n                \"properties\": [\n                    { \"name\": \"value\", \"type\": \"any\", \"optional\": true, \"description\": \"Primitive value, or description string if the value can not be JSON-stringified (like NaN, Infinity, -Infinity, -0).\" },\n                    { \"name\": \"objectId\", \"$ref\": \"RemoteObjectId\", \"optional\": true, \"description\": \"Remote object handle.\" },\n                    { \"name\": \"type\", \"optional\": true, \"hidden\": true, \"type\": \"string\", \"enum\": [\"object\", \"function\", \"undefined\", \"string\", \"number\", \"boolean\", \"symbol\"], \"description\": \"Object type.\" }\n                ]\n            },\n            {\n                \"id\": \"ExecutionContextId\",\n                \"type\": \"integer\",\n                \"description\": \"Id of an execution context.\"\n            },\n            {\n                \"id\": \"ExecutionContextDescription\",\n                \"type\": \"object\",\n                \"description\": \"Description of an isolated world.\",\n                \"properties\": [\n                    { \"name\": \"id\", \"$ref\": \"ExecutionContextId\", \"description\": \"Unique id of the execution context. It can be used to specify in which execution context script evaluation should be performed.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"optional\": true, \"description\": \"Context type. It is used e.g. to distinguish content scripts from web page script.\", \"hidden\": true },\n                    { \"name\": \"origin\", \"type\": \"string\", \"description\": \"Execution context origin.\", \"hidden\": true},\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Human readable name describing given context.\", \"hidden\": true},\n                    { \"name\": \"frameId\", \"type\": \"string\", \"description\": \"Id of the owning frame. May be an empty string if the context is not associated with a frame.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"evaluate\",\n                \"parameters\": [\n                    { \"name\": \"expression\", \"type\": \"string\", \"description\": \"Expression to evaluate.\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"Symbolic group name that can be used to release multiple objects.\" },\n                    { \"name\": \"includeCommandLineAPI\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Determines whether Command Line API should be available during the evaluation.\", \"hidden\": true },\n                    { \"name\": \"doNotPauseOnExceptionsAndMuteConsole\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.\", \"hidden\": true },\n                    { \"name\": \"contextId\", \"$ref\": \"ExecutionContextId\", \"optional\": true, \"description\": \"Specifies in which isolated context to perform evaluation. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page.\" },\n                    { \"name\": \"returnByValue\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the result is expected to be a JSON object that should be sent by value.\" },\n                    { \"name\": \"generatePreview\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true, \"description\": \"Whether preview should be generated for the result.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"$ref\": \"RemoteObject\", \"description\": \"Evaluation result.\" },\n                    { \"name\": \"wasThrown\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the result was thrown during the evaluation.\" },\n                    { \"name\": \"exceptionDetails\", \"$ref\": \"Debugger.ExceptionDetails\", \"optional\": true, \"hidden\": true, \"description\": \"Exception details.\"}\n                ],\n                \"description\": \"Evaluates expression on global object.\"\n            },\n            {\n                \"name\": \"callFunctionOn\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"RemoteObjectId\", \"description\": \"Identifier of the object to call function on.\" },\n                    { \"name\": \"functionDeclaration\", \"type\": \"string\", \"description\": \"Declaration of the function to call.\" },\n                    { \"name\": \"arguments\", \"type\": \"array\", \"items\": { \"$ref\": \"CallArgument\", \"description\": \"Call argument.\" }, \"optional\": true, \"description\": \"Call arguments. All call arguments must belong to the same JavaScript world as the target object.\" },\n                    { \"name\": \"doNotPauseOnExceptionsAndMuteConsole\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies whether function call should stop on exceptions and mute console. Overrides setPauseOnException state.\", \"hidden\": true },\n                    { \"name\": \"returnByValue\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the result is expected to be a JSON object which should be sent by value.\" },\n                    { \"name\": \"generatePreview\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true, \"description\": \"Whether preview should be generated for the result.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"$ref\": \"RemoteObject\", \"description\": \"Call result.\" },\n                    { \"name\": \"wasThrown\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the result was thrown during the evaluation.\" }\n                ],\n                \"description\": \"Calls function with given declaration on the given object. Object group of the result is inherited from the target object.\"\n            },\n            {\n                \"name\": \"getProperties\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"RemoteObjectId\", \"description\": \"Identifier of the object to return properties for.\" },\n                    { \"name\": \"ownProperties\", \"optional\": true, \"type\": \"boolean\", \"description\": \"If true, returns properties belonging only to the element itself, not to its prototype chain.\" },\n                    { \"name\": \"accessorPropertiesOnly\", \"optional\": true, \"type\": \"boolean\", \"description\": \"If true, returns accessor properties (with getter/setter) only; internal properties are not returned either.\", \"hidden\": true },\n                    { \"name\": \"generatePreview\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true, \"description\": \"Whether preview should be generated for the results.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"array\", \"items\": { \"$ref\": \"PropertyDescriptor\" }, \"description\": \"Object properties.\" },\n                    { \"name\": \"internalProperties\", \"optional\": true, \"type\": \"array\", \"items\": { \"$ref\": \"InternalPropertyDescriptor\" }, \"description\": \"Internal object properties (only of the element itself).\", \"hidden\": true },\n                    { \"name\": \"exceptionDetails\", \"$ref\": \"Debugger.ExceptionDetails\", \"optional\": true, \"hidden\": true, \"description\": \"Exception details.\"}\n                ],\n                \"description\": \"Returns properties of a given object. Object group of the result is inherited from the target object.\"\n            },\n            {\n                \"name\": \"releaseObject\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"RemoteObjectId\", \"description\": \"Identifier of the object to release.\" }\n                ],\n                \"description\": \"Releases remote object with given id.\"\n            },\n            {\n                \"name\": \"releaseObjectGroup\",\n                \"parameters\": [\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"description\": \"Symbolic object group name.\" }\n                ],\n                \"description\": \"Releases all remote objects that belong to a given group.\"\n            },\n            {\n                \"name\": \"run\",\n                \"hidden\": true,\n                \"description\": \"Tells inspected instance(worker or page) that it can run in case it was started paused.\"\n            },\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables reporting of execution contexts creation by means of <code>executionContextCreated</code> event. When the reporting gets enabled the event will be sent immediately for each existing execution context.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"hidden\": true,\n                \"description\": \"Disables reporting of execution contexts creation.\"\n            },\n            {\n                \"name\": \"isRunRequired\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if the Runtime is in paused on start state.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setCustomObjectFormatterEnabled\",\n                \"parameters\": [\n                    {\n                        \"name\": \"enabled\",\n                        \"type\": \"boolean\"\n                    }\n                ],\n                \"hidden\": true\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"executionContextCreated\",\n                \"parameters\": [\n                    { \"name\": \"context\", \"$ref\": \"ExecutionContextDescription\", \"description\": \"A newly created execution contex.\" }\n                ],\n                \"description\": \"Issued when new execution context is created.\"\n            },\n            {\n                \"name\": \"executionContextDestroyed\",\n                \"parameters\": [\n                    { \"name\": \"executionContextId\", \"$ref\": \"ExecutionContextId\", \"description\": \"Id of the destroyed context\" }\n                ],\n                \"description\": \"Issued when execution context is destroyed.\"\n            },\n            {\n                \"name\": \"executionContextsCleared\",\n                \"description\": \"Issued when all executionContexts were cleared in browser\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Console\",\n        \"description\": \"Console domain defines methods and events for interaction with the JavaScript console. Console collects messages created by means of the <a href='http://getfirebug.com/wiki/index.php/Console_API'>JavaScript Console API</a>. One needs to enable this domain using <code>enable</code> command in order to start receiving the console messages. Browser collects messages issued while console domain is not enabled as well and reports them using <code>messageAdded</code> notification upon enabling.\",\n        \"types\": [\n            {\n                \"id\": \"Timestamp\",\n                \"type\": \"number\",\n                \"description\": \"Number of seconds since epoch.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"ConsoleMessage\",\n                \"type\": \"object\",\n                \"description\": \"Console message.\",\n                \"properties\": [\n                    { \"name\": \"source\", \"type\": \"string\", \"enum\": [\"xml\", \"javascript\", \"network\", \"console-api\", \"storage\", \"appcache\", \"rendering\", \"security\", \"other\", \"deprecation\"], \"description\": \"Message source.\" },\n                    { \"name\": \"level\", \"type\": \"string\", \"enum\": [\"log\", \"warning\", \"error\", \"debug\", \"info\", \"revokedError\"], \"description\": \"Message severity.\" },\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"Message text.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"optional\": true, \"enum\": [\"log\", \"dir\", \"dirxml\", \"table\", \"trace\", \"clear\", \"startGroup\", \"startGroupCollapsed\", \"endGroup\", \"assert\", \"profile\", \"profileEnd\"], \"description\": \"Console message type.\" },\n                    { \"name\": \"scriptId\", \"type\": \"string\", \"optional\": true, \"description\": \"Script ID of the message origin.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of the message origin.\" },\n                    { \"name\": \"line\", \"type\": \"integer\", \"optional\": true, \"description\": \"Line number in the resource that generated this message.\" },\n                    { \"name\": \"column\", \"type\": \"integer\", \"optional\": true, \"description\": \"Column number in the resource that generated this message.\" },\n                    { \"name\": \"repeatCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"Repeat count for repeated messages.\" },\n                    { \"name\": \"parameters\", \"type\": \"array\", \"items\": { \"$ref\": \"Runtime.RemoteObject\" }, \"optional\": true, \"description\": \"Message parameters in case of the formatted message.\" },\n                    { \"name\": \"stackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"JavaScript stack trace for assertions and error messages.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"AsyncStackTrace\", \"optional\": true, \"description\": \"Asynchronous JavaScript stack trace that preceded this message, if available.\", \"hidden\": true },\n                    { \"name\": \"networkRequestId\", \"$ref\": \"Network.RequestId\", \"optional\": true, \"description\": \"Identifier of the network request associated with this message.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp, when this message was fired.\", \"hidden\": true },\n                    { \"name\": \"executionContextId\", \"$ref\": \"Runtime.ExecutionContextId\", \"optional\": true, \"description\": \"Identifier of the context where this message was created\", \"hidden\": true },\n                    { \"name\": \"messageId\", \"type\": \"integer\", \"hidden\": true, \"optional\": true, \"description\": \"Message id.\" },\n                    { \"name\": \"relatedMessageId\", \"type\": \"integer\", \"hidden\": true, \"optional\": true, \"description\": \"Related message id.\" }\n                ]\n            },\n            {\n                \"id\": \"CallFrame\",\n                \"type\": \"object\",\n                \"description\": \"Stack entry for console errors and assertions.\",\n                \"properties\": [\n                    { \"name\": \"functionName\", \"type\": \"string\", \"description\": \"JavaScript function name.\" },\n                    { \"name\": \"scriptId\", \"type\": \"string\", \"description\": \"JavaScript script id.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"JavaScript script name or url.\" },\n                    { \"name\": \"lineNumber\", \"type\": \"integer\", \"description\": \"JavaScript script line number.\" },\n                    { \"name\": \"columnNumber\", \"type\": \"integer\", \"description\": \"JavaScript script column number.\" }\n                ]\n            },\n            {\n                \"id\": \"StackTrace\",\n                \"type\": \"array\",\n                \"items\": { \"$ref\": \"CallFrame\" },\n                \"description\": \"Call frames for assertions or error messages.\"\n            },\n            {\n                \"id\": \"AsyncStackTrace\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"Call frames of the stack trace.\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"optional\": true, \"description\": \"String label of this stack trace. For async traces this may be a name of the function that initiated the async call.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"AsyncStackTrace\", \"optional\": true, \"description\": \"Next asynchronous stack trace, if any.\" }\n                ],\n                \"description\": \"Asynchronous JavaScript call stack.\",\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables console domain, sends the messages collected so far to the client by means of the <code>messageAdded</code> notification.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables console domain, prevents further console messages from being reported to the client.\"\n            },\n            {\n                \"name\": \"clearMessages\",\n                \"description\": \"Clears console messages collected in the browser.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"messageAdded\",\n                \"parameters\": [\n                    { \"name\": \"message\", \"$ref\": \"ConsoleMessage\", \"description\": \"Console message that has been added.\" }\n                ],\n                \"description\": \"Issued when new console message is added.\"\n            },\n            {\n                \"name\": \"messageRepeatCountUpdated\",\n                \"parameters\": [\n                    { \"name\": \"count\", \"type\": \"integer\", \"description\": \"New repeat count value.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp of most recent message in batch.\", \"hidden\": true }\n                ],\n                \"description\": \"Is not issued. Will be gone in the future versions of the protocol.\",\n                \"deprecated\": true\n            },\n            {\n                \"name\": \"messagesCleared\",\n                \"description\": \"Issued when console is cleared. This happens either upon <code>clearMessages</code> command or after page navigation.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Network\",\n        \"description\": \"Network domain allows tracking network activities of the page. It exposes information about http, file, data and other requests and responses, their headers, bodies, timing, etc.\",\n        \"types\": [\n            {\n                \"id\": \"LoaderId\",\n                \"type\": \"string\",\n                \"description\": \"Unique loader identifier.\"\n            },\n            {\n                \"id\": \"RequestId\",\n                \"type\": \"string\",\n                \"description\": \"Unique request identifier.\"\n            },\n            {\n                \"id\": \"Timestamp\",\n                \"type\": \"number\",\n                \"description\": \"Number of seconds since epoch.\"\n            },\n            {\n                \"id\": \"Headers\",\n                \"type\": \"object\",\n                \"description\": \"Request / response headers as keys / values of JSON object.\"\n            },\n            {\n                \"id\": \"ResourceTiming\",\n                \"type\": \"object\",\n                \"description\": \"Timing information for the request.\",\n                \"properties\": [\n                    { \"name\": \"requestTime\", \"type\": \"number\", \"description\": \"Timing's requestTime is a baseline in seconds, while the other numbers are ticks in milliseconds relatively to this requestTime.\" },\n                    { \"name\": \"proxyStart\", \"type\": \"number\", \"description\": \"Started resolving proxy.\" },\n                    { \"name\": \"proxyEnd\", \"type\": \"number\", \"description\": \"Finished resolving proxy.\" },\n                    { \"name\": \"dnsStart\", \"type\": \"number\", \"description\": \"Started DNS address resolve.\" },\n                    { \"name\": \"dnsEnd\", \"type\": \"number\", \"description\": \"Finished DNS address resolve.\" },\n                    { \"name\": \"connectStart\", \"type\": \"number\", \"description\": \"Started connecting to the remote host.\" },\n                    { \"name\": \"connectEnd\", \"type\": \"number\", \"description\": \"Connected to the remote host.\" },\n                    { \"name\": \"sslStart\", \"type\": \"number\", \"description\": \"Started SSL handshake.\" },\n                    { \"name\": \"sslEnd\", \"type\": \"number\", \"description\": \"Finished SSL handshake.\" },\n                    { \"name\": \"workerStart\", \"type\": \"number\", \"description\": \"Started running ServiceWorker.\", \"hidden\": true },\n                    { \"name\": \"workerReady\", \"type\": \"number\", \"description\": \"Finished Starting ServiceWorker.\", \"hidden\": true },\n                    { \"name\": \"sendStart\", \"type\": \"number\", \"description\": \"Started sending request.\" },\n                    { \"name\": \"sendEnd\", \"type\": \"number\", \"description\": \"Finished sending request.\" },\n                    { \"name\": \"receiveHeadersEnd\", \"type\": \"number\", \"description\": \"Finished receiving response headers.\" }\n                ]\n            },\n            {\n                \"id\": \"Request\",\n                \"type\": \"object\",\n                \"description\": \"HTTP request data.\",\n                \"properties\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Request URL.\" },\n                    { \"name\": \"method\", \"type\": \"string\", \"description\": \"HTTP request method.\" },\n                    { \"name\": \"headers\", \"$ref\": \"Headers\", \"description\": \"HTTP request headers.\" },\n                    { \"name\": \"postData\", \"type\": \"string\", \"optional\": true, \"description\": \"HTTP POST request data.\" }\n                ]\n            },\n            {\n                \"id\": \"Response\",\n                \"type\": \"object\",\n                \"description\": \"HTTP response data.\",\n                \"properties\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Response URL. This URL can be different from CachedResource.url in case of redirect.\" },\n                    { \"name\": \"status\", \"type\": \"number\", \"description\": \"HTTP response status code.\" },\n                    { \"name\": \"statusText\", \"type\": \"string\", \"description\": \"HTTP response status text.\" },\n                    { \"name\": \"headers\", \"$ref\": \"Headers\", \"description\": \"HTTP response headers.\" },\n                    { \"name\": \"headersText\", \"type\": \"string\", \"optional\": true, \"description\": \"HTTP response headers text.\" },\n                    { \"name\": \"mimeType\", \"type\": \"string\", \"description\": \"Resource mimeType as determined by the browser.\" },\n                    { \"name\": \"requestHeaders\", \"$ref\": \"Headers\", \"optional\": true, \"description\": \"Refined HTTP request headers that were actually transmitted over the network.\" },\n                    { \"name\": \"requestHeadersText\", \"type\": \"string\", \"optional\": true, \"description\": \"HTTP request headers text.\" },\n                    { \"name\": \"connectionReused\", \"type\": \"boolean\", \"description\": \"Specifies whether physical connection was actually reused for this request.\" },\n                    { \"name\": \"connectionId\", \"type\": \"number\", \"description\": \"Physical connection id that was actually used for this request.\" },\n                    { \"name\": \"remoteIPAddress\", \"type\": \"string\", \"optional\": true, \"hidden\": true, \"description\": \"Remote IP address.\" },\n                    { \"name\": \"remotePort\", \"type\": \"integer\", \"optional\": true, \"hidden\": true, \"description\": \"Remote port.\"},\n                    { \"name\": \"fromDiskCache\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies that the request was served from the disk cache.\" },\n                    { \"name\": \"fromServiceWorker\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies that the request was served from the ServiceWorker.\" },\n                    { \"name\": \"encodedDataLength\", \"type\": \"number\", \"optional\": false, \"description\": \"Total number of bytes received for this request so far.\" },\n                    { \"name\": \"timing\", \"$ref\": \"ResourceTiming\", \"optional\": true, \"description\": \"Timing information for the given request.\" },\n                    { \"name\": \"protocol\", \"type\": \"string\", \"optional\": true, \"description\": \"Protocol used to fetch this request.\" }\n                ]\n            },\n            {\n                \"id\": \"WebSocketRequest\",\n                \"type\": \"object\",\n                \"description\": \"WebSocket request data.\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"headers\", \"$ref\": \"Headers\", \"description\": \"HTTP request headers.\" }\n                ]\n            },\n            {\n                \"id\": \"WebSocketResponse\",\n                \"type\": \"object\",\n                \"description\": \"WebSocket response data.\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"status\", \"type\": \"number\", \"description\": \"HTTP response status code.\" },\n                    { \"name\": \"statusText\", \"type\": \"string\", \"description\": \"HTTP response status text.\" },\n                    { \"name\": \"headers\", \"$ref\": \"Headers\", \"description\": \"HTTP response headers.\" },\n                    { \"name\": \"headersText\", \"type\": \"string\", \"optional\": true, \"description\": \"HTTP response headers text.\" },\n                    { \"name\": \"requestHeaders\", \"$ref\": \"Headers\", \"optional\": true, \"description\": \"HTTP request headers.\" },\n                    { \"name\": \"requestHeadersText\", \"type\": \"string\", \"optional\": true, \"description\": \"HTTP request headers text.\" }\n                ]\n            },\n            {\n                \"id\": \"WebSocketFrame\",\n                \"type\": \"object\",\n                \"description\": \"WebSocket frame data.\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"opcode\", \"type\": \"number\", \"description\": \"WebSocket frame opcode.\" },\n                    { \"name\": \"mask\", \"type\": \"boolean\", \"description\": \"WebSocke frame mask.\" },\n                    { \"name\": \"payloadData\", \"type\": \"string\", \"description\": \"WebSocke frame payload data.\" }\n                ]\n            },\n            {\n                \"id\": \"CachedResource\",\n                \"type\": \"object\",\n                \"description\": \"Information about the cached resource.\",\n                \"properties\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Resource URL. This is the url of the original network request.\" },\n                    { \"name\": \"type\", \"$ref\": \"Page.ResourceType\", \"description\": \"Type of this resource.\" },\n                    { \"name\": \"response\", \"$ref\": \"Response\", \"optional\": true, \"description\": \"Cached response data.\" },\n                    { \"name\": \"bodySize\", \"type\": \"number\", \"description\": \"Cached response body size.\" }\n                ]\n            },\n            {\n                \"id\": \"Initiator\",\n                \"type\": \"object\",\n                \"description\": \"Information about the request initiator.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"parser\", \"script\", \"other\"], \"description\": \"Type of this initiator.\" },\n                    { \"name\": \"stackTrace\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"description\": \"Initiator JavaScript stack trace, set for Script only.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"optional\": true, \"description\": \"Initiator URL, set for Parser type only.\" },\n                    { \"name\": \"lineNumber\", \"type\": \"number\", \"optional\": true, \"description\": \"Initiator line number, set for Parser type only.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"Console.AsyncStackTrace\", \"optional\": true, \"description\": \"Initiator asynchronous JavaScript stack trace, if available.\", \"hidden\": true }\n                ]\n            },\n            {\n                \"id\": \"Cookie\",\n                \"type\": \"object\",\n                \"description\": \"Cookie object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Cookie name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Cookie value.\" },\n                    { \"name\": \"domain\", \"type\": \"string\", \"description\": \"Cookie domain.\" },\n                    { \"name\": \"path\", \"type\": \"string\", \"description\": \"Cookie path.\" },\n                    { \"name\": \"expires\", \"type\": \"number\", \"description\": \"Cookie expires.\" },\n                    { \"name\": \"size\", \"type\": \"integer\", \"description\": \"Cookie size.\" },\n                    { \"name\": \"httpOnly\", \"type\": \"boolean\", \"description\": \"True if cookie is http-only.\" },\n                    { \"name\": \"secure\", \"type\": \"boolean\", \"description\": \"True if cookie is secure.\" },\n                    { \"name\": \"session\", \"type\": \"boolean\", \"description\": \"True in case of session cookie.\" }\n                ],\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables network tracking, network events will now be delivered to the client.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables network tracking, prevents network events from being sent to the client.\"\n            },\n            {\n                \"name\": \"setUserAgentOverride\",\n                \"description\": \"Allows overriding user agent with the given string.\",\n                \"parameters\": [\n                    { \"name\": \"userAgent\", \"type\": \"string\", \"description\": \"User agent to use.\" }\n                ]\n            },\n            {\n                \"name\": \"setExtraHTTPHeaders\",\n                \"description\": \"Specifies whether to always send extra HTTP headers with the requests from this page.\",\n                \"parameters\": [\n                    { \"name\": \"headers\", \"$ref\": \"Headers\", \"description\": \"Map with extra HTTP headers.\" }\n                ]\n            },\n            {\n                \"name\": \"getResponseBody\",\n                \"async\": true,\n                \"description\": \"Returns content served for the given request.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Identifier of the network request to get content for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"body\", \"type\": \"string\", \"description\": \"Response body.\" },\n                    { \"name\": \"base64Encoded\", \"type\": \"boolean\", \"description\": \"True, if content was sent as base64.\" }\n                ]\n            },\n            {\n                \"name\": \"replayXHR\",\n                \"description\": \"This method sends a new XMLHttpRequest which is identical to the original one. The following parameters should be identical: method, url, async, request body, extra headers, withCredentials attribute, user, password.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Identifier of XHR to replay.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setMonitoringXHREnabled\",\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"Monitoring enabled state.\" }\n                ],\n                \"description\": \"Toggles monitoring of XMLHttpRequest. If <code>true</code>, console will receive messages upon each XHR issued.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"canClearBrowserCache\",\n                \"description\": \"Tells whether clearing browser cache is supported.\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if browser cache can be cleared.\" }\n                ]\n            },\n            {\n                \"name\": \"clearBrowserCache\",\n                \"description\": \"Clears browser cache.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"canClearBrowserCookies\",\n                \"description\": \"Tells whether clearing browser cookies is supported.\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if browser cookies can be cleared.\" }\n                ]\n            },\n            {\n                \"name\": \"clearBrowserCookies\",\n                \"description\": \"Clears browser cookies.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"getCookies\",\n                \"returns\": [\n                    { \"name\": \"cookies\", \"type\": \"array\", \"items\": { \"$ref\": \"Cookie\" }, \"description\": \"Array of cookie objects.\" }\n                ],\n                \"description\": \"Returns all browser cookies. Depending on the backend support, will return detailed cookie information in the <code>cookies</code> field.\",\n                \"handlers\": [\"browser\"],\n                \"async\": true,\n                \"hidden\": true\n            },\n            {\n                \"name\": \"deleteCookie\",\n                \"parameters\": [\n                    { \"name\": \"cookieName\", \"type\": \"string\", \"description\": \"Name of the cookie to remove.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL to match cooke domain and path.\" }\n                ],\n                \"description\": \"Deletes browser cookie with given name, domain and path.\",\n                \"handlers\": [\"browser\"],\n                \"async\": true,\n                \"hidden\": true\n            },\n            {\n                \"name\": \"canEmulateNetworkConditions\",\n                \"description\": \"Tells whether emulation of network conditions is supported.\",\n                \"returns\": [\n                  { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if emulation of network conditions is supported.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"emulateNetworkConditions\",\n                \"description\": \"Activates emulation of network conditions.\",\n                \"parameters\": [\n                    { \"name\": \"offline\", \"type\": \"boolean\", \"description\": \"True to emulate internet disconnection.\" },\n                    { \"name\": \"latency\", \"type\": \"number\", \"description\": \"Additional latency (ms).\" },\n                    { \"name\": \"downloadThroughput\", \"type\": \"number\", \"description\": \"Maximal aggregated download throughput.\" },\n                    { \"name\": \"uploadThroughput\", \"type\": \"number\", \"description\": \"Maximal aggregated upload throughput.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"setCacheDisabled\",\n                \"parameters\": [\n                    { \"name\": \"cacheDisabled\", \"type\": \"boolean\", \"description\": \"Cache disabled state.\" }\n                ],\n                \"description\": \"Toggles ignoring cache for each request. If <code>true</code>, cache will not be used.\"\n            },\n            {\n                \"name\": \"setDataSizeLimitsForTest\",\n                \"parameters\": [\n                    { \"name\": \"maxTotalSize\", \"type\": \"integer\", \"description\": \"Maximum total buffer size.\" },\n                    { \"name\": \"maxResourceSize\", \"type\": \"integer\", \"description\": \"Maximum per-resource size.\" }\n                ],\n                \"description\": \"For testing.\",\n                \"hidden\": true\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"requestWillBeSent\",\n                \"description\": \"Fired when page is about to send HTTP request.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Frame identifier.\", \"hidden\": true },\n                    { \"name\": \"loaderId\", \"$ref\": \"LoaderId\", \"description\": \"Loader identifier.\" },\n                    { \"name\": \"documentURL\", \"type\": \"string\", \"description\": \"URL of the document this request is loaded for.\" },\n                    { \"name\": \"request\", \"$ref\": \"Request\", \"description\": \"Request data.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"wallTime\", \"$ref\": \"Timestamp\", \"hidden\": true, \"description\": \"UTC Timestamp.\" },\n                    { \"name\": \"initiator\", \"$ref\": \"Initiator\", \"description\": \"Request initiator.\" },\n                    { \"name\": \"redirectResponse\", \"optional\": true, \"$ref\": \"Response\", \"description\": \"Redirect response data.\" },\n                    { \"name\": \"type\", \"$ref\": \"Page.ResourceType\", \"optional\": true, \"hidden\": true, \"description\": \"Type of this resource.\" }\n                ]\n            },\n            {\n                \"name\": \"requestServedFromCache\",\n                \"description\": \"Fired if request ended up loading from cache.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" }\n                ]\n            },\n            {\n                \"name\": \"responseReceived\",\n                \"description\": \"Fired when HTTP response is available.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Frame identifier.\", \"hidden\": true },\n                    { \"name\": \"loaderId\", \"$ref\": \"LoaderId\", \"description\": \"Loader identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"type\", \"$ref\": \"Page.ResourceType\", \"description\": \"Resource type.\" },\n                    { \"name\": \"response\", \"$ref\": \"Response\", \"description\": \"Response data.\" }\n                ]\n            },\n            {\n                \"name\": \"dataReceived\",\n                \"description\": \"Fired when data chunk was received over the network.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"dataLength\", \"type\": \"integer\", \"description\": \"Data chunk length.\" },\n                    { \"name\": \"encodedDataLength\", \"type\": \"integer\", \"description\": \"Actual bytes received (might be less than dataLength for compressed encodings).\" }\n                ]\n            },\n            {\n                \"name\": \"loadingFinished\",\n                \"description\": \"Fired when HTTP request has finished loading.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"encodedDataLength\", \"type\": \"number\", \"description\": \"Total number of bytes received for this request.\" }\n                ]\n            },\n            {\n                \"name\": \"loadingFailed\",\n                \"description\": \"Fired when HTTP request has failed to load.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"type\", \"$ref\": \"Page.ResourceType\", \"description\": \"Resource type.\" },\n                    { \"name\": \"errorText\", \"type\": \"string\", \"description\": \"User friendly error message.\" },\n                    { \"name\": \"canceled\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if loading was canceled.\" }\n                ]\n            },\n            {\n                \"name\": \"webSocketWillSendHandshakeRequest\",\n                \"description\": \"Fired when WebSocket is about to initiate handshake.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"wallTime\", \"$ref\": \"Timestamp\", \"hidden\": true, \"description\": \"UTC Timestamp.\" },\n                    { \"name\": \"request\", \"$ref\": \"WebSocketRequest\", \"description\": \"WebSocket request data.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketHandshakeResponseReceived\",\n                \"description\": \"Fired when WebSocket handshake response becomes available.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"response\", \"$ref\": \"WebSocketResponse\", \"description\": \"WebSocket response data.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketCreated\",\n                \"description\": \"Fired upon WebSocket creation.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"WebSocket request URL.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketClosed\",\n                \"description\": \"Fired when WebSocket is closed.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketFrameReceived\",\n                \"description\": \"Fired when WebSocket frame is received.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"response\", \"$ref\": \"WebSocketFrame\", \"description\": \"WebSocket response data.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketFrameError\",\n                \"description\": \"Fired when WebSocket frame error occurs.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"errorMessage\", \"type\": \"string\", \"description\": \"WebSocket frame error message.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"webSocketFrameSent\",\n                \"description\": \"Fired when WebSocket frame is sent.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"response\", \"$ref\": \"WebSocketFrame\", \"description\": \"WebSocket response data.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"name\": \"eventSourceMessageReceived\",\n                \"description\": \"Fired when EventSource message is received.\",\n                \"parameters\": [\n                    { \"name\": \"requestId\", \"$ref\": \"RequestId\", \"description\": \"Request identifier.\" },\n                    { \"name\": \"timestamp\", \"$ref\": \"Timestamp\", \"description\": \"Timestamp.\" },\n                    { \"name\": \"eventName\", \"type\": \"string\", \"description\": \"Message type.\" },\n                    { \"name\": \"eventId\", \"type\": \"string\", \"description\": \"Message identifier.\" },\n                    { \"name\": \"data\", \"type\": \"string\", \"description\": \"Message content.\" }\n                ],\n                \"hidden\": true\n            }\n        ]\n    },\n    {\n        \"domain\": \"Database\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"DatabaseId\",\n                \"type\": \"string\",\n                \"description\": \"Unique identifier of Database object.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"Database\",\n                \"type\": \"object\",\n                \"description\": \"Database object.\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"id\", \"$ref\": \"DatabaseId\", \"description\": \"Database ID.\" },\n                    { \"name\": \"domain\", \"type\": \"string\", \"description\": \"Database domain.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Database name.\" },\n                    { \"name\": \"version\", \"type\": \"string\", \"description\": \"Database version.\" }\n                ]\n            },\n            {\n                \"id\": \"Error\",\n                \"type\": \"object\",\n                \"description\": \"Database error.\",\n                \"properties\": [\n                    { \"name\": \"message\", \"type\": \"string\", \"description\": \"Error message.\" },\n                    { \"name\": \"code\", \"type\": \"integer\", \"description\": \"Error code.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables database tracking, database events will now be delivered to the client.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables database tracking, prevents database events from being sent to the client.\"\n            },\n            {\n                \"name\": \"getDatabaseTableNames\",\n                \"parameters\": [\n                    { \"name\": \"databaseId\", \"$ref\": \"DatabaseId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"tableNames\", \"type\": \"array\", \"items\": { \"type\": \"string\" } }\n                ]\n            },\n            {\n                \"name\": \"executeSQL\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"databaseId\", \"$ref\": \"DatabaseId\" },\n                    { \"name\": \"query\", \"type\": \"string\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"columnNames\", \"type\": \"array\", \"optional\": true, \"items\": { \"type\": \"string\" } },\n                    { \"name\": \"values\", \"type\": \"array\", \"optional\": true, \"items\": { \"type\": \"any\" }},\n                    { \"name\": \"sqlError\", \"$ref\": \"Error\", \"optional\": true }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"addDatabase\",\n                \"parameters\": [\n                    { \"name\": \"database\", \"$ref\": \"Database\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"IndexedDB\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"DatabaseWithObjectStores\",\n                \"type\": \"object\",\n                \"description\": \"Database with an array of object stores.\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Database name.\" },\n                    { \"name\": \"version\", \"type\": \"string\", \"description\": \"Deprecated string database version.\" },\n                    { \"name\": \"intVersion\", \"type\": \"integer\", \"description\": \"Integer database version.\" },\n                    { \"name\": \"objectStores\", \"type\": \"array\", \"items\": { \"$ref\": \"ObjectStore\" }, \"description\": \"Object stores in this database.\" }\n                ]\n            },\n            {\n                \"id\": \"ObjectStore\",\n                \"type\": \"object\",\n                \"description\": \"Object store.\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Object store name.\" },\n                    { \"name\": \"keyPath\", \"$ref\": \"KeyPath\", \"description\": \"Object store key path.\" },\n                    { \"name\": \"autoIncrement\", \"type\": \"boolean\", \"description\": \"If true, object store has auto increment flag set.\" },\n                    { \"name\": \"indexes\", \"type\": \"array\", \"items\": { \"$ref\": \"ObjectStoreIndex\" }, \"description\": \"Indexes in this object store.\" }\n                ]\n            },\n            {\n                \"id\": \"ObjectStoreIndex\",\n                \"type\": \"object\",\n                \"description\": \"Object store index.\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Index name.\" },\n                    { \"name\": \"keyPath\", \"$ref\": \"KeyPath\", \"description\": \"Index key path.\" },\n                    { \"name\": \"unique\", \"type\": \"boolean\", \"description\": \"If true, index is unique.\" },\n                    { \"name\": \"multiEntry\", \"type\": \"boolean\", \"description\": \"If true, index allows multiple entries for a key.\" }\n                ]\n            },\n            {\n                \"id\": \"Key\",\n                \"type\": \"object\",\n                \"description\": \"Key.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"number\", \"string\", \"date\", \"array\"], \"description\": \"Key type.\" },\n                    { \"name\": \"number\", \"type\": \"number\", \"optional\": true, \"description\": \"Number value.\" },\n                    { \"name\": \"string\", \"type\": \"string\", \"optional\": true, \"description\": \"String value.\" },\n                    { \"name\": \"date\", \"type\": \"number\", \"optional\": true, \"description\": \"Date value.\" },\n                    { \"name\": \"array\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"Key\" }, \"description\": \"Array value.\" }\n                ]\n            },\n            {\n                \"id\": \"KeyRange\",\n                \"type\": \"object\",\n                \"description\": \"Key range.\",\n                \"properties\": [\n                    { \"name\": \"lower\", \"$ref\": \"Key\", \"optional\": true, \"description\": \"Lower bound.\" },\n                    { \"name\": \"upper\", \"$ref\": \"Key\", \"optional\": true, \"description\": \"Upper bound.\" },\n                    { \"name\": \"lowerOpen\", \"type\": \"boolean\", \"description\": \"If true lower bound is open.\" },\n                    { \"name\": \"upperOpen\", \"type\": \"boolean\", \"description\": \"If true upper bound is open.\" }\n                ]\n            },\n            {\n                \"id\": \"DataEntry\",\n                \"type\": \"object\",\n                \"description\": \"Data entry.\",\n                \"properties\": [\n                    { \"name\": \"key\", \"type\": \"string\", \"description\": \"JSON-stringified key object.\" },\n                    { \"name\": \"primaryKey\", \"type\": \"string\", \"description\": \"JSON-stringified primary key object.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"JSON-stringified value object.\" }\n                ]\n            },\n            {\n                \"id\": \"KeyPath\",\n                \"type\": \"object\",\n                \"description\": \"Key path.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"null\", \"string\", \"array\"], \"description\": \"Key path type.\" },\n                    { \"name\": \"string\", \"type\": \"string\", \"optional\": true, \"description\": \"String value.\" },\n                    { \"name\": \"array\", \"type\": \"array\", \"optional\": true, \"items\": { \"type\": \"string\" }, \"description\": \"Array value.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables events from backend.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables events from backend.\"\n            },\n            {\n                \"name\": \"requestDatabaseNames\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"databaseNames\", \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"description\": \"Database names for origin.\" }\n                ],\n                \"description\": \"Requests database names for given security origin.\"\n            },\n            {\n                \"name\": \"requestDatabase\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin.\" },\n                    { \"name\": \"databaseName\", \"type\": \"string\", \"description\": \"Database name.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"databaseWithObjectStores\", \"$ref\": \"DatabaseWithObjectStores\", \"description\": \"Database with an array of object stores.\" }\n                ],\n                \"description\": \"Requests database with given name in given frame.\"\n            },\n            {\n                \"name\": \"requestData\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin.\" },\n                    { \"name\": \"databaseName\", \"type\": \"string\", \"description\": \"Database name.\" },\n                    { \"name\": \"objectStoreName\", \"type\": \"string\", \"description\": \"Object store name.\" },\n                    { \"name\": \"indexName\", \"type\": \"string\", \"description\": \"Index name, empty string for object store data requests.\" },\n                    { \"name\": \"skipCount\", \"type\": \"integer\", \"description\": \"Number of records to skip.\" },\n                    { \"name\": \"pageSize\", \"type\": \"integer\", \"description\": \"Number of records to fetch.\" },\n                    { \"name\": \"keyRange\", \"$ref\": \"KeyRange\", \"optional\": true, \"description\": \"Key range.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"objectStoreDataEntries\", \"type\": \"array\", \"items\": { \"$ref\": \"DataEntry\" }, \"description\": \"Array of object store data entries.\" },\n                    { \"name\": \"hasMore\", \"type\": \"boolean\", \"description\": \"If true, there are more entries to fetch in the given range.\" }\n                ],\n                \"description\": \"Requests data from object store or index.\"\n            },\n            {\n                \"name\": \"clearObjectStore\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin.\" },\n                    { \"name\": \"databaseName\", \"type\": \"string\", \"description\": \"Database name.\" },\n                    { \"name\": \"objectStoreName\", \"type\": \"string\", \"description\": \"Object store name.\" }\n                ],\n                \"returns\": [\n                ],\n                \"description\": \"Clears all entries from an object store.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"CacheStorage\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"CacheId\",\n                \"type\": \"string\",\n                \"description\": \"Unique identifier of the Cache object.\"\n            },\n            {\n                \"id\": \"DataEntry\",\n                \"type\": \"object\",\n                \"description\": \"Data entry.\",\n                \"properties\": [\n                    { \"name\": \"request\", \"type\": \"string\", \"description\": \"Request url spec.\" },\n                    { \"name\": \"response\", \"type\": \"string\", \"description\": \"Response stataus text.\" }\n                ]\n            },\n            {\n                \"id\": \"Cache\",\n                \"type\": \"object\",\n                \"description\": \"Cache identifier.\",\n                \"properties\": [\n                    { \"name\": \"cacheId\", \"$ref\": \"CacheId\", \"description\": \"An opaque unique id of the cache.\" },\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin of the cache.\" },\n                    { \"name\": \"cacheName\", \"type\": \"string\", \"description\": \"The name of the cache.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"requestCacheNames\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"caches\", \"type\": \"array\", \"items\": { \"$ref\": \"Cache\" }, \"description\": \"Caches for the security origin.\" }\n                ],\n                \"description\": \"Requests cache names.\"\n            },\n            {\n                \"name\": \"requestEntries\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"cacheId\", \"$ref\": \"CacheId\", \"description\": \"ID of cache to get entries from.\" },\n                    { \"name\": \"skipCount\", \"type\": \"integer\", \"description\": \"Number of records to skip.\" },\n                    { \"name\": \"pageSize\", \"type\": \"integer\", \"description\": \"Number of records to fetch.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"cacheDataEntries\", \"type\": \"array\", \"items\": { \"$ref\": \"DataEntry\" }, \"description\": \"Array of object store data entries.\" },\n                    { \"name\": \"hasMore\", \"type\": \"boolean\", \"description\": \"If true, there are more entries to fetch in the given range.\" }\n                ],\n                \"description\": \"Requests data from cache.\"\n            },\n            {\n                \"name\": \"deleteCache\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"cacheId\", \"$ref\": \"CacheId\", \"description\": \"Id of cache for deletion.\" }\n                ],\n                \"description\": \"Deletes a cache.\"\n            },\n            {\n                \"name\": \"deleteEntry\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"cacheId\", \"$ref\": \"CacheId\", \"description\": \"Id of cache where the entry will be deleted.\" },\n                    { \"name\": \"request\", \"type\": \"string\", \"description\": \"URL spec of the request.\" }\n                ],\n                \"description\": \"Deletes a cache entry.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"DOMStorage\",\n        \"hidden\": true,\n        \"description\": \"Query and modify DOM storage.\",\n        \"types\": [\n            {\n                \"id\": \"StorageId\",\n                \"type\": \"object\",\n                \"description\": \"DOM Storage identifier.\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"securityOrigin\", \"type\": \"string\", \"description\": \"Security origin for the storage.\" },\n                    { \"name\": \"isLocalStorage\", \"type\": \"boolean\", \"description\": \"Whether the storage is local storage (not session storage).\" }\n                ]\n            },\n            {\n                \"id\": \"Item\",\n                \"type\": \"array\",\n                \"description\": \"DOM Storage item.\",\n                \"hidden\": true,\n                \"items\": { \"type\": \"string\" }\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables storage tracking, storage events will now be delivered to the client.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables storage tracking, prevents storage events from being sent to the client.\"\n            },\n            {\n                \"name\": \"getDOMStorageItems\",\n                \"parameters\": [\n                    { \"name\": \"storageId\", \"$ref\": \"StorageId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"entries\", \"type\": \"array\", \"items\": { \"$ref\": \"Item\" } }\n                ]\n            },\n            {\n                \"name\": \"setDOMStorageItem\",\n                \"parameters\": [\n                    { \"name\": \"storageId\", \"$ref\": \"StorageId\" },\n                    { \"name\": \"key\", \"type\": \"string\" },\n                    { \"name\": \"value\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"removeDOMStorageItem\",\n                \"parameters\": [\n                    { \"name\": \"storageId\", \"$ref\": \"StorageId\" },\n                    { \"name\": \"key\", \"type\": \"string\" }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"domStorageItemsCleared\",\n                \"parameters\": [\n                    { \"name\": \"storageId\",  \"$ref\": \"StorageId\" }\n                ]\n            },\n            {\n                \"name\": \"domStorageItemRemoved\",\n                \"parameters\": [\n                    { \"name\": \"storageId\",  \"$ref\": \"StorageId\" },\n                    { \"name\": \"key\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"domStorageItemAdded\",\n                \"parameters\": [\n                    { \"name\": \"storageId\",  \"$ref\": \"StorageId\" },\n                    { \"name\": \"key\", \"type\": \"string\" },\n                    { \"name\": \"newValue\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"domStorageItemUpdated\",\n                \"parameters\": [\n                    { \"name\": \"storageId\",  \"$ref\": \"StorageId\" },\n                    { \"name\": \"key\", \"type\": \"string\" },\n                    { \"name\": \"oldValue\", \"type\": \"string\" },\n                    { \"name\": \"newValue\", \"type\": \"string\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"ApplicationCache\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"ApplicationCacheResource\",\n                \"type\": \"object\",\n                \"description\": \"Detailed application cache resource information.\",\n                \"properties\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Resource url.\" },\n                    { \"name\": \"size\", \"type\": \"integer\", \"description\": \"Resource size.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"description\": \"Resource type.\" }\n                ]\n            },\n            {\n                \"id\": \"ApplicationCache\",\n                \"type\": \"object\",\n                \"description\": \"Detailed application cache information.\",\n                \"properties\": [\n                    { \"name\": \"manifestURL\", \"type\": \"string\", \"description\": \"Manifest URL.\" },\n                    { \"name\": \"size\", \"type\": \"number\", \"description\": \"Application cache size.\" },\n                    { \"name\": \"creationTime\", \"type\": \"number\", \"description\": \"Application cache creation time.\" },\n                    { \"name\": \"updateTime\", \"type\": \"number\", \"description\": \"Application cache update time.\" },\n                    { \"name\": \"resources\", \"type\": \"array\", \"items\": { \"$ref\": \"ApplicationCacheResource\" }, \"description\": \"Application cache resources.\" }\n                ]\n            },\n            {\n                \"id\": \"FrameWithManifest\",\n                \"type\": \"object\",\n                \"description\": \"Frame identifier - manifest URL pair.\",\n                \"properties\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Frame identifier.\" },\n                    { \"name\": \"manifestURL\", \"type\": \"string\", \"description\": \"Manifest URL.\" },\n                    { \"name\": \"status\", \"type\": \"integer\", \"description\": \"Application cache status.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"getFramesWithManifests\",\n                \"returns\": [\n                    { \"name\": \"frameIds\", \"type\": \"array\", \"items\": { \"$ref\": \"FrameWithManifest\" }, \"description\": \"Array of frame identifiers with manifest urls for each frame containing a document associated with some application cache.\" }\n                ],\n                \"description\": \"Returns array of frame identifiers with manifest urls for each frame containing a document associated with some application cache.\"\n            },\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables application cache domain notifications.\"\n            },\n            {\n                \"name\": \"getManifestForFrame\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Identifier of the frame containing document whose manifest is retrieved.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"manifestURL\", \"type\": \"string\", \"description\": \"Manifest URL for document in the given frame.\" }\n                ],\n                \"description\": \"Returns manifest URL for document in the given frame.\"\n            },\n            {\n                \"name\": \"getApplicationCacheForFrame\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Identifier of the frame containing document whose application cache is retrieved.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"applicationCache\", \"$ref\": \"ApplicationCache\", \"description\": \"Relevant application cache data for the document in given frame.\" }\n                ],\n                \"description\": \"Returns relevant application cache data for the document in given frame.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"applicationCacheStatusUpdated\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Identifier of the frame containing document whose application cache updated status.\" },\n                    { \"name\": \"manifestURL\", \"type\": \"string\", \"description\": \"Manifest URL.\" },\n                    { \"name\": \"status\", \"type\": \"integer\", \"description\": \"Updated application cache status.\" }\n                ]\n            },\n            {\n                \"name\": \"networkStateUpdated\",\n                \"parameters\": [\n                    { \"name\": \"isNowOnline\", \"type\": \"boolean\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"FileSystem\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"Entry\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"filesystem: URL for the entry.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"The name of the file or directory.\" },\n                    { \"name\": \"isDirectory\", \"type\": \"boolean\", \"description\": \"True if the entry is a directory.\" },\n                    { \"name\": \"mimeType\", \"type\": \"string\", \"optional\": true, \"description\": \"MIME type of the entry, available for a file only.\" },\n                    { \"name\": \"resourceType\", \"$ref\": \"Page.ResourceType\", \"optional\": true, \"description\": \"ResourceType of the entry, available for a file only.\" },\n                    { \"name\": \"isTextFile\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the entry is a text file.\" }\n                ],\n                \"description\": \"Represents a browser side file or directory.\"\n            },\n            {\n                \"id\": \"Metadata\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"modificationTime\", \"type\": \"number\", \"description\": \"Modification time.\" },\n                    { \"name\": \"size\", \"type\": \"number\", \"description\": \"File size. This field is always zero for directories.\" }\n                ],\n                \"description\": \"Represents metadata of a file or entry.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables events from backend.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables events from backend.\"\n            },\n            {\n                \"name\": \"requestFileSystemRoot\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"origin\", \"type\": \"string\", \"description\": \"Security origin of requesting FileSystem. One of frames in current page needs to have this security origin.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"temporary\", \"persistent\"], \"description\": \"FileSystem type of requesting FileSystem.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"errorCode\", \"type\": \"integer\", \"description\": \"0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.\" },\n                    { \"name\": \"root\", \"$ref\": \"Entry\", \"optional\": true, \"description\": \"Contains root of the requested FileSystem if the command completed successfully.\" }\n                ],\n                \"description\": \"Returns root directory of the FileSystem, if exists.\"\n            },\n            {\n                \"name\": \"requestDirectoryContent\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the directory that the frontend is requesting to read from.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"errorCode\", \"type\": \"integer\", \"description\": \"0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.\" },\n                    { \"name\": \"entries\", \"type\": \"array\", \"items\": { \"$ref\": \"Entry\" }, \"optional\": true, \"description\": \"Contains all entries on directory if the command completed successfully.\" }\n                ],\n                \"description\": \"Returns content of the directory.\"\n            },\n            {\n                \"name\": \"requestMetadata\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the entry that the frontend is requesting to get metadata from.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"errorCode\", \"type\": \"integer\", \"description\": \"0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.\" },\n                    { \"name\": \"metadata\", \"$ref\": \"Metadata\", \"optional\": true, \"description\": \"Contains metadata of the entry if the command completed successfully.\" }\n                ],\n                \"description\": \"Returns metadata of the entry.\"\n            },\n            {\n                \"name\": \"requestFileContent\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the file that the frontend is requesting to read from.\" },\n                    { \"name\": \"readAsText\", \"type\": \"boolean\", \"description\": \"True if the content should be read as text, otherwise the result will be returned as base64 encoded text.\" },\n                    { \"name\": \"start\", \"type\": \"integer\", \"optional\": true, \"description\": \"Specifies the start of range to read.\" },\n                    { \"name\": \"end\", \"type\": \"integer\", \"optional\": true, \"description\": \"Specifies the end of range to read exclusively.\" },\n                    { \"name\": \"charset\", \"type\": \"string\", \"optional\": true, \"description\": \"Overrides charset of the content when content is served as text.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"errorCode\", \"type\": \"integer\", \"description\": \"0, if no error. Otherwise, errorCode is set to FileError::ErrorCode value.\" },\n                    { \"name\": \"content\", \"type\": \"string\", \"optional\": true, \"description\": \"Content of the file.\" },\n                    { \"name\": \"charset\", \"type\": \"string\", \"optional\": true, \"description\": \"Charset of the content if it is served as text.\" }\n                ],\n                \"description\": \"Returns content of the file. Result should be sliced into [start, end).\"\n            },\n            {\n                \"name\": \"deleteEntry\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL of the entry to delete.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"errorCode\", \"type\": \"integer\", \"description\": \"0, if no error. Otherwise errorCode is set to FileError::ErrorCode value.\" }\n                ],\n                \"description\": \"Deletes specified entry. If the entry is a directory, the agent deletes children recursively.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"DOM\",\n        \"description\": \"This domain exposes DOM read/write operations. Each DOM Node is represented with its mirror object that has an <code>id</code>. This <code>id</code> can be used to get additional information on the Node, resolve it into the JavaScript object wrapper, etc. It is important that client receives DOM events only for the nodes that are known to the client. Backend keeps track of the nodes that were sent to the client and never sends the same node twice. It is client's responsibility to collect information about the nodes that were sent to the client.<p>Note that <code>iframe</code> owner elements will return corresponding document elements as their child nodes.</p>\",\n        \"types\": [\n            {\n                \"id\": \"NodeId\",\n                \"type\": \"integer\",\n                \"description\": \"Unique DOM node identifier.\"\n            },\n            {\n                \"id\": \"BackendNodeId\",\n                \"type\": \"integer\",\n                \"description\": \"Unique DOM node identifier used to reference a node that may not have been pushed to the front-end.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"BackendNode\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"nodeType\", \"type\": \"integer\", \"description\": \"<code>Node</code>'s nodeType.\" },\n                    { \"name\": \"nodeName\", \"type\": \"string\", \"description\": \"<code>Node</code>'s nodeName.\" },\n                    { \"name\": \"backendNodeId\", \"$ref\": \"BackendNodeId\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Backend node with a friendly name.\"\n            },\n            {\n                \"id\": \"PseudoType\",\n                \"type\": \"string\",\n                \"enum\": [\n                    \"first-line\",\n                    \"first-letter\",\n                    \"before\",\n                    \"after\",\n                    \"backdrop\",\n                    \"selection\",\n                    \"first-line-inherited\",\n                    \"scrollbar\",\n                    \"scrollbar-thumb\",\n                    \"scrollbar-button\",\n                    \"scrollbar-track\",\n                    \"scrollbar-track-piece\",\n                    \"scrollbar-corner\",\n                    \"resizer\",\n                    \"input-list-button\"\n                ],\n                \"description\": \"Pseudo element type.\"\n            },\n            {\n                \"id\": \"ShadowRootType\",\n                \"type\": \"string\",\n                \"enum\": [\"user-agent\", \"author\"],\n                \"description\": \"Shadow root type.\"\n            },\n            {\n                \"id\": \"Node\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Node identifier that is passed into the rest of the DOM messages as the <code>nodeId</code>. Backend will only push node with given <code>id</code> once. It is aware of all requested nodes and will only fire DOM events for nodes known to the client.\" },\n                    { \"name\": \"nodeType\", \"type\": \"integer\", \"description\": \"<code>Node</code>'s nodeType.\" },\n                    { \"name\": \"nodeName\", \"type\": \"string\", \"description\": \"<code>Node</code>'s nodeName.\" },\n                    { \"name\": \"localName\", \"type\": \"string\", \"description\": \"<code>Node</code>'s localName.\" },\n                    { \"name\": \"nodeValue\", \"type\": \"string\", \"description\": \"<code>Node</code>'s nodeValue.\" },\n                    { \"name\": \"childNodeCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"Child count for <code>Container</code> nodes.\" },\n                    { \"name\": \"children\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"Node\" }, \"description\": \"Child nodes of this node when requested with children.\" },\n                    { \"name\": \"attributes\", \"type\": \"array\", \"optional\": true, \"items\": { \"type\": \"string\" }, \"description\": \"Attributes of the <code>Element</code> node in the form of flat array <code>[name1, value1, name2, value2]</code>.\" },\n                    { \"name\": \"documentURL\", \"type\": \"string\", \"optional\": true, \"description\": \"Document URL that <code>Document</code> or <code>FrameOwner</code> node points to.\" },\n                    { \"name\": \"baseURL\", \"type\": \"string\", \"optional\": true, \"description\": \"Base URL that <code>Document</code> or <code>FrameOwner</code> node uses for URL completion.\", \"hidden\": true },\n                    { \"name\": \"publicId\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>DocumentType</code>'s publicId.\" },\n                    { \"name\": \"systemId\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>DocumentType</code>'s systemId.\" },\n                    { \"name\": \"internalSubset\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>DocumentType</code>'s internalSubset.\" },\n                    { \"name\": \"xmlVersion\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>Document</code>'s XML version in case of XML documents.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>Attr</code>'s name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"optional\": true, \"description\": \"<code>Attr</code>'s value.\" },\n                    { \"name\": \"pseudoType\", \"$ref\": \"PseudoType\", \"optional\": true, \"description\": \"Pseudo element type for this node.\" },\n                    { \"name\": \"shadowRootType\", \"$ref\": \"ShadowRootType\", \"optional\": true, \"description\": \"Shadow root type.\" },\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"optional\": true, \"description\": \"Frame ID for frame owner elements.\", \"hidden\": true },\n                    { \"name\": \"contentDocument\", \"$ref\": \"Node\", \"optional\": true, \"description\": \"Content document for frame owner elements.\" },\n                    { \"name\": \"shadowRoots\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"Node\" }, \"description\": \"Shadow root list for given element host.\", \"hidden\": true },\n                    { \"name\": \"templateContent\", \"$ref\": \"Node\", \"optional\": true, \"description\": \"Content document fragment for template elements.\", \"hidden\": true },\n                    { \"name\": \"pseudoElements\", \"type\": \"array\", \"items\": { \"$ref\": \"Node\" }, \"optional\": true, \"description\": \"Pseudo elements associated with this node.\", \"hidden\": true },\n                    { \"name\": \"importedDocument\", \"$ref\": \"Node\", \"optional\": true, \"description\": \"Import document for the HTMLImport links.\" },\n                    { \"name\": \"distributedNodes\", \"type\": \"array\", \"items\": { \"$ref\": \"BackendNode\" }, \"optional\": true, \"description\": \"Distributed nodes for given insertion point.\", \"hidden\": true }\n                ],\n                \"description\": \"DOM interaction is implemented in terms of mirror objects that represent the actual DOM nodes. DOMNode is a base node mirror type.\"\n            },\n            {\n                \"id\": \"RGBA\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"r\", \"type\": \"integer\", \"description\": \"The red component, in the [0-255] range.\" },\n                    { \"name\": \"g\", \"type\": \"integer\", \"description\": \"The green component, in the [0-255] range.\" },\n                    { \"name\": \"b\", \"type\": \"integer\", \"description\": \"The blue component, in the [0-255] range.\" },\n                    { \"name\": \"a\", \"type\": \"number\", \"optional\": true, \"description\": \"The alpha component, in the [0-1] range (default: 1).\" }\n                ],\n                \"description\": \"A structure holding an RGBA color.\"\n            },\n            {\n                \"id\": \"Quad\",\n                \"type\": \"array\",\n                \"items\": { \"type\": \"number\" },\n                \"minItems\": 8,\n                \"maxItems\": 8,\n                \"description\": \"An array of quad vertices, x immediately followed by y for each point, points clock-wise.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"BoxModel\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"content\", \"$ref\": \"Quad\", \"description\": \"Content box\" },\n                    { \"name\": \"padding\", \"$ref\": \"Quad\", \"description\": \"Padding box\" },\n                    { \"name\": \"border\", \"$ref\": \"Quad\", \"description\": \"Border box\" },\n                    { \"name\": \"margin\", \"$ref\": \"Quad\", \"description\": \"Margin box\" },\n                    { \"name\": \"width\", \"type\": \"integer\", \"description\": \"Node width\" },\n                    { \"name\": \"height\", \"type\": \"integer\", \"description\": \"Node height\" },\n                    { \"name\": \"shapeOutside\", \"$ref\": \"ShapeOutsideInfo\", \"optional\": true, \"description\": \"Shape outside coordinates\" }\n                ],\n                \"description\": \"Box model.\"\n            },\n            {\n                \"id\": \"ShapeOutsideInfo\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"bounds\", \"$ref\": \"Quad\", \"description\": \"Shape bounds\" },\n                    { \"name\": \"shape\", \"type\": \"array\", \"items\": { \"type\": \"any\"}, \"description\": \"Shape coordinate details\" },\n                    { \"name\": \"marginShape\", \"type\": \"array\", \"items\": { \"type\": \"any\"}, \"description\": \"Margin shape bounds\" }\n                ],\n                \"description\": \"CSS Shape Outside details.\"\n            },\n            {\n                \"id\": \"Rect\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"x\", \"type\": \"number\", \"description\": \"X coordinate\" },\n                    { \"name\": \"y\", \"type\": \"number\", \"description\": \"Y coordinate\" },\n                    { \"name\": \"width\", \"type\": \"number\", \"description\": \"Rectangle width\" },\n                    { \"name\": \"height\", \"type\": \"number\", \"description\": \"Rectangle height\" }\n                ],\n                \"description\": \"Rectangle.\"\n            },\n            {\n                \"id\": \"HighlightConfig\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"showInfo\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the node info tooltip should be shown (default: false).\" },\n                    { \"name\": \"showRulers\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the rulers should be shown (default: false).\" },\n                    { \"name\": \"showExtensionLines\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the extension lines from node to the rulers should be shown (default: false).\" },\n                    { \"name\": \"showLayoutEditor\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true},\n                    { \"name\": \"contentColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The content box highlight fill color (default: transparent).\" },\n                    { \"name\": \"paddingColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The padding highlight fill color (default: transparent).\" },\n                    { \"name\": \"borderColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The border highlight fill color (default: transparent).\" },\n                    { \"name\": \"marginColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The margin highlight fill color (default: transparent).\" },\n                    { \"name\": \"eventTargetColor\", \"$ref\": \"RGBA\", \"optional\": true, \"hidden\": true, \"description\": \"The event target element highlight fill color (default: transparent).\" },\n                    { \"name\": \"shapeColor\", \"$ref\": \"RGBA\", \"optional\": true, \"hidden\": true, \"description\": \"The shape outside fill color (default: transparent).\" },\n                    { \"name\": \"shapeMarginColor\", \"$ref\": \"RGBA\", \"optional\": true, \"hidden\": true, \"description\": \"The shape margin fill color (default: transparent).\" }\n                ],\n                \"description\": \"Configuration data for the highlighting of page elements.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables DOM agent for the given page.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables DOM agent for the given page.\"\n            },\n            {\n                \"name\": \"getDocument\",\n                \"returns\": [\n                    { \"name\": \"root\", \"$ref\": \"Node\", \"description\": \"Resulting node.\" }\n                ],\n                \"description\": \"Returns the root DOM node to the caller.\"\n            },\n            {\n                \"name\": \"requestChildNodes\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to get children for.\" },\n                    { \"name\": \"depth\", \"type\": \"integer\", \"optional\": true, \"description\": \"The maximum depth at which children should be retrieved, defaults to 1. Use -1 for the entire subtree or provide an integer larger than 0.\", \"hidden\": true }\n                ],\n                \"description\": \"Requests that children of the node with given id are returned to the caller in form of <code>setChildNodes</code> events where not only immediate children are retrieved, but all children down to the specified depth.\"\n            },\n            {\n                \"name\": \"querySelector\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to query upon.\" },\n                    { \"name\": \"selector\", \"type\": \"string\", \"description\": \"Selector string.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Query selector result.\" }\n                ],\n                \"description\": \"Executes <code>querySelector</code> on a given node.\"\n            },\n            {\n                \"name\": \"querySelectorAll\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to query upon.\" },\n                    { \"name\": \"selector\", \"type\": \"string\", \"description\": \"Selector string.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeIds\", \"type\": \"array\", \"items\": { \"$ref\": \"NodeId\" }, \"description\": \"Query selector result.\" }\n                ],\n                \"description\": \"Executes <code>querySelectorAll</code> on a given node.\"\n            },\n            {\n                \"name\": \"setNodeName\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to set name for.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"New node's name.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"New node's id.\" }\n                ],\n                \"description\": \"Sets node name for a node with given id.\"\n            },\n            {\n                \"name\": \"setNodeValue\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to set value for.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"New node's value.\" }\n                ],\n                \"description\": \"Sets node value for a node with given id.\"\n            },\n            {\n                \"name\": \"removeNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to remove.\" }\n                ],\n                \"description\": \"Removes node with given id.\"\n            },\n            {\n                \"name\": \"setAttributeValue\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the element to set attribute for.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Attribute name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Attribute value.\" }\n                ],\n                \"description\": \"Sets attribute for an element with given id.\"\n            },\n            {\n                \"name\": \"setAttributesAsText\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the element to set attributes for.\" },\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"Text with a number of attributes. Will parse this text using HTML parser.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"optional\": true, \"description\": \"Attribute name to replace with new attributes derived from text in case text parsed successfully.\" }\n                ],\n                \"description\": \"Sets attributes on element with given id. This method is useful when user edits some existing attribute value and types in several attribute name/value pairs.\"\n            },\n            {\n                \"name\": \"removeAttribute\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the element to remove attribute from.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Name of the attribute to remove.\" }\n                ],\n                \"description\": \"Removes attribute with given name from an element with given id.\"\n            },\n            {\n                \"name\": \"getOuterHTML\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to get markup for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"outerHTML\", \"type\": \"string\", \"description\": \"Outer HTML markup.\" }\n                ],\n                \"description\": \"Returns node's HTML markup.\"\n            },\n            {\n                \"name\": \"setOuterHTML\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to set markup for.\" },\n                    { \"name\": \"outerHTML\", \"type\": \"string\", \"description\": \"Outer HTML markup to set.\" }\n                ],\n                \"description\": \"Sets node HTML markup, returns new node id.\"\n            },\n            {\n                \"name\": \"performSearch\",\n                \"parameters\": [\n                    { \"name\": \"query\", \"type\": \"string\", \"description\": \"Plain text or query selector or XPath search query.\" },\n                    { \"name\": \"includeUserAgentShadowDOM\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True to search in user agent shadow DOM.\", \"hidden\": true }\n                ],\n                \"returns\": [\n                    { \"name\": \"searchId\", \"type\": \"string\", \"description\": \"Unique search session identifier.\" },\n                    { \"name\": \"resultCount\", \"type\": \"integer\", \"description\": \"Number of search results.\" }\n                ],\n                \"description\": \"Searches for a given string in the DOM tree. Use <code>getSearchResults</code> to access search results or <code>cancelSearch</code> to end this search session.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getSearchResults\",\n                \"parameters\": [\n                    { \"name\": \"searchId\", \"type\": \"string\", \"description\": \"Unique search session identifier.\" },\n                    { \"name\": \"fromIndex\", \"type\": \"integer\", \"description\": \"Start index of the search result to be returned.\" },\n                    { \"name\": \"toIndex\", \"type\": \"integer\", \"description\": \"End index of the search result to be returned.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeIds\", \"type\": \"array\", \"items\": { \"$ref\": \"NodeId\" }, \"description\": \"Ids of the search result nodes.\" }\n                ],\n                \"description\": \"Returns search results from given <code>fromIndex</code> to given <code>toIndex</code> from the sarch with the given identifier.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"discardSearchResults\",\n                \"parameters\": [\n                    { \"name\": \"searchId\", \"type\": \"string\", \"description\": \"Unique search session identifier.\" }\n                ],\n                \"description\": \"Discards search results from the session with the given id. <code>getSearchResults</code> should no longer be called for that search.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"requestNode\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"JavaScript object id to convert into node.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Node id for given object.\" }\n                ],\n                \"description\": \"Requests that the node is sent to the caller given the JavaScript node object reference. All nodes that form the path from the node to the root are also sent to the client as a series of <code>setChildNodes</code> notifications.\"\n            },\n            {\n                \"name\": \"setInspectModeEnabled\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"enabled\", \"type\": \"boolean\", \"description\": \"True to enable inspection mode, false to disable it.\" },\n                    { \"name\": \"inspectUAShadowDOM\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True to enable inspection mode for user agent shadow DOM.\" },\n                    { \"name\": \"highlightConfig\", \"$ref\": \"HighlightConfig\", \"optional\": true, \"description\": \"A descriptor for the highlight appearance of hovered-over nodes. May be omitted if <code>enabled == false</code>.\" }\n                ],\n                \"description\": \"Enters the 'inspect' mode. In this mode, elements that user is hovering over are highlighted. Backend then generates 'inspectNodeRequested' event upon element selection.\"\n            },\n            {\n                \"name\": \"highlightRect\",\n                \"parameters\": [\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate\" },\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate\" },\n                    { \"name\": \"width\", \"type\": \"integer\", \"description\": \"Rectangle width\" },\n                    { \"name\": \"height\", \"type\": \"integer\", \"description\": \"Rectangle height\" },\n                    { \"name\": \"color\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The highlight fill color (default: transparent).\" },\n                    { \"name\": \"outlineColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The highlight outline color (default: transparent).\" }\n                ],\n                \"description\": \"Highlights given rectangle. Coordinates are absolute with respect to the main frame viewport.\"\n            },\n            {\n                \"name\": \"highlightQuad\",\n                \"parameters\": [\n                    { \"name\": \"quad\", \"$ref\": \"Quad\", \"description\": \"Quad to highlight\" },\n                    { \"name\": \"color\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The highlight fill color (default: transparent).\" },\n                    { \"name\": \"outlineColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The highlight outline color (default: transparent).\" }\n                ],\n                \"description\": \"Highlights given quad. Coordinates are absolute with respect to the main frame viewport.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"highlightNode\",\n                \"parameters\": [\n                    { \"name\": \"highlightConfig\", \"$ref\": \"HighlightConfig\",  \"description\": \"A descriptor for the highlight appearance.\" },\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"optional\": true, \"description\": \"Identifier of the node to highlight.\" },\n                    { \"name\": \"backendNodeId\", \"$ref\": \"BackendNodeId\", \"optional\": true, \"description\": \"Identifier of the backend node to highlight.\" },\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"optional\": true, \"description\": \"JavaScript object id of the node to be highlighted.\", \"hidden\": true }\n                ],\n                \"description\": \"Highlights DOM node with given id or with the given JavaScript object wrapper. Either nodeId or objectId must be specified.\"\n            },\n            {\n                \"name\": \"hideHighlight\",\n                \"description\": \"Hides DOM node highlight.\"\n            },\n            {\n                \"name\": \"highlightFrame\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Identifier of the frame to highlight.\" },\n                    { \"name\": \"contentColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The content box highlight fill color (default: transparent).\" },\n                    { \"name\": \"contentOutlineColor\", \"$ref\": \"RGBA\", \"optional\": true, \"description\": \"The content box highlight outline color (default: transparent).\" }\n                ],\n                \"description\": \"Highlights owner element of the frame with given id.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"pushNodeByPathToFrontend\",\n                \"parameters\": [\n                    { \"name\": \"path\", \"type\": \"string\", \"description\": \"Path to node in the proprietary format.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node for given path.\" }\n                ],\n                \"description\": \"Requests that the node is sent to the caller given its path. // FIXME, use XPath\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"pushNodesByBackendIdsToFrontend\",\n                \"parameters\": [\n                    { \"name\": \"backendNodeIds\", \"type\": \"array\", \"items\": {\"$ref\": \"BackendNodeId\"}, \"description\": \"The array of backend node ids.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeIds\", \"type\": \"array\", \"items\": {\"$ref\": \"NodeId\"}, \"description\": \"The array of ids of pushed nodes that correspond to the backend ids specified in backendNodeIds.\" }\n                ],\n                \"description\": \"Requests that a batch of nodes is sent to the caller given their backend node ids.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setInspectedNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"DOM node id to be accessible by means of $x command line API.\" }\n                ],\n                \"description\": \"Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions).\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"resolveNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to resolve.\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"Symbolic group name that can be used to release multiple objects.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"object\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"JavaScript object wrapper for given node.\" }\n                ],\n                \"description\": \"Resolves JavaScript node object for given node id.\"\n            },\n            {\n                \"name\": \"getAttributes\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to retrieve attibutes for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"attributes\", \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"description\": \"An interleaved array of node attribute names and values.\" }\n                ],\n                \"description\": \"Returns attributes for the specified node.\"\n            },\n            {\n                \"name\": \"copyTo\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to copy.\" },\n                    { \"name\": \"targetNodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the element to drop the copy into.\" },\n                    { \"name\": \"insertBeforeNodeId\", \"$ref\": \"NodeId\", \"optional\": true, \"description\": \"Drop the copy before this node (if absent, the copy becomes the last child of <code>targetNodeId</code>).\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node clone.\" }\n                ],\n                \"description\": \"Creates a deep copy of the specified node and places it into the target container before the given anchor.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"moveTo\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to move.\" },\n                    { \"name\": \"targetNodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the element to drop the moved node into.\" },\n                    { \"name\": \"insertBeforeNodeId\", \"$ref\": \"NodeId\", \"optional\": true, \"description\": \"Drop node before this one (if absent, the moved node becomes the last child of <code>targetNodeId</code>).\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"New id of the moved node.\" }\n                ],\n                \"description\": \"Moves node into the new container, places it before the given anchor.\"\n            },\n            {\n                \"name\": \"undo\",\n                \"description\": \"Undoes the last performed action.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"redo\",\n                \"description\": \"Re-does the last undone action.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"markUndoableState\",\n                \"description\": \"Marks last undoable state.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"focus\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to focus.\" }\n                ],\n                \"description\": \"Focuses the given element.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setFileInputFiles\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the file input node to set files for.\" },\n                    { \"name\": \"files\", \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"description\": \"Array of file paths to set.\" }\n                ],\n                \"description\": \"Sets files for the given file input element.\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"getBoxModel\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to get box model for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"model\", \"$ref\": \"BoxModel\", \"description\": \"Box model for the node.\" }\n                ],\n                \"description\": \"Returns boxes for the currently selected nodes.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getNodeForLocation\",\n                \"parameters\": [\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate.\" },\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node at given coordinates.\" }\n                ],\n                \"description\": \"Returns node id at given location.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getRelayoutBoundary\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Relayout boundary node id for the given node.\" }\n                ],\n                \"description\": \"Returns the id of the nearest ancestor that is a relayout boundary.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getHighlightObjectForTest\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node to get highlight object for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"highlight\", \"type\": \"object\", \"description\": \"Highlight data for the node.\" }\n                ],\n                \"description\": \"For testing.\",\n                \"hidden\": true\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"documentUpdated\",\n                \"description\": \"Fired when <code>Document</code> has been totally updated. Node ids are no longer valid.\"\n            },\n            {\n                \"name\": \"inspectNodeRequested\",\n                \"parameters\": [\n                    { \"name\": \"backendNodeId\", \"$ref\": \"BackendNodeId\", \"description\": \"Id of the node to inspect.\" }\n                ],\n                \"description\": \"Fired when the node should be inspected. This happens after call to <code>setInspectModeEnabled</code>.\",\n                \"hidden\" : true\n            },\n            {\n                \"name\": \"setChildNodes\",\n                \"parameters\": [\n                    { \"name\": \"parentId\", \"$ref\": \"NodeId\", \"description\": \"Parent node id to populate with children.\" },\n                    { \"name\": \"nodes\", \"type\": \"array\", \"items\": { \"$ref\": \"Node\" }, \"description\": \"Child nodes array.\" }\n                ],\n                \"description\": \"Fired when backend wants to provide client with the missing DOM structure. This happens upon most of the calls requesting node ids.\"\n            },\n            {\n                \"name\": \"attributeModified\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has changed.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Attribute name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Attribute value.\" }\n                ],\n                \"description\": \"Fired when <code>Element</code>'s attribute is modified.\"\n            },\n            {\n                \"name\": \"attributeRemoved\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has changed.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"A ttribute name.\" }\n                ],\n                \"description\": \"Fired when <code>Element</code>'s attribute is removed.\"\n            },\n            {\n                \"name\": \"inlineStyleInvalidated\",\n                \"parameters\": [\n                    { \"name\": \"nodeIds\", \"type\": \"array\", \"items\": { \"$ref\": \"NodeId\" }, \"description\": \"Ids of the nodes for which the inline styles have been invalidated.\" }\n                ],\n                \"description\": \"Fired when <code>Element</code>'s inline style is modified via a CSS property modification.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"characterDataModified\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has changed.\" },\n                    { \"name\": \"characterData\", \"type\": \"string\", \"description\": \"New text value.\" }\n                ],\n                \"description\": \"Mirrors <code>DOMCharacterDataModified</code> event.\"\n            },\n            {\n                \"name\": \"childNodeCountUpdated\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has changed.\" },\n                    { \"name\": \"childNodeCount\", \"type\": \"integer\", \"description\": \"New node count.\" }\n                ],\n                \"description\": \"Fired when <code>Container</code>'s child node count has changed.\"\n            },\n            {\n                \"name\": \"childNodeInserted\",\n                \"parameters\": [\n                    { \"name\": \"parentNodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has changed.\" },\n                    { \"name\": \"previousNodeId\", \"$ref\": \"NodeId\", \"description\": \"If of the previous siblint.\" },\n                    { \"name\": \"node\", \"$ref\": \"Node\", \"description\": \"Inserted node data.\" }\n                ],\n                \"description\": \"Mirrors <code>DOMNodeInserted</code> event.\"\n            },\n            {\n                \"name\": \"childNodeRemoved\",\n                \"parameters\": [\n                    { \"name\": \"parentNodeId\", \"$ref\": \"NodeId\", \"description\": \"Parent id.\" },\n                    { \"name\": \"nodeId\", \"$ref\": \"NodeId\", \"description\": \"Id of the node that has been removed.\" }\n                ],\n                \"description\": \"Mirrors <code>DOMNodeRemoved</code> event.\"\n            },\n            {\n                \"name\": \"shadowRootPushed\",\n                \"parameters\": [\n                    { \"name\": \"hostId\", \"$ref\": \"NodeId\", \"description\": \"Host element id.\" },\n                    { \"name\": \"root\", \"$ref\": \"Node\", \"description\": \"Shadow root.\" }\n                ],\n                \"description\": \"Called when shadow root is pushed into the element.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"shadowRootPopped\",\n                \"parameters\": [\n                    { \"name\": \"hostId\", \"$ref\": \"NodeId\", \"description\": \"Host element id.\" },\n                    { \"name\": \"rootId\", \"$ref\": \"NodeId\", \"description\": \"Shadow root id.\" }\n                ],\n                \"description\": \"Called when shadow root is popped from the element.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"pseudoElementAdded\",\n                \"parameters\": [\n                    { \"name\": \"parentId\", \"$ref\": \"NodeId\", \"description\": \"Pseudo element's parent element id.\" },\n                    { \"name\": \"pseudoElement\", \"$ref\": \"Node\", \"description\": \"The added pseudo element.\" }\n                ],\n                \"description\": \"Called when a pseudo element is added to an element.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"pseudoElementRemoved\",\n                \"parameters\": [\n                    { \"name\": \"parentId\", \"$ref\": \"NodeId\", \"description\": \"Pseudo element's parent element id.\" },\n                    { \"name\": \"pseudoElementId\", \"$ref\": \"NodeId\", \"description\": \"The removed pseudo element id.\" }\n                ],\n                \"description\": \"Called when a pseudo element is removed from an element.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"distributedNodesUpdated\",\n                \"parameters\": [\n                    { \"name\": \"insertionPointId\", \"$ref\": \"NodeId\", \"description\": \"Insertion point where distrubuted nodes were updated.\" },\n                    { \"name\": \"distributedNodes\", \"type\": \"array\", \"items\": { \"$ref\": \"BackendNode\" }, \"description\": \"Distributed nodes for given insertion point.\" }\n                ],\n                \"description\": \"Called when distrubution is changed.\",\n                \"hidden\": true\n            }\n        ]\n    },\n    {\n        \"domain\": \"CSS\",\n        \"hidden\": true,\n        \"description\": \"This domain exposes CSS read/write operations. All CSS objects (stylesheets, rules, and styles) have an associated <code>id</code> used in subsequent operations on the related object. Each object type has a specific <code>id</code> structure, and those are not interchangeable between objects of different kinds. CSS objects can be loaded using the <code>get*ForNode()</code> calls (which accept a DOM node id). A client can also discover all the existing stylesheets with the <code>getAllStyleSheets()</code> method (or keeping track of the <code>styleSheetAdded</code>/<code>styleSheetRemoved</code> events) and subsequently load the required stylesheet contents using the <code>getStyleSheet[Text]()</code> methods.\",\n        \"types\": [\n            {\n                \"id\": \"StyleSheetId\",\n                \"type\": \"string\"\n            },\n            {\n                \"id\": \"StyleSheetOrigin\",\n                \"type\": \"string\",\n                \"enum\": [\"injected\", \"user-agent\", \"inspector\", \"regular\"],\n                \"description\": \"Stylesheet type: \\\"injected\\\" for stylesheets injected via extension, \\\"user-agent\\\" for user-agent stylesheets, \\\"inspector\\\" for stylesheets created by the inspector (i.e. those holding the \\\"via inspector\\\" rules), \\\"regular\\\" for regular stylesheets.\"\n            },\n            {\n                \"id\": \"PseudoIdMatches\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"pseudoId\", \"type\": \"integer\", \"description\": \"Pseudo style identifier (see <code>enum PseudoId</code> in <code>ComputedStyleConstants.h</code>).\"},\n                    { \"name\": \"matches\", \"type\": \"array\", \"items\": { \"$ref\": \"RuleMatch\" }, \"description\": \"Matches of CSS rules applicable to the pseudo style.\"}\n                ],\n                \"description\": \"CSS rule collection for a single pseudo style.\"\n            },\n            {\n                \"id\": \"InheritedStyleEntry\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"inlineStyle\", \"$ref\": \"CSSStyle\", \"optional\": true, \"description\": \"The ancestor node's inline style, if any, in the style inheritance chain.\" },\n                    { \"name\": \"matchedCSSRules\", \"type\": \"array\", \"items\": { \"$ref\": \"RuleMatch\" }, \"description\": \"Matches of CSS rules matching the ancestor node in the style inheritance chain.\" }\n                ],\n                \"description\": \"Inherited CSS rule collection from ancestor node.\"\n            },\n            {\n                \"id\": \"RuleMatch\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"rule\", \"$ref\": \"CSSRule\", \"description\": \"CSS rule in the match.\" },\n                    { \"name\": \"matchingSelectors\", \"type\": \"array\", \"items\": { \"type\": \"integer\" }, \"description\": \"Matching selector indices in the rule's selectorList selectors (0-based).\" }\n                ],\n                \"description\": \"Match data for a CSS rule.\"\n            },\n            {\n                \"id\": \"Selector\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Selector text.\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\", \"optional\": true, \"description\": \"Selector range in the underlying resource (if available).\" }\n                ],\n                \"description\": \"Data for a simple selector (these are delimited by commas in a selector list).\"\n            },\n            {\n                \"id\": \"SelectorList\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"selectors\", \"type\": \"array\", \"items\": { \"$ref\": \"Selector\" }, \"description\": \"Selectors in the list.\" },\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"Rule selector text.\" }\n                ],\n                \"description\": \"Selector list data.\"\n            },\n            {\n                \"id\": \"CSSStyleSheetHeader\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"description\": \"The stylesheet identifier.\"},\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Owner frame identifier.\"},\n                    { \"name\": \"sourceURL\", \"type\": \"string\", \"description\": \"Stylesheet resource URL.\"},\n                    { \"name\": \"sourceMapURL\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of source map associated with the stylesheet (if any).\" },\n                    { \"name\": \"origin\", \"$ref\": \"StyleSheetOrigin\", \"description\": \"Stylesheet origin.\"},\n                    { \"name\": \"title\", \"type\": \"string\", \"description\": \"Stylesheet title.\"},\n                    { \"name\": \"ownerNode\", \"$ref\": \"DOM.BackendNodeId\", \"optional\": true, \"description\": \"The backend id for the owner node of the stylesheet.\" },\n                    { \"name\": \"disabled\", \"type\": \"boolean\", \"description\": \"Denotes whether the stylesheet is disabled.\"},\n                    { \"name\": \"hasSourceURL\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the sourceURL field value comes from the sourceURL comment.\" },\n                    { \"name\": \"isInline\", \"type\": \"boolean\", \"description\": \"Whether this stylesheet is created for STYLE tag by parser. This flag is not set for document.written STYLE tags.\" },\n                    { \"name\": \"startLine\", \"type\": \"number\", \"description\": \"Line offset of the stylesheet within the resource (zero based).\" },\n                    { \"name\": \"startColumn\", \"type\": \"number\", \"description\": \"Column offset of the stylesheet within the resource (zero based).\" }\n                ],\n                \"description\": \"CSS stylesheet metainformation.\"\n            },\n            {\n                \"id\": \"CSSRule\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"optional\": true, \"description\": \"The css style sheet identifier (absent for user agent stylesheet and user-specified stylesheet rules) this rule came from.\" },\n                    { \"name\": \"selectorList\", \"$ref\": \"SelectorList\", \"description\": \"Rule selector data.\" },\n                    { \"name\": \"origin\", \"$ref\": \"StyleSheetOrigin\", \"description\": \"Parent stylesheet's origin.\"},\n                    { \"name\": \"style\", \"$ref\": \"CSSStyle\", \"description\": \"Associated style declaration.\" },\n                    { \"name\": \"media\", \"type\": \"array\", \"items\": { \"$ref\": \"CSSMedia\" }, \"optional\": true, \"description\": \"Media list array (for rules involving media queries). The array enumerates media queries starting with the innermost one, going outwards.\" }\n                ],\n                \"description\": \"CSS rule representation.\"\n            },\n            {\n                \"id\": \"SourceRange\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"startLine\", \"type\": \"integer\", \"description\": \"Start line of range.\" },\n                    { \"name\": \"startColumn\", \"type\": \"integer\", \"description\": \"Start column of range (inclusive).\" },\n                    { \"name\": \"endLine\", \"type\": \"integer\", \"description\": \"End line of range\" },\n                    { \"name\": \"endColumn\", \"type\": \"integer\", \"description\": \"End column of range (exclusive).\" }\n                ],\n                \"description\": \"Text range within a resource. All numbers are zero-based.\"\n            },\n            {\n                \"id\": \"ShorthandEntry\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Shorthand name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Shorthand value.\" },\n                    { \"name\": \"important\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the property has \\\"!important\\\" annotation (implies <code>false</code> if absent).\" }\n                ]\n            },\n            {\n                \"id\": \"CSSComputedStyleProperty\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"Computed style property name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"Computed style property value.\" }\n                ]\n            },\n            {\n                \"id\": \"CSSStyle\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"optional\": true, \"description\": \"The css style sheet identifier (absent for user agent stylesheet and user-specified stylesheet rules) this rule came from.\" },\n                    { \"name\": \"cssProperties\", \"type\": \"array\", \"items\": { \"$ref\": \"CSSProperty\" }, \"description\": \"CSS properties in the style.\" },\n                    { \"name\": \"shorthandEntries\", \"type\": \"array\", \"items\": { \"$ref\": \"ShorthandEntry\" }, \"description\": \"Computed values for all shorthands found in the style.\" },\n                    { \"name\": \"cssText\", \"type\": \"string\", \"optional\": true, \"description\": \"Style declaration text (if available).\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\", \"optional\": true, \"description\": \"Style declaration range in the enclosing stylesheet (if available).\" }\n                ],\n                \"description\": \"CSS style representation.\"\n            },\n            {\n                \"id\": \"CSSProperty\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"The property name.\" },\n                    { \"name\": \"value\", \"type\": \"string\", \"description\": \"The property value.\" },\n                    { \"name\": \"important\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the property has \\\"!important\\\" annotation (implies <code>false</code> if absent).\" },\n                    { \"name\": \"implicit\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the property is implicit (implies <code>false</code> if absent).\" },\n                    { \"name\": \"text\", \"type\": \"string\", \"optional\": true, \"description\": \"The full property text as specified in the style.\" },\n                    { \"name\": \"parsedOk\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the property is understood by the browser (implies <code>true</code> if absent).\" },\n                    { \"name\": \"disabled\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the property is disabled by the user (present for source-based properties only).\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\", \"optional\": true, \"description\": \"The entire property range in the enclosing style declaration (if available).\" }\n                ],\n                \"description\": \"CSS property declaration data.\"\n            },\n            {\n                \"id\": \"CSSMedia\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"Media query text.\" },\n                    { \"name\": \"source\", \"type\": \"string\", \"enum\": [\"mediaRule\", \"importRule\", \"linkedSheet\", \"inlineSheet\"], \"description\": \"Source of the media query: \\\"mediaRule\\\" if specified by a @media rule, \\\"importRule\\\" if specified by an @import rule, \\\"linkedSheet\\\" if specified by a \\\"media\\\" attribute in a linked stylesheet's LINK tag, \\\"inlineSheet\\\" if specified by a \\\"media\\\" attribute in an inline stylesheet's STYLE tag.\" },\n                    { \"name\": \"sourceURL\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of the document containing the media query description.\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\", \"optional\": true, \"description\": \"The associated rule (@media or @import) header range in the enclosing stylesheet (if available).\" },\n                    { \"name\": \"parentStyleSheetId\", \"$ref\": \"StyleSheetId\", \"optional\": true, \"description\": \"Identifier of the stylesheet containing this object (if exists).\" },\n                    { \"name\": \"mediaList\", \"type\": \"array\", \"items\": { \"$ref\": \"MediaQuery\" }, \"optional\": true, \"hidden\": true, \"description\": \"Array of media queries.\" }\n                ],\n                \"description\": \"CSS media rule descriptor.\"\n            },\n            {\n                \"id\": \"MediaQuery\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"expressions\", \"type\": \"array\", \"items\": { \"$ref\": \"MediaQueryExpression\" }, \"description\": \"Array of media query expressions.\" },\n                    { \"name\": \"active\", \"type\": \"boolean\", \"description\": \"Whether the media query condition is satisfied.\" }\n                ],\n                \"description\": \"Media query descriptor.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"MediaQueryExpression\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"value\", \"type\": \"number\", \"description\": \"Media query expression value.\"},\n                    { \"name\": \"unit\", \"type\": \"string\", \"description\": \"Media query expression units.\"},\n                    { \"name\": \"feature\", \"type\": \"string\", \"description\": \"Media query expression feature.\"},\n                    { \"name\": \"valueRange\", \"$ref\": \"SourceRange\", \"optional\": true, \"description\": \"The associated range of the value text in the enclosing stylesheet (if available).\" },\n                    { \"name\": \"computedLength\", \"type\": \"number\", \"optional\": true, \"description\": \"Computed length of media query expression (if applicable).\"}\n                ],\n                \"description\": \"Media query expression descriptor.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"PlatformFontUsage\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"familyName\", \"type\": \"string\", \"description\": \"Font's family name reported by platform.\"},\n                    { \"name\": \"glyphCount\", \"type\": \"number\", \"description\": \"Amount of glyphs that were rendered with this font.\"}\n                ],\n                \"description\": \"Information about amount of glyphs that were rendered with given font.\",\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"async\": true,\n                \"description\": \"Enables the CSS agent for the given page. Clients should not assume that the CSS agent has been enabled until the result of this command is received.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables the CSS agent for the given page.\"\n            },\n            {\n                \"name\": \"getMatchedStylesForNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\" },\n                    { \"name\": \"excludePseudo\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether to exclude pseudo styles (default: false).\" },\n                    { \"name\": \"excludeInherited\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether to exclude inherited styles (default: false).\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"matchedCSSRules\", \"type\": \"array\", \"items\": { \"$ref\": \"RuleMatch\" }, \"optional\": true, \"description\": \"CSS rules matching this node, from all applicable stylesheets.\" },\n                    { \"name\": \"pseudoElements\", \"type\": \"array\", \"items\": { \"$ref\": \"PseudoIdMatches\" }, \"optional\": true, \"description\": \"Pseudo style matches for this node.\" },\n                    { \"name\": \"inherited\", \"type\": \"array\", \"items\": { \"$ref\": \"InheritedStyleEntry\" }, \"optional\": true, \"description\": \"A chain of inherited styles (from the immediate node parent up to the DOM tree root).\" }\n                ],\n                \"description\": \"Returns requested styles for a DOM node identified by <code>nodeId</code>.\"\n            },\n            {\n                \"name\": \"getInlineStylesForNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"inlineStyle\", \"$ref\": \"CSSStyle\", \"optional\": true, \"description\": \"Inline style for the specified DOM node.\" },\n                    { \"name\": \"attributesStyle\", \"$ref\": \"CSSStyle\", \"optional\": true, \"description\": \"Attribute-defined element style (e.g. resulting from \\\"width=20 height=100%\\\").\"}\n                ],\n                \"description\": \"Returns the styles defined inline (explicitly in the \\\"style\\\" attribute and implicitly, using DOM attributes) for a DOM node identified by <code>nodeId</code>.\"\n            },\n            {\n                \"name\": \"getComputedStyleForNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"computedStyle\", \"type\": \"array\", \"items\": { \"$ref\": \"CSSComputedStyleProperty\" }, \"description\": \"Computed style for the specified DOM node.\" }\n                ],\n                \"description\": \"Returns the computed style for a DOM node identified by <code>nodeId</code>.\"\n            },\n            {\n                \"name\": \"getPlatformFontsForNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"fonts\", \"type\": \"array\", \"items\": { \"$ref\": \"PlatformFontUsage\" }, \"description\": \"Usage statistics for every employed platform font.\" }\n                ],\n                \"description\": \"Requests information about platform fonts which we used to render child TextNodes in the given node.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"getStyleSheetText\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"The stylesheet text.\" }\n                ],\n                \"description\": \"Returns the current textual content and the URL for a stylesheet.\"\n            },\n            {\n                \"name\": \"setStyleSheetText\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" },\n                    { \"name\": \"text\", \"type\": \"string\" }\n                ],\n                \"description\": \"Sets the new stylesheet text.\"\n            },\n            {\n                \"name\": \"setRuleSelector\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\" },\n                    { \"name\": \"selector\", \"type\": \"string\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"rule\", \"$ref\": \"CSSRule\", \"description\": \"The resulting rule after the selector modification.\" }\n                ],\n                \"description\": \"Modifies the rule selector.\"\n            },\n            {\n                \"name\": \"setStyleText\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\" },\n                    { \"name\": \"text\", \"type\": \"string\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"style\", \"$ref\": \"CSSStyle\", \"description\": \"The resulting style after the selector modification.\" }\n                ],\n                \"description\": \"Modifies the style text.\"\n            },\n            {\n                \"name\": \"setMediaText\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" },\n                    { \"name\": \"range\", \"$ref\": \"SourceRange\" },\n                    { \"name\": \"text\", \"type\": \"string\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"media\", \"$ref\": \"CSSMedia\", \"description\": \"The resulting CSS media rule after modification.\" }\n                ],\n                \"description\": \"Modifies the rule selector.\"\n            },\n            {\n                \"name\": \"createStyleSheet\",\n                \"parameters\": [\n                    { \"name\": \"frameId\", \"$ref\": \"Page.FrameId\", \"description\": \"Identifier of the frame where \\\"via-inspector\\\" stylesheet should be created.\"}\n                ],\n                \"returns\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"description\": \"Identifier of the created \\\"via-inspector\\\" stylesheet.\" }\n                ],\n                \"description\": \"Creates a new special \\\"via-inspector\\\" stylesheet in the frame with given <code>frameId</code>.\"\n            },\n            {\n                \"name\": \"addRule\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"description\": \"The css style sheet identifier where a new rule should be inserted.\" },\n                    { \"name\": \"ruleText\", \"type\": \"string\", \"description\": \"The text of a new rule.\" },\n                    { \"name\": \"location\", \"$ref\": \"SourceRange\", \"description\": \"Text position of a new rule in the target style sheet.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"rule\", \"$ref\": \"CSSRule\", \"description\": \"The newly created rule.\" }\n                ],\n                \"description\": \"Inserts a new rule with the given <code>ruleText</code> in a stylesheet with given <code>styleSheetId</code>, at the position specified by <code>location</code>.\"\n            },\n            {\n                \"name\": \"forcePseudoState\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\", \"description\": \"The element id for which to force the pseudo state.\" },\n                    { \"name\": \"forcedPseudoClasses\", \"type\": \"array\", \"items\": { \"type\": \"string\", \"enum\": [\"active\", \"focus\", \"hover\", \"visited\"] }, \"description\": \"Element pseudo classes to force when computing the element's style.\" }\n                ],\n                \"description\": \"Ensures that the given node will have specified pseudo-classes whenever its style is computed by the browser.\"\n            },\n            {\n                \"name\": \"getMediaQueries\",\n                \"returns\": [\n                    { \"name\": \"medias\", \"type\": \"array\", \"items\": { \"$ref\": \"CSSMedia\" } }\n                ],\n                \"description\": \"Returns all media queries parsed by the rendering engine.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setEffectivePropertyValueForNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\", \"description\": \"The element id for which to set property.\" },\n                    { \"name\": \"propertyName\", \"type\": \"string\"},\n                    { \"name\": \"value\", \"type\": \"string\"}\n                ],\n                \"description\": \"Find a rule with the given active property for the given node and set the new value for this property\",\n                \"hidden\": true\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"mediaQueryResultChanged\",\n                \"description\": \"Fires whenever a MediaQuery result changes (for example, after a browser window has been resized.) The current implementation considers only viewport-dependent media features.\"\n            },\n            {\n                \"name\": \"styleSheetChanged\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\" }\n                ],\n                \"description\": \"Fired whenever a stylesheet is changed as a result of the client operation.\"\n            },\n            {\n                \"name\": \"styleSheetAdded\",\n                \"parameters\": [\n                    { \"name\": \"header\", \"$ref\": \"CSSStyleSheetHeader\", \"description\": \"Added stylesheet metainfo.\" }\n                ],\n                \"description\": \"Fired whenever an active document stylesheet is added.\"\n            },\n            {\n                \"name\": \"styleSheetRemoved\",\n                \"parameters\": [\n                    { \"name\": \"styleSheetId\", \"$ref\": \"StyleSheetId\", \"description\": \"Identifier of the removed stylesheet.\" }\n                ],\n                \"description\": \"Fired whenever an active document stylesheet is removed.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Timeline\",\n        \"description\": \"Timeline domain is deprecated. Please use Tracing instead.\",\n        \"types\": [\n            {\n                \"id\": \"TimelineEvent\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"description\": \"Event type.\" },\n                    { \"name\": \"data\", \"type\": \"object\", \"description\": \"Event data.\" },\n                    { \"name\": \"startTime\", \"type\": \"number\", \"description\": \"Start time.\" },\n                    { \"name\": \"endTime\", \"type\": \"number\", \"optional\": true, \"description\": \"End time.\" },\n                    { \"name\": \"children\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"TimelineEvent\" }, \"description\": \"Nested records.\" },\n                    { \"name\": \"thread\", \"type\": \"string\", \"optional\": true, \"hidden\": true, \"description\": \"If present, identifies the thread that produced the event.\" },\n                    { \"name\": \"stackTrace\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"hidden\": true, \"description\": \"Stack trace.\" },\n                    { \"name\": \"frameId\", \"type\": \"string\", \"optional\": true, \"hidden\": true, \"description\": \"Unique identifier of the frame within the page that the event relates to.\" }\n                ],\n                \"description\": \"Timeline record contains information about the recorded activity.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Deprecated.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Deprecated.\"\n            },\n            {\n                \"name\": \"start\",\n                \"parameters\": [\n                    { \"name\": \"maxCallStackDepth\", \"optional\": true, \"type\": \"integer\", \"description\": \"Samples JavaScript stack traces up to <code>maxCallStackDepth</code>, defaults to 5.\" },\n                    { \"name\": \"bufferEvents\", \"optional\": true, \"type\": \"boolean\", \"hidden\": true, \"description\": \"Whether instrumentation events should be buffered and returned upon <code>stop</code> call.\" },\n                    { \"name\": \"liveEvents\", \"optional\": true, \"type\": \"string\", \"hidden\": true, \"description\": \"Coma separated event types to issue although bufferEvents is set.\"},\n                    { \"name\": \"includeCounters\", \"optional\": true, \"type\": \"boolean\", \"hidden\": true, \"description\": \"Whether counters data should be included into timeline events.\" },\n                    { \"name\": \"includeGPUEvents\", \"optional\": true, \"type\": \"boolean\", \"hidden\": true, \"description\": \"Whether events from GPU process should be collected.\" }\n                ],\n                \"description\": \"Deprecated.\"\n            },\n            {\n                \"name\": \"stop\",\n                \"description\": \"Deprecated.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"eventRecorded\",\n                \"parameters\": [\n                    { \"name\": \"record\", \"$ref\": \"TimelineEvent\", \"description\": \"Timeline event record data.\" }\n                ],\n                \"description\": \"Deprecated.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Debugger\",\n        \"description\": \"Debugger domain exposes JavaScript debugging capabilities. It allows setting and removing breakpoints, stepping through execution, exploring stack traces, etc.\",\n        \"types\": [\n            {\n                \"id\": \"BreakpointId\",\n                \"type\": \"string\",\n                \"description\": \"Breakpoint identifier.\"\n            },\n            {\n                \"id\": \"ScriptId\",\n                \"type\": \"string\",\n                \"description\": \"Unique script identifier.\"\n            },\n            {\n                \"id\": \"CallFrameId\",\n                \"type\": \"string\",\n                \"description\": \"Call frame identifier.\"\n            },\n            {\n                \"id\": \"Location\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Script identifier as reported in the <code>Debugger.scriptParsed</code>.\" },\n                    { \"name\": \"lineNumber\", \"type\": \"integer\", \"description\": \"Line number in the script (0-based).\" },\n                    { \"name\": \"columnNumber\", \"type\": \"integer\", \"optional\": true, \"description\": \"Column number in the script (0-based).\" }\n                ],\n                \"description\": \"Location in the source code.\"\n            },\n            {\n                \"id\": \"FunctionDetails\",\n                \"hidden\": true,\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"optional\": true, \"description\": \"Location of the function, none for native functions.\" },\n                    { \"name\": \"functionName\", \"type\": \"string\", \"description\": \"Name of the function.\" },\n                    { \"name\": \"isGenerator\", \"type\": \"boolean\", \"description\": \"Whether this is a generator function.\" },\n                    { \"name\": \"scopeChain\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"Scope\" }, \"description\": \"Scope chain for this closure.\" }\n                ],\n                \"description\": \"Information about the function.\"\n            },\n            {\n                \"id\": \"GeneratorObjectDetails\",\n                \"hidden\": true,\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"function\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Generator function.\" },\n                    { \"name\": \"functionName\", \"type\": \"string\", \"description\": \"Name of the generator function.\" },\n                    { \"name\": \"status\", \"type\": \"string\", \"enum\": [\"running\", \"suspended\", \"closed\"], \"description\": \"Current generator object status.\" },\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"optional\": true, \"description\": \"If suspended, location where generator function was suspended (e.g. location of the last 'yield'). Otherwise, location of the generator function.\" }\n                ],\n                \"description\": \"Information about the generator object.\"\n            },\n            {\n                \"id\": \"CollectionEntry\",\n                \"hidden\": true,\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"key\", \"$ref\": \"Runtime.RemoteObject\", \"optional\": true, \"description\": \"Entry key of a map-like collection, otherwise not provided.\" },\n                    { \"name\": \"value\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Entry value.\" }\n                ],\n                \"description\": \"Collection entry.\"\n            },\n            {\n                \"id\": \"CallFrame\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"callFrameId\", \"$ref\": \"CallFrameId\", \"description\": \"Call frame identifier. This identifier is only valid while the virtual machine is paused.\" },\n                    { \"name\": \"functionName\", \"type\": \"string\", \"description\": \"Name of the JavaScript function called on this call frame.\" },\n                    { \"name\": \"functionLocation\", \"$ref\": \"Location\", \"optional\": true, \"hidden\": true, \"description\": \"Location in the source code.\" },\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"description\": \"Location in the source code.\" },\n                    { \"name\": \"scopeChain\", \"type\": \"array\", \"items\": { \"$ref\": \"Scope\" }, \"description\": \"Scope chain for this call frame.\" },\n                    { \"name\": \"this\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"<code>this</code> object for this call frame.\" },\n                    { \"name\": \"returnValue\", \"$ref\": \"Runtime.RemoteObject\", \"optional\": true, \"hidden\": true, \"description\": \"The value being returned, if the function is at return point.\" }\n                ],\n                \"description\": \"JavaScript call frame. Array of call frames form the call stack.\"\n            },\n            {\n                \"id\": \"StackTrace\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"Call frames of the stack trace.\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"optional\": true, \"description\": \"String label of this stack trace. For async traces this may be a name of the function that initiated the async call.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"Async stack trace, if any.\" }\n                ],\n                \"description\": \"JavaScript call stack, including async stack traces.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"Scope\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"global\", \"local\", \"with\", \"closure\", \"catch\", \"block\", \"script\"], \"description\": \"Scope type.\" },\n                    { \"name\": \"object\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Object representing the scope. For <code>global</code> and <code>with</code> scopes it represents the actual object; for the rest of the scopes, it is artificial transient object enumerating scope variables as its properties.\" }\n                ],\n                \"description\": \"Scope description.\"\n            },\n            {\n                \"id\": \"ExceptionDetails\",\n                \"type\": \"object\",\n                \"description\": \"Detailed information on exception (or error) that was thrown during script compilation or execution.\",\n                \"properties\": [\n                    { \"name\": \"text\", \"type\": \"string\", \"description\": \"Exception text.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of the message origin.\" },\n                    { \"name\": \"scriptId\", \"type\": \"string\", \"optional\": true, \"description\": \"Script ID of the message origin.\" },\n                    { \"name\": \"line\", \"type\": \"integer\", \"optional\": true, \"description\": \"Line number in the resource that generated this message.\" },\n                    { \"name\": \"column\", \"type\": \"integer\", \"optional\": true, \"description\": \"Column number in the resource that generated this message.\" },\n                    { \"name\": \"stackTrace\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"description\": \"JavaScript stack trace for assertions and error messages.\" }\n                ]\n            },\n            {\n                \"id\": \"SetScriptSourceError\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"compileError\", \"optional\": true, \"type\": \"object\", \"properties\":\n                        [\n                            { \"name\": \"message\", \"type\": \"string\", \"description\": \"Compiler error message\" },\n                            { \"name\": \"lineNumber\", \"type\": \"integer\", \"description\": \"Compile error line number (1-based)\" },\n                            { \"name\": \"columnNumber\", \"type\": \"integer\", \"description\": \"Compile error column number (1-based)\" }\n                        ]\n                    }\n                ],\n                \"description\": \"Error data for setScriptSource command. compileError is a case type for uncompilable script source error.\",\n                \"hidden\": true\n            },\n            {\n                \"id\": \"PromiseDetails\",\n                \"type\": \"object\",\n                \"description\": \"Information about the promise. All fields but id are optional and if present they reflect the new state of the property on the promise with given id.\",\n                \"properties\": [\n                    { \"name\": \"id\", \"type\": \"integer\", \"description\": \"Unique id of the promise.\" },\n                    { \"name\": \"status\", \"type\": \"string\", \"optional\": true, \"enum\": [\"pending\", \"resolved\", \"rejected\"], \"description\": \"Status of the promise.\" },\n                    { \"name\": \"parentId\", \"type\": \"integer\", \"optional\": true, \"description\": \"Id of the parent promise.\" },\n                    { \"name\": \"callFrame\", \"$ref\": \"Console.CallFrame\", \"optional\": true, \"description\": \"Top call frame on promise creation.\"},\n                    { \"name\": \"creationTime\", \"type\": \"number\", \"optional\": true, \"description\": \"Creation time of the promise.\" },\n                    { \"name\": \"settlementTime\", \"type\": \"number\", \"optional\": true, \"description\": \"Settlement time of the promise.\" },\n                    { \"name\": \"creationStack\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"description\": \"JavaScript stack trace on promise creation.\" },\n                    { \"name\": \"asyncCreationStack\", \"$ref\": \"Console.AsyncStackTrace\", \"optional\": true, \"description\": \"JavaScript asynchronous stack trace on promise creation, if available.\" },\n                    { \"name\": \"settlementStack\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"description\": \"JavaScript stack trace on promise settlement.\" },\n                    { \"name\": \"asyncSettlementStack\", \"$ref\": \"Console.AsyncStackTrace\", \"optional\": true, \"description\": \"JavaScript asynchronous stack trace on promise settlement, if available.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"AsyncOperation\",\n                \"type\": \"object\",\n                \"description\": \"Information about the async operation.\",\n                \"properties\": [\n                    { \"name\": \"id\", \"type\": \"integer\", \"description\": \"Unique id of the async operation.\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"description\": \"String description of the async operation.\" },\n                    { \"name\": \"stackTrace\", \"$ref\": \"Console.StackTrace\", \"optional\": true, \"description\": \"Stack trace where async operation was scheduled.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"Console.AsyncStackTrace\", \"optional\": true, \"description\": \"Asynchronous stack trace where async operation was scheduled, if available.\" }\n                ],\n                \"hidden\": true\n            },\n            {\n                \"id\": \"SearchMatch\",\n                \"type\": \"object\",\n                \"description\": \"Search match for resource.\",\n                \"properties\": [\n                    { \"name\": \"lineNumber\", \"type\": \"number\", \"description\": \"Line number in resource content.\" },\n                    { \"name\": \"lineContent\", \"type\": \"string\", \"description\": \"Line with match content.\" }\n                ],\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables debugger for the given page. Clients should not assume that the debugging has been enabled until the result for this command is received.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables debugger for given page.\"\n            },\n            {\n                \"name\": \"setBreakpointsActive\",\n                \"parameters\": [\n                    { \"name\": \"active\", \"type\": \"boolean\", \"description\": \"New value for breakpoints active state.\" }\n                ],\n                \"description\": \"Activates / deactivates all breakpoints on the page.\"\n            },\n            {\n                \"name\": \"setSkipAllPauses\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"skipped\", \"type\": \"boolean\", \"description\": \"New value for skip pauses state.\" }\n                ],\n                \"description\": \"Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).\"\n            },\n            {\n                \"name\": \"setBreakpointByUrl\",\n                \"parameters\": [\n                    { \"name\": \"lineNumber\", \"type\": \"integer\", \"description\": \"Line number to set breakpoint at.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of the resources to set breakpoint on.\" },\n                    { \"name\": \"urlRegex\", \"type\": \"string\", \"optional\": true, \"description\": \"Regex pattern for the URLs of the resources to set breakpoints on. Either <code>url</code> or <code>urlRegex</code> must be specified.\" },\n                    { \"name\": \"columnNumber\", \"type\": \"integer\", \"optional\": true, \"description\": \"Offset in the line to set breakpoint at.\" },\n                    { \"name\": \"condition\", \"type\": \"string\", \"optional\": true, \"description\": \"Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"breakpointId\", \"$ref\": \"BreakpointId\", \"description\": \"Id of the created breakpoint for further reference.\" },\n                    { \"name\": \"locations\", \"type\": \"array\", \"items\": { \"$ref\": \"Location\" }, \"description\": \"List of the locations this breakpoint resolved into upon addition.\" }\n                ],\n                \"description\": \"Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this command is issued, all existing parsed scripts will have breakpoints resolved and returned in <code>locations</code> property. Further matching script parsing will result in subsequent <code>breakpointResolved</code> events issued. This logical breakpoint will survive page reloads.\"\n            },\n            {\n                \"name\": \"setBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"description\": \"Location to set breakpoint in.\" },\n                    { \"name\": \"condition\", \"type\": \"string\", \"optional\": true, \"description\": \"Expression to use as a breakpoint condition. When specified, debugger will only stop on the breakpoint if this expression evaluates to true.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"breakpointId\", \"$ref\": \"BreakpointId\", \"description\": \"Id of the created breakpoint for further reference.\" },\n                    { \"name\": \"actualLocation\", \"$ref\": \"Location\", \"description\": \"Location this breakpoint resolved into.\" }\n                ],\n                \"description\": \"Sets JavaScript breakpoint at a given location.\"\n            },\n            {\n                \"name\": \"removeBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"breakpointId\", \"$ref\": \"BreakpointId\" }\n                ],\n                \"description\": \"Removes JavaScript breakpoint.\"\n            },\n            {\n                \"name\": \"continueToLocation\",\n                \"parameters\": [\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"description\": \"Location to continue to.\" },\n                    { \"name\": \"interstatementLocation\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true, \"description\": \"Allows breakpoints at the intemediate positions inside statements.\" }\n                ],\n                \"description\": \"Continues execution until specific location is reached.\"\n            },\n            {\n                \"name\": \"stepOver\",\n                \"description\": \"Steps over the statement.\"\n            },\n            {\n                \"name\": \"stepInto\",\n                \"description\": \"Steps into the function call.\"\n            },\n            {\n                \"name\": \"stepOut\",\n                \"description\": \"Steps out of the function call.\"\n            },\n            {\n                \"name\": \"pause\",\n                \"description\": \"Stops on the next JavaScript statement.\"\n            },\n            {\n                \"name\": \"resume\",\n                \"description\": \"Resumes JavaScript execution.\"\n            },\n            {\n                \"name\": \"stepIntoAsync\",\n                \"description\": \"Steps into the first async operation handler that was scheduled by or after the current statement.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"searchInContent\",\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Id of the script to search in.\" },\n                    { \"name\": \"query\", \"type\": \"string\", \"description\": \"String to search for.\"  },\n                    { \"name\": \"caseSensitive\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true, search is case sensitive.\" },\n                    { \"name\": \"isRegex\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true, treats string parameter as regex.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"array\", \"items\": { \"$ref\": \"SearchMatch\" }, \"description\": \"List of search matches.\" }\n                ],\n                \"description\": \"Searches for given string in script content.\"\n            },\n            {\n                \"name\": \"canSetScriptSource\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if <code>setScriptSource</code> is supported.\" }\n                ],\n                \"description\": \"Always returns true.\"\n            },\n            {\n                \"name\": \"setScriptSource\",\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Id of the script to edit.\" },\n                    { \"name\": \"scriptSource\", \"type\": \"string\", \"description\": \"New content of the script.\" },\n                    { \"name\": \"preview\", \"type\": \"boolean\", \"optional\": true, \"description\": \" If true the change will not actually be applied. Preview mode may be used to get result description without actually modifying the code.\", \"hidden\": true }\n                ],\n                \"returns\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"New stack trace in case editing has happened while VM was stopped.\" },\n                    { \"name\": \"result\", \"type\": \"object\", \"optional\": true, \"description\": \"VM-specific description of the changes applied.\", \"hidden\": true },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"Async stack trace, if any.\", \"hidden\": true }\n                ],\n                \"error\": {\n                    \"$ref\": \"SetScriptSourceError\"\n                },\n                \"description\": \"Edits JavaScript source live.\"\n            },\n            {\n                \"name\": \"restartFrame\",\n                \"parameters\": [\n                    { \"name\": \"callFrameId\", \"$ref\": \"CallFrameId\", \"description\": \"Call frame identifier to evaluate on.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"New stack trace.\" },\n                    { \"name\": \"result\", \"type\": \"object\", \"description\": \"VM-specific description.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"Async stack trace, if any.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Restarts particular call frame from the beginning.\"\n            },\n            {\n                \"name\": \"getScriptSource\",\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Id of the script to get source for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"scriptSource\", \"type\": \"string\", \"description\": \"Script source.\" }\n                ],\n                \"description\": \"Returns source for the script with given id.\"\n            },\n            {\n                \"name\": \"getFunctionDetails\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"functionId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"Id of the function to get details for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"details\", \"$ref\": \"FunctionDetails\", \"description\": \"Information about the function.\" }\n                ],\n                \"description\": \"Returns detailed information on given function.\"\n            },\n            {\n                \"name\": \"getGeneratorObjectDetails\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"Id of the generator object to get details for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"details\", \"$ref\": \"GeneratorObjectDetails\", \"description\": \"Information about the generator object.\" }\n                ],\n                \"description\": \"Returns detailed information on given generator object.\"\n            },\n            {\n                \"name\": \"getCollectionEntries\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"Id of the collection to get entries for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"entries\", \"type\": \"array\", \"items\": { \"$ref\": \"CollectionEntry\" }, \"description\": \"Array of collection entries.\" }\n                ],\n                \"description\": \"Returns entries of given collection.\"\n            },\n            {\n                \"name\": \"setPauseOnExceptions\",\n                \"parameters\": [\n                    { \"name\": \"state\", \"type\": \"string\", \"enum\": [\"none\", \"uncaught\", \"all\"], \"description\": \"Pause on exceptions mode.\" }\n                ],\n                \"description\": \"Defines pause on exceptions state. Can be set to stop on all exceptions, uncaught exceptions or no exceptions. Initial pause on exceptions state is <code>none</code>.\"\n            },\n            {\n                \"name\": \"evaluateOnCallFrame\",\n                \"parameters\": [\n                    { \"name\": \"callFrameId\", \"$ref\": \"CallFrameId\", \"description\": \"Call frame identifier to evaluate on.\" },\n                    { \"name\": \"expression\", \"type\": \"string\", \"description\": \"Expression to evaluate.\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"String object group name to put result into (allows rapid releasing resulting object handles using <code>releaseObjectGroup</code>).\" },\n                    { \"name\": \"includeCommandLineAPI\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies whether command line API should be available to the evaluated expression, defaults to false.\", \"hidden\": true },\n                    { \"name\": \"doNotPauseOnExceptionsAndMuteConsole\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies whether evaluation should stop on exceptions and mute console. Overrides setPauseOnException state.\", \"hidden\": true },\n                    { \"name\": \"returnByValue\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the result is expected to be a JSON object that should be sent by value.\" },\n                    { \"name\": \"generatePreview\", \"type\": \"boolean\", \"optional\": true, \"hidden\": true, \"description\": \"Whether preview should be generated for the result.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Object wrapper for the evaluation result.\" },\n                    { \"name\": \"wasThrown\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True if the result was thrown during the evaluation.\" },\n                    { \"name\": \"exceptionDetails\", \"$ref\": \"ExceptionDetails\", \"optional\": true, \"hidden\": true, \"description\": \"Exception details.\"}\n                ],\n                \"description\": \"Evaluates expression on a given call frame.\"\n            },\n            {\n                \"name\": \"compileScript\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"expression\", \"type\": \"string\", \"description\": \"Expression to compile.\" },\n                    { \"name\": \"sourceURL\", \"type\": \"string\", \"description\": \"Source url to be set for the script.\" },\n                    { \"name\": \"persistScript\", \"type\": \"boolean\", \"description\": \"Specifies whether the compiled script should be persisted.\" },\n                    { \"name\": \"executionContextId\", \"$ref\": \"Runtime.ExecutionContextId\", \"optional\": true, \"description\": \"Specifies in which isolated context to perform script run. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"optional\": true, \"description\": \"Id of the script.\" },\n                    { \"name\": \"exceptionDetails\", \"$ref\": \"ExceptionDetails\", \"optional\": true, \"description\": \"Exception details.\"}\n                ],\n                \"description\": \"Compiles expression.\"\n            },\n            {\n                \"name\": \"runScript\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Id of the script to run.\" },\n                    { \"name\": \"executionContextId\", \"$ref\": \"Runtime.ExecutionContextId\", \"optional\": true, \"description\": \"Specifies in which isolated context to perform script run. Each content script lives in an isolated context and this parameter may be used to specify one of those contexts. If the parameter is omitted or 0 the evaluation will be performed in the context of the inspected page.\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"Symbolic group name that can be used to release multiple objects.\" },\n                    { \"name\": \"doNotPauseOnExceptionsAndMuteConsole\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Specifies whether script run should stop on exceptions and mute console. Overrides setPauseOnException state.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Run result.\" },\n                    { \"name\": \"exceptionDetails\", \"$ref\": \"ExceptionDetails\", \"optional\": true, \"description\": \"Exception details.\"}\n                ],\n                \"description\": \"Runs script with given id in a given context.\"\n            },\n            {\n                \"name\": \"setVariableValue\",\n                \"parameters\": [\n                    { \"name\": \"scopeNumber\", \"type\": \"integer\", \"description\": \"0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch' scope types are allowed. Other scopes could be manipulated manually.\" },\n                    { \"name\": \"variableName\", \"type\": \"string\", \"description\": \"Variable name.\" },\n                    { \"name\": \"newValue\", \"$ref\": \"Runtime.CallArgument\", \"description\": \"New variable value.\" },\n                    { \"name\": \"callFrameId\", \"$ref\": \"CallFrameId\", \"optional\": true, \"description\": \"Id of callframe that holds variable.\" },\n                    { \"name\": \"functionObjectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"optional\": true, \"description\": \"Object id of closure (function) that holds variable.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Changes value of variable in a callframe or a closure. Either callframe or function must be specified. Object-based scopes are not supported and must be mutated manually.\"\n            },\n            {\n                \"name\": \"getStepInPositions\",\n                \"parameters\": [\n                    { \"name\": \"callFrameId\", \"$ref\": \"CallFrameId\", \"description\": \"Id of a call frame where the current statement should be analized\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"stepInPositions\", \"type\": \"array\", \"items\": { \"$ref\": \"Location\" }, \"optional\": true, \"description\": \"experimental\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Lists all positions where step-in is possible for a current statement in a specified call frame\"\n            },\n            {\n                \"name\": \"getBacktrace\",\n                \"returns\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"Call stack the virtual machine stopped on.\" },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"Async stack trace, if any.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Returns call stack including variables changed since VM was paused. VM must be paused.\"\n            },\n            {\n                \"name\": \"skipStackFrames\",\n                \"parameters\": [\n                    { \"name\": \"script\", \"type\": \"string\", \"optional\": true, \"description\": \"Regular expression defining the scripts to ignore while stepping.\" },\n                    { \"name\": \"skipContentScripts\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True, if all content scripts should be ignored.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Makes backend skip steps in the sources with names matching given pattern. VM will try leave blacklisted scripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\"\n            },\n            {\n                \"name\": \"setAsyncCallStackDepth\",\n                \"parameters\": [\n                    { \"name\": \"maxDepth\", \"type\": \"integer\", \"description\": \"Maximum depth of async call stacks. Setting to <code>0</code> will effectively disable collecting async call stacks (default).\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Enables or disables async call stacks tracking.\"\n            },\n            {\n                \"name\": \"enablePromiseTracker\",\n                \"parameters\": [\n                    { \"name\": \"captureStacks\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether to capture stack traces for promise creation and settlement events (default: false).\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Enables promise tracking, information about <code>Promise</code>s created or updated will now be stored on the backend.\"\n            },\n            {\n                \"name\": \"disablePromiseTracker\",\n                \"hidden\": true,\n                \"description\": \"Disables promise tracking.\"\n            },\n            {\n                \"name\": \"getPromiseById\",\n                \"parameters\": [\n                    { \"name\": \"promiseId\", \"type\": \"integer\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"Symbolic group name that can be used to release multiple objects.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"promise\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Object wrapper for <code>Promise</code> with specified ID, if any.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Returns <code>Promise</code> with specified ID.\"\n            },\n            {\n                \"name\": \"flushAsyncOperationEvents\",\n                \"hidden\": true,\n                \"description\": \"Fires pending <code>asyncOperationStarted</code> events (if any), as if a debugger stepping session has just been started.\"\n            },\n            {\n                \"name\": \"setAsyncOperationBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"operationId\", \"type\": \"integer\", \"description\": \"ID of the async operation to set breakpoint for.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Sets breakpoint on AsyncOperation callback handler.\"\n            },\n            {\n                \"name\": \"removeAsyncOperationBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"operationId\", \"type\": \"integer\", \"description\": \"ID of the async operation to remove breakpoint for.\" }\n                ],\n                \"hidden\": true,\n                \"description\": \"Removes AsyncOperation breakpoint.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"globalObjectCleared\",\n                \"description\": \"Called when global has been cleared and debugger client should reset its state. Happens upon navigation or reload.\"\n            },\n            {\n                \"name\": \"scriptParsed\",\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Identifier of the script parsed.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL or name of the script parsed (if any).\" },\n                    { \"name\": \"startLine\", \"type\": \"integer\", \"description\": \"Line offset of the script within the resource with given URL (for script tags).\" },\n                    { \"name\": \"startColumn\", \"type\": \"integer\", \"description\": \"Column offset of the script within the resource with given URL.\" },\n                    { \"name\": \"endLine\", \"type\": \"integer\", \"description\": \"Last line of the script.\" },\n                    { \"name\": \"endColumn\", \"type\": \"integer\", \"description\": \"Length of the last line of the script.\" },\n                    { \"name\": \"isContentScript\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Determines whether this script is a user extension script.\" },\n                    { \"name\": \"isInternalScript\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Determines whether this script is an internal script.\", \"hidden\": true },\n                    { \"name\": \"sourceMapURL\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of source map associated with script (if any).\" },\n                    { \"name\": \"hasSourceURL\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True, if this script has sourceURL.\", \"hidden\": true }\n                ],\n                \"description\": \"Fired when virtual machine parses script. This event is also fired for all known and uncollected scripts upon enabling debugger.\"\n            },\n            {\n                \"name\": \"scriptFailedToParse\",\n                \"parameters\": [\n                    { \"name\": \"scriptId\", \"$ref\": \"ScriptId\", \"description\": \"Identifier of the script parsed.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL or name of the script parsed (if any).\" },\n                    { \"name\": \"startLine\", \"type\": \"integer\", \"description\": \"Line offset of the script within the resource with given URL (for script tags).\" },\n                    { \"name\": \"startColumn\", \"type\": \"integer\", \"description\": \"Column offset of the script within the resource with given URL.\" },\n                    { \"name\": \"endLine\", \"type\": \"integer\", \"description\": \"Last line of the script.\" },\n                    { \"name\": \"endColumn\", \"type\": \"integer\", \"description\": \"Length of the last line of the script.\" },\n                    { \"name\": \"isContentScript\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Determines whether this script is a user extension script.\" },\n                    { \"name\": \"isInternalScript\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Determines whether this script is an internal script.\", \"hidden\": true },\n                    { \"name\": \"sourceMapURL\", \"type\": \"string\", \"optional\": true, \"description\": \"URL of source map associated with script (if any).\" },\n                    { \"name\": \"hasSourceURL\", \"type\": \"boolean\", \"optional\": true, \"description\": \"True, if this script has sourceURL.\", \"hidden\": true }\n                ],\n                \"description\": \"Fired when virtual machine fails to parse the script.\"\n            },\n            {\n                \"name\": \"breakpointResolved\",\n                \"parameters\": [\n                    { \"name\": \"breakpointId\", \"$ref\": \"BreakpointId\", \"description\": \"Breakpoint unique identifier.\" },\n                    { \"name\": \"location\", \"$ref\": \"Location\", \"description\": \"Actual breakpoint location.\" }\n                ],\n                \"description\": \"Fired when breakpoint is resolved to an actual script and location.\"\n            },\n            {\n                \"name\": \"paused\",\n                \"parameters\": [\n                    { \"name\": \"callFrames\", \"type\": \"array\", \"items\": { \"$ref\": \"CallFrame\" }, \"description\": \"Call stack the virtual machine stopped on.\" },\n                    { \"name\": \"reason\", \"type\": \"string\", \"enum\": [ \"XHR\", \"DOM\", \"EventListener\", \"exception\", \"assert\", \"CSPViolation\", \"debugCommand\", \"promiseRejection\", \"AsyncOperation\", \"other\" ], \"description\": \"Pause reason.\" },\n                    { \"name\": \"data\", \"type\": \"object\", \"optional\": true, \"description\": \"Object containing break-specific auxiliary properties.\" },\n                    { \"name\": \"hitBreakpoints\", \"type\": \"array\", \"optional\": true, \"items\": { \"type\": \"string\" }, \"description\": \"Hit breakpoints IDs\", \"hidden\": true },\n                    { \"name\": \"asyncStackTrace\", \"$ref\": \"StackTrace\", \"optional\": true, \"description\": \"Async stack trace, if any.\", \"hidden\": true }\n                ],\n                \"description\": \"Fired when the virtual machine stopped on breakpoint or exception or any other stop criteria.\"\n            },\n            {\n                \"name\": \"resumed\",\n                \"description\": \"Fired when the virtual machine resumed execution.\"\n            },\n            {\n                \"name\": \"promiseUpdated\",\n                \"parameters\": [\n                    { \"name\": \"eventType\", \"type\": \"string\", \"enum\": [\"new\", \"update\", \"gc\"], \"description\": \"Type of the event.\" },\n                    { \"name\": \"promise\", \"$ref\": \"PromiseDetails\", \"description\": \"Information about the updated <code>Promise</code>.\" }\n                ],\n                \"description\": \"Fired when a <code>Promise</code> is created, updated or garbage collected.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"asyncOperationStarted\",\n                \"parameters\": [\n                    { \"name\": \"operation\", \"$ref\": \"AsyncOperation\", \"description\": \"Information about the async operation.\" }\n                ],\n                \"description\": \"Fired when an async operation is scheduled (while in a debugger stepping session).\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"asyncOperationCompleted\",\n                \"parameters\": [\n                    { \"name\": \"id\", \"type\": \"integer\", \"description\": \"ID of the async operation that was completed.\" }\n                ],\n                \"description\": \"Fired when an async operation is completed (while in a debugger stepping session).\",\n                \"hidden\": true\n            }\n        ]\n    },\n    {\n        \"domain\": \"DOMDebugger\",\n        \"description\": \"DOM debugging allows setting breakpoints on particular DOM operations and events. JavaScript execution will stop on these operations as if there was a regular breakpoint set.\",\n        \"types\": [\n            {\n                \"id\": \"DOMBreakpointType\",\n                \"type\": \"string\",\n                \"enum\": [\"subtree-modified\", \"attribute-modified\", \"node-removed\"],\n                \"description\": \"DOM breakpoint type.\"\n            },\n            {\n                \"id\": \"EventListener\",\n                \"type\": \"object\",\n                \"description\": \"Object event listener.\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"description\": \"<code>EventListener</code>'s type.\" },\n                    { \"name\": \"useCapture\", \"type\": \"boolean\", \"description\": \"<code>EventListener</code>'s useCapture.\" },\n                    { \"name\": \"location\", \"$ref\": \"Debugger.Location\", \"description\": \"Handler code location.\" },\n                    { \"name\": \"handler\", \"$ref\": \"Runtime.RemoteObject\", \"optional\": true, \"description\": \"Event handler function value.\" }\n                ],\n                \"hidden\": true\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"setDOMBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\", \"description\": \"Identifier of the node to set breakpoint on.\" },\n                    { \"name\": \"type\", \"$ref\": \"DOMBreakpointType\", \"description\": \"Type of the operation to stop upon.\" }\n                ],\n                \"description\": \"Sets breakpoint on particular operation with DOM.\"\n            },\n            {\n                \"name\": \"removeDOMBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\", \"description\": \"Identifier of the node to remove breakpoint from.\" },\n                    { \"name\": \"type\", \"$ref\": \"DOMBreakpointType\", \"description\": \"Type of the breakpoint to remove.\" }\n                ],\n                \"description\": \"Removes DOM breakpoint that was set using <code>setDOMBreakpoint</code>.\"\n            },\n            {\n                \"name\": \"setEventListenerBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"eventName\", \"type\": \"string\", \"description\": \"DOM Event name to stop on (any DOM event will do).\" },\n                    { \"name\": \"targetName\", \"type\": \"string\", \"optional\": true, \"description\": \"EventTarget interface name to stop on. If equal to <code>\\\"*\\\"</code> or not provided, will stop on any EventTarget.\", \"hidden\": true }\n                ],\n                \"description\": \"Sets breakpoint on particular DOM event.\"\n            },\n            {\n                \"name\": \"removeEventListenerBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"eventName\", \"type\": \"string\", \"description\": \"Event name.\" },\n                    { \"name\": \"targetName\", \"type\": \"string\", \"optional\": true, \"description\": \"EventTarget interface name.\", \"hidden\": true }\n                ],\n                \"description\": \"Removes breakpoint on particular DOM event.\"\n            },\n            {\n                \"name\": \"setInstrumentationBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"eventName\", \"type\": \"string\", \"description\": \"Instrumentation name to stop on.\" }\n                ],\n                \"description\": \"Sets breakpoint on particular native event.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"removeInstrumentationBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"eventName\", \"type\": \"string\", \"description\": \"Instrumentation name to stop on.\" }\n                ],\n                \"description\": \"Removes breakpoint on particular native event.\",\n                \"hidden\": true\n            },\n            {\n                \"name\": \"setXHRBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Resource URL substring. All XHRs having this substring in the URL will get stopped upon.\" }\n                ],\n                \"description\": \"Sets breakpoint on XMLHttpRequest.\"\n            },\n            {\n                \"name\": \"removeXHRBreakpoint\",\n                \"parameters\": [\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"Resource URL substring.\" }\n                ],\n                \"description\": \"Removes breakpoint from XMLHttpRequest.\"\n            },\n            {\n                \"name\": \"getEventListeners\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"Identifier of the object to return listeners for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"listeners\", \"type\": \"array\", \"items\": { \"$ref\": \"EventListener\" }, \"description\": \"Array of relevant listeners.\" }\n                ],\n                \"description\": \"Returns event listeners of the given object.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Profiler\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"CPUProfileNode\",\n                \"type\": \"object\",\n                \"description\": \"CPU Profile node. Holds callsite information, execution statistics and child nodes.\",\n                \"properties\": [\n                    { \"name\": \"functionName\", \"type\": \"string\", \"description\": \"Function name.\" },\n                    { \"name\": \"scriptId\", \"$ref\": \"Debugger.ScriptId\", \"description\": \"Script identifier.\" },\n                    { \"name\": \"url\", \"type\": \"string\", \"description\": \"URL.\" },\n                    { \"name\": \"lineNumber\", \"type\": \"integer\", \"description\": \"1-based line number of the function start position.\" },\n                    { \"name\": \"columnNumber\", \"type\": \"integer\", \"description\": \"1-based column number of the function start position.\" },\n                    { \"name\": \"hitCount\", \"type\": \"integer\", \"description\": \"Number of samples where this node was on top of the call stack.\" },\n                    { \"name\": \"callUID\", \"type\": \"number\", \"description\": \"Call UID.\" },\n                    { \"name\": \"children\", \"type\": \"array\", \"items\": { \"$ref\": \"CPUProfileNode\" }, \"description\": \"Child nodes.\" },\n                    { \"name\": \"deoptReason\", \"type\": \"string\", \"description\": \"The reason of being not optimized. The function may be deoptimized or marked as don't optimize.\"},\n                    { \"name\": \"id\", \"type\": \"integer\", \"description\": \"Unique id of the node.\" },\n                    { \"name\": \"positionTicks\", \"type\": \"array\", \"items\": { \"$ref\": \"PositionTickInfo\" }, \"description\": \"An array of source position ticks.\" }\n                ]\n            },\n            {\n                \"id\": \"CPUProfile\",\n                \"type\": \"object\",\n                \"description\": \"Profile.\",\n                \"properties\": [\n                    { \"name\": \"head\", \"$ref\": \"CPUProfileNode\" },\n                    { \"name\": \"startTime\", \"type\": \"number\", \"description\": \"Profiling start time in seconds.\" },\n                    { \"name\": \"endTime\", \"type\": \"number\", \"description\": \"Profiling end time in seconds.\" },\n                    { \"name\": \"samples\", \"optional\": true, \"type\": \"array\", \"items\": { \"type\": \"integer\" }, \"description\": \"Ids of samples top nodes.\" },\n                    { \"name\": \"timestamps\", \"optional\": true, \"type\": \"array\", \"items\": { \"type\": \"number\" }, \"description\": \"Timestamps of the samples in microseconds.\" }\n                ]\n            },\n            {\n                \"id\": \"PositionTickInfo\",\n                \"type\": \"object\",\n                \"description\": \"Specifies a number of samples attributed to a certain source position.\",\n                \"properties\": [\n                    { \"name\": \"line\", \"type\": \"integer\", \"description\": \"Source line number (1-based).\" },\n                    { \"name\": \"ticks\", \"type\": \"integer\", \"description\": \"Number of samples attributed to the source line.\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\"\n            },\n            {\n                \"name\": \"disable\"\n            },\n            {\n                \"name\": \"setSamplingInterval\",\n                \"parameters\": [\n                    { \"name\": \"interval\", \"type\": \"integer\", \"description\": \"New sampling interval in microseconds.\" }\n                ],\n                \"description\": \"Changes CPU profiler sampling interval. Must be called before CPU profiles recording started.\"\n            },\n            {\n                \"name\": \"start\"\n            },\n            {\n                \"name\": \"stop\",\n                \"returns\": [\n                    { \"name\": \"profile\", \"$ref\": \"CPUProfile\", \"description\": \"Recorded profile.\" }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"consoleProfileStarted\",\n                \"parameters\": [\n                    { \"name\": \"id\", \"type\": \"string\" },\n                    { \"name\": \"location\", \"$ref\": \"Debugger.Location\", \"description\": \"Location of console.profile().\" },\n                    { \"name\": \"title\", \"type\": \"string\", \"optional\": true, \"description\": \"Profile title passed as argument to console.profile().\" }\n\n                ],\n                \"description\": \"Sent when new profile recodring is started using console.profile() call.\"\n            },\n            {\n                \"name\": \"consoleProfileFinished\",\n                \"parameters\": [\n                    { \"name\": \"id\", \"type\": \"string\" },\n                    { \"name\": \"location\", \"$ref\": \"Debugger.Location\", \"description\": \"Location of console.profileEnd().\" },\n                    { \"name\": \"profile\", \"$ref\": \"CPUProfile\" },\n                    { \"name\": \"title\", \"type\": \"string\", \"optional\": true, \"description\": \"Profile title passed as argunet to console.profile().\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"HeapProfiler\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"HeapSnapshotObjectId\",\n                \"type\": \"string\",\n                \"description\": \"Heap snapshot object id.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\"\n            },\n            {\n                \"name\": \"disable\"\n            },\n            {\n                \"name\": \"startTrackingHeapObjects\",\n                \"parameters\": [\n                    { \"name\": \"trackAllocations\", \"type\": \"boolean\", \"optional\": true }\n                ]\n            },\n            {\n                \"name\": \"stopTrackingHeapObjects\",\n                \"parameters\": [\n                    { \"name\": \"reportProgress\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken when the tracking is stopped.\" }\n                ]\n\n            },\n            {\n                \"name\": \"takeHeapSnapshot\",\n                \"parameters\": [\n                    { \"name\": \"reportProgress\", \"type\": \"boolean\", \"optional\": true, \"description\": \"If true 'reportHeapSnapshotProgress' events will be generated while snapshot is being taken.\" }\n                ]\n            },\n            {\n                \"name\": \"collectGarbage\"\n            },\n            {\n                \"name\": \"getObjectByHeapObjectId\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"HeapSnapshotObjectId\" },\n                    { \"name\": \"objectGroup\", \"type\": \"string\", \"optional\": true, \"description\": \"Symbolic group name that can be used to release multiple objects.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"result\", \"$ref\": \"Runtime.RemoteObject\", \"description\": \"Evaluation result.\" }\n                ]\n            },\n            {\n                \"name\": \"addInspectedHeapObject\",\n                \"parameters\": [\n                    { \"name\": \"heapObjectId\", \"$ref\": \"HeapSnapshotObjectId\", \"description\": \"Heap snapshot object id to be accessible by means of $x command line API.\" }\n                ],\n                \"description\": \"Enables console to refer to the node with given id via $x (see Command Line API for more details $x functions).\"\n            },\n            {\n                \"name\": \"getHeapObjectId\",\n                \"parameters\": [\n                    { \"name\": \"objectId\", \"$ref\": \"Runtime.RemoteObjectId\", \"description\": \"Identifier of the object to get heap object id for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"heapSnapshotObjectId\", \"$ref\": \"HeapSnapshotObjectId\", \"description\": \"Id of the heap snapshot object corresponding to the passed remote object id.\" }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"addHeapSnapshotChunk\",\n                \"parameters\": [\n                    { \"name\": \"chunk\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"resetProfiles\"\n            },\n            {\n                \"name\": \"reportHeapSnapshotProgress\",\n                \"parameters\": [\n                    { \"name\": \"done\", \"type\": \"integer\" },\n                    { \"name\": \"total\", \"type\": \"integer\" },\n                    { \"name\": \"finished\", \"type\": \"boolean\", \"optional\": true }\n                ]\n            },\n            {\n                \"name\": \"lastSeenObjectId\",\n                \"description\": \"If heap objects tracking has been started then backend regulary sends a current value for last seen object id and corresponding timestamp. If the were changes in the heap since last event then one or more heapStatsUpdate events will be sent before a new lastSeenObjectId event.\",\n                \"parameters\": [\n                    { \"name\": \"lastSeenObjectId\", \"type\": \"integer\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\" }\n                ]\n            },\n            {\n                \"name\": \"heapStatsUpdate\",\n                \"description\": \"If heap objects tracking has been started then backend may send update for one or more fragments\",\n                \"parameters\": [\n                    { \"name\": \"statsUpdate\", \"type\": \"array\", \"items\": { \"type\": \"integer\" }, \"description\": \"An array of triplets. Each triplet describes a fragment. The first integer is the fragment index, the second integer is a total count of objects for the fragment, the third integer is a total size of the objects for the fragment.\"}\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Worker\",\n        \"hidden\": true,\n        \"types\": [],\n        \"commands\": [\n            {\n                \"name\": \"enable\"\n            },\n            {\n                \"name\": \"disable\"\n            },\n            {\n                \"name\": \"sendMessageToWorker\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"message\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"connectToWorker\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"disconnectFromWorker\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"setAutoconnectToWorkers\",\n                \"parameters\": [\n                    { \"name\": \"value\", \"type\": \"boolean\" }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"workerCreated\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"url\", \"type\": \"string\" },\n                    { \"name\": \"inspectorConnected\", \"type\": \"boolean\" }\n                ]\n            },\n            {\n                \"name\": \"workerTerminated\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" }\n                ]\n            },\n            {\n                \"name\": \"dispatchMessageFromWorker\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"message\", \"type\": \"string\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"ServiceWorker\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"ServiceWorkerRegistration\",\n                \"type\": \"object\",\n                \"description\": \"ServiceWorker registration.\",\n                \"properties\": [\n                    { \"name\": \"registrationId\", \"type\": \"string\" },\n                    { \"name\": \"scopeURL\", \"type\": \"string\" },\n                    { \"name\": \"isDeleted\", \"type\": \"boolean\", \"optional\": true }\n                ]\n            },\n            {\n                \"id\": \"ServiceWorkerVersionRunningStatus\",\n                \"type\": \"string\",\n                \"enum\": [\"stopped\", \"starting\", \"running\", \"stopping\"]\n            },\n            {\n                \"id\": \"ServiceWorkerVersionStatus\",\n                \"type\": \"string\",\n                \"enum\": [\"new\", \"installing\", \"installed\", \"activating\", \"activated\", \"redundant\"]\n            },\n            {\n                \"id\": \"TargetID\",\n                \"type\": \"string\"\n            },\n            {\n                \"id\": \"ServiceWorkerVersion\",\n                \"type\": \"object\",\n                \"description\": \"ServiceWorker version.\",\n                \"properties\": [\n                    { \"name\": \"versionId\", \"type\": \"string\" },\n                    { \"name\": \"registrationId\", \"type\": \"string\" },\n                    { \"name\": \"scriptURL\", \"type\": \"string\" },\n                    { \"name\": \"runningStatus\", \"$ref\": \"ServiceWorkerVersionRunningStatus\" },\n                    { \"name\": \"status\", \"$ref\": \"ServiceWorkerVersionStatus\" },\n                    { \"name\": \"scriptLastModified\", \"type\": \"number\", \"optional\": true, \"description\": \"The Last-Modified header value of the main script.\" },\n                    { \"name\": \"scriptResponseTime\", \"type\": \"number\", \"optional\": true, \"description\": \"The time at which the response headers of the main script were received from the server.  For cached script it is the last time the cache entry was validated.\" },\n                    { \"name\": \"controlledClients\", \"type\": \"array\", \"optional\": true, \"items\": { \"$ref\": \"TargetID\" } }\n                ]\n            },\n            {\n                \"id\": \"ServiceWorkerErrorMessage\",\n                \"type\": \"object\",\n                \"description\": \"ServiceWorker error message.\",\n                \"properties\": [\n                    { \"name\": \"errorMessage\", \"type\": \"string\" },\n                    { \"name\": \"registrationId\", \"type\": \"string\" },\n                    { \"name\": \"versionId\", \"type\": \"string\" },\n                    { \"name\": \"sourceURL\", \"type\": \"string\" },\n                    { \"name\": \"lineNumber\", \"type\": \"integer\" },\n                    { \"name\": \"columnNumber\", \"type\": \"integer\" }\n                ]\n            },\n            {\n                \"id\": \"TargetInfo\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"id\", \"$ref\": \"TargetID\" },\n                    { \"name\": \"type\", \"type\": \"string\" },\n                    { \"name\": \"title\", \"type\": \"string\" },\n                    { \"name\": \"url\", \"type\": \"string\" }\n                ]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"disable\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"sendMessage\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"message\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"stop\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"unregister\",\n                \"parameters\": [\n                    { \"name\": \"scopeURL\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"updateRegistration\",\n                \"parameters\": [\n                    { \"name\": \"scopeURL\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"startWorker\",\n                \"parameters\": [\n                    { \"name\": \"scopeURL\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"stopWorker\",\n                \"parameters\": [\n                    { \"name\": \"versionId\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"inspectWorker\",\n                \"parameters\": [\n                    { \"name\": \"versionId\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"skipWaiting\",\n                \"parameters\": [\n                    { \"name\": \"versionId\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"setDebugOnStart\",\n                \"parameters\": [\n                    { \"name\": \"debugOnStart\", \"type\": \"boolean\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"deliverPushMessage\",\n                \"parameters\": [\n                    { \"name\": \"origin\", \"type\": \"string\" },\n                    { \"name\": \"registrationId\", \"type\": \"string\" },\n                    { \"name\": \"data\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"getTargetInfo\",\n                \"parameters\": [\n                    { \"name\": \"targetId\", \"$ref\": \"TargetID\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"targetInfo\",\"$ref\": \"TargetInfo\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"activateTarget\",\n                \"parameters\": [\n                    { \"name\": \"targetId\", \"$ref\": \"TargetID\" }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"workerCreated\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"url\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"workerTerminated\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"dispatchMessage\",\n                \"parameters\": [\n                    { \"name\": \"workerId\", \"type\": \"string\" },\n                    { \"name\": \"message\", \"type\": \"string\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"workerRegistrationUpdated\",\n                \"parameters\": [\n                    { \"name\": \"registrations\", \"type\": \"array\", \"items\": { \"$ref\": \"ServiceWorkerRegistration\" } }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"workerVersionUpdated\",\n                \"parameters\": [\n                    { \"name\": \"versions\", \"type\": \"array\", \"items\": { \"$ref\": \"ServiceWorkerVersion\" } }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"workerErrorReported\",\n                \"parameters\": [\n                    { \"name\": \"errorMessage\", \"$ref\": \"ServiceWorkerErrorMessage\" }\n                ],\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"debugOnStartUpdated\",\n                \"parameters\": [\n                    { \"name\": \"debugOnStart\", \"type\": \"boolean\" }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Input\",\n        \"types\": [\n            {\n                \"id\": \"TouchPoint\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"state\", \"type\": \"string\", \"enum\": [\"touchPressed\", \"touchReleased\", \"touchMoved\", \"touchStationary\", \"touchCancelled\"], \"description\": \"State of the touch point.\" },\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the event relative to the main frame's viewport.\"},\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport.\"},\n                    { \"name\": \"radiusX\", \"type\": \"integer\", \"optional\": true, \"description\": \"X radius of the touch area (default: 1).\"},\n                    { \"name\": \"radiusY\", \"type\": \"integer\", \"optional\": true, \"description\": \"Y radius of the touch area (default: 1).\"},\n                    { \"name\": \"rotationAngle\", \"type\": \"number\", \"optional\": true, \"description\": \"Rotation angle (default: 0.0).\"},\n                    { \"name\": \"force\", \"type\": \"number\", \"optional\": true, \"description\": \"Force (default: 1.0).\"},\n                    { \"name\": \"id\", \"type\": \"number\", \"optional\": true, \"description\": \"Identifier used to track touch sources between events, must be unique within an event.\"}\n                ]\n            },\n            {\n                \"id\": \"GestureSourceType\",\n                \"type\": \"string\",\n                \"hidden\": true,\n                \"enum\": [\"default\", \"touch\", \"mouse\"]\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"dispatchKeyEvent\",\n                \"parameters\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"keyDown\", \"keyUp\", \"rawKeyDown\", \"char\"], \"description\": \"Type of the key event.\" },\n                    { \"name\": \"modifiers\", \"type\": \"integer\", \"optional\": true, \"description\": \"Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"optional\": true, \"description\": \"Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).\" },\n                    { \"name\": \"text\", \"type\": \"string\", \"optional\": true, \"description\": \"Text as generated by processing a virtual key code with a keyboard layout. Not needed for for <code>keyUp</code> and <code>rawKeyDown</code> events (default: \\\"\\\")\" },\n                    { \"name\": \"unmodifiedText\", \"type\": \"string\", \"optional\": true, \"description\": \"Text that would have been generated by the keyboard if no modifiers were pressed (except for shift). Useful for shortcut (accelerator) key handling (default: \\\"\\\").\" },\n                    { \"name\": \"keyIdentifier\", \"type\": \"string\", \"optional\": true, \"description\": \"Unique key identifier (e.g., 'U+0041') (default: \\\"\\\").\" },\n                    { \"name\": \"code\", \"type\": \"string\", \"optional\": true, \"description\": \"Unique DOM defined string value for each physical key (e.g., 'KeyA') (default: \\\"\\\").\" },\n                    { \"name\": \"key\", \"type\": \"string\", \"optional\": true, \"description\": \"Unique DOM defined string value describing the meaning of the key in the context of active modifiers, keyboard layout, etc (e.g., 'AltGr') (default: \\\"\\\").\" },\n                    { \"name\": \"windowsVirtualKeyCode\", \"type\": \"integer\", \"optional\": true, \"description\": \"Windows virtual key code (default: 0).\" },\n                    { \"name\": \"nativeVirtualKeyCode\", \"type\": \"integer\", \"optional\": true, \"description\": \"Native virtual key code (default: 0).\" },\n                    { \"name\": \"autoRepeat\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the event was generated from auto repeat (default: false).\" },\n                    { \"name\": \"isKeypad\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the event was generated from the keypad (default: false).\" },\n                    { \"name\": \"isSystemKey\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Whether the event was a system key event (default: false).\" }\n                ],\n                \"description\": \"Dispatches a key event to the page.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"dispatchMouseEvent\",\n                \"parameters\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"mousePressed\", \"mouseReleased\", \"mouseMoved\"], \"description\": \"Type of the mouse event.\" },\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the event relative to the main frame's viewport.\"},\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the event relative to the main frame's viewport. 0 refers to the top of the viewport and Y increases as it proceeds towards the bottom of the viewport.\"},\n                    { \"name\": \"modifiers\", \"type\": \"integer\", \"optional\": true, \"description\": \"Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"optional\": true, \"description\": \"Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).\" },\n                    { \"name\": \"button\", \"type\": \"string\", \"enum\": [\"none\", \"left\", \"middle\", \"right\"], \"optional\": true, \"description\": \"Mouse button (default: \\\"none\\\").\" },\n                    { \"name\": \"clickCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"Number of times the mouse button was clicked (default: 0).\" }\n                ],\n                \"description\": \"Dispatches a mouse event to the page.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"dispatchTouchEvent\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"touchStart\", \"touchEnd\", \"touchMove\"], \"description\": \"Type of the touch event.\" },\n                    { \"name\": \"touchPoints\", \"type\": \"array\", \"items\": { \"$ref\": \"TouchPoint\" }, \"description\": \"Touch points.\" },\n                    { \"name\": \"modifiers\", \"type\": \"integer\", \"optional\": true, \"description\": \"Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"optional\": true, \"description\": \"Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970 (default: current time).\" }\n                ],\n                \"description\": \"Dispatches a touch event to the page.\"\n            },\n            {\n                \"name\": \"emulateTouchFromMouseEvent\",\n                \"hidden\": true,\n                \"parameters\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"mousePressed\", \"mouseReleased\", \"mouseMoved\", \"mouseWheel\"], \"description\": \"Type of the mouse event.\" },\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the mouse pointer in DIP.\"},\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the mouse pointer in DIP.\"},\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"description\": \"Time at which the event occurred. Measured in UTC time in seconds since January 1, 1970.\" },\n                    { \"name\": \"button\", \"type\": \"string\", \"enum\": [\"none\", \"left\", \"middle\", \"right\"], \"description\": \"Mouse button.\" },\n                    { \"name\": \"deltaX\", \"type\": \"number\", \"optional\": true, \"description\": \"X delta in DIP for mouse wheel event (default: 0).\"},\n                    { \"name\": \"deltaY\", \"type\": \"number\", \"optional\": true, \"description\": \"Y delta in DIP for mouse wheel event (default: 0).\"},\n                    { \"name\": \"modifiers\", \"type\": \"integer\", \"optional\": true, \"description\": \"Bit field representing pressed modifier keys. Alt=1, Ctrl=2, Meta/Command=4, Shift=8 (default: 0).\" },\n                    { \"name\": \"clickCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"Number of times the mouse button was clicked (default: 0).\" }\n                ],\n                \"description\": \"Emulates touch event from the mouse event parameters.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"synthesizePinchGesture\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"scaleFactor\", \"type\": \"number\", \"description\": \"Relative scale factor after zooming (>1.0 zooms in, <1.0 zooms out).\" },\n                    { \"name\": \"relativeSpeed\", \"type\": \"integer\", \"optional\": true, \"description\": \"Relative pointer speed in pixels per second (default: 800).\" },\n                    { \"name\": \"gestureSourceType\", \"$ref\": \"GestureSourceType\", \"optional\": true, \"description\": \"Which type of input events to be generated (default: 'default', which queries the platform for the preferred input type).\" }\n                ],\n                \"description\": \"Synthesizes a pinch gesture over a time period by issuing appropriate touch events.\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"synthesizeScrollGesture\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"xDistance\", \"type\": \"integer\", \"optional\": true, \"description\": \"The distance to scroll along the X axis (positive to scroll left).\" },\n                    { \"name\": \"yDistance\", \"type\": \"integer\", \"optional\": true, \"description\": \"The distance to scroll along the Y axis (positive to scroll up).\" },\n                    { \"name\": \"xOverscroll\", \"type\": \"integer\", \"optional\": true, \"description\": \"The number of additional pixels to scroll back along the X axis, in addition to the given distance.\" },\n                    { \"name\": \"yOverscroll\", \"type\": \"integer\", \"optional\": true, \"description\": \"The number of additional pixels to scroll back along the Y axis, in addition to the given distance.\" },\n                    { \"name\": \"preventFling\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Prevent fling (default: true).\" },\n                    { \"name\": \"speed\", \"type\": \"integer\", \"optional\": true, \"description\": \"Swipe speed in pixels per second (default: 800).\" },\n                    { \"name\": \"gestureSourceType\", \"$ref\": \"GestureSourceType\", \"optional\": true, \"description\": \"Which type of input events to be generated (default: 'default', which queries the platform for the preferred input type).\" }\n                ],\n                \"description\": \"Synthesizes a scroll gesture over a time period by issuing appropriate touch events.\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"synthesizeTapGesture\",\n                \"async\": true,\n                \"parameters\": [\n                    { \"name\": \"x\", \"type\": \"integer\", \"description\": \"X coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"y\", \"type\": \"integer\", \"description\": \"Y coordinate of the start of the gesture in CSS pixels.\" },\n                    { \"name\": \"duration\", \"type\": \"integer\", \"optional\": true, \"description\": \"Duration between touchdown and touchup events in ms (default: 50).\" },\n                    { \"name\": \"tapCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"Number of times to perform the tap (e.g. 2 for double tap, default: 1).\" },\n                    { \"name\": \"gestureSourceType\", \"$ref\": \"GestureSourceType\", \"optional\": true, \"description\": \"Which type of input events to be generated (default: 'default', which queries the platform for the preferred input type).\" }\n                ],\n                \"description\": \"Synthesizes a tap gesture over a time period by issuing appropriate touch events.\",\n                \"hidden\": true,\n                \"handlers\": [\"browser\"]\n            }\n        ],\n        \"events\": []\n    },\n    {\n        \"domain\": \"LayerTree\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"LayerId\",\n                \"type\": \"string\",\n                \"description\": \"Unique Layer identifier.\"\n            },\n            {\n                \"id\": \"SnapshotId\",\n                \"type\": \"string\",\n                \"description\": \"Unique snapshot identifier.\"\n            },\n            {\n                \"id\": \"ScrollRect\",\n                \"type\": \"object\",\n                \"description\": \"Rectangle where scrolling happens on the main thread.\",\n                \"properties\": [\n                    { \"name\": \"rect\", \"$ref\": \"DOM.Rect\", \"description\": \"Rectangle itself.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"RepaintsOnScroll\", \"TouchEventHandler\", \"WheelEventHandler\"], \"description\": \"Reason for rectangle to force scrolling on the main thread\" }\n                ]\n            },\n            {\n                \"id\": \"PictureTile\",\n                \"type\": \"object\",\n                \"description\": \"Serialized fragment of layer picture along with its offset within the layer.\",\n                \"properties\": [\n                    { \"name\": \"x\", \"type\": \"number\", \"description\": \"Offset from owning layer left boundary\" },\n                    { \"name\": \"y\", \"type\": \"number\", \"description\": \"Offset from owning layer top boundary\" },\n                    { \"name\": \"picture\", \"type\": \"string\", \"description\": \"Base64-encoded snapshot data.\" }\n                ]\n            },\n            {\n                \"id\": \"Layer\",\n                \"type\": \"object\",\n                \"description\": \"Information about a compositing layer.\",\n                \"properties\": [\n                    { \"name\": \"layerId\", \"$ref\": \"LayerId\", \"description\": \"The unique id for this layer.\" },\n                    { \"name\": \"parentLayerId\", \"$ref\": \"LayerId\", \"optional\": true, \"description\": \"The id of parent (not present for root).\" },\n                    { \"name\": \"backendNodeId\", \"$ref\": \"DOM.BackendNodeId\", \"optional\": true, \"description\": \"The backend id for the node associated with this layer.\" },\n                    { \"name\": \"offsetX\", \"type\": \"number\", \"description\": \"Offset from parent layer, X coordinate.\" },\n                    { \"name\": \"offsetY\", \"type\": \"number\", \"description\": \"Offset from parent layer, Y coordinate.\" },\n                    { \"name\": \"width\", \"type\": \"number\", \"description\": \"Layer width.\" },\n                    { \"name\": \"height\", \"type\": \"number\", \"description\": \"Layer height.\" },\n                    { \"name\": \"transform\", \"type\": \"array\", \"items\": { \"type\": \"number\" }, \"minItems\": 16, \"maxItems\": 16, \"optional\": true, \"description\": \"Transformation matrix for layer, default is identity matrix\" },\n                    { \"name\": \"anchorX\", \"type\": \"number\", \"optional\": true, \"description\": \"Transform anchor point X, absent if no transform specified\" },\n                    { \"name\": \"anchorY\", \"type\": \"number\", \"optional\": true, \"description\": \"Transform anchor point Y, absent if no transform specified\" },\n                    { \"name\": \"anchorZ\", \"type\": \"number\", \"optional\": true, \"description\": \"Transform anchor point Z, absent if no transform specified\" },\n                    { \"name\": \"paintCount\", \"type\": \"integer\", \"description\": \"Indicates how many time this layer has painted.\" },\n                    { \"name\": \"drawsContent\", \"type\": \"boolean\", \"description\": \"Indicates whether this layer hosts any content, rather than being used for transform/scrolling purposes only.\" },\n                    { \"name\": \"invisible\", \"type\": \"boolean\", \"optional\": true, \"description\": \"Set if layer is not visible.\" },\n                    { \"name\": \"scrollRects\", \"type\": \"array\", \"items\": { \"$ref\": \"ScrollRect\"}, \"optional\": true, \"description\": \"Rectangles scrolling on main thread only.\"}\n                ]\n            },\n            {\n                \"id\": \"PaintProfile\",\n                \"type\": \"array\",\n                \"description\": \"Array of timings, one per paint step.\",\n                \"items\": {\n                    \"type\": \"number\",\n                    \"description\": \"A time in seconds since the end of previous step (for the first step, time since painting started)\"\n                }\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables compositing tree inspection.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables compositing tree inspection.\"\n            },\n            {\n                \"name\": \"compositingReasons\",\n                \"parameters\": [\n                    { \"name\": \"layerId\", \"$ref\": \"LayerId\", \"description\": \"The id of the layer for which we want to get the reasons it was composited.\" }\n                ],\n                \"description\": \"Provides the reasons why the given layer was composited.\",\n                \"returns\": [\n                    { \"name\": \"compositingReasons\", \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"description\": \"A list of strings specifying reasons for the given layer to become composited.\" }\n                ]\n            },\n            {\n                \"name\": \"makeSnapshot\",\n                \"parameters\": [\n                    { \"name\": \"layerId\", \"$ref\": \"LayerId\", \"description\": \"The id of the layer.\" }\n                ],\n                \"description\": \"Returns the layer snapshot identifier.\",\n                \"returns\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the layer snapshot.\" }\n                ]\n            },\n            {\n                \"name\": \"loadSnapshot\",\n                \"parameters\": [\n                    { \"name\": \"tiles\", \"type\": \"array\", \"items\": { \"$ref\": \"PictureTile\" }, \"minItems\": 1, \"description\": \"An array of tiles composing the snapshot.\" }\n                ],\n                \"description\": \"Returns the snapshot identifier.\",\n                \"returns\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the snapshot.\" }\n                ]\n            },\n            {\n                \"name\": \"releaseSnapshot\",\n                \"parameters\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the layer snapshot.\" }\n                ],\n                \"description\": \"Releases layer snapshot captured by the back-end.\"\n            },\n            {\n                \"name\": \"profileSnapshot\",\n                \"parameters\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the layer snapshot.\" },\n                    { \"name\": \"minRepeatCount\", \"type\": \"integer\", \"optional\": true, \"description\": \"The maximum number of times to replay the snapshot (1, if not specified).\" },\n                    { \"name\": \"minDuration\", \"type\": \"number\", \"optional\": true, \"description\": \"The minimum duration (in seconds) to replay the snapshot.\" },\n                    { \"name\": \"clipRect\", \"$ref\": \"DOM.Rect\", \"optional\": true, \"description\": \"The clip rectangle to apply when replaying the snapshot.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"timings\", \"type\": \"array\", \"items\": { \"$ref\": \"PaintProfile\" }, \"description\": \"The array of paint profiles, one per run.\" }\n                ]\n            },\n            {\n                \"name\": \"replaySnapshot\",\n                \"parameters\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the layer snapshot.\" },\n                    { \"name\": \"fromStep\", \"type\": \"integer\", \"optional\": true, \"description\": \"The first step to replay from (replay from the very start if not specified).\" },\n                    { \"name\": \"toStep\", \"type\": \"integer\", \"optional\": true, \"description\": \"The last step to replay to (replay till the end if not specified).\" },\n                    { \"name\": \"scale\", \"type\": \"number\", \"optional\": true, \"description\": \"The scale to apply while replaying (defaults to 1).\" }\n                ],\n                \"description\": \"Replays the layer snapshot and returns the resulting bitmap.\",\n                \"returns\": [\n                    { \"name\": \"dataURL\", \"type\": \"string\", \"description\": \"A data: URL for resulting image.\" }\n                ]\n            },\n            {\n                \"name\": \"snapshotCommandLog\",\n                \"parameters\": [\n                    { \"name\": \"snapshotId\", \"$ref\": \"SnapshotId\", \"description\": \"The id of the layer snapshot.\" }\n                ],\n                \"description\": \"Replays the layer snapshot and returns canvas log.\",\n                \"returns\": [\n                    { \"name\": \"commandLog\", \"type\": \"array\", \"items\": { \"type\": \"object\" }, \"description\": \"The array of canvas function calls.\" }\n                ]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"layerTreeDidChange\",\n                \"parameters\": [\n                    { \"name\": \"layers\", \"type\": \"array\", \"items\": { \"$ref\": \"Layer\" }, \"optional\": true, \"description\": \"Layer tree, absent if not in the comspositing mode.\" }\n                ]\n            },\n            {\n                \"name\": \"layerPainted\",\n                \"parameters\": [\n                    { \"name\": \"layerId\", \"$ref\": \"LayerId\", \"description\": \"The id of the painted layer.\" },\n                    { \"name\": \"clip\", \"$ref\": \"DOM.Rect\", \"description\": \"Clip rectangle.\" }\n                ]\n            }\n        ]\n    },\n    {\n        \"domain\": \"DeviceOrientation\",\n        \"hidden\": true,\n        \"commands\": [\n            {\n                \"name\": \"setDeviceOrientationOverride\",\n                \"description\": \"Overrides the Device Orientation.\",\n                \"parameters\": [\n                    { \"name\": \"alpha\", \"type\": \"number\", \"description\": \"Mock alpha\"},\n                    { \"name\": \"beta\", \"type\": \"number\", \"description\": \"Mock beta\"},\n                    { \"name\": \"gamma\", \"type\": \"number\", \"description\": \"Mock gamma\"}\n                ]\n            },\n            {\n                \"name\": \"clearDeviceOrientationOverride\",\n                \"description\": \"Clears the overridden Device Orientation.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"ScreenOrientation\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"OrientationType\",\n                \"type\": \"string\",\n                \"enum\": [\"portraitPrimary\", \"portraitSecondary\", \"landscapePrimary\", \"landscapeSecondary\"],\n                \"description\": \"Orientation type\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"setScreenOrientationOverride\",\n                \"description\": \"Overrides the Screen Orientation.\",\n                \"parameters\": [\n                    { \"name\": \"angle\", \"type\": \"integer\", \"description\": \"Orientation angle\" },\n                    { \"name\": \"type\", \"$ref\": \"OrientationType\", \"description\": \"Orientation type\" }\n                ]\n            },\n            {\n                \"name\": \"clearScreenOrientationOverride\",\n                \"description\": \"Clears the overridden Screen Orientation.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Tracing\",\n        \"commands\": [\n            {\n                \"name\": \"start\",\n                \"async\": true,\n                \"description\": \"Start trace events collection.\",\n                \"parameters\": [\n                    { \"name\": \"categories\", \"type\": \"string\", \"optional\": true, \"description\": \"Category/tag filter\" },\n                    { \"name\": \"options\", \"type\": \"string\", \"optional\": true, \"description\": \"Tracing options\" },\n                    { \"name\": \"bufferUsageReportingInterval\", \"type\": \"number\", \"optional\": true, \"description\": \"If set, the agent will issue bufferUsage events at this interval, specified in milliseconds\" }\n                ],\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"end\",\n                \"async\": true,\n                \"description\": \"Stop trace events collection.\",\n                \"handlers\": [\"browser\", \"renderer\"]\n            },\n            {\n                \"name\": \"getCategories\",\n                \"async\": true,\n                \"description\": \"Gets supported tracing categories.\",\n                \"returns\": [\n                    { \"name\": \"categories\", \"type\": \"array\", \"items\": { \"type\": \"string\" }, \"description\": \"A list of supported tracing categories.\" }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"dataCollected\",\n                \"parameters\": [\n                    { \"name\": \"value\", \"type\": \"array\", \"items\": { \"type\": \"object\" } }\n                ],\n                \"description\": \"Contains an bucket of collected trace events. When tracing is stopped collected events will be send as a sequence of dataCollected events followed by tracingComplete event.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"tracingComplete\",\n                \"description\": \"Signals that tracing is stopped and there is no trace buffers pending flush, all data were delivered via dataCollected events.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"bufferUsage\",\n                \"parameters\": [\n                    { \"name\": \"percentFull\", \"type\": \"number\", \"optional\": true, \"description\": \"A number in range [0..1] that indicates the used size of event buffer as a fraction of its total size.\" },\n                    { \"name\": \"eventCount\", \"type\": \"number\", \"optional\": true, \"description\": \"An approximate number of events in the trace log.\" },\n                    { \"name\": \"value\", \"type\": \"number\", \"optional\": true, \"description\": \"A number in range [0..1] that indicates the used size of event buffer as a fraction of its total size.\" }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Power\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"PowerEvent\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"type\", \"type\": \"string\", \"description\": \"Power Event Type.\" },\n                    { \"name\": \"timestamp\", \"type\": \"number\", \"description\": \"Power Event Time, in milliseconds.\" },\n                    { \"name\": \"value\", \"type\": \"number\", \"description\": \"Power Event Value.\" }\n                ],\n            \"description\": \"PowerEvent item\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"start\",\n                \"description\": \"Start power events collection.\",\n                \"parameters\": [],\n                \"handlers\": [\"browser\", \"frontend\"]\n            },\n            {\n                \"name\": \"end\",\n                \"description\": \"Stop power events collection.\",\n                \"parameters\": [],\n                \"handlers\": [\"browser\", \"frontend\"]\n            },\n            {\n                \"name\": \"canProfilePower\",\n                \"description\": \"Tells whether power profiling is supported.\",\n                \"returns\": [\n                  { \"name\": \"result\", \"type\": \"boolean\", \"description\": \"True if power profiling is supported.\" }\n                ],\n                \"hidden\": true,\n                \"handlers\": [\"browser\", \"frontend\"]\n            },\n            {\n                \"name\": \"getAccuracyLevel\",\n                \"description\": \"Describes the accuracy level of the data provider.\",\n                \"returns\": [\n                    { \"name\": \"result\", \"type\": \"string\", \"enum\": [\"high\", \"moderate\", \"low\"] }\n                ],\n                \"handlers\": [\"browser\", \"frontend\"]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"dataAvailable\",\n                \"parameters\": [\n                    {\"name\": \"value\", \"type\": \"array\", \"items\": { \"$ref\": \"PowerEvent\" }, \"description\": \"List of power events.\" }\n                ],\n                \"handlers\": [\"browser\", \"frontend\"]\n            }\n        ]\n    },\n    {\n        \"domain\": \"Animation\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"Animation\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"id\", \"type\": \"string\", \"description\": \"<code>Animation</code>'s id.\" },\n                    { \"name\": \"pausedState\", \"type\": \"boolean\", \"hidden\": \"true\", \"description\": \"<code>Animation</code>'s internal paused state.\" },\n                    { \"name\": \"playState\", \"type\": \"string\", \"description\": \"<code>Animation</code>'s play state.\" },\n                    { \"name\": \"playbackRate\", \"type\": \"number\", \"description\": \"<code>Animation</code>'s playback rate.\" },\n                    { \"name\": \"startTime\", \"type\": \"number\", \"description\": \"<code>Animation</code>'s start time.\" },\n                    { \"name\": \"currentTime\", \"type\": \"number\", \"description\": \"<code>Animation</code>'s current time.\" },\n                    { \"name\": \"source\", \"$ref\": \"AnimationEffect\", \"description\": \"<code>Animation</code>'s source animation node.\" },\n                    { \"name\": \"type\", \"type\": \"string\", \"enum\": [\"CSSTransition\", \"CSSAnimation\", \"WebAnimation\"], \"description\": \"Animation type of <code>Animation</code>.\" }\n                ],\n                \"description\": \"Animation instance.\"\n            },\n            {\n                \"id\": \"AnimationEffect\",\n                \"type\": \"object\",\n                \"hidden\": true,\n                \"properties\": [\n                    { \"name\": \"delay\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s delay.\" },\n                    { \"name\": \"endDelay\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s end delay.\" },\n                    { \"name\": \"playbackRate\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s playbackRate.\" },\n                    { \"name\": \"iterationStart\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s iteration start.\" },\n                    { \"name\": \"iterations\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s iterations.\" },\n                    { \"name\": \"duration\", \"type\": \"number\", \"description\": \"<code>AnimationEffect</code>'s iteration duration.\" },\n                    { \"name\": \"direction\", \"type\": \"string\", \"description\": \"<code>AnimationEffect</code>'s playback direction.\" },\n                    { \"name\": \"fill\", \"type\": \"string\", \"description\": \"<code>AnimationEffect</code>'s fill mode.\" },\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"<code>AnimationEffect</code>'s name.\" },\n                    { \"name\": \"backendNodeId\", \"$ref\": \"DOM.BackendNodeId\", \"description\": \"<code>AnimationEffect</code>'s target node.\" },\n                    { \"name\": \"keyframesRule\", \"$ref\": \"KeyframesRule\", \"optional\": true, \"description\": \"<code>AnimationEffect</code>'s keyframes.\" },\n                    { \"name\": \"easing\", \"type\": \"string\", \"description\": \"<code>AnimationEffect</code>'s timing function.\" }\n                ],\n                \"description\": \"AnimationEffect instance\"\n            },\n            {\n                \"id\": \"KeyframesRule\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"optional\": true, \"description\": \"CSS keyframed animation's name.\" },\n                    { \"name\": \"keyframes\", \"type\": \"array\", \"items\": { \"$ref\": \"KeyframeStyle\" }, \"description\": \"List of animation keyframes.\" }\n                ],\n                \"description\": \"Keyframes Rule\"\n            },\n            {\n                \"id\": \"KeyframeStyle\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"offset\", \"type\": \"string\", \"description\": \"Keyframe's time offset.\" },\n                    { \"name\": \"easing\", \"type\": \"string\", \"description\": \"<code>AnimationEffect</code>'s timing function.\" }\n                ],\n                \"description\": \"Keyframe Style\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables animation domain notifications.\"\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables animation domain notifications.\"\n            },\n            {\n                \"name\": \"getPlaybackRate\",\n                \"returns\": [\n                    { \"name\": \"playbackRate\", \"type\": \"number\", \"description\": \"Playback rate for animations on page.\"}\n                ],\n                \"description\": \"Gets the playback rate of the document timeline.\"\n            },\n            {\n                \"name\": \"setPlaybackRate\",\n                \"parameters\": [\n                    { \"name\": \"playbackRate\", \"type\": \"number\", \"description\": \"Playback rate for animations on page\" }\n                ],\n                \"description\": \"Sets the playback rate of the document timeline.\"\n            },\n            {\n                \"name\": \"setCurrentTime\",\n                \"parameters\": [\n                    { \"name\": \"currentTime\", \"type\": \"number\", \"description\": \"Current time for the page animation timeline\" }\n                ],\n                \"description\": \"Sets the current time of the document timeline.\"\n            },\n            {\n                \"name\": \"setTiming\",\n                \"parameters\": [\n                    { \"name\": \"playerId\", \"type\": \"string\", \"description\": \"AnimationPlayer id.\" },\n                    { \"name\": \"duration\", \"type\": \"number\", \"description\": \"Duration of the animation.\" },\n                    { \"name\": \"delay\", \"type\": \"number\", \"description\": \"Delay of the animation.\" }\n                ],\n                \"description\": \"Sets the timing of an animation node.\"\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"animationCreated\",\n                \"parameters\": [\n                    { \"name\": \"player\", \"$ref\": \"Animation\", \"description\": \"Animation that was created.\" },\n                    { \"name\": \"resetTimeline\", \"type\": \"boolean\", \"description\": \"Whether the timeline should be reset.\" }\n                ],\n                \"description\": \"Event for each animation player that has been created.\"\n            },\n            {\n                \"name\": \"animationCanceled\",\n                \"parameters\": [\n                    { \"name\": \"id\", \"type\": \"string\", \"description\": \"Id of the Animation that was cancelled.\" }\n                ],\n                \"description\": \"Event for Animations in the frontend that have been cancelled.\"\n            }\n        ]\n    },\n    {\n        \"domain\": \"Accessibility\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"AXNodeId\",\n                \"type\": \"string\",\n                \"description\": \"Unique accessibility node identifier.\"\n            },\n            {\n                \"id\": \"AXValueType\",\n                \"type\": \"string\",\n                \"enum\": [ \"boolean\", \"tristate\", \"booleanOrUndefined\", \"idref\", \"idrefList\", \"integer\", \"number\", \"string\", \"token\", \"tokenList\", \"domRelation\", \"role\", \"internalRole\" ],\n                \"description\": \"Enum of possible property types.\"\n            },\n            {\n                \"id\": \"AXPropertySourceType\",\n                \"type\": \"string\",\n                \"enum\": [ \"attribute\", \"implicit\", \"style\" ],\n                \"description\": \"Enum of possible property sources.\"\n            },\n            {\n                \"id\": \"AXPropertySource\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"The name/label of this source.\" },\n                    { \"name\": \"sourceType\", \"$ref\": \"AXPropertySourceType\", \"description\": \"What type of source this is.\" },\n                    { \"name\": \"value\", \"type\": \"any\", \"description\": \"The value of this property source.\" },\n                    { \"name\": \"type\", \"$ref\": \"AXValueType\", \"description\": \"What type the value should be interpreted as.\" },\n                    { \"name\": \"invalid\", \"type\": \"boolean\", \"description\": \"Whether the value for this property is invalid.\", \"optional\": true },\n                    { \"name\": \"invalidReason\", \"type\": \"string\", \"description\": \"Reason for the value being invalid, if it is.\", \"optional\": true }\n                ],\n                \"description\": \"A single source for a computed AX property.\"\n            },\n            {\n                \"id\": \"AXRelatedNode\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"idref\", \"type\": \"string\", \"description\": \"The IDRef value provided, if any.\", \"optional\": true },\n                    { \"name\": \"backendNodeId\", \"$ref\": \"DOM.BackendNodeId\", \"description\": \"The BackendNodeId of the related node.\" }\n                ]\n            },\n            {\n                \"id\": \"AXProperty\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"name\", \"type\": \"string\", \"description\": \"The name of this property.\" },\n                    { \"name\": \"value\", \"$ref\": \"AXValue\", \"description\": \"The value of this property.\" }\n                ]\n            },\n            {\n                \"id\": \"AXValue\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"type\", \"$ref\": \"AXValueType\", \"description\": \"The type of this value.\" },\n\n                    { \"name\": \"value\", \"type\": \"any\", \"description\": \"The computed value of this property.\", \"optional\": true },\n                    { \"name\": \"relatedNodeValue\", \"$ref\": \"AXRelatedNode\", \"description\": \"The related node value, if any.\", \"optional\": true },\n                    { \"name\": \"relatedNodeArrayValue\", \"type\": \"array\", \"items\": { \"$ref\": \"AXRelatedNode\" }, \"description\": \"Multiple relted nodes, if applicable.\", \"optional\": true },\n                    { \"name\": \"sources\", \"type\": \"array\", \"items\": { \"$ref\": \"AXPropertySource\" }, \"description\": \"The sources which contributed to the computation of this property.\", \"optional\": true }\n                ],\n                \"description\": \"A single computed AX property.\"\n            },\n            {\n                \"id\": \"AXGlobalStates\",\n                \"type\": \"string\",\n                \"enum\": [ \"disabled\", \"hidden\", \"hiddenRoot\", \"invalid\" ],\n                \"description\": \"States which apply to every AX node.\"\n            },\n            {\n                \"id\": \"AXLiveRegionAttributes\",\n                \"type\": \"string\",\n                \"enum\": [ \"live\", \"atomic\", \"relevant\", \"busy\", \"root\" ],\n                \"description\": \"Attributes which apply to nodes in live regions.\"\n            },\n            {\n                \"id\": \"AXWidgetAttributes\",\n                \"type\": \"string\",\n                \"enum\": [ \"autocomplete\", \"haspopup\", \"level\", \"multiselectable\", \"orientation\", \"multiline\", \"readonly\", \"required\", \"valuemin\", \"valuemax\", \"valuetext\" ],\n                \"Description\": \"Attributes which apply to widgets.\"\n            },\n            {\n                \"id\": \"AXWidgetStates\",\n                \"type\": \"string\",\n                \"enum\": [ \"checked\", \"expanded\", \"pressed\", \"selected\" ],\n                \"description\": \"States which apply to widgets.\"\n            },\n            {\n                \"id\": \"AXRelationshipAttributes\",\n                \"type\": \"string\",\n                \"enum\": [ \"activedescendant\", \"flowto\", \"controls\", \"describedby\", \"labelledby\", \"owns\" ],\n                \"description\": \"Relationships between elements other than parent/child/sibling.\"\n            },\n            {\n                \"id\": \"AXNode\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"AXNodeId\", \"description\": \"Unique identifier for this node.\" },\n                    { \"name\": \"ignored\", \"type\": \"boolean\", \"description\": \"Whether this node is ignored for accessibility\" },\n                    { \"name\": \"ignoredReasons\", \"type\": \"array\", \"items\": { \"$ref\": \"AXProperty\" }, \"description\": \"Collection of reasons why this node is hidden.\", \"optional\": true },\n                    { \"name\": \"role\", \"$ref\": \"AXValue\", \"description\": \"This <code>Node</code>'s role, whether explicit or implicit.\", \"optional\": true},\n                    { \"name\": \"name\", \"$ref\": \"AXValue\", \"description\": \"The accessible name for this <code>Node</code>.\", \"optional\": true },\n                    { \"name\": \"description\", \"$ref\": \"AXValue\", \"description\": \"The accessible description for this <code>Node</code>.\", \"optional\": true },\n                    { \"name\": \"value\", \"$ref\": \"AXValue\", \"description\": \"The value for this <code>Node</code>.\", \"optional\": true },\n                    { \"name\": \"help\", \"$ref\": \"AXValue\", \"description\": \"Help.\", \"optional\": true },\n                    { \"name\": \"properties\", \"type\": \"array\", \"items\": { \"$ref\": \"AXProperty\" }, \"description\": \"All other properties\", \"optional\": true }\n                ],\n                \"description\": \"A node in the accessibility tree.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"getAXNode\",\n                \"parameters\": [\n                    { \"name\": \"nodeId\", \"$ref\": \"DOM.NodeId\", \"description\": \"ID of node to get accessibility node for.\" }\n                ],\n                \"returns\": [\n                    { \"name\": \"accessibilityNode\", \"$ref\": \"AXNode\", \"description\": \"The <code>Accessibility.AXNode</code> for this DOM node, if it exists.\", \"optional\": true }\n                ],\n                \"description\": \"Fetches the accessibility node for this DOM node, if it exists.\",\n                \"hidden\": true\n            }\n        ]\n    },\n    {\n        \"domain\": \"Security\",\n        \"description\": \"Security\",\n        \"hidden\": true,\n        \"types\": [\n            {\n                \"id\": \"SecurityState\",\n                \"type\": \"string\",\n                \"enum\": [\"unknown\", \"http\", \"insecure\", \"warning\", \"secure\"],\n                \"description\": \"The security level of a page or resource.\"\n            },\n            {\n                \"id\": \"SecurityStateExplanation\",\n                \"type\": \"object\",\n                \"properties\": [\n                    { \"name\": \"securityState\", \"$ref\": \"SecurityState\", \"description\": \"Security state representing the severity of the factor being explained.\" },\n                    { \"name\": \"summary\", \"type\": \"string\", \"description\": \"Short phrase describing the type of factor.\" },\n                    { \"name\": \"description\", \"type\": \"string\", \"description\": \"Full text explanation of the factor.\" }\n                ],\n                \"description\": \"An explanation of an factor contributing to the security state.\"\n            }\n        ],\n        \"commands\": [\n            {\n                \"name\": \"enable\",\n                \"description\": \"Enables tracking security state changes.\",\n                \"handlers\": [\"browser\"]\n            },\n            {\n                \"name\": \"disable\",\n                \"description\": \"Disables tracking security state changes.\",\n                \"handlers\": [\"browser\"]\n            }\n        ],\n        \"events\": [\n            {\n                \"name\": \"securityStateChanged\",\n                \"description\": \"The security state of the page changed.\",\n                \"parameters\": [\n                    { \"name\": \"securityState\", \"$ref\": \"SecurityState\", \"description\": \"Security state.\" },\n                    { \"name\": \"explanations\", \"type\": \"array\", \"items\": { \"$ref\": \"SecurityStateExplanation\" }, \"description\": \"List of explanations for the security state. If the overall security state is `insecure` or `warning`, at least one corresponding explanation should be included.\", \"optional\": true }\n                ],\n                \"handlers\": [\"browser\"]\n            }\n        ]\n    }]\n}"
  },
  {
    "path": "build-tools/readme.md",
    "content": "#scraper\nExample:\n\n```\n➜ node scraper.js protocol.json Debugger.FunctionDetails\n```\n\n... generates the `FunctionDetails` object defined in the `Debugger` namespace using the file `protocol.json`, which is downloaded from google code. \nNote: if not specified, the domain is implicit.\n\nHere's the code that was generated:\n\n```\npublic static class FunctionDetails {\n\t@JsonProperty\n\tpublic Location location;\n\n\t@JsonProperty(required = true)\n\tpublic String functionName;\n\n\t@JsonProperty(required = true)\n\tpublic boolean isGenerator;\n\n\t@JsonProperty\n\tpublic List<Scope> scopeChain;\n}\n\npublic static class Scope {\n\t@JsonProperty(required = true)\n\tpublic String type;\n\n\t@JsonProperty(required = true)\n\tpublic Runtime.RemoteObject object;\n}\n\n... and so on (omited for brevity)\n```\n"
  },
  {
    "path": "build-tools/scraper.js",
    "content": "var fs = require('fs');\n\nvar dependencies = [];\nvar alreadyGenerated = [];\nvar anonymousTypesToGenerate = [];\nvar anonymousTypeCount = 0;\n\nvar primitiveObjectTypes = {\n    'string': 'String',\n    'boolean': 'boolean',\n    'integer': 'int',\n    'number': 'double',\n    'any': 'Object'\n};\n\nvar primitiveOptionalObjectTypes = {\n    'string': 'String',\n    'boolean': 'Boolean',\n    'integer': 'Integer',\n    'number': 'Double',\n    'any': 'Object'\n};\n\nfunction tab(amount) {\n    amount = amount || 1;\n\n    // two spaces per indent\n    return new Array(amount + 1).join('  ');\n}\n\nfunction ret(amount) {\n    amount = amount || 1;\n    return new Array(amount + 1).join('\\r\\n');\n}\n\nfunction getAnonymousTypeName() {\n    return 'AnonType' + anonymousTypeCount++;\n}\n\nfunction isPrimitive(typeName) {\n    return primitiveObjectTypes.hasOwnProperty(typeName);\n}\n\nfunction isPrimitiveOrArray(typeName) {\n    return isPrimitive(typeName) || typeName == 'array';\n}\n\nfunction resolveName(name) {\n    var containsDot = name.indexOf('.') >= 0;\n    var split = name.split('.');\n\n    return {\n        domain: containsDot ? split[0] : '',\n        name: containsDot ? split[1] : name\n    };\n}\n\nfunction findCommandOrEventDefinition(resolved) {\n    var match = null;\n\n    documentation.domains.forEach(function (domain) {\n        if (!resolved.domain || resolved.domain == domain.domain) {\n            if (domain.types) {\n                var filterFunc = function(commandOrEvent) {\n                    return commandOrEvent.name == resolved.name;\n                };\n\n                var matches = domain.commands.filter(filterFunc);\n                matches = matches.concat(domain.events.filter(filterFunc));\n\n                if (matches.length > 0) {\n                    match = matches[0];\n                    resolved.domain = domain.domain;\n                }\n            }\n        }\n    });\n\n    return match;\n}\n\nfunction findTypeDefinition(resolved) {\n    var match = null;\n\n    documentation.domains.forEach(function (domain) {\n        if (!resolved.domain || resolved.domain == domain.domain) {\n            if (domain.types) {\n                var matches = domain.types.filter(function (type) {\n                    return type.id == resolved.name;\n                });\n\n                if (matches.length > 0) {\n                    match = matches[0];\n                    resolved.domain = domain.domain;\n                }\n            }\n        }\n    });\n\n    return match;\n}\n\nfunction generateJavaTypeEquivalent(currentType, prop) {\n    if (prop.hasOwnProperty('type')) {\n        // if it's a primitive type, then just map to the java equivalent\n        var type = primitiveObjectTypes.hasOwnProperty(prop.type) ? primitiveObjectTypes[prop.type] : prop.type;\n\n        // if the property is optional, it is nullable\n        if (prop.optional && primitiveOptionalObjectTypes.hasOwnProperty(prop.type)) {\n            type = primitiveOptionalObjectTypes[prop.type];\n        }\n\n        if (prop.type == 'array') {\n            if (prop.items.hasOwnProperty('$ref')) {\n                if (currentType != prop.items.$ref) {\n                    addDependencyIfNotGenerated(prop.items.$ref);\n                }\n                type = 'List<' + prop.items.$ref + '>';\n            } else {\n                type = getAnonymousTypeName();\n                var typeDef = {\n                    id: type,\n                    type: prop.items.type,\n                    properties: prop.items.properties\n                };\n\n                type = 'List<' + type + '>';\n\n                anonymousTypesToGenerate.push(typeDef);\n\n            }\n        }\n\n        return type;\n    } else {\n        var typeDefinition = findTypeDefinition(resolveName(prop.$ref));\n\n        if (!isPrimitiveOrArray(typeDefinition.type)) {\n            if (typeDefinition.type != currentType) {\n                addDependencyIfNotGenerated(prop.$ref);\n            }\n            return prop.$ref;\n        } else {\n            var type = typeDefinition.type;\n            var resolvedType = primitiveObjectTypes[type] || type;\n\n            if (prop.optional) {\n                if (primitiveOptionalObjectTypes.hasOwnProperty(type)) {\n                    resolvedType = primitiveOptionalObjectTypes[type];\n                }\n            }\n\n            if (type == 'array') {\n                addDependencyIfNotGenerated(typeDefinition.items.$ref);\n                return 'List<' + typeDefinition.items.$ref + '>';\n            }\n\n            return resolvedType;\n        }\n\n    }\n}\n\nfunction generateJavaClassForType(typeDefinition) {\n    var result = 'public static class ' + typeDefinition.id + ' {';\n\n    if (typeDefinition.properties != undefined) {\n        typeDefinition.properties.forEach(function (prop) {\n            result += ret() + tab() + '\\@JsonProperty';\n    \n            if (!prop.optional) {\n                result += '(required = true)';\n            }\n    \n            result += ret();\n    \n            result += tab() + 'public ' + generateJavaTypeEquivalent(typeDefinition.id, prop) + ' ' + prop.name + ';' + ret();\n        });\n    } else {\n        result += ret();\n    }\n\n    result += '}';\n\n    return result;\n}\n\nfunction addDependencyIfNotGenerated(dependencyString) {\n    var resolved = resolveName(dependencyString);\n\n    if (!dependencyExists(resolved) && !isPrimitiveOrArray(findTypeDefinition(resolved).type)) {\n        dependencies.push(resolved);\n    }\n}\n\nfunction dependencyExists(resolvedDependency) {\n    return dependencies.filter(function (existing) {\n            return resolvedDependency.name == existing.name && resolvedDependency.domain == existing.domain;\n        }).length != 0 ||\n        alreadyGenerated.filter(function (existing) {\n            return resolvedDependency.name == existing.name && resolvedDependency.domain == existing.domain;\n        }).length != 0;\n}\n\nfunction generateCommandOrEvent(commandDef) {\n    var className = commandDef.name.charAt(0).toUpperCase() + commandDef.name.slice(1);\n\n    var hasParams = !!commandDef.parameters;\n    var hasReturns = !!commandDef.returns;\n\n    var paramsTypeName = className + 'Request';\n    var returnsTypeName = className + 'Response';\n\n    var result = '' +\n        '@ChromeDevtoolsMethod' + ret() +\n        'public JsonRpcResult ' + commandDef.name + '(JsonRpcPeer peer, JSONObject params) {' + ret();\n\n    if (hasParams) {\n        result += '' +\n            tab(1) + 'final ' + paramsTypeName + ' = mObjectMapper.convertValue' + ret() +\n            tab(2) + 'params,' + ret() +\n            tab(2) + paramsTypeName + '.type);' + ret(2);\n    }\n\n    if (hasReturns) {\n        result += '' +\n            tab() + 'final ' + returnsTypeName + 'response = new ' + returnsTypeName + '();' + ret() +\n            tab() + 'return response;' + ret();\n    }\n\n    result += '}' + ret(2);\n\n    if (hasParams) {\n        result += generateType({\n                id: paramsTypeName,\n                properties: commandDef.parameters\n            }) + ret(2);\n    }\n\n    if (hasReturns) {\n        result += generateType({\n            id: returnsTypeName,\n            properties: commandDef.returns\n        });\n    }\n\n    return result;\n}\n\nfunction generateDependencies() {\n    var result = '';\n\n    while (dependencies.length > 0 || anonymousTypesToGenerate.length > 0) {\n        while (dependencies.length > 0) {\n            result += ret(2)\n                + generateType(\n                    findTypeDefinition(\n                        dependencies.pop()));\n        }\n\n        while (anonymousTypesToGenerate.length > 0) {\n            result += ret(2) +\n                generateType(anonymousTypesToGenerate.pop());\n        }\n    }\n\n    return result;\n}\n\nfunction generateType(typeDef) {\n    alreadyGenerated.push(resolveName(typeDef.id));\n\n    if (isPrimitiveOrArray(typeDef.type)) {\n        return 'The type \\'' + typeDef.id + '\\' is a primitive type (' + typeDef.type + '), so no class generation is necessary.';\n    }\n\n    var result = generateJavaClassForType(typeDef);\n\n    result += generateDependencies();\n\n    return result;\n}\n\nfunction generate(name) {\n    var resolved = resolveName(name);\n    var command = findCommandOrEventDefinition(resolved);\n\n    if (command != null) {\n        return generateCommandOrEvent(command);\n    }\n\n    var type = findTypeDefinition(resolved);\n\n    if (type != null) {\n        return generateType(type);\n    }\n\n    return 'no command or type \\'' + name + '\\' found';\n}\n\n// first two args are path to node and to this file\nvar arguments = process.argv.slice(2);\n\n\nif (arguments.length == 0) {\n    console.log('usage:' + ret() +\n        tab() + 'node scraper.js path_to_protocol_json name_of_method_or_type' + ret() +\n        '  node scraper.js path_to_protocol_json domain.name_of_method_or_type' + ret() +\n        'description:' + ret() +\n        tab() + 'This script generates Java code representing a type or method defined in `protocol.json`,' +\n        ' which can be found at: https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/devtools/protocol.json')\n} else {\n    var documentation = JSON.parse(fs.readFileSync(arguments[0], {encoding: 'utf8'}));\n    console.log(generate(arguments[1]));\n}\n\n"
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        google()\n        jcenter()\n    }\n    dependencies {\n        classpath 'com.android.tools.build:gradle:4.1.2'\n    }\n}\n\nallprojects {\n    repositories {\n        google()\n        jcenter()\n    }\n}\n\next {\n    compileSdkVersion = 30\n    targetSdkVersion = 30\n}"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Mon Mar 08 22:57:34 PST 2021\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.5-bin.zip\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\norg.gradle.jvmargs=-Xmx1536m\n\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n\nandroid.useAndroidX=true\n\nVERSION_NAME=1.6.1-SNAPSHOT\nGROUP=com.facebook.stetho\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\r\n@rem ##########################################################################\r\n@rem\r\n@rem  Gradle startup script for Windows\r\n@rem\r\n@rem ##########################################################################\r\n\r\n@rem Set local scope for the variables with windows NT shell\r\nif \"%OS%\"==\"Windows_NT\" setlocal\r\n\r\nset DIRNAME=%~dp0\r\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\r\nset APP_BASE_NAME=%~n0\r\nset APP_HOME=%DIRNAME%\r\n\r\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\r\nset DEFAULT_JVM_OPTS=\r\n\r\n@rem Find java.exe\r\nif defined JAVA_HOME goto findJavaFromJavaHome\r\n\r\nset JAVA_EXE=java.exe\r\n%JAVA_EXE% -version >NUL 2>&1\r\nif \"%ERRORLEVEL%\" == \"0\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:findJavaFromJavaHome\r\nset JAVA_HOME=%JAVA_HOME:\"=%\r\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\r\n\r\nif exist \"%JAVA_EXE%\" goto init\r\n\r\necho.\r\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\r\necho.\r\necho Please set the JAVA_HOME variable in your environment to match the\r\necho location of your Java installation.\r\n\r\ngoto fail\r\n\r\n:init\r\n@rem Get command-line arguments, handling Windows variants\r\n\r\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\r\n\r\n:win9xME_args\r\n@rem Slurp the command line arguments.\r\nset CMD_LINE_ARGS=\r\nset _SKIP=2\r\n\r\n:win9xME_args_slurp\r\nif \"x%~1\" == \"x\" goto execute\r\n\r\nset CMD_LINE_ARGS=%*\r\n\r\n:execute\r\n@rem Setup the command line\r\n\r\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\r\n\r\n@rem Execute Gradle\r\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\r\n\r\n:end\r\n@rem End local scope for the variables with windows NT shell\r\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\r\n\r\n:fail\r\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\r\nrem the _cmd.exe /c_ return code!\r\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\r\nexit /b 1\r\n\r\n:mainEnd\r\nif \"%OS%\"==\"Windows_NT\" endlocal\r\n\r\n:omega\r\n"
  },
  {
    "path": "release.gradle",
    "content": "apply plugin: 'maven'\napply plugin: 'signing'\n\ndef isReleaseBuild() {\n    return VERSION_NAME.contains(\"SNAPSHOT\") == false\n}\n\ndef getMavenRepositoryUrl() {\n    return hasProperty('repositoryUrl') ? property('repositoryUrl') : \"https://oss.sonatype.org/service/local/staging/deploy/maven2/\"\n}\n\ndef getMavenRepositoryUsername() {\n    return hasProperty('repositoryUsername') ? property('repositoryUsername') : \"\"\n}\n\ndef getMavenRepositoryPassword() {\n    return hasProperty('repositoryPassword') ? property('repositoryPassword') : \"\"\n}\n\ndef configureStethoPom(def pom) {\n    pom.whenConfigured {\n        applyOptionalDeps it, getOptionalDeps()\n    }\n    pom.project {\n        name POM_NAME\n        artifactId POM_ARTIFACT_ID\n        packaging POM_PACKAGING\n        description 'Stetho Debugging Platform for Android'\n        url 'https://github.com/facebook/stetho'\n\n        scm {\n            url 'https://github.com/facebook/stetho.git'\n            connection 'scm:git:https://github.com/facebook/stetho.git'\n            developerConnection 'scm:git:git@github.com:facebook/stetho.git'\n        }\n\n        licenses {\n            license {\n                name 'MIT License'\n                url 'https://github.com/facebook/stetho/blob/master/LICENSE'\n                distribution 'repo'\n            }\n        }\n\n        developers {\n            developer {\n                id 'facebook'\n                name 'Facebook'\n            }\n        }\n    }\n}\n\n// Hack to modify the resulting pom's dependencies to use\n// <optional>true</optional> where appropriate.\ndef applyOptionalDeps(def pom, def optionalDeps) {\n    pom.dependencies.each { dep ->\n        def artifactLabel = dep.groupId + ':' + dep.artifactId\n        if (optionalDeps.contains(artifactLabel)) {\n            dep.optional = true\n        }\n    }\n}\n\ndef getOptionalDeps() {\n    if (hasProperty('POM_OPTIONAL_DEPS')) {\n        return property('POM_OPTIONAL_DEPS').split(',') as Set\n    } else {\n        return []\n    }\n}\n\nafterEvaluate { project ->\n    task androidJavadoc(type: Javadoc) {\n        source = android.sourceSets.main.java.srcDirs\n        classpath += files(android.bootClasspath)\n        if (JavaVersion.current().isJava8Compatible()) {\n            options.addStringOption('Xdoclint:none', '-quiet')\n        }\n    }\n\n    task androidJavadocJar(type: Jar, dependsOn: androidJavadoc) {\n        classifier = 'javadoc'\n        from androidJavadoc.destinationDir\n    }\n\n    task androidSourcesJar(type: Jar) {\n        classifier = 'sources'\n        from android.sourceSets.main.java.srcDirs\n    }\n\n    android.libraryVariants.all { variant ->\n        def name = variant.name.capitalize()\n        task \"jar${name}\"(type: Jar, dependsOn: variant.javaCompile) {\n            from variant.javaCompile.destinationDir\n        }\n    }\n\n    artifacts {\n        archives androidJavadocJar\n        archives androidSourcesJar\n    }\n\n    version = VERSION_NAME\n    group = GROUP\n\n    signing {\n        required { isReleaseBuild() && gradle.taskGraph.hasTask(\"uploadArchives\") }\n        sign configurations.archives\n    }\n\n    uploadArchives {\n        configuration = configurations.archives\n        repositories.mavenDeployer {\n            beforeDeployment {\n                MavenDeployment deployment -> signing.signPom(deployment)\n            }\n\n            repository(url: getMavenRepositoryUrl()) {\n                authentication(\n                        userName: getMavenRepositoryUsername(),\n                        password: getMavenRepositoryPassword())\n\n            }\n\n            configureStethoPom pom\n        }\n    }\n\n    task installArchives(type: Upload) {\n        configuration = configurations.archives\n        repositories {\n            mavenDeployer {\n                repository url: \"file://${System.properties['user.home']}/.m2/repository\"\n                configureStethoPom pom\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "scripts/.gitignore",
    "content": "__pycache__\n"
  },
  {
    "path": "scripts/dumpapp",
    "content": "#!/usr/bin/env python3\n\nimport sys\nimport os\nimport io\n\nfrom stetho_open import *\n\ndef main():\n  # Manually parse out -p <process>, all other option handling occurs inside\n  # the hosting process.\n\n  # Connect to the process passed in via -p. If that is not supplied fallback\n  # the process defined in STETHO_PROCESS. If neither are defined throw.\n  process = os.environ.get('STETHO_PROCESS')\n\n  args = sys.argv[1:]\n  if len(args) > 0 and (args[0] == '-p' or args[0] == '--process'):\n    if len(args) < 2:\n      sys.exit('Missing <process>')\n    else:\n      process = args[1]\n      args = args[2:]\n\n  # Connect to ANDROID_SERIAL if supplied, otherwise fallback to any\n  # transport.\n  device = os.environ.get('ANDROID_SERIAL')\n\n  # Connect on the overridden port if specified\n  port = get_adb_server_port()\n\n  try:\n    sock = stetho_open(device, process, port)\n\n    # Send dumpapp hello (DUMP + version=1)\n    sock.send(b'DUMP' + struct.pack('!l', 1))\n\n    enter_frame = b'!' + struct.pack('!l', len(args))\n    for arg in args:\n      argAsUTF8 = arg.encode('utf-8')\n      enter_frame += struct.pack(\n          '!H' + str(len(argAsUTF8)) + 's',\n          len(argAsUTF8),\n          argAsUTF8)\n    sock.send(enter_frame)\n\n    read_frames(sock)\n  except HumanReadableError as e:\n    sys.exit(e)\n  except BrokenPipeError as e:\n    sys.exit(0)\n  except KeyboardInterrupt:\n    sys.exit(1)\n\ndef read_frames(sock):\n  while True:\n    # All frames have a single character code followed by a big-endian int\n    code = read_input(sock, 1, 'code')\n    n = struct.unpack('!l', read_input(sock, 4, 'int4'))[0]\n\n    if code == b'1':\n      if n > 0:\n        sys.stdout.buffer.write(read_input(sock, n, 'stdout blob'))\n        sys.stdout.buffer.flush()\n    elif code == b'2':\n      if n > 0:\n        sys.stderr.buffer.write(read_input(sock, n, 'stderr blob'))\n        sys.stderr.buffer.flush()\n    elif code == b'_':\n      if n > 0:\n        data = sys.stdin.buffer.read(n)\n        if len(data) == 0:\n          sock.send(b'-' + struct.pack('!l', -1))\n        else:\n          sock.send(b'-' + struct.pack('!l', len(data)) + data)\n    elif code == b'x':\n      sys.exit(n)\n    else:\n      if raise_on_eof:\n        raise IOError('Unexpected header: %s' % code)\n      break\n\nif __name__ == '__main__':\n  main()\n"
  },
  {
    "path": "scripts/hprof_dump.sh",
    "content": "#!/bin/bash\n\nDIR=\"$(cd \"$(dirname \"${BASH_SOURCE[0]}\")\" && pwd)\"\nDUMPAPP=\"$DIR/dumpapp\"\n\nset -e\n\n# This will generate an hprof on the device, download it locally, convert the\n# hprof to the standard format, and store it in the current working directory.\n# The resulting file can be explored with a tool such as the standalone Eclipse\n# MemoryAnalyzer: https://eclipse.org/mat/\n\nif [[ -z \"$1\" ]]; then\n  OUTFILE=\"out.hprof\"\nelse\n  OUTFILE=$1\nfi\nTEMPFILE=\"${OUTFILE}-dalvik.tmp\"\n\necho \"Generating hprof on device (this can take a while)...\"\n$DUMPAPP \"$@\" hprof - > ${TEMPFILE}\n\necho \"Converting $TEMPFILE to standard format...\"\nhprof-conv $TEMPFILE $OUTFILE\nrm $TEMPFILE\n\necho \"Stored ${OUTFILE}\"\n"
  },
  {
    "path": "scripts/stetho_open.py",
    "content": "#!/usr/bin/env python3\n###############################################################################\n##\n## Simple utility class to create a forwarded socket connection to an\n## application's stetho domain socket.\n##\n## Usage:\n##\n##   sock = stetho_open(\n##       device='<serial-no>',\n##       process='com.facebook.stetho.sample')\n##   doHttp(sock)\n##\n###############################################################################\n\nimport os\nimport socket\nimport struct\nimport re\n\ndef get_adb_server_port_from_server_socket():\n  socket_spec = os.environ.get('ADB_SERVER_SOCKET')\n  if not socket_spec:\n      return None\n  if not socket_spec.startswith('tcp:'):\n    raise HumanReadableError(\n      'Invalid or unsupported socket spec \\'%s\\' specified in ADB_SERVER_SOCKET.' % (\n        socket_spec))\n  return socket_spec.split(':')[-1]\n\ndef get_adb_server_port():\n  defaultPort = 5037\n  portStr = get_adb_server_port_from_server_socket() or os.environ.get('ANDROID_ADB_SERVER_PORT')\n  if portStr is None:\n    return defaultPort\n  elif portStr.isdigit():\n    return int(portStr)\n  else:\n    raise HumanReadableError(\n      'Invalid integer \\'%s\\' specified in ANDROID_ADB_SERVER_PORT or ADB_SERVER_SOCKET.' % (\n        portStr))\n\ndef stetho_open(device=None, process=None, port=None):\n  if port is None:\n    port = get_adb_server_port()\n\n  adb = _connect_to_device(device, port)\n\n  socket_name = None\n  if process is None:\n    socket_name = _find_only_stetho_socket(device, port)\n  else:\n    socket_name = _format_process_as_stetho_socket(process)\n\n  try:\n    adb.select_service('localabstract:%s' % (socket_name))\n  except SelectServiceError as e:\n    raise HumanReadableError(\n        'Failure to target process %s: %s (is it running?)' % (\n            process, e.reason))\n\n  return adb.sock\n\ndef read_input(sock, n, tag):\n  data = b'';\n  while len(data) < n:\n    incoming_data = sock.recv(n - len(data))\n    if len(incoming_data) == 0:\n      break\n    data += incoming_data\n  if len(data) != n:\n    raise IOError('Unexpected end of stream while reading %s.' % tag)\n  return data\n\ndef _find_only_stetho_socket(device, port):\n  adb = _connect_to_device(device, port)\n  try:\n    adb.select_service('shell:cat /proc/net/unix')\n    last_stetho_socket_name = None\n    process_names = []\n    for line in adb.sock.makefile():\n      row = re.split(r'\\s+', line.rstrip())\n      if len(row) < 8:\n        continue\n      socket_name = row[7]\n      if not socket_name.startswith('@stetho_'):\n        continue\n      # Filter out entries that are not server sockets\n      if int(row[3], 16) != 0x10000 or int(row[5]) != 1:\n        continue\n      last_stetho_socket_name = socket_name[1:]\n      process_names.append(\n          _parse_process_from_stetho_socket(socket_name))\n    if len(process_names) > 1:\n      raise HumanReadableError(\n          'Multiple stetho-enabled processes available:%s\\n' % (\n              '\\n\\t'.join([''] + list(set(process_names)))) +\n          'Use -p <process> or the environment variable STETHO_PROCESS to ' +\n          'select one')\n    elif last_stetho_socket_name == None:\n      raise HumanReadableError('No stetho-enabled processes running')\n    else:\n      return last_stetho_socket_name\n  finally:\n    adb.sock.close()\n\ndef _connect_to_device(device=None, port=None):\n  if port is None:\n    raise HumanReadableError('Must specify a port when calling _connect_to_device')\n\n  adb = AdbSmartSocketClient()\n  adb.connect(port)\n\n  try:\n    if device is None:\n      adb.select_service('host:transport-any')\n    else:\n      adb.select_service('host:transport:%s' % (device))\n\n    return adb\n  except SelectServiceError as e:\n    raise HumanReadableError(\n        'Failure to target device %s: %s' % (device, e.reason))\n\ndef _parse_process_from_stetho_socket(socket_name):\n  m = re.match(\"^\\@stetho_(.+)_devtools_remote$\", socket_name)\n  if m is None:\n    raise Exception('Unexpected Stetho socket formatting: %s' % (socket_name))\n  return m.group(1)\n\ndef _format_process_as_stetho_socket(process):\n  return 'stetho_%s_devtools_remote' % (process)\n\nclass AdbSmartSocketClient(object):\n  \"\"\"Implements the smartsockets system defined by:\n  https://android.googlesource.com/platform/system/core/+/master/adb/protocol.txt\n  \"\"\"\n\n  def __init__(self):\n    pass\n\n  def connect(self, port=5037):\n    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    try:\n      sock.connect(('localhost', port))\n    except ConnectionRefusedError:\n      sock = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)\n      sock.connect(('localhost', port))\n    self.sock = sock\n\n  def select_service(self, service):\n    message = '%04x%s' % (len(service), service)\n    self.sock.send(message.encode('ascii'))\n    status = read_input(self.sock, 4, \"status\")\n    if status == b'OKAY':\n      # All good...\n      pass\n    elif status == b'FAIL':\n      reason_len = int(read_input(self.sock, 4, \"fail reason\"), 16)\n      reason = read_input(self.sock, reason_len, \"fail reason lean\").decode('ascii')\n      raise SelectServiceError(reason)\n    else:\n      raise Exception('Unrecognized status=%s' % (status))\n\nclass SelectServiceError(Exception):\n  def __init__(self, reason):\n    self.reason = reason\n\n  def __str__(self):\n    return repr(self.reason)\n\nclass HumanReadableError(Exception):\n  def __init__(self, reason):\n    self.reason = reason\n\n  def __str__(self):\n    return self.reason\n"
  },
  {
    "path": "settings.gradle",
    "content": "include ':stetho'\ninclude ':stetho-urlconnection'\ninclude ':stetho-okhttp'\ninclude ':stetho-okhttp3'\ninclude ':stetho-js-rhino'\ninclude ':stetho-sample'\ninclude ':stetho-timber'\n"
  },
  {
    "path": "stetho/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n        consumerProguardFiles 'proguard-consumer.pro'\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n}\n\ndependencies {\n    implementation 'commons-cli:commons-cli:1.2'\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n    implementation 'androidx.annotation:annotation:1.1.0'\n\n    // Optional: reflection is used to test whether Fragment (and the transient AndroidX Core) are actually present.\n    implementation 'androidx.appcompat:appcompat:1.2.0' // optional\n\n    testImplementation 'junit:junit:4.12'\n    testImplementation('org.robolectric:robolectric:2.4') {\n        exclude module: 'commons-logging'\n        exclude module: 'httpclient'\n    }\n    testImplementation 'org.powermock:powermock-api-mockito:1.6.6'\n    testImplementation 'org.powermock:powermock-module-junit4:1.6.6'\n}\n\napply from: rootProject.file('release.gradle')\n\nandroid.libraryVariants.all { variant ->\n    def name = variant.name.capitalize()\n\n    // Ugly kludge to rename license files in the bundled commons-cli\n    // dependency so that they do not appear to describe Stetho's license.\n    task \"tidyCommonsCli${name}\"(type: Copy) {\n        from {\n            variant.javaCompile.classpath.findAll {\n                it.getName() == 'commons-cli-1.2.jar'\n            }.collect {\n                zipTree(it)\n            }\n        }\n        into \"build/commons-cli-tidy-${name}\"\n        rename 'LICENSE', 'commons-cli-LICENSE'\n        rename 'NOTICE', 'commons-cli-NOTICE'\n    }\n\n    task \"metainf${name}\"(type: Copy) {\n        from rootProject.file('LICENSE')\n        into \"build/metainf-${name}/META-INF\"\n    }\n\n    task \"fatjar${name}\"(type: Jar, dependsOn: [ \"jar${name}\", \"tidyCommonsCli${name}\", \"metainf${name}\" ]) {\n        classifier = 'fatjar'\n        from variant.javaCompile.destinationDir\n        from \"build/commons-cli-tidy-${name}\"\n        from \"build/metainf-${name}\"\n        exclude \"android/support/**/*\"\n    }\n}\n"
  },
  {
    "path": "stetho/gradle.properties",
    "content": "POM_NAME=Stetho\nPOM_ARTIFACT_ID=stetho\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho/proguard-consumer.pro",
    "content": "-keep class com.facebook.stetho.** { *; }\n-dontwarn com.facebook.stetho.**\n"
  },
  {
    "path": "stetho/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/DumperPluginsProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho;\n\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\n/**\n * Provider interface to lazily supply dumpers to be initialized on demand.  It is critical\n * that the main initialization flow of Stetho perform no actual work beyond simply\n * binding a socket and starting the listener thread.\n */\npublic interface DumperPluginsProvider {\n  Iterable<DumperPlugin> get();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/InspectorModulesProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho;\n\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\n\npublic interface InspectorModulesProvider {\n  Iterable<ChromeDevtoolsDomain> get();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/Stetho.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho;\n\nimport android.app.Application;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.os.Build;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.dumpapp.DumpappHttpSocketLikeHandler;\nimport com.facebook.stetho.dumpapp.DumpappSocketLikeHandler;\nimport com.facebook.stetho.dumpapp.Dumper;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\nimport com.facebook.stetho.dumpapp.plugins.CrashDumperPlugin;\nimport com.facebook.stetho.dumpapp.plugins.FilesDumperPlugin;\nimport com.facebook.stetho.dumpapp.plugins.HprofDumperPlugin;\nimport com.facebook.stetho.dumpapp.plugins.SharedPreferencesDumperPlugin;\nimport com.facebook.stetho.inspector.DevtoolsSocketHandler;\nimport com.facebook.stetho.inspector.console.RuntimeReplFactory;\nimport com.facebook.stetho.inspector.database.ContentProviderDatabaseDriver;\nimport com.facebook.stetho.inspector.database.DatabaseDriver2Adapter;\nimport com.facebook.stetho.inspector.database.DatabaseFilesProvider;\nimport com.facebook.stetho.inspector.database.DefaultDatabaseConnectionProvider;\nimport com.facebook.stetho.inspector.database.DefaultDatabaseFilesProvider;\nimport com.facebook.stetho.inspector.database.SqliteDatabaseDriver;\nimport com.facebook.stetho.inspector.elements.DescriptorProvider;\nimport com.facebook.stetho.inspector.elements.Document;\nimport com.facebook.stetho.inspector.elements.DocumentProviderFactory;\nimport com.facebook.stetho.inspector.elements.android.ActivityTracker;\nimport com.facebook.stetho.inspector.elements.android.AndroidDocumentConstants;\nimport com.facebook.stetho.inspector.elements.android.AndroidDocumentProviderFactory;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.module.CSS;\nimport com.facebook.stetho.inspector.protocol.module.Console;\nimport com.facebook.stetho.inspector.protocol.module.DOM;\nimport com.facebook.stetho.inspector.protocol.module.DOMStorage;\nimport com.facebook.stetho.inspector.protocol.module.Database;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseConstants;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDriver2;\nimport com.facebook.stetho.inspector.protocol.module.Debugger;\nimport com.facebook.stetho.inspector.protocol.module.HeapProfiler;\nimport com.facebook.stetho.inspector.protocol.module.Inspector;\nimport com.facebook.stetho.inspector.protocol.module.Network;\nimport com.facebook.stetho.inspector.protocol.module.Page;\nimport com.facebook.stetho.inspector.protocol.module.Profiler;\nimport com.facebook.stetho.inspector.protocol.module.Runtime;\nimport com.facebook.stetho.inspector.protocol.module.Worker;\nimport com.facebook.stetho.inspector.runtime.RhinoDetectingRuntimeReplFactory;\nimport com.facebook.stetho.server.AddressNameHelper;\nimport com.facebook.stetho.server.LazySocketHandler;\nimport com.facebook.stetho.server.LocalSocketServer;\nimport com.facebook.stetho.server.ProtocolDetectingSocketHandler;\nimport com.facebook.stetho.server.ServerManager;\nimport com.facebook.stetho.server.SocketHandler;\nimport com.facebook.stetho.server.SocketHandlerFactory;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.annotation.Nullable;\n\n/**\n * Initialization and configuration entry point for the Stetho debugging system.  Simple usage with\n * default plugins and features enabled:\n * <p />\n * <pre>\n *   Stetho.initializeWithDefaults(context)\n * </pre>\n * <p />\n * For more advanced configuration, see {@link #newInitializerBuilder(Context)} or\n * the {@code stetho-sample} for more information.\n */\npublic class Stetho {\n  private Stetho() {\n  }\n\n  /**\n   * Construct a simple initializer helper which allows you to customize stetho behaviour\n   * with additional features, plugins, etc.  See {@link DefaultDumperPluginsBuilder} and\n   * {@link DefaultInspectorModulesBuilder} for more information.\n   * <p />\n   * For simple use cases, consider {@link #initializeWithDefaults(Context)}.\n   */\n  public static InitializerBuilder newInitializerBuilder(Context context) {\n    return new InitializerBuilder(context);\n  }\n\n  /**\n   * Start the listening server.  Most of the heavy lifting initialization is deferred until the\n   * first socket connection is received, allowing this to be safely used for debug builds on\n   * even low-end hardware without noticeably affecting performance.\n   */\n  public static void initializeWithDefaults(final Context context) {\n    initialize(new Initializer(context) {\n      @Override\n      protected Iterable<DumperPlugin> getDumperPlugins() {\n        return new DefaultDumperPluginsBuilder(context).finish();\n      }\n\n      @Override\n      protected Iterable<ChromeDevtoolsDomain> getInspectorModules() {\n        return new DefaultInspectorModulesBuilder(context).finish();\n      }\n    });\n  }\n\n  /**\n   * Start the listening service, providing a custom initializer as per\n   * {@link #newInitializerBuilder}.\n   *\n   * @see #initializeWithDefaults(Context)\n   */\n  public static void initialize(final Initializer initializer) {\n    // Hook activity tracking so that after Stetho is attached we can figure out what\n    // activities are present.\n    boolean isTrackingActivities = ActivityTracker.get().beginTrackingIfPossible(\n        (Application)initializer.mContext.getApplicationContext());\n    if (!isTrackingActivities) {\n      LogUtil.w(\"Automatic activity tracking not available on this API level, caller must invoke \" +\n          \"ActivityTracker methods manually!\");\n    }\n\n    initializer.start();\n  }\n\n  public static DumperPluginsProvider defaultDumperPluginsProvider(final Context context) {\n    return new DumperPluginsProvider() {\n      @Override\n      public Iterable<DumperPlugin> get() {\n        return new DefaultDumperPluginsBuilder(context).finish();\n      }\n    };\n  }\n\n  public static InspectorModulesProvider defaultInspectorModulesProvider(final Context context) {\n    return new InspectorModulesProvider() {\n      @Override\n      public Iterable<ChromeDevtoolsDomain> get() {\n        return new DefaultInspectorModulesBuilder(context).finish();\n      }\n    };\n  }\n\n  private static class PluginBuilder<T> {\n    private final Set<String> mProvidedNames = new HashSet<>();\n    private final Set<String> mRemovedNames = new HashSet<>();\n\n    private final ArrayList<T> mPlugins = new ArrayList<>();\n\n    private boolean mFinished;\n\n    public void provide(String name, T plugin) {\n      throwIfFinished();\n      mPlugins.add(plugin);\n      mProvidedNames.add(name);\n    }\n\n    public void provideIfDesired(String name, T plugin) {\n      throwIfFinished();\n      if (!mRemovedNames.contains(name)) {\n        if (mProvidedNames.add(name)) {\n          mPlugins.add(plugin);\n        }\n      }\n    }\n\n    public void remove(String pluginName) {\n      throwIfFinished();\n      mRemovedNames.add(pluginName);\n    }\n\n    private void throwIfFinished() {\n      if (mFinished) {\n        throw new IllegalStateException(\"Must not continue to build after finish()\");\n      }\n    }\n\n    public Iterable<T> finish() {\n      mFinished = true;\n      return mPlugins;\n    }\n  }\n\n  /**\n   * Convenience mechanism to extend the default set of dumper plugins provided by Stetho.\n   *\n   * @see #initializeWithDefaults(Context)\n   */\n  public static final class DefaultDumperPluginsBuilder {\n    private final Context mContext;\n    private final PluginBuilder<DumperPlugin> mDelegate = new PluginBuilder<>();\n\n    public DefaultDumperPluginsBuilder(Context context) {\n      mContext = context;\n    }\n\n    public DefaultDumperPluginsBuilder provide(DumperPlugin plugin) {\n      mDelegate.provide(plugin.getName(), plugin);\n      return this;\n    }\n\n    private DefaultDumperPluginsBuilder provideIfDesired(DumperPlugin plugin) {\n      mDelegate.provideIfDesired(plugin.getName(), plugin);\n      return this;\n    }\n\n    public DefaultDumperPluginsBuilder remove(String pluginName) {\n      mDelegate.remove(pluginName);\n      return this;\n    }\n\n    public Iterable<DumperPlugin> finish() {\n      provideIfDesired(new HprofDumperPlugin(mContext));\n      provideIfDesired(new SharedPreferencesDumperPlugin(mContext));\n      provideIfDesired(new CrashDumperPlugin());\n      provideIfDesired(new FilesDumperPlugin(mContext));\n      return mDelegate.finish();\n    }\n  }\n\n  /**\n   * Configuration mechanism to customize the behaviour of the standard set of inspector\n   * modules satisfying the Chrome DevTools protocol.  Note that while it is still technically\n   * possible to manually control these modules, this API is strongly discouraged and will not\n   * necessarily be supported in future releases.\n   */\n  public static final class DefaultInspectorModulesBuilder {\n    private final Application mContext;\n    private final PluginBuilder<ChromeDevtoolsDomain> mDelegate = new PluginBuilder<>();\n\n    @Nullable private DocumentProviderFactory mDocumentProvider;\n    @Nullable private RuntimeReplFactory mRuntimeRepl;\n    @Nullable private DatabaseFilesProvider mDatabaseFilesProvider;\n    @Nullable private List<DatabaseDriver2> mDatabaseDrivers;\n    private boolean mExcludeSqliteDatabaseDriver;\n\n    public DefaultInspectorModulesBuilder(Context context) {\n      mContext = (Application)context.getApplicationContext();\n    }\n\n    /**\n     * Provide a custom document provider factory which can operate on the logical DOM exposed to\n     * Chrome in the Elements tab.  An Android View hierarchy instance is provided by\n     * default if this method is not called.\n     * <p />\n     * <i>Experimental.</i>  This API may be changed or removed in the future.\n     */\n    public DefaultInspectorModulesBuilder documentProvider(DocumentProviderFactory factory) {\n      mDocumentProvider = factory;\n      return this;\n    }\n\n    /**\n     * Provide a custom runtime REPL (read-eval-print loop) implementation for the Console tab.\n     * By default an implementation will be provided for you that automatically detects\n     * the existence of {@code stetho-js-rhino} (Mozilla's Rhino engine) and uses it if available.\n     * <p />\n     * To customize the Rhino implementation, see {@code stetho-js-rhino} documentation.\n     */\n    public DefaultInspectorModulesBuilder runtimeRepl(RuntimeReplFactory factory) {\n      mRuntimeRepl = factory;\n      return this;\n    }\n\n    /**\n     * Customize the location of database files that Stetho will propogate in the UI.  Android's\n     * {@link Context#getDatabasePath} method will be used by default if not overridden here.\n     *\n     * <p>This method is deprecated and instead it is recommended that you explicitly\n     * configure the {@link SqliteDatabaseDriver} as with:</p>\n     * <pre>\n     *   provideDatabaseDriver(\n     *     new SqliteDatabaseDriver(\n     *       context,\n     *       new MyDatabaseFilesProvider(...),\n     *       new DefaultDatabaseConnectionProvider(...)))\n     * </pre>\n     *\n     * @deprecated Use {@link #provideDatabaseDriver(DatabaseDriver2)} with\n     *     {@link SqliteDatabaseDriver} explicitly.\n     */\n    @Deprecated\n    public DefaultInspectorModulesBuilder databaseFiles(DatabaseFilesProvider provider) {\n      mDatabaseFilesProvider = provider;\n      return this;\n    }\n\n    /**\n     * @deprecated Convert your custom database driver to {@link DatabaseDriver2}.\n     */\n    @Deprecated\n    public DefaultInspectorModulesBuilder provideDatabaseDriver(Database.DatabaseDriver databaseDriver) {\n      provideDatabaseDriver(new DatabaseDriver2Adapter(databaseDriver));\n      return this;\n    }\n\n    /**\n     * Extend and provide additional database drivers.  Stetho provides two database\n     * drivers by default, with the option for developers to provide their own:\n     * <ol>\n     *   <li>{@link SqliteDatabaseDriver} - Presents SQLite databases.</li>\n     *   <li>{@link ContentProviderDatabaseDriver} - Configure and present content provider\n     *   data.</li>\n     * </ol>\n     *\n     * <p>Stetho assumes the {@link SqliteDatabaseDriver} should be installed if\n     * no driver of that type is provided and {@link #excludeSqliteDatabaseDriver} is not\n     * used.</p>\n     */\n    public DefaultInspectorModulesBuilder provideDatabaseDriver(DatabaseDriver2 databaseDriver) {\n      if (mDatabaseDrivers == null) {\n        mDatabaseDrivers = new ArrayList<>();\n      }\n      mDatabaseDrivers.add(databaseDriver);\n      return this;\n    }\n\n    /**\n     * Do not automatically provide the {@link SqliteDatabaseDriver} instance.  The instance\n     * is provided by default for backwards compatibility purposes and simplicity of API, with\n     * this API provided to disable that functionality if desired.\n     */\n    public DefaultInspectorModulesBuilder excludeSqliteDatabaseDriver(boolean exclude) {\n      mExcludeSqliteDatabaseDriver = exclude;\n      return this;\n    }\n\n    /**\n     * Provide either a new domain module or override an existing one.\n     *\n     * @deprecated This fine-grained control of the devtools modules is no longer supportable\n     *     given the lack of isolation of modules in the actual protocol (many cross dependencies\n     *     emerge when you implement more and more of the real protocol).\n     */\n    @Deprecated\n    public DefaultInspectorModulesBuilder provide(ChromeDevtoolsDomain module) {\n      mDelegate.provide(module.getClass().getName(), module);\n      return this;\n    }\n\n    private DefaultInspectorModulesBuilder provideIfDesired(ChromeDevtoolsDomain module) {\n      mDelegate.provideIfDesired(module.getClass().getName(), module);\n      return this;\n    }\n\n    /**\n     * Remove an existing domain module.\n     *\n     * @deprecated This fine-grained control of the devtools modules is no longer supportable\n     *     given the lack of isolation of modules in the actual protocol (many cross dependencies\n     *     emerge when you implement more and more of the real protocol).\n     */\n    @Deprecated\n    public DefaultInspectorModulesBuilder remove(String moduleName) {\n      mDelegate.remove(moduleName);\n      return this;\n    }\n\n    public Iterable<ChromeDevtoolsDomain> finish() {\n      provideIfDesired(new Console());\n      provideIfDesired(new Debugger());\n      DocumentProviderFactory documentModel = resolveDocumentProvider();\n      if (documentModel != null) {\n        Document document = new Document(documentModel);\n        provideIfDesired(new DOM(document));\n        provideIfDesired(new CSS(document));\n      }\n      provideIfDesired(new DOMStorage(mContext));\n      provideIfDesired(new HeapProfiler());\n      provideIfDesired(new Inspector());\n      provideIfDesired(new Network(mContext));\n      provideIfDesired(new Page(mContext));\n      provideIfDesired(new Profiler());\n      provideIfDesired(\n          new Runtime(\n              mRuntimeRepl != null ?\n              mRuntimeRepl :\n              new RhinoDetectingRuntimeReplFactory(mContext)));\n      provideIfDesired(new Worker());\n      if (Build.VERSION.SDK_INT >= DatabaseConstants.MIN_API_LEVEL) {\n        Database database = new Database();\n        boolean hasSqliteDatabaseDriver = false;\n        if (mDatabaseDrivers != null) {\n          for (DatabaseDriver2 databaseDriver : mDatabaseDrivers) {\n            database.add(databaseDriver);\n            if (databaseDriver instanceof SqliteDatabaseDriver) {\n              hasSqliteDatabaseDriver = true;\n            }\n          }\n        }\n        if (!hasSqliteDatabaseDriver && !mExcludeSqliteDatabaseDriver) {\n          database.add(\n              new SqliteDatabaseDriver(mContext,\n                  mDatabaseFilesProvider != null ?\n                      mDatabaseFilesProvider :\n                      new DefaultDatabaseFilesProvider(mContext),\n                  new DefaultDatabaseConnectionProvider()));\n        }\n        provideIfDesired(database);\n      }\n      return mDelegate.finish();\n    }\n\n    @Nullable\n    private DocumentProviderFactory resolveDocumentProvider() {\n      if (mDocumentProvider != null) {\n        return mDocumentProvider;\n      }\n      if (Build.VERSION.SDK_INT >= AndroidDocumentConstants.MIN_API_LEVEL) {\n        return new AndroidDocumentProviderFactory(mContext, Collections.<DescriptorProvider>emptyList());\n      }\n      return null;\n    }\n  }\n\n  /**\n   * Callers can choose to subclass this directly to provide the initialization configuration\n   * or they can construct a concrete instance using {@link #newInitializerBuilder(Context)}.\n   */\n  public static abstract class Initializer {\n    private final Context mContext;\n\n    protected Initializer(Context context) {\n      mContext = context.getApplicationContext();\n    }\n\n    @Nullable\n    protected abstract Iterable<DumperPlugin> getDumperPlugins();\n\n    @Nullable\n    protected abstract Iterable<ChromeDevtoolsDomain> getInspectorModules();\n\n    final void start() {\n      // Note that _devtools_remote is a magic suffix understood by Chrome which causes\n      // the discovery process to begin.\n      LocalSocketServer server = new LocalSocketServer(\n          \"main\",\n          AddressNameHelper.createCustomAddress(\"_devtools_remote\"),\n          new LazySocketHandler(new RealSocketHandlerFactory()));\n\n      ServerManager serverManager = new ServerManager(server);\n      serverManager.start();\n    }\n\n    private class RealSocketHandlerFactory implements SocketHandlerFactory {\n      @Override\n      public SocketHandler create() {\n        ProtocolDetectingSocketHandler socketHandler =\n            new ProtocolDetectingSocketHandler(mContext);\n\n        Iterable<DumperPlugin> dumperPlugins = getDumperPlugins();\n        if (dumperPlugins != null) {\n          Dumper dumper = new Dumper(dumperPlugins);\n\n          socketHandler.addHandler(\n              new ProtocolDetectingSocketHandler.ExactMagicMatcher(\n                  DumpappSocketLikeHandler.PROTOCOL_MAGIC),\n              new DumpappSocketLikeHandler(dumper));\n\n          // Support the old HTTP-based protocol since it's relatively straight forward to do.\n          DumpappHttpSocketLikeHandler legacyHandler = new DumpappHttpSocketLikeHandler(dumper);\n          socketHandler.addHandler(\n              new ProtocolDetectingSocketHandler.ExactMagicMatcher(\n                  \"GET /dumpapp\".getBytes()),\n              legacyHandler);\n          socketHandler.addHandler(\n              new ProtocolDetectingSocketHandler.ExactMagicMatcher(\n                  \"POST /dumpapp\".getBytes()),\n              legacyHandler);\n        }\n\n        Iterable<ChromeDevtoolsDomain> inspectorModules = getInspectorModules();\n        if (inspectorModules != null) {\n          socketHandler.addHandler(\n              new ProtocolDetectingSocketHandler.AlwaysMatchMatcher(),\n              new DevtoolsSocketHandler(mContext, inspectorModules));\n        }\n\n        return socketHandler;\n      }\n    }\n  }\n\n  /**\n   * Configure what services are to be enabled in this instance of Stetho.\n   */\n  public static class InitializerBuilder {\n    final Context mContext;\n\n    @Nullable DumperPluginsProvider mDumperPlugins;\n    @Nullable InspectorModulesProvider mInspectorModules;\n\n    private InitializerBuilder(Context context) {\n      mContext = context.getApplicationContext();\n    }\n\n    /**\n     * Enable use of the {@code dumpapp} system.  This is an extension to Stetho which allows\n     * developers to configure custom debug endpoints as tiny programs embedded inside of a larger\n     * running Android application.  Examples of this would be simple utilities to visualize and\n     * edit {@link SharedPreferences} data, kick off sync or other background tasks, inject custom\n     * data temporarily into the process for debugging/reproducibility, upload error reports,\n     * etc.\n     * <p>\n     * See {@code ./scripts/dumpapp} for more information on how to use this system once\n     * enabled.\n     *\n     * @param plugins The set of plugins to use.\n     */\n    public InitializerBuilder enableDumpapp(DumperPluginsProvider plugins) {\n      mDumperPlugins = Util.throwIfNull(plugins);\n      return this;\n    }\n\n    public InitializerBuilder enableWebKitInspector(InspectorModulesProvider modules) {\n      mInspectorModules = modules;\n      return this;\n    }\n\n    public Initializer build() {\n      return new BuilderBasedInitializer(this);\n    }\n  }\n\n  private static class BuilderBasedInitializer extends Initializer {\n    @Nullable private final DumperPluginsProvider mDumperPlugins;\n    @Nullable private final InspectorModulesProvider mInspectorModules;\n\n    private BuilderBasedInitializer(InitializerBuilder b) {\n      super(b.mContext);\n      mDumperPlugins = b.mDumperPlugins;\n      mInspectorModules = b.mInspectorModules;\n    }\n\n    @Nullable\n    @Override\n    protected Iterable<DumperPlugin> getDumperPlugins() {\n      return mDumperPlugins != null ? mDumperPlugins.get() : null;\n    }\n\n    @Nullable\n    @Override\n    protected Iterable<ChromeDevtoolsDomain> getInspectorModules() {\n      return mInspectorModules != null ? mInspectorModules.get() : null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/Accumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\npublic interface Accumulator<E> {\n  void store(E object);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ArrayListAccumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.util.ArrayList;\n\npublic final class ArrayListAccumulator<E> extends ArrayList<E> implements Accumulator<E> {\n  @Override\n  public void store(E object) {\n    add(object);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ExceptionUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\npublic class ExceptionUtil {\n  @SuppressWarnings(\"unchecked\")\n  public static <T extends Throwable> void propagateIfInstanceOf(Throwable t, Class<T> type)\n      throws T {\n    if (type.isInstance(t)) {\n      throw (T)t;\n    }\n  }\n\n  public static RuntimeException propagate(Throwable t) {\n    propagateIfInstanceOf(t, Error.class);\n    propagateIfInstanceOf(t, RuntimeException.class);\n    throw new RuntimeException(t);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public static <T extends Throwable> void sneakyThrow(Throwable t) throws T {\n    throw (T)t;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ListUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.util.AbstractList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.RandomAccess;\n\npublic final class ListUtil {\n  private ListUtil() {\n  }\n\n  /**\n   * Compares the contents of two {@link List}s by using object identity.\n   */\n  public static <T> boolean identityEquals(List<? extends T> list1, List<? extends T> list2) {\n    if (list1 == list2) {\n      return true;\n    }\n    int size = list1.size();\n    if (size != list2.size()) {\n      return false;\n    }\n    for (int i = 0; i < size; ++i) {\n      if (list1.get(i) != list2.get(i)) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  /**\n   * Copies the given {@link List} and returns the copy as an immutable {@link List}.\n   */\n  public static <T> List<T> copyToImmutableList(List<T> list) {\n    if (list instanceof ImmutableList) {\n      return list;\n    }\n    int size = list.size();\n    switch (size) {\n      case 0:\n        return Collections.emptyList();\n      case 1:\n        return new OneItemImmutableList<>(list.get(0));\n      case 2:\n        return new TwoItemImmutableList<>(list.get(0), list.get(1));\n      case 3:\n        return new ThreeItemImmutableList<>(list.get(0), list.get(1), list.get(2));\n      case 4:\n        return new FourItemImmutableList<>(list.get(0), list.get(1), list.get(2), list.get(3));\n      case 5:\n        return new FiveItemImmutableList<>(\n            list.get(0), list.get(1), list.get(2), list.get(3), list.get(4));\n      default:\n        Object[] array = list.toArray();\n        return new ImmutableArrayList<>(array);\n    }\n  }\n\n  public static <T> List<T> newImmutableList(T item) {\n    return new OneItemImmutableList<>(item);\n  }\n\n  public static <T> List<T> newImmutableList(T itemOne, T itemTwo) {\n    return new TwoItemImmutableList<>(itemOne, itemTwo);\n  }\n\n  private static interface ImmutableList<E> extends List<E>, RandomAccess {\n  }\n\n  private static final class ImmutableArrayList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final Object[] mArray;\n\n    public ImmutableArrayList(Object[] array) {\n      mArray = array;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public E get(int location) {\n      return (E) mArray[location];\n    }\n\n    @Override\n    public int size() {\n      return mArray.length;\n    }\n  }\n\n  private static final class OneItemImmutableList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final E mItem;\n\n    public OneItemImmutableList(E item) {\n      mItem = item;\n    }\n\n    @Override\n    public E get(int location) {\n      if (location == 0) {\n        return mItem;\n      } else {\n        throw new IndexOutOfBoundsException();\n      }\n    }\n\n    @Override\n    public int size() {\n      return 1;\n    }\n  }\n\n  private static final class TwoItemImmutableList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final E mItem0;\n    private final E mItem1;\n\n    public TwoItemImmutableList(E item0, E item1) {\n      mItem0 = item0;\n      mItem1 = item1;\n    }\n\n    @Override\n    public E get(int location) {\n      switch (location) {\n        case 0:\n          return mItem0;\n        case 1:\n          return mItem1;\n        default:\n          throw new IndexOutOfBoundsException();\n      }\n    }\n\n    @Override\n    public int size() {\n      return 2;\n    }\n  }\n\n  private static final class ThreeItemImmutableList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final E mItem0;\n    private final E mItem1;\n    private final E mItem2;\n\n    public ThreeItemImmutableList(E item0, E item1, E item2) {\n      mItem0 = item0;\n      mItem1 = item1;\n      mItem2 = item2;\n    }\n\n    @Override\n    public E get(int location) {\n      switch (location) {\n        case 0:\n          return mItem0;\n        case 1:\n          return mItem1;\n        case 2:\n          return mItem2;\n        default:\n          throw new IndexOutOfBoundsException();\n      }\n    }\n\n    @Override\n    public int size() {\n      return 3;\n    }\n  }\n\n  private static final class FourItemImmutableList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final E mItem0;\n    private final E mItem1;\n    private final E mItem2;\n    private final E mItem3;\n\n    public FourItemImmutableList(E item0, E item1, E item2, E item3) {\n      mItem0 = item0;\n      mItem1 = item1;\n      mItem2 = item2;\n      mItem3 = item3;\n    }\n\n    @Override\n    public E get(int location) {\n      switch (location) {\n        case 0:\n          return mItem0;\n        case 1:\n          return mItem1;\n        case 2:\n          return mItem2;\n        case 3:\n          return mItem3;\n        default:\n          throw new IndexOutOfBoundsException();\n      }\n    }\n\n    @Override\n    public int size() {\n      return 4;\n    }\n  }\n\n  private static final class FiveItemImmutableList<E>\n      extends AbstractList<E> implements ImmutableList<E> {\n    private final E mItem0;\n    private final E mItem1;\n    private final E mItem2;\n    private final E mItem3;\n    private final E mItem4;\n\n    public FiveItemImmutableList(E item0, E item1, E item2, E item3, E item4) {\n      mItem0 = item0;\n      mItem1 = item1;\n      mItem2 = item2;\n      mItem3 = item3;\n      mItem4 = item4;\n    }\n\n    @Override\n    public E get(int location) {\n      switch (location) {\n        case 0:\n          return mItem0;\n        case 1:\n          return mItem1;\n        case 2:\n          return mItem2;\n        case 3:\n          return mItem3;\n        case 4:\n          return mItem4;\n        default:\n          throw new IndexOutOfBoundsException();\n      }\n    }\n\n    @Override\n    public int size() {\n      return 5;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/LogRedirector.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\nimport android.util.Log;\n\n/**\n * Logging interface which allows third party code to replace Android's {@link Log} API with\n * their own implementation.  Useful in public libraries so that callers can more finely control\n * how log messages are produced.\n * <p>\n * Note that the API and implementation of this class are designed to match the semantics of\n * the default system {@link Log} API.\n */\npublic class LogRedirector {\n  private static volatile Logger sLogger;\n\n  /**\n   * Override Android's default {@link Log} API with a custom logger interface.  This affects\n   * all subsequent calls to {@link LogRedirector} APIs.\n   */\n  public static void setLogger(Logger logger) {\n    Util.throwIfNull(logger);\n    Util.throwIfNotNull(sLogger);\n    sLogger = logger;\n  }\n\n  public static void e(String tag, String message, Throwable t) {\n    e(tag, message + \"\\n\" + formatThrowable(t));\n  }\n\n  public static void e(String tag, String message) {\n    log(Log.ERROR, tag, message);\n  }\n\n  public static void w(String tag, String message, Throwable t) {\n    w(tag, message + \"\\n\" + formatThrowable(t));\n  }\n\n  public static void w(String tag, String message) {\n    log(Log.WARN, tag, message);\n  }\n\n  public static void i(String tag, String message, Throwable t) {\n    i(tag, message + \"\\n\" + formatThrowable(t));\n  }\n\n  public static void i(String tag, String message) {\n    log(Log.INFO, tag, message);\n  }\n\n  public static void d(String tag, String message, Throwable t) {\n    d(tag, message + \"\\n\" + formatThrowable(t));\n  }\n\n  public static void d(String tag, String message) {\n    log(Log.DEBUG, tag, message);\n  }\n\n  public static void v(String tag, String message, Throwable t) {\n    v(tag, message + \"\\n\" + formatThrowable(t));\n  }\n\n  public static void v(String tag, String message) {\n    log(Log.VERBOSE, tag, message);\n  }\n\n  private static String formatThrowable(Throwable t) {\n    StringWriter buf = new StringWriter();\n    PrintWriter writer = new PrintWriter(buf);\n    t.printStackTrace();\n    writer.flush();\n    return buf.toString();\n  }\n\n  private static void log(int priority, String tag, String message) {\n    Logger logger = sLogger;\n    if (logger != null) {\n      logger.log(priority, tag, message);\n    } else {\n      Log.println(priority, tag, message);\n    }\n  }\n\n  public static boolean isLoggable(String tag, int priority) {\n    Logger logger = sLogger;\n    if (logger != null) {\n      return logger.isLoggable(tag, priority);\n    } else {\n      return Log.isLoggable(tag, priority);\n    }\n  }\n\n  /**\n   * Custom logger implementation that can forward logs to something other than Android's\n   * {@link Log} API.\n   */\n  public interface Logger {\n    /**\n     * Allows the caller to query if a particular tag and priority will be logged.  Note that just\n     * like with {@link Log#isLoggable}, callers are required to consult this manually if the\n     * result is to be considered.\n     */\n    boolean isLoggable(String tag, int priority);\n\n    /**\n     * Submit a log message.\n     */\n    void log(int priority, String tag, String message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/LogUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.util.Locale;\n\nimport android.util.Log;\n\n/**\n * Logging helper specifically for use by Stetho internals.\n */\npublic class LogUtil {\n  private static final String TAG = \"stetho\";\n\n  public static void e(String format, Object... args) {\n    e(format(format, args));\n  }\n\n  public static void e(Throwable t, String format, Object... args) {\n    e(t, format(format, args));\n  }\n\n  public static void e(String message) {\n    if (isLoggable(Log.ERROR)) {\n      LogRedirector.e(TAG, message);\n    }\n  }\n\n  public static void e(Throwable t, String message) {\n    if (isLoggable(Log.ERROR)) {\n      LogRedirector.e(TAG, message, t);\n    }\n  }\n\n  public static void w(String format, Object... args) {\n    w(format(format, args));\n  }\n\n  public static void w(Throwable t, String format, Object... args) {\n    w(t, format(format, args));\n  }\n\n  public static void w(String message) {\n    if (isLoggable(Log.WARN)) {\n      LogRedirector.w(TAG, message);\n    }\n  }\n\n  public static void w(Throwable t, String message) {\n    if (isLoggable(Log.WARN)) {\n      LogRedirector.w(TAG, message, t);\n    }\n  }\n\n  public static void i(String format, Object... args) {\n    i(format(format, args));\n  }\n\n  public static void i(Throwable t, String format, Object... args) {\n    i(t, format(format, args));\n  }\n\n  public static void i(String message) {\n    if (isLoggable(Log.INFO)) {\n      LogRedirector.i(TAG, message);\n    }\n  }\n\n  public static void i(Throwable t, String message) {\n    if (isLoggable(Log.INFO)) {\n      LogRedirector.i(TAG, message, t);\n    }\n  }\n\n  public static void d(String format, Object... args) {\n    d(format(format, args));\n  }\n\n  public static void d(Throwable t, String format, Object... args) {\n    d(t, format(format, args));\n  }\n\n  public static void d(String message) {\n    if (isLoggable(Log.DEBUG)) {\n      LogRedirector.d(TAG, message);\n    }\n  }\n\n  public static void d(Throwable t, String message) {\n    if (isLoggable(Log.DEBUG)) {\n      LogRedirector.d(TAG, message, t);\n    }\n  }\n\n  public static void v(String format, Object... args) {\n    v(format(format, args));\n  }\n\n  public static void v(Throwable t, String format, Object... args) {\n    v(t, format(format, args));\n  }\n\n  public static void v(String message) {\n    if (isLoggable(Log.VERBOSE)) {\n      LogRedirector.v(TAG, message);\n    }\n  }\n\n  public static void v(Throwable t, String message) {\n    if (isLoggable(Log.VERBOSE)) {\n      LogRedirector.v(TAG, message, t);\n    }\n  }\n\n  private static String format(String format, Object... args) {\n    return String.format(Locale.US, format, args);\n  }\n\n  /**\n   * Applies an internal policy on whether to use {@link LogRedirector#isLoggable(String, int)}.\n   * This interface is sometimes sidestepped to avoid Android's default fairly awkward\n   * {@link Log#isLoggable(String, int)} interface from blocking important messages while still\n   * offering users of Stetho to suppress our error/warning logs via\n   * {@link LogRedirector#setLogger(LogRedirector.Logger)}.\n   */\n  public static boolean isLoggable(int priority) {\n    switch (priority) {\n      case Log.ERROR:\n      case Log.WARN:\n        return true;\n      default:\n        return LogRedirector.isLoggable(TAG, priority);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/Predicate.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\npublic interface Predicate<T> {\n  boolean apply(T t);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ProcessUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport android.os.Build;\nimport android.os.Process;\nimport android.os.UserManager;\n\nimport androidx.annotation.Keep;\nimport androidx.annotation.RequiresApi;\n\nimport javax.annotation.Nullable;\nimport java.io.FileInputStream;\nimport java.io.IOException;\n\npublic class ProcessUtil {\n  /**\n   * Maximum length allowed in {@code /proc/self/cmdline}.  Imposed to avoid a large buffer\n   * allocation during the init path.\n   */\n  private static final int CMDLINE_BUFFER_SIZE = 64;\n\n  private static String sProcessName;\n  private static boolean sProcessNameRead;\n\n  /**\n   * Get process name by reading {@code /proc/self/cmdline}.\n   *\n   * @return Process name or null if there was an error reading from {@code /proc/self/cmdline}.\n   *     It is unknown how this error can occur in practice and should be considered extremely\n   *     rare.\n   */\n  @Nullable\n  public static synchronized String getProcessName() {\n    if (!sProcessNameRead) {\n      sProcessNameRead = true;\n      try {\n        sProcessName = readProcessName();\n      } catch (IOException e) {\n      }\n    }\n    return sProcessName;\n  }\n\n  private static String readProcessName() throws IOException {\n    byte[] cmdlineBuffer = new byte[CMDLINE_BUFFER_SIZE];\n\n    // Avoid using a Reader to not pick up a forced 16K buffer.  Silly java.io...\n    FileInputStream stream = new FileInputStream(\"/proc/self/cmdline\");\n    boolean success = false;\n    try {\n      int n = stream.read(cmdlineBuffer);\n      success = true;\n      int endIndex = indexOf(cmdlineBuffer, 0, n, (byte)0 /* needle */);\n      return new String(cmdlineBuffer, 0, endIndex > 0 ? endIndex : n);\n    } finally {\n      Util.close(stream, !success);\n    }\n  }\n\n  private static int indexOf(byte[] haystack, int offset, int length, byte needle) {\n    for (int i = 0; i < haystack.length; i++) {\n      if (haystack[i] == needle) {\n        return i;\n      }\n    }\n    return -1;\n  }\n\n  public static int getUserId() {\n    // On multi-user devices, user id is calculated from process uid.\n    // On single-user devices, user id is always 0.\n    // https://cs.android.com/android/platform/superproject/+/master:frameworks/base/core/java/android/os/UserHandle.java;l=282;drc=5a5038ddb87b9c4ac576935b77cab4688169ee48\n    final boolean supportsMultipleUsers = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && UserManager21Impl.supportsMultipleUsers();\n    return supportsMultipleUsers ? Process.myUid() / 100000 : 0;\n  }\n\n  @Keep\n  @RequiresApi(Build.VERSION_CODES.N)\n  private static class UserManager21Impl {\n    public static boolean supportsMultipleUsers() {\n      return UserManager.supportsMultipleUsers();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ReflectionUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\nimport javax.annotation.Nullable;\n\npublic final class ReflectionUtil {\n  private ReflectionUtil() {\n  }\n\n  @Nullable\n  public static Class<?> tryGetClassForName(String className) {\n    try {\n      return Class.forName(className);\n    } catch (ClassNotFoundException e) {\n      return null;\n    }\n  }\n\n  @Nullable\n  public static Field tryGetDeclaredField(Class<?> theClass, String fieldName) {\n    try {\n      return theClass.getDeclaredField(fieldName);\n    } catch (NoSuchFieldException e) {\n      LogUtil.d(\n          e,\n          \"Could not retrieve %s field from %s\",\n          fieldName,\n          theClass);\n\n      return null;\n    }\n  }\n\n  @Nullable\n  public static Object getFieldValue(Field field, Object target) {\n    try {\n      return field.get(target);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/StringUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\npublic final class StringUtil {\n  private StringUtil() {\n  }\n\n  @SuppressWarnings(\"StringEquality\")\n  public static String removePrefix(String string, String prefix, String previousAttempt) {\n    if (string != previousAttempt) {\n      return previousAttempt;\n    } else {\n      return removePrefix(string, prefix);\n    }\n  }\n\n  public static String removePrefix(String string, String prefix) {\n    if (string.startsWith(prefix)) {\n      return string.substring(prefix.length());\n    } else {\n      return string;\n    }\n  }\n\n  public static String removeAll(String string, char target) {\n    final int length = string.length();\n    final StringBuilder builder = new StringBuilder(length);\n    for (int i = 0; i < length; ++i) {\n      char c = string.charAt(i);\n      if (c != target) {\n        builder.append(c);\n      }\n    }\n    return builder.toString();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/ThreadBound.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.lang.IllegalStateException;\n\n/**\n * Implemented by an object whose methods must be called on a specific thread. If a method is\n * called from a disallowed thread then {@link IllegalStateException} will be thrown.\n * To marshal a call to the correct thread, you can use {@link #postAndWait(UncheckedCallable)} or\n * {@link #postAndWait(Runnable)}, both of which complete synchronously.\n */\npublic interface ThreadBound {\n  /**\n   * Checks whether the current thread has access to this object.\n   * @return true if this thread has access to this object; otherwise false\n   */\n  boolean checkThreadAccess();\n\n  /**\n   * Enforces that the current thread has access to this object.\n   * @throws IllegalStateException if the current thread does not have access to this object\n   */\n  void verifyThreadAccess();\n\n  /**\n   * Synchronously executes an {@link UncheckedCallable} on the thread that this object is bound to,\n   * and returns its result.\n   * @param c the {@link UncheckedCallable} to execute\n   * @param <V> the return type of the {@link UncheckedCallable}\n   * @return the return value from {@link UncheckedCallable#call()}\n   * @throws RuntimeException if the {@link UncheckedCallable} could not be executed (the cause\n   * will be null), or if {@link UncheckedCallable#call()} threw an exception (the cause will be the\n   * exception that it threw).\n   */\n  <V> V postAndWait(UncheckedCallable<V> c);\n\n  /**\n   * Synchronously executes a {@link Runnable} on the thread that this object is bound to.\n   * @param r the {@link Runnable} to execute\n   * @throws RuntimeException if the {@link Runnable} could not be executed (the cause will be\n   * null), or if {@link Runnable#run()} threw an exception (the cause will be the exception that\n   * it threw).\n   */\n  void postAndWait(Runnable r);\n\n  /**\n   * Asynchronously executes a {@link Runnable} on the thread that this object is bound to\n   * after the specified delay.\n   * @param r the {@link Runnable} to execute\n   * @param delayMillis The delay (in milliseconds) until the {@link Runnable} will be executed.\n   * @throws RuntimeException if the {@link Runnable} could not be enqueued.\n   */\n  void postDelayed(Runnable r, long delayMillis);\n\n  /**\n   * Removes any pending posts of the given {@link Runnable} that are in the queue.\n   * @param r the {@link Runnable} to remove from the queue\n   */\n  void removeCallbacks(Runnable r);\n}\n\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/UncheckedCallable.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\n/**\n * A task that returns a result. Implementers define a single method with no arguments called\n * {@code call}.\n *\n * <p>This interface is identical to {@link java.util.concurrent.Callable} but without the checked\n * exception.\n *\n * @param <V> the result type of method {@code call}\n */\npublic interface UncheckedCallable<V> {\n  V call();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/Utf8Charset.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.io.UnsupportedEncodingException;\nimport java.nio.charset.Charset;\n\npublic class Utf8Charset {\n\n  public static final String NAME = \"UTF-8\";\n  public static final Charset INSTANCE = Charset.forName(NAME);\n\n  public static byte[] encodeUTF8(String str) {\n    try {\n      return str.getBytes(NAME);\n    } catch (UnsupportedEncodingException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static String decodeUTF8(byte[] bytes) {\n    return new String(bytes, INSTANCE);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/Util.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.concurrent.CountDownLatch;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\npublic class Util {\n  public static <T> T throwIfNull(T item) {\n    if (item == null) {\n      throw new NullPointerException();\n    }\n    return item;\n  }\n\n  public static <T1, T2> void throwIfNull(T1 item1, T2 item2) {\n    throwIfNull(item1);\n    throwIfNull(item2);\n  }\n\n  public static <T1, T2, T3> void throwIfNull(T1 item1, T2 item2, T3 item3) {\n    throwIfNull(item1);\n    throwIfNull(item2);\n    throwIfNull(item3);\n  }\n\n  public static void throwIfNotNull(Object item) {\n    if (item != null) {\n      throw new IllegalStateException();\n    }\n  }\n\n  public static void throwIf(boolean condition) {\n    if (condition) {\n      throw new IllegalStateException();\n    }\n  }\n\n  public static void throwIfNot(boolean condition) {\n    if (!condition) {\n      throw new IllegalStateException();\n    }\n  }\n\n  public static void throwIfNot(boolean condition, String format, Object...args) {\n    if (!condition) {\n      String message = String.format(format, args);\n      throw new IllegalStateException(message);\n    }\n  }\n\n  public static void copy(InputStream input, OutputStream output, byte[] buffer)\n      throws IOException {\n    int n;\n    while ((n = input.read(buffer)) != -1) {\n      output.write(buffer, 0, n);\n    }\n  }\n\n  public static void close(Closeable closeable, boolean hideException) throws IOException {\n    if (closeable != null) {\n      if (hideException) {\n        try {\n          closeable.close();\n        } catch (IOException e) {\n          LogUtil.e(e, \"Hiding IOException because another is pending\");\n        }\n      } else {\n        closeable.close();\n      }\n    }\n  }\n\n  public static void sleepUninterruptibly(long millis) {\n    long remaining = millis;\n    long startTime = System.currentTimeMillis();\n    do {\n      try {\n        Thread.sleep(remaining);\n        return;\n      } catch (InterruptedException e) {\n        long sleptFor = System.currentTimeMillis() - startTime;\n        remaining -= sleptFor;\n      }\n    } while (remaining > 0);\n  }\n\n  public static void joinUninterruptibly(Thread t) {\n    while (true) {\n      try {\n        t.join();\n        return;\n      } catch (InterruptedException e) {\n        // Keep going...\n      }\n    }\n  }\n\n  public static void awaitUninterruptibly(CountDownLatch latch) {\n    while (true) {\n      try {\n        latch.await();\n        return;\n      } catch (InterruptedException e) {\n        // Keep going...\n      }\n    }\n  }\n\n  public static <T> T getUninterruptibly(\n      Future<T> future,\n      long timeout,\n      TimeUnit unit) throws TimeoutException, ExecutionException {\n    long remaining = unit.toMillis(timeout);\n    long startTime = System.currentTimeMillis();\n    while (true) {\n      try {\n        return future.get(remaining, TimeUnit.MILLISECONDS);\n      } catch (InterruptedException e) {\n        long gotFor = System.currentTimeMillis() - startTime;\n        remaining -= gotFor;\n      }\n    }\n  }\n\n  public static <T> T getUninterruptibly(Future<T> future)\n      throws ExecutionException {\n    while (true) {\n      try {\n        return future.get();\n      } catch (InterruptedException e) {\n        //Keep going...\n      }\n    }\n  }\n\n  public static String readAsUTF8(InputStream in) throws IOException {\n    ByteArrayOutputStream out = new ByteArrayOutputStream();\n    copy(in, out, new byte[1024]);\n    return out.toString(\"UTF-8\");\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/AccessibilityUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\nimport android.widget.AdapterView;\nimport android.widget.HorizontalScrollView;\nimport android.widget.ScrollView;\nimport android.widget.Spinner;\n\nimport java.util.List;\n\nimport androidx.annotation.Nullable;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.accessibility.AccessibilityNodeInfoCompat;\n\n/**\n * This class provides utility methods for determining certain accessibility properties of\n * {@link View}s and {@link AccessibilityNodeInfoCompat}s. It is porting some of the checks from\n * {@link com.googlecode.eyesfree.utils.AccessibilityNodeInfoUtils}, but has stripped many features\n * which are unnecessary here.\n */\npublic final class AccessibilityUtil {\n  private AccessibilityUtil() {\n  }\n\n  /**\n   * Returns whether the specified node has text or a content description.\n   *\n   * @param node The node to check.\n   * @return {@code true} if the node has text.\n   */\n  public static boolean hasText(@Nullable AccessibilityNodeInfoCompat node) {\n    if (node == null) {\n      return false;\n    }\n\n    return !TextUtils.isEmpty(node.getText()) || !TextUtils.isEmpty(node.getContentDescription());\n  }\n\n  /**\n   * Returns whether the supplied {@link View} and {@link AccessibilityNodeInfoCompat} would\n   * produce spoken feedback if it were accessibility focused.  NOTE: not all speaking nodes are\n   * focusable.\n   *\n   * @param view The {@link View} to evaluate\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if it meets the criterion for producing spoken feedback\n   */\n  public static boolean isSpeakingNode(\n      @Nullable AccessibilityNodeInfoCompat node,\n      @Nullable View view) {\n    if (node == null || view == null) {\n      return false;\n    }\n\n    if (!node.isVisibleToUser()) {\n      return false;\n    }\n\n    int important = ViewCompat.getImportantForAccessibility(view);\n    if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS ||\n        (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO &&\n            node.getChildCount() <= 0)) {\n      return false;\n    }\n\n    return node.isCheckable() || hasText(node) || hasNonActionableSpeakingDescendants(node, view);\n  }\n\n  /**\n   * Determines if the supplied {@link View} and {@link AccessibilityNodeInfoCompat} has any\n   * children which are not independently accessibility focusable and also have a spoken\n   * description.\n   * <p>\n   * NOTE: Accessibility services will include these children's descriptions in the closest\n   * focusable ancestor.\n   *\n   * @param view The {@link View} to evaluate\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if it has any non-actionable speaking descendants within its subtree\n   */\n  public static boolean hasNonActionableSpeakingDescendants(\n      @Nullable AccessibilityNodeInfoCompat node,\n      @Nullable View view) {\n\n    if (node == null || view == null || !(view instanceof ViewGroup)) {\n      return false;\n    }\n\n    ViewGroup viewGroup = (ViewGroup) view;\n    for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {\n      View childView = viewGroup.getChildAt(i);\n\n      if (childView == null) {\n        continue;\n      }\n\n      AccessibilityNodeInfoCompat childNode = AccessibilityNodeInfoCompat.obtain();\n      try {\n        ViewCompat.onInitializeAccessibilityNodeInfo(childView, childNode);\n\n        if (isAccessibilityFocusable(childNode, childView)) {\n          continue;\n        }\n\n        if (isSpeakingNode(childNode, childView)) {\n          return true;\n        }\n      } finally {\n        childNode.recycle();\n      }\n    }\n\n    return false;\n  }\n\n  /**\n   * Determines if the provided {@link View} and {@link AccessibilityNodeInfoCompat} meet the\n   * criteria for gaining accessibility focus.\n   *\n   * @param view The {@link View} to evaluate\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if it is possible to gain accessibility focus\n   */\n  public static boolean isAccessibilityFocusable(\n      @Nullable AccessibilityNodeInfoCompat node,\n      @Nullable View view) {\n    if (node == null || view == null) {\n      return false;\n    }\n\n    // Never focus invisible nodes.\n    if (!node.isVisibleToUser()) {\n      return false;\n    }\n\n    // Always focus \"actionable\" nodes.\n    if (isActionableForAccessibility(node)) {\n      return true;\n    }\n\n    // only focus top-level list items with non-actionable speaking children.\n    return isTopLevelScrollItem(node, view) && isSpeakingNode(node, view);\n  }\n\n  /**\n   * Determines whether the provided {@link View} and {@link AccessibilityNodeInfoCompat} is a\n   * top-level item in a scrollable container.\n   *\n   * @param view The {@link View} to evaluate\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if it is a top-level item in a scrollable container.\n   */\n  public static boolean isTopLevelScrollItem(\n      @Nullable AccessibilityNodeInfoCompat node,\n      @Nullable View view) {\n    if (node == null || view == null) {\n      return false;\n    }\n\n    View parent = (View) ViewCompat.getParentForAccessibility(view);\n    if (parent == null) {\n      return false;\n    }\n\n    if (node.isScrollable()) {\n      return true;\n    }\n\n    List actionList = node.getActionList();\n    if (actionList.contains(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD) ||\n        actionList.contains(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD)) {\n      return true;\n    }\n\n    // AdapterView, ScrollView, and HorizontalScrollView are focusable\n    // containers, but Spinner is a special case.\n    if (parent instanceof Spinner) {\n      return false;\n    }\n\n    return\n        parent instanceof AdapterView ||\n            parent instanceof ScrollView ||\n            parent instanceof HorizontalScrollView;\n  }\n\n  /**\n   * Returns whether a node is actionable. That is, the node supports one of\n   * {@link AccessibilityNodeInfoCompat#isClickable()},\n   * {@link AccessibilityNodeInfoCompat#isFocusable()}, or\n   * {@link AccessibilityNodeInfoCompat#isLongClickable()}.\n   *\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if node is actionable.\n   */\n  public static boolean isActionableForAccessibility(@Nullable AccessibilityNodeInfoCompat node) {\n    if (node == null) {\n      return false;\n    }\n\n    if (node.isClickable() || node.isLongClickable() || node.isFocusable()) {\n      return true;\n    }\n\n    List actionList = node.getActionList();\n    return\n        actionList.contains(AccessibilityNodeInfoCompat.ACTION_CLICK) ||\n            actionList.contains(AccessibilityNodeInfoCompat.ACTION_LONG_CLICK) ||\n            actionList.contains(AccessibilityNodeInfoCompat.ACTION_FOCUS);\n  }\n\n  /**\n   * Determines if any of the provided {@link View}'s and {@link AccessibilityNodeInfoCompat}'s\n   * ancestors can receive accessibility focus\n   *\n   * @param view The {@link View} to evaluate\n   * @param node The {@link AccessibilityNodeInfoCompat} to evaluate\n   * @return {@code true} if an ancestor of may receive accessibility focus\n   */\n  public static boolean hasFocusableAncestor(\n      @Nullable AccessibilityNodeInfoCompat node,\n      @Nullable View view) {\n    if (node == null || view == null) {\n      return false;\n    }\n\n    ViewParent parentView = ViewCompat.getParentForAccessibility(view);\n    if (!(parentView instanceof View)) {\n      return false;\n    }\n\n    AccessibilityNodeInfoCompat parentNode = AccessibilityNodeInfoCompat.obtain();\n    try {\n      ViewCompat.onInitializeAccessibilityNodeInfo((View) parentView, parentNode);\n      if (parentNode == null) {\n        return false;\n      }\n\n      if (isAccessibilityFocusable(parentNode, (View) parentView)) {\n        return true;\n      }\n\n      if (hasFocusableAncestor(parentNode, (View) parentView)) {\n        return true;\n      }\n    } finally {\n      parentNode.recycle();\n    }\n    return false;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/DialogFragmentAccessor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Dialog;\n\npublic interface DialogFragmentAccessor<DIALOG_FRAGMENT, FRAGMENT, FRAGMENT_MANAGER>\n    extends FragmentAccessor<FRAGMENT, FRAGMENT_MANAGER> {\n  Dialog getDialog(DIALOG_FRAGMENT dialogFragment);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentAccessor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.content.res.Resources;\nimport android.view.View;\n\nimport javax.annotation.Nullable;\n\npublic interface FragmentAccessor<FRAGMENT, FRAGMENT_MANAGER> {\n  int NO_ID = 0;\n\n  @Nullable\n  FRAGMENT_MANAGER getFragmentManager(FRAGMENT fragment);\n\n  Resources getResources(FRAGMENT fragment);\n\n  int getId(FRAGMENT fragment);\n\n  @Nullable\n  String getTag(FRAGMENT fragment);\n\n  @Nullable\n  View getView(FRAGMENT fragment);\n\n  @Nullable\n  FRAGMENT_MANAGER getChildFragmentManager(FRAGMENT fragment);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentActivityAccessor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Activity;\n\nimport javax.annotation.Nullable;\n\npublic interface FragmentActivityAccessor<\n    FRAGMENT_ACTIVITY extends Activity,\n    FRAGMENT_MANAGER> {\n  @Nullable\n  FRAGMENT_MANAGER getFragmentManager(FRAGMENT_ACTIVITY activity);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentCompat.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Activity;\n\nimport com.facebook.stetho.common.ReflectionUtil;\n\nimport java.lang.reflect.Field;\nimport java.util.List;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.NotThreadSafe;\n\n/**\n * Compatibility abstraction which allows us to generalize access to both the\n * support library's fragments and the built-in framework version.  Note: both versions\n * can be live at the same time in a single application and even on a single object\n * instance.\n * <p/>\n * Type safety is enforced via generics internal to the implementation but treated\n * as opaque from the outside.\n *\n * @param <FRAGMENT>\n * @param <DIALOG_FRAGMENT>\n * @param <FRAGMENT_MANAGER>\n * @param <FRAGMENT_ACTIVITY>\n */\n@NotThreadSafe\npublic abstract class FragmentCompat<\n    FRAGMENT,\n    DIALOG_FRAGMENT,\n    FRAGMENT_MANAGER,\n    FRAGMENT_ACTIVITY extends Activity> {\n  private static FragmentCompat sFrameworkInstance;\n  private static FragmentCompat sSupportInstance;\n\n  private static final boolean sHasSupportFragment;\n\n  static {\n    sHasSupportFragment = ReflectionUtil.tryGetClassForName(\n        \"androidx.fragment.app.Fragment\") != null;\n  }\n\n  @Nullable\n  public static FragmentCompat getFrameworkInstance() {\n    if (sFrameworkInstance == null) {\n      sFrameworkInstance = new FragmentCompatFramework();\n    }\n    return sFrameworkInstance;\n  }\n\n  @Nullable\n  public static FragmentCompat getSupportLibInstance() {\n    if (sSupportInstance == null &&\n        sHasSupportFragment) {\n      sSupportInstance = new FragmentCompatSupportLib();\n    }\n    return sSupportInstance;\n  }\n\n  FragmentCompat() {\n  }\n\n  public abstract Class<FRAGMENT> getFragmentClass();\n  public abstract Class<DIALOG_FRAGMENT> getDialogFragmentClass();\n  public abstract Class<FRAGMENT_ACTIVITY> getFragmentActivityClass();\n\n  public abstract FragmentAccessor<FRAGMENT, FRAGMENT_MANAGER> forFragment();\n  public abstract DialogFragmentAccessor<DIALOG_FRAGMENT, FRAGMENT, FRAGMENT_MANAGER>\n      forDialogFragment();\n  public abstract FragmentManagerAccessor<FRAGMENT_MANAGER, FRAGMENT> forFragmentManager();\n  public abstract FragmentActivityAccessor<FRAGMENT_ACTIVITY, FRAGMENT_MANAGER> forFragmentActivity();\n\n  static class FragmentManagerAccessorViaReflection<FRAGMENT_MANAGER, FRAGMENT>\n      implements FragmentManagerAccessor<FRAGMENT_MANAGER, FRAGMENT> {\n    @Nullable\n    private Field mFieldMAdded;\n\n    @SuppressWarnings(\"unchecked\")\n    @Nullable\n    @Override\n    public List<FRAGMENT> getAddedFragments(FRAGMENT_MANAGER fragmentManager) {\n      // This field is actually sitting on FragmentManagerImpl, which derives from FragmentManager.\n      if (mFieldMAdded == null) {\n        Field fieldMAdded = ReflectionUtil.tryGetDeclaredField(\n            fragmentManager.getClass(),\n            \"mAdded\");\n\n        if (fieldMAdded != null) {\n          fieldMAdded.setAccessible(true);\n          mFieldMAdded = fieldMAdded;\n        }\n      }\n\n      return (mFieldMAdded != null)\n          ? (List<FRAGMENT>)ReflectionUtil.getFieldValue(mFieldMAdded, fragmentManager)\n          : null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentCompatFramework.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.app.DialogFragment;\nimport android.app.Fragment;\nimport android.app.FragmentManager;\nimport android.content.res.Resources;\nimport android.os.Build;\nimport android.view.View;\n\nimport javax.annotation.Nullable;\n\nfinal class FragmentCompatFramework\n    extends FragmentCompat<Fragment, DialogFragment, FragmentManager, Activity> {\n  private static final FragmentAccessorFrameworkHoneycomb sFragmentAccessor;\n  private static final DialogFragmentAccessorFramework sDialogFragmentAccessor;\n  private static final FragmentManagerAccessorViaReflection<FragmentManager, Fragment>\n      sFragmentManagerAccessor = new FragmentManagerAccessorViaReflection<>();\n  private static final FragmentActivityAccessorFramework sFragmentActivityAccessor =\n      new FragmentActivityAccessorFramework();\n\n  static {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {\n      sFragmentAccessor = new FragmentAccessorFrameworkJellyBean();\n    } else {\n      sFragmentAccessor = new FragmentAccessorFrameworkHoneycomb();\n    }\n\n    sDialogFragmentAccessor = new DialogFragmentAccessorFramework(sFragmentAccessor);\n  }\n\n  @Override\n  public Class<Fragment> getFragmentClass() {\n    return Fragment.class;\n  }\n\n  @Override\n  public Class<DialogFragment> getDialogFragmentClass() {\n    return DialogFragment.class;\n  }\n\n  @Override\n  public Class<Activity> getFragmentActivityClass() {\n    return Activity.class;\n  }\n\n  @Override\n  public FragmentAccessorFrameworkHoneycomb forFragment() {\n    return sFragmentAccessor;\n  }\n\n  @Override\n  public DialogFragmentAccessorFramework forDialogFragment() {\n    return sDialogFragmentAccessor;\n  }\n\n  @Override\n  public FragmentManagerAccessorViaReflection<FragmentManager, Fragment> forFragmentManager() {\n    return sFragmentManagerAccessor;\n  }\n\n  @Override\n  public FragmentActivityAccessorFramework forFragmentActivity() {\n    return sFragmentActivityAccessor;\n  }\n\n  private static class FragmentAccessorFrameworkHoneycomb\n      implements FragmentAccessor<Fragment, FragmentManager> {\n    @Nullable\n    @Override\n    public FragmentManager getFragmentManager(Fragment fragment) {\n      return fragment.getFragmentManager();\n    }\n\n    @Override\n    public Resources getResources(Fragment fragment) {\n      return fragment.getResources();\n    }\n\n    @Override\n    public int getId(Fragment fragment) {\n      return fragment.getId();\n    }\n\n    @Nullable\n    @Override\n    public String getTag(Fragment fragment) {\n      return fragment.getTag();\n    }\n\n    @Nullable\n    @Override\n    public View getView(Fragment fragment) {\n      return fragment.getView();\n    }\n\n    @Nullable\n    @Override\n    public FragmentManager getChildFragmentManager(Fragment fragment) {\n      return null;\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)\n  private static class FragmentAccessorFrameworkJellyBean\n      extends FragmentAccessorFrameworkHoneycomb {\n    @Nullable\n    @Override\n    public FragmentManager getChildFragmentManager(Fragment fragment) {\n      return fragment.getChildFragmentManager();\n    }\n  }\n\n  private static class DialogFragmentAccessorFramework\n      implements DialogFragmentAccessor<DialogFragment, Fragment, FragmentManager> {\n    private final FragmentAccessor<Fragment, FragmentManager> mFragmentAccessor;\n\n    public DialogFragmentAccessorFramework(\n        FragmentAccessor<Fragment, FragmentManager> fragmentAccessor) {\n      mFragmentAccessor = fragmentAccessor;\n    }\n\n    @Override\n    public Dialog getDialog(DialogFragment dialogFragment) {\n      return dialogFragment.getDialog();\n    }\n\n    @Nullable\n    @Override\n    public FragmentManager getFragmentManager(Fragment fragment) {\n      return mFragmentAccessor.getFragmentManager(fragment);\n    }\n\n    @Override\n    public Resources getResources(Fragment fragment) {\n      return mFragmentAccessor.getResources(fragment);\n    }\n\n    @Override\n    public int getId(Fragment fragment) {\n      return mFragmentAccessor.getId(fragment);\n    }\n\n    @Nullable\n    @Override\n    public String getTag(Fragment fragment) {\n      return mFragmentAccessor.getTag(fragment);\n    }\n\n    @Nullable\n    @Override\n    public View getView(Fragment fragment) {\n      return mFragmentAccessor.getView(fragment);\n    }\n\n    @Nullable\n    @Override\n    public FragmentManager getChildFragmentManager(Fragment fragment) {\n      return mFragmentAccessor.getChildFragmentManager(fragment);\n    }\n  }\n\n  private static class FragmentActivityAccessorFramework\n      implements FragmentActivityAccessor<Activity, FragmentManager> {\n    @Nullable\n    @Override\n    public FragmentManager getFragmentManager(Activity activity) {\n      return activity.getFragmentManager();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentCompatSupportLib.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Dialog;\nimport android.content.res.Resources;\nimport android.view.View;\n\nimport javax.annotation.Nullable;\n\nimport androidx.fragment.app.DialogFragment;\nimport androidx.fragment.app.Fragment;\nimport androidx.fragment.app.FragmentActivity;\nimport androidx.fragment.app.FragmentManager;\n\nfinal class FragmentCompatSupportLib\n    extends FragmentCompat<Fragment, DialogFragment, FragmentManager, FragmentActivity> {\n  private static final FragmentAccessorSupportLib sFragmentAccessor =\n      new FragmentAccessorSupportLib();\n  private static final DialogFragmentAccessorSupportLib sDialogFragmentAccessor =\n      new DialogFragmentAccessorSupportLib();\n  private static final FragmentManagerAccessorViaReflection<FragmentManager, Fragment>\n      sFragmentManagerAccessor = new FragmentManagerAccessorViaReflection<>();\n  private static final FragmentActivityAccessorSupportLib sFragmentActivityAccessor =\n      new FragmentActivityAccessorSupportLib();\n\n  @Override\n  public Class<Fragment> getFragmentClass() {\n    return Fragment.class;\n  }\n\n  @Override\n  public Class<DialogFragment> getDialogFragmentClass() {\n    return DialogFragment.class;\n  }\n\n  @Override\n  public Class<FragmentActivity> getFragmentActivityClass() {\n    return FragmentActivity.class;\n  }\n\n  @Override\n  public FragmentAccessorSupportLib forFragment() {\n    return sFragmentAccessor;\n  }\n\n  @Override\n  public DialogFragmentAccessorSupportLib forDialogFragment() {\n    return sDialogFragmentAccessor;\n  }\n\n  @Override\n  public FragmentManagerAccessor<FragmentManager, Fragment> forFragmentManager() {\n    return sFragmentManagerAccessor;\n  }\n\n  @Override\n  public FragmentActivityAccessorSupportLib forFragmentActivity() {\n    return sFragmentActivityAccessor;\n  }\n\n  private static class FragmentAccessorSupportLib\n      implements FragmentAccessor<Fragment, FragmentManager> {\n    @Nullable\n    @Override\n    public FragmentManager getFragmentManager(Fragment fragment) {\n      return fragment.getFragmentManager();\n    }\n\n    @Override\n    public Resources getResources(Fragment fragment) {\n      return fragment.getResources();\n    }\n\n    @Override\n    public int getId(Fragment fragment) {\n      return fragment.getId();\n    }\n\n    @Nullable\n    @Override\n    public String getTag(Fragment fragment) {\n      return fragment.getTag();\n    }\n\n    @Nullable\n    @Override\n    public View getView(Fragment fragment) {\n      return fragment.getView();\n    }\n\n    @Nullable\n    @Override\n    public FragmentManager getChildFragmentManager(Fragment fragment) {\n      return fragment.getChildFragmentManager();\n    }\n  }\n\n  private static class DialogFragmentAccessorSupportLib\n      extends FragmentAccessorSupportLib\n      implements DialogFragmentAccessor<DialogFragment, Fragment, FragmentManager> {\n    @Override\n    public Dialog getDialog(DialogFragment dialogFragment) {\n      return dialogFragment.getDialog();\n    }\n  }\n\n  private static class FragmentActivityAccessorSupportLib\n      implements FragmentActivityAccessor<FragmentActivity, FragmentManager> {\n    @Nullable\n    @Override\n    public FragmentManager getFragmentManager(FragmentActivity activity) {\n      return activity.getSupportFragmentManager();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentCompatUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Activity;\nimport android.view.View;\n\nimport java.util.List;\n\nimport javax.annotation.Nullable;\n\npublic final class FragmentCompatUtil {\n  private FragmentCompatUtil() {\n  }\n\n  public static boolean isDialogFragment(Object fragment) {\n    FragmentCompat supportLib = FragmentCompat.getSupportLibInstance();\n    if (supportLib != null &&\n        supportLib.getDialogFragmentClass().isInstance(fragment)) {\n      return true;\n    }\n\n    FragmentCompat framework = FragmentCompat.getFrameworkInstance();\n    if (framework != null &&\n        framework.getDialogFragmentClass().isInstance(fragment)) {\n      return true;\n    }\n\n    return false;\n  }\n\n  @Nullable\n  public static Object findFragmentForView(View view) {\n    Activity activity = ViewUtil.tryGetActivity(view);\n    if (activity == null) {\n      return null;\n    }\n\n    return findFragmentForViewInActivity(activity, view);\n  }\n\n  @Nullable\n  private static Object findFragmentForViewInActivity(Activity activity, View view) {\n    FragmentCompat supportLib = FragmentCompat.getSupportLibInstance();\n\n    // Try the support library version if it is present and the activity is FragmentActivity.\n    if (supportLib != null &&\n        supportLib.getFragmentActivityClass().isInstance(activity)) {\n      Object fragment = findFragmentForViewInActivity(supportLib, activity, view);\n      if (fragment != null) {\n        return fragment;\n      }\n    }\n\n    // Try the actual Android runtime version if we are on a sufficiently high API level for it to\n    // exist.  Note that technically we can have both the support library and the framework\n    // version in the same object instance due to FragmentActivity extending Activity (which has\n    // fragment support in the system).\n    FragmentCompat framework = FragmentCompat.getFrameworkInstance();\n    if (framework != null) {\n      Object fragment = findFragmentForViewInActivity(framework, activity, view);\n      if (fragment != null) {\n        return fragment;\n      }\n    }\n\n    return null;\n  }\n\n  private static Object findFragmentForViewInActivity(\n      FragmentCompat compat,\n      Activity activity,\n      View view) {\n    Object fragmentManager = compat.forFragmentActivity().getFragmentManager(activity);\n    if (fragmentManager != null) {\n      return findFragmentForViewInFragmentManager(compat, fragmentManager, view);\n    } else {\n      return null;\n    }\n  }\n\n  @Nullable\n  private static Object findFragmentForViewInFragmentManager(\n      FragmentCompat compat,\n      Object fragmentManager,\n      View view) {\n    List<?> fragments = compat.forFragmentManager().getAddedFragments(fragmentManager);\n\n    if (fragments != null) {\n      for (int i = 0, N = fragments.size(); i < N; ++i) {\n        Object fragment = fragments.get(i);\n        Object result = findFragmentForViewInFragment(compat, fragment, view);\n        if (result != null) {\n          return result;\n        }\n      }\n    }\n\n    return null;\n  }\n\n  @Nullable\n  private static Object findFragmentForViewInFragment(\n      FragmentCompat compat,\n      Object fragment,\n      View view) {\n    FragmentAccessor accessor = compat.forFragment();\n\n    if (accessor.getView(fragment) == view) {\n      return fragment;\n    }\n\n    Object childFragmentManager = accessor.getChildFragmentManager(fragment);\n    if (childFragmentManager != null) {\n      return findFragmentForViewInFragmentManager(compat, childFragmentManager, view);\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/FragmentManagerAccessor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport java.util.List;\n\nimport javax.annotation.Nullable;\n\npublic interface FragmentManagerAccessor<FRAGMENT_MANAGER, FRAGMENT> {\n  @Nullable\n  List<FRAGMENT> getAddedFragments(FRAGMENT_MANAGER fragmentManager);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/HandlerUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport com.facebook.stetho.common.UncheckedCallable;\nimport com.facebook.stetho.common.Util;\n\npublic final class HandlerUtil {\n  private HandlerUtil() {\n  }\n\n  /**\n   * Checks whether the current thread is the same thread that the {@link Handler} is associated\n   * with.\n   * @return true if the current thread is the same thread that the {@link Handler} is associated\n   * with; otherwise false.\n   */\n  public static boolean checkThreadAccess(Handler handler) {\n    return Looper.myLooper() == handler.getLooper();\n  }\n\n  /**\n   * Enforces that the current thread is the same thread that the {@link Handler} is associated\n   * with.\n   * @throws IllegalStateException if the current thread is not the same thread that the\n   * {@link Handler} is associated with.\n   */\n  public static void verifyThreadAccess(Handler handler) {\n    Util.throwIfNot(checkThreadAccess(handler));\n  }\n\n  /**\n   * Synchronously executes an {@link UncheckedCallable} on the thread that this Handler is\n   * associated with, and returns its result.\n   * @param c the {@link UncheckedCallable} to execute\n   * @param <V> the return type of the {@link UncheckedCallable}\n   * @return the return value from {@link UncheckedCallable#call()}\n   * @throws RuntimeException if the {@link UncheckedCallable} could not be executed (the cause\n   * will be null), or if {@link UncheckedCallable#call()} threw an exception (the cause will be the\n   * exception that it threw).\n   */\n  public static <V> V postAndWait(Handler handler, final UncheckedCallable<V> c) {\n    if (checkThreadAccess(handler)) {\n      try {\n        return c.call();\n      } catch (Exception e) {\n        throw new RuntimeException(e);\n      }\n    }\n\n    WaitableRunnable<V> wrapper = new WaitableRunnable<V>() {\n      @Override\n      protected V onRun() {\n        return c.call();\n      }\n    };\n\n    return wrapper.invoke(handler);\n  }\n\n  /**\n   * Synchronously executes a {@link Runnable} on the thread that this Handler is associated with.\n   * @param r the {@link Runnable} to execute\n   * @throws RuntimeException if the {@link Runnable} could not be executed (the cause will be\n   * null), or if {@link Runnable#run()} threw an exception (the cause will be the exception that\n   * it threw).\n   */\n  public static void postAndWait(Handler handler, final Runnable r) {\n    if (checkThreadAccess(handler)) {\n      try {\n        r.run();\n        return;\n      } catch (RuntimeException e) {\n        throw new RuntimeException(e);\n      }\n    }\n\n    WaitableRunnable<Void> wrapper = new WaitableRunnable<Void>() {\n      @Override\n      protected Void onRun() {\n        r.run();\n        return null;\n      }\n    };\n\n    wrapper.invoke(handler);\n  }\n\n  private static abstract class WaitableRunnable<V> implements Runnable {\n    private boolean mIsDone;\n    private V mValue;\n    private Exception mException;\n\n    protected WaitableRunnable() {\n    }\n\n    @Override\n    public final void run() {\n      try {\n        mValue = onRun();\n        mException = null;\n      } catch (Exception e) {\n        mValue = null;\n        mException = e;\n      } finally {\n        synchronized (this) {\n          mIsDone = true;\n          notifyAll();\n        }\n      }\n    }\n\n    protected abstract V onRun();\n\n    public V invoke(Handler handler) {\n      if (!handler.post(this)) {\n        throw new RuntimeException(\"Handler.post() returned false\");\n      }\n\n      join();\n\n      if (mException != null) {\n        throw new RuntimeException(mException);\n      }\n\n      return mValue;\n    }\n\n    private void join() {\n      synchronized (this) {\n        while (!mIsDone) {\n          try {\n            wait();\n          } catch (InterruptedException e) {\n          }\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/ResourcesUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.content.res.Resources;\nimport com.facebook.stetho.common.LogUtil;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\npublic class ResourcesUtil {\n  private ResourcesUtil() {\n  }\n\n  @Nonnull\n  public static String getIdStringQuietly(Object idContext, @Nullable Resources r, int resourceId) {\n    try {\n      return getIdString(r, resourceId);\n    } catch (Resources.NotFoundException e) {\n      String idString = getFallbackIdString(resourceId);\n      LogUtil.w(\"Unknown identifier encountered on \" + idContext + \": \" + idString);\n      return idString;\n    }\n  }\n\n  public static String getIdString(@Nullable Resources r, int resourceId)\n      throws Resources.NotFoundException {\n    if (r == null) {\n      return getFallbackIdString(resourceId);\n    }\n\n    String prefix;\n    String prefixSeparator;\n    switch (getResourcePackageId(resourceId)) {\n      case 0x7f:\n        prefix = \"\";\n        prefixSeparator = \"\";\n        break;\n      default:\n        prefix = r.getResourcePackageName(resourceId);\n        prefixSeparator = \":\";\n        break;\n    }\n\n    String typeName = r.getResourceTypeName(resourceId);\n    String entryName = r.getResourceEntryName(resourceId);\n\n    StringBuilder sb = new StringBuilder(\n        1 + prefix.length() + prefixSeparator.length() +\n            typeName.length() + 1 + entryName.length());\n    sb.append(\"@\");\n    sb.append(prefix);\n    sb.append(prefixSeparator);\n    sb.append(typeName);\n    sb.append(\"/\");\n    sb.append(entryName);\n\n    return sb.toString();\n  }\n\n  private static String getFallbackIdString(int resourceId) {\n    return \"#\" + Integer.toHexString(resourceId);\n  }\n\n  private static int getResourcePackageId(int id) {\n    return (id >>> 24) & 0xff;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/ViewGroupUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.view.View;\nimport android.view.ViewGroup;\n\npublic final class ViewGroupUtil {\n  private ViewGroupUtil() {\n  }\n\n  public static int findChildIndex(ViewGroup parent, View child) {\n    int count = parent.getChildCount();\n    for (int i = 0; i < count; ++i) {\n      if (parent.getChildAt(i) == child) {\n        return i;\n      }\n    }\n    return -1;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/common/android/ViewUtil.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.common.android;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.ContextWrapper;\nimport android.view.View;\nimport android.view.ViewParent;\n\nimport javax.annotation.Nullable;\n\nfinal class ViewUtil {\n  private ViewUtil() {\n  }\n\n  @Nullable\n  static Activity tryGetActivity(View view) {\n    if (view == null) {\n      return null;\n    }\n\n    Context context = view.getContext();\n\n    Activity activityFromContext = tryGetActivity(context);\n    if (activityFromContext != null) {\n      return activityFromContext;\n    }\n\n    ViewParent parent = view.getParent();\n    if (parent instanceof View) {\n      View parentView = (View)parent;\n      return tryGetActivity(parentView);\n    }\n\n    return null;\n  }\n\n  @Nullable\n  private static Activity tryGetActivity(Context context) {\n    while (context != null) {\n      if (context instanceof Activity) {\n        return (Activity) context;\n      } else if (context instanceof ContextWrapper) {\n        context = ((ContextWrapper) context).getBaseContext();\n      } else {\n        return null;\n      }\n    }\n\n    return null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/ArgsHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class ArgsHelper {\n  public static String nextOptionalArg(Iterator<String> argsIter, String defaultValue) {\n    return argsIter.hasNext() ? argsIter.next() : defaultValue;\n  }\n\n  public static String nextArg(Iterator<String> argsIter, String errorIfMissing)\n      throws DumpUsageException {\n    if (!argsIter.hasNext()) {\n      throw new DumpUsageException(errorIfMissing);\n    }\n    return argsIter.next();\n  }\n\n  public static String[] drainToArray(Iterator<String> argsIter) {\n    List<String> args = new ArrayList<>();\n    while (argsIter.hasNext()) {\n      args.add(argsIter.next());\n    }\n    return args.toArray(new String[args.size()]);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport org.apache.commons.cli.ParseException;\n\n/**\n * Exception thrown if there is a functional issue executing the specified commands.  This is\n * not thrown on {@link ParseException} (which represents a command-line syntax issue).\n * <p>\n * This exception's message should be human readable and will be printed to the dumpapp\n * caller.  dumpapp will also exit with a non-zero exit code.\n */\npublic class DumpException extends Exception {\n  public DumpException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpUsageException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\n/**\n * Usage error in a {@link DumperPlugin}.\n */\npublic class DumpUsageException extends DumpException {\n  public DumpUsageException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpappFramingException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport java.io.IOException;\n\n/**\n * Thrown to indicate an error in the dumpapp framing protocol as received from the remote\n * peer.\n */\nclass DumpappFramingException extends IOException {\n  public DumpappFramingException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpappHttpSocketLikeHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport com.facebook.stetho.server.SocketLike;\nimport com.facebook.stetho.server.SocketLikeHandler;\nimport com.facebook.stetho.server.http.ExactPathMatcher;\nimport com.facebook.stetho.server.http.HandlerRegistry;\nimport com.facebook.stetho.server.http.HttpHandler;\nimport com.facebook.stetho.server.http.HttpStatus;\nimport com.facebook.stetho.server.http.LightHttpBody;\nimport com.facebook.stetho.server.http.LightHttpRequest;\nimport com.facebook.stetho.server.http.LightHttpResponse;\nimport com.facebook.stetho.server.http.LightHttpServer;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.List;\n\n/**\n * Provides a legacy hook to support old dumpapp scripts which have not been\n * updated to the new binary protocol.  Since the framing protocol is still compatible this\n * can be easily supported to reduce upgrade friction since so many developers forgot to\n * copy the {@code dumpapp} script on upgrade.\n *\n * <p>{@code stdin} is not supported with this legacy mode.  Streaming output is also not\n * supported.</p>\n *\n * <p>This script sends a warning to stderr informing the developer of its deprecated nature.</p>\n */\n@Deprecated\npublic class DumpappHttpSocketLikeHandler implements SocketLikeHandler {\n  private final LightHttpServer mServer;\n\n  public DumpappHttpSocketLikeHandler(Dumper dumper) {\n    HandlerRegistry registry = new HandlerRegistry();\n    registry.register(\n        new ExactPathMatcher(\"/dumpapp\"),\n        new DumpappLegacyHttpHandler(dumper));\n    mServer = new LightHttpServer(registry);\n  }\n\n  @Override\n  public void onAccepted(SocketLike socket) throws IOException {\n    mServer.serve(socket);\n  }\n\n  private static class DumpappLegacyHttpHandler implements HttpHandler {\n    private static final String QUERY_PARAM_ARGV = \"argv\";\n    private static final String RESPONSE_HEADER_ALLOW_ORIGIN = \"Access-Control-Allow-Origin\";\n    private static final String CONTENT_TYPE = \"application/octet-stream\";\n\n    private final Dumper mDumper;\n\n    public DumpappLegacyHttpHandler(Dumper dumper) {\n      mDumper = dumper;\n    }\n\n    @Override\n    public boolean handleRequest(\n        SocketLike socket,\n        LightHttpRequest request,\n        LightHttpResponse response) throws IOException {\n      boolean postMethod = \"POST\".equals(request.method);\n      boolean getMethod = !postMethod && \"GET\".equals(request.method);\n\n      if (getMethod || postMethod) {\n        List<String> argv = request.uri.getQueryParameters(QUERY_PARAM_ARGV);\n\n        ByteArrayOutputStream outputBuffer = new ByteArrayOutputStream();\n        Framer framer = new Framer(\n            new ByteArrayInputStream(new byte[0]),\n            outputBuffer);\n\n        String warningPrefix = postMethod ? \"ERROR\" : \"WARNING\";\n\n        framer.getStderr().println(\n            \"*** \" + warningPrefix + \": Using legacy HTTP protocol; update dumpapp script! ***\");\n\n        if (getMethod) {\n          DumpappSocketLikeHandler.dump(mDumper, framer, argv.toArray(new String[argv.size()]));\n        } else {\n          // stdin access is completely unsupported, so we can't allow any dumper plugins\n          // to run...\n          framer.writeExitCode(1);\n        }\n\n        response.code = HttpStatus.HTTP_OK;\n        response.reasonPhrase = \"OK\";\n        response.addHeader(RESPONSE_HEADER_ALLOW_ORIGIN, \"*\");\n        response.body = LightHttpBody.create(outputBuffer.toByteArray(), CONTENT_TYPE);\n      } else {\n        response.code = HttpStatus.HTTP_NOT_IMPLEMENTED;\n        response.reasonPhrase = \"Not implemented\";\n        response.body = LightHttpBody.create(request.method + \" not implemented\", \"text/plain\");\n      }\n\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpappOutputBrokenException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\n\n/**\n * When streaming output, it is common for the user to just hit Ctrl-C\n * to terminate the stream.  When this happens, the underlying output\n * stream throws an {@link IOException} to indicate the pipe is broken.\n * Dumpapp uses a {@link PrintStream} to wrap the underlying {@link OutputStream}\n * though, and {@link PrintStream} silently swallows {@link IOException}.\n * <p/>\n * While streaming dumpers can/should check {@link PrintStream#checkError},\n * this is used in cases where we know the stream has gone bad to force flow\n * control out of the dumper and back into the calling machinery that controls\n * the stream framer.\n */\nclass DumpappOutputBrokenException extends RuntimeException {\n\n  public DumpappOutputBrokenException() {\n  }\n\n  public DumpappOutputBrokenException(String detailMessage) {\n    super(detailMessage);\n  }\n\n  public DumpappOutputBrokenException(String detailMessage, Throwable throwable) {\n    super(detailMessage, throwable);\n  }\n\n  public DumpappOutputBrokenException(Throwable throwable) {\n    super(throwable);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumpappSocketLikeHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.server.SocketLike;\nimport com.facebook.stetho.server.SocketLikeHandler;\n\nimport java.io.DataInputStream;\nimport java.io.IOException;\nimport java.util.Arrays;\n\n/**\n * Provides a kind of CLI-over-HTTP support for the ./scripts/dumpapp tool.\n * <p>\n * This handler accepts a list of text-based arguments to a FAB endpoint and responds with\n * a stream as furnished by the Dumper implementation on the app side.  A special \"exit code\"\n * property is also returned that the dumpapp tool uses to pass along the exit code of the\n * script.\n */\npublic class DumpappSocketLikeHandler implements SocketLikeHandler {\n  public static final byte[] PROTOCOL_MAGIC = new byte[] { 'D', 'U', 'M', 'P' };\n  public static final int PROTOCOL_VERSION = 1;\n\n  private final Dumper mDumper;\n\n  public DumpappSocketLikeHandler(Dumper dumper) {\n    mDumper = dumper;\n  }\n\n  @Override\n  public void onAccepted(SocketLike socket) throws IOException {\n    DataInputStream in = new DataInputStream(socket.getInput());\n\n    // Get through the initial hello...\n    establishConversation(in);\n\n    Framer framer = new Framer(in, socket.getOutput());\n    String[] args = readArgs(framer);\n\n    dump(mDumper, framer, args);\n  }\n\n  static void dump(Dumper dumper, Framer framer, String[] args) throws IOException {\n    try {\n      // We intentionally do not catch-all and write an exit code here.\n      //\n      // The dumper catches all expected exceptions and translates\n      // them to an exit code, so the normal case is all good.\n      //\n      // DumpappOutputBrokenException is thrown in cases where we know\n      // we are unable to write any more, including possibly while\n      // writing the error code itself.\n      //\n      // Because other unchecked exceptions could also be thrown in\n      // cases where the underlying stream is broken, and making\n      // further calls on the broken stream (to write an exit code)\n      // can corrupt the stream and throw still more unchecked\n      // exceptions, we cannot safely write an exit code in this case.\n      int exitCode = dumper.dump(\n          framer.getStdin(),\n          framer.getStdout(),\n          framer.getStderr(),\n          args);\n      framer.writeExitCode(exitCode);\n    } catch (DumpappOutputBrokenException e) {\n      // This exception indicates we must stop all writes to the underlying stream\n      // because there was IOException.  We interpret this to mean that we should\n      // also shutdown the whole pipeline, similar to how SIGPIPE would behave\n      // for command-line apps.\n    }\n  }\n\n  private void establishConversation(DataInputStream in) throws IOException {\n    byte[] magic = new byte[4];\n    in.readFully(magic);\n    if (!Arrays.equals(PROTOCOL_MAGIC, magic)) {\n      throw logAndThrowProtocolException(\n          \"Incompatible protocol, are you using an old dumpapp script?\");\n    }\n\n    int version = in.readInt();\n    if (version != PROTOCOL_VERSION) {\n      throw logAndThrowProtocolException(\n          \"Expected version=\" + PROTOCOL_VERSION + \"; got=\" + version);\n    }\n  }\n\n  private static IOException logAndThrowProtocolException(String message) throws IOException {\n    LogUtil.w(message);\n    throw new IOException(message);\n  }\n\n  private String[] readArgs(Framer framer) throws IOException {\n    synchronized (framer) {\n      byte type = framer.readFrameType();\n      switch (type) {\n        case Framer.ENTER_FRAME_PREFIX:\n          int argc = framer.readInt();\n          String[] argv = new String[argc];\n          for (int i = 0; i < argc; i++) {\n            argv[i] = framer.readString();\n          }\n          return argv;\n        default:\n          throw new DumpappFramingException(\"Expected enter frame, got: \" + type);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/Dumper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport java.io.InputStream;\nimport java.io.PrintStream;\nimport java.io.PrintWriter;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport org.apache.commons.cli.CommandLine;\nimport org.apache.commons.cli.CommandLineParser;\nimport org.apache.commons.cli.GnuParser;\nimport org.apache.commons.cli.HelpFormatter;\nimport org.apache.commons.cli.ParseException;\n\npublic class Dumper {\n\n  /**\n   * Map of dumper plugin name to the plugin itself.\n   */\n  private final Map<String, DumperPlugin> mDumperPlugins;\n  private final CommandLineParser mParser;\n  private final GlobalOptions mGlobalOptions;\n\n  public Dumper(Iterable<DumperPlugin> dumperPlugins) {\n    this(dumperPlugins, new GnuParser());\n  }\n\n  public Dumper(\n      Iterable<DumperPlugin> dumperPlugins,\n      CommandLineParser parser) {\n    mDumperPlugins = generatePluginMap(dumperPlugins);\n    mParser = parser;\n    mGlobalOptions = new GlobalOptions();\n  }\n\n  private static Map<String, DumperPlugin> generatePluginMap(Iterable<DumperPlugin> plugins) {\n    Map<String, DumperPlugin> map = new HashMap<String, DumperPlugin>();\n    for (DumperPlugin plugin : plugins) {\n      map.put(plugin.getName(), plugin);\n    }\n    return Collections.unmodifiableMap(map);\n  }\n\n  /**\n   * Execute the dump method as if it were a fragile external command.  Swallows exceptions\n   * and translates them to dumped output text.\n   *\n   * @param input Input that came from the CLI's stdin.\n   * @param out Output that will eventually make its way to the CLI's stdout.\n   * @param err Output that will eventually make its way to the CLI's stderr.\n   * @param args Argument list as passed from the CLI.\n   *\n   * @return Exit code as if this were a command-line invocation.\n   */\n  public int dump(InputStream input, PrintStream out, PrintStream err, String[] args) {\n    try {\n      return doDump(input, out, err, args);\n    } catch (ParseException e) {\n      err.println(e.getMessage());\n      dumpUsage(err);\n      return 1;\n    } catch (DumpException e) {\n      err.println(e.getMessage());\n      return 1;\n    } catch (DumpappOutputBrokenException e) {\n      // The peer is already gone, no sense in sending the exception stack to them.\n      throw e;\n    } catch (RuntimeException e) {\n      e.printStackTrace(err);\n      return 1;\n    }\n  }\n\n  /**\n   * @throws ParseException If any args syntax constraint is violated and the dump was not able to\n   *     proceed.\n   * @throws DumpException Human readable error executing the dump command.\n   */\n  private int doDump(InputStream input, PrintStream out, PrintStream err, String[] args)\n      throws ParseException, DumpException {\n    CommandLine parsedArgs = mParser.parse(mGlobalOptions.options,\n        args,\n        true /* stopAtNonOption */);\n\n    if (parsedArgs.hasOption(mGlobalOptions.optionHelp.getOpt())) {\n      dumpUsage(out);\n      return 0;\n    } else if (parsedArgs.hasOption(mGlobalOptions.optionListPlugins.getOpt())) {\n      dumpAvailablePlugins(out);\n      return 0;\n    } else if (!parsedArgs.getArgList().isEmpty()) {\n      dumpPluginOutput(input, out, err, parsedArgs);\n      return 0;\n    } else {\n      // Didn't understand the options, spit out help but use a non-success exit code.\n      dumpUsage(err);\n      return 1;\n    }\n  }\n\n  private void dumpAvailablePlugins(PrintStream output) {\n    List<String> pluginNames = new ArrayList<String>();\n    for (DumperPlugin pluginToDump : mDumperPlugins.values()) {\n      pluginNames.add(pluginToDump.getName());\n    }\n    Collections.sort(pluginNames);\n    for (String pluginName : pluginNames) {\n      output.println(pluginName);\n    }\n  }\n\n  private void dumpPluginOutput(InputStream input,\n      PrintStream out,\n      PrintStream err,\n      CommandLine parsedArgs) throws DumpException {\n    List<String> args = new ArrayList(parsedArgs.getArgList());\n    if (args.size() < 1) {\n      throw new DumpException(\"Expected plugin argument\");\n    }\n    String pluginName = args.remove(0);\n\n    DumperPlugin plugin = mDumperPlugins.get(pluginName);\n    if (plugin == null) {\n      throw new DumpException(\"No plugin named '\" + pluginName + \"'\");\n    }\n\n    DumperContext dumperContext = new DumperContext(input, out, err, mParser, args);\n    plugin.dump(dumperContext);\n  }\n\n  private void dumpUsage(PrintStream output) {\n    final String cmdName = \"dumpapp\";\n    HelpFormatter formatter = new HelpFormatter();\n\n    output.println(\"Usage: \" + cmdName + \" [options] <plugin> [plugin-options]\");\n\n    PrintWriter writer = new PrintWriter(output);\n    try {\n      formatter.printOptions(\n          writer,\n          formatter.getWidth(),\n          mGlobalOptions.options,\n          formatter.getLeftPadding(),\n          formatter.getDescPadding());\n    } finally {\n      writer.flush();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumperContext.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport javax.annotation.concurrent.Immutable;\n\nimport java.io.InputStream;\nimport java.io.PrintStream;\nimport java.util.List;\n\nimport com.facebook.stetho.common.Util;\n\nimport org.apache.commons.cli.CommandLineParser;\n\n@Immutable\npublic class DumperContext {\n  private final InputStream mStdin;\n  private final PrintStream mStdout;\n  private final PrintStream mStderr;\n  private final CommandLineParser mParser;\n  private final List<String> mArgs;\n\n  /**\n   * Construct a new context instance using a new set of remaining arguments after invoking\n   * {@link CommandLineParser#parse}.\n   */\n  protected DumperContext(\n      DumperContext existingContext,\n      List<String> newRemainingArguments) {\n    this(\n        existingContext.getStdin(),\n        existingContext.getStdout(),\n        existingContext.getStderr(),\n        existingContext.getParser(),\n        newRemainingArguments);\n  }\n\n  public DumperContext(\n      InputStream stdin,\n      PrintStream stdout,\n      PrintStream stderr,\n      CommandLineParser parser,\n      List<String> args) {\n    mStdin = Util.throwIfNull(stdin);\n    mStdout = Util.throwIfNull(stdout);\n    mStderr = Util.throwIfNull(stderr);\n    mParser = Util.throwIfNull(parser);\n    mArgs = Util.throwIfNull(args);\n  }\n\n  /**\n   * Access the caller's stdin input stream.  This stream should only be read once (do not rely on\n   * it having been buffered fully).\n   */\n  public InputStream getStdin() {\n    return mStdin;\n  }\n\n  /**\n   * Access the caller's stdout output stream.\n   */\n  public PrintStream getStdout() {\n    return mStdout;\n  }\n\n  /**\n   * Access the caller's stderr output stream.\n   */\n  public PrintStream getStderr() {\n    return mStderr;\n  }\n\n  public CommandLineParser getParser() {\n    return mParser;\n  }\n\n  public List<String> getArgsAsList() {\n    return mArgs;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/DumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\n/**\n * Provides a CLI (command-line interface) facility for {@code fbandroid} modules.\n *\n * <p>Binding an instance of this interface allows you to inject custom debug/dumping code\n * into the app that is accessible when a phone running the app is connected via ADB.  To see a\n * list of current plugins, see:\n * <pre>\n *   ./scripts/dumpapp --list\n * </pre>\n *\n * <h3>General Utility</h3>\n *\n * <p>This system allows for complex components to inform developers of what's been going on\n * recently (or over the process lifetime) in a simple, human-readable fashion.  Good candidates\n * for this kind of interface are those that are otherwise hard to isolate or visualize such as\n * the network stack or task runners (BlueService, Executor, etc).  It is also possible to create\n * a kind of simple instrumentation around components that otherwise have no UI.  For example, it\n * may be useful to be able to clear the image cache or delete individual entries on demand as\n * well as viewing the current state of all cached entries.\n *\n * <h3>Implementation Guidelines</h3>\n *\n * Almost any kind of simple interface will work however there are some basic guidelines that you\n * should follow:\n *\n * <ol>\n * <li>Avoid heavy-weight plugins that add significant additional code to the app.  While this\n * code does not ship in katana (proguard strips it out), it is generally a good idea to evaluate\n * large or complex instrumentation projects in the greater context of tools available at Facebook\n * (in particular Scuba which can aggregate data for our user base in the wild).  1 or 2 classes\n * is a good rule of thumb for reasonable plugin size.\n * <li>Don't implement long-running jobs in {@link #dump}.  If the instrumented action takes a\n * long time, consider a way to \"fire-and-forget\" so that you can return some message to the\n * caller quickly.\n * <li>Don't perform extra up-front initialization not normally needed by the app.  Dumpers should\n * init on demand so that there is no runtime impact to the app.\n * </ol>\n *\n * <h3>Gotchas</h3>\n *\n * <p>In order for dumpapp to work the app must currently be running and there is an\n * initialization overhead of the \"dumper\" system which may make real-time debug output during\n * app startup tricky or impossible.  However, you can work around this by implementing simple\n * stat counters that can be pulled and reset within your plugin.  After initialization completes\n * (and thus dumpapp becomes available), you can query for the stats that were being collected\n * while the app was starting.\n */\npublic interface DumperPlugin {\n\n  /**\n   * Plugin name according to the dumpsys command-line interface.  Please be mindful of the\n   * fact that this is part of a command-line interface and should be generally easy to type and\n   * remember.  Avoid underscores, capital letters, and plural names.  Instead, prefer terse names\n   * like \"network\", \"logging\", etc.\n   */\n  String getName();\n\n  /**\n   * Invoked in response to the user running the dumpapp command and specifying your plugin.\n   * <p>\n   * Any output written to {@link DumperContext#getStdout()} will be displayed to the caller.\n   *\n   * @param dumpContext Contains the command-line state (extra arguments, output channel, etc).\n   * @throws DumpException Your plugin can throw this event to easily bail from a dump sequence\n   *     on unexpected errors.  The message will be displayed directly to the caller and the\n   *     dumpapp script will terminate with a non-successful exit code.\n   */\n  void dump(DumperContext dumpContext) throws DumpException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/Framer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport com.facebook.stetho.common.LogUtil;\n\nimport java.io.BufferedOutputStream;\nimport java.io.DataInputStream;\nimport java.io.DataOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.nio.charset.Charset;\n\nimport javax.annotation.Nullable;\n\n/**\n * Implements framing protocol that allows us to implement a command-line protocol via\n * sockets complete with stdin/stdout/stderr, exit codes, and input arguments.\n * <p />\n * This is the server handler of that protocol, with the client handler in Stetho's {@code dumpapp}\n * script.\n * <p />\n * The framing protocol involves 5-byte fixed headers, possibly followed by a variable\n * size content body.\n * The grammar is:\n * <pre>\n *   CLIENT_FRAME = STDIN_FRAME | ENTER_FRAME\n *   SERVER_FRAME = STDIN_REQUEST_FRAME | STDOUT_FRAME | STDERR_FRAME | EXIT_FRAME\n *   STDIN_REQUEST_FRAME = '_' BIG_ENDIAN_INT\n *   STDIN_FRAME = '-' BIG_ENDIAN_INT BLOB\n *   STDOUT_FRAME = '1' BIG_ENDIAN_INT BLOB\n *   STDERR_FRAME = '2' BIG_ENDIAN_INT BLOB\n *   ENTER_FRAME = '!' BIG_ENDIAN_INT [ BIG_ENDIAN_SHORT STRING ]...\n *   EXIT_FRAME = 'x' BIG_ENDIAN_INT\n *   BIG_ENDIAN_SHORT = (2 bytes as written by {@link DataOutputStream#writeShort})\n *   BIG_ENDIAN_INT = (4 bytes as written by {@link DataOutputStream#writeInt})\n *   BLOB = (variable-size byte array)\n *   STRING = (variable-size UTF8 string)\n * </pre>\n * The BIG_ENDIAN_INT in STDIN/STDOUT/STDERR_FRAME specifies the size (in bytes) of\n * the immediately following BLOB.  For STDIN_REQUEST_FRAME it represents a request\n * for that much data.\n * <p />\n * The BIG_ENDIAN_INT in ENTER_FRAME specifies the number of arguments, with that number of string\n * to follow.\n * <p />\n * The BIG_ENDIAN_INT in EXIT_FRAME specifies the exit code.\n */\nclass Framer {\n  private static final String TAG = \"FramingSocket\";\n\n  public static final byte STDIN_FRAME_PREFIX = '-';\n  public static final byte STDIN_REQUEST_FRAME_PREFIX = '_';\n  public static final byte STDOUT_FRAME_PREFIX = '1';\n  public static final byte STDERR_FRAME_PREFIX = '2';\n  public static final byte ENTER_FRAME_PREFIX = '!';\n  public static final byte EXIT_FRAME_PREFIX = 'x';\n\n  private final DataInputStream mInput;\n  private final InputStream mStdin;\n  private final PrintStream mStdout;\n  private final PrintStream mStderr;\n  private final DataOutputStream mMultiplexedOutputStream;\n\n  public Framer(InputStream input, OutputStream output) throws IOException {\n    mInput = new DataInputStream(input);\n    mMultiplexedOutputStream = new DataOutputStream(output);\n    mStdin = new FramingInputStream();\n    mStdout = new PrintStream(\n        new BufferedOutputStream(\n            new FramingOutputStream(STDOUT_FRAME_PREFIX)));\n    mStderr = new PrintStream(\n        new FramingOutputStream(STDERR_FRAME_PREFIX));\n  }\n\n  public InputStream getStdin() {\n    return mStdin;\n  }\n\n  public PrintStream getStdout() {\n    return mStdout;\n  }\n\n  public PrintStream getStderr() {\n    return mStderr;\n  }\n\n  public byte readFrameType() throws IOException {\n    return mInput.readByte();\n  }\n\n  public int readInt() throws IOException {\n    return mInput.readInt();\n  }\n\n  public String readString() throws IOException {\n    int size = mInput.readUnsignedShort();\n    byte[] buf = new byte[size];\n    mInput.readFully(buf);\n    return new String(buf, Charset.forName(\"UTF-8\"));\n  }\n\n  public void writeExitCode(int exitCode) throws IOException {\n    mStdout.flush();\n    mStderr.flush();\n    writeIntFrame(EXIT_FRAME_PREFIX, exitCode);\n  }\n\n  public void writeIntFrame(byte type, int intParameter) throws IOException {\n    mMultiplexedOutputStream.write(type);\n    mMultiplexedOutputStream.writeInt(intParameter);\n  }\n\n  public void writeBlob(byte[] data, int offset, int count) throws IOException {\n    mMultiplexedOutputStream.write(data, offset, count);\n  }\n\n  private static <T extends Throwable> T handleSuppression(@Nullable T previous, T current) {\n    if (previous == null) {\n      return current;\n    } else {\n      LogUtil.i(TAG, current, \"Suppressed while handling \" + previous);\n      return previous;\n    }\n  }\n\n  private class FramingInputStream extends InputStream {\n    private final ClosedHelper mClosedHelper = new ClosedHelper();\n\n    @Override\n    public int read() throws IOException {\n      byte[] buf = new byte[1];\n      if (read(buf) == 0) {\n        return -1;\n      }\n      return buf[0];\n    }\n\n    @Override\n    public int read(byte[] buffer) throws IOException {\n      return read(buffer, 0, buffer.length);\n    }\n\n    @Override\n    public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {\n      mClosedHelper.throwIfClosed();\n\n      synchronized (Framer.this) {\n        // Ask the client for more data...\n        writeIntFrame(STDIN_REQUEST_FRAME_PREFIX, byteCount);\n        byte b = readFrameType();\n        if (b != STDIN_FRAME_PREFIX) {\n          throw new UnexpectedFrameException(STDIN_FRAME_PREFIX, b);\n        }\n\n        // Read what they gave us...\n        int length = readInt();\n        if (length > 0) {\n          if (length > byteCount) {\n            throw new DumpappFramingException(\n                \"Expected at most \" + byteCount + \" bytes, got: \" + length);\n          }\n          mInput.readFully(buffer, byteOffset, length);\n        }\n        return length;\n      }\n    }\n\n    @Override\n    public long skip(long byteCount) throws IOException {\n      long skipped = 0;\n      int bufSize = (int)Math.min(byteCount, 2048);\n      byte[] buf = new byte[bufSize];\n      synchronized (Framer.this) {\n        while (skipped < byteCount) {\n          int n = read(buf);\n          if (n < 0) {\n            break;\n          }\n          skipped += n;\n        }\n      }\n      return skipped;\n    }\n\n    @Override\n    public void close() throws IOException {\n      mClosedHelper.close();\n    }\n  }\n\n  private class FramingOutputStream extends OutputStream {\n\n    private final byte mPrefix;\n    private final ClosedHelper mClosedHelper = new ClosedHelper();\n\n    public FramingOutputStream(byte prefix) {\n      mPrefix = prefix;\n    }\n\n    @Override\n    public void write(byte[] buffer, int offset, int length) throws IOException {\n      mClosedHelper.throwIfClosed();\n      if (length > 0) {\n        try {\n          synchronized (Framer.this) {\n            writeIntFrame(mPrefix, length);\n            writeBlob(buffer, offset, length);\n            mMultiplexedOutputStream.flush();\n          }\n        } catch (IOException e) {\n          // I/O error here can indicate the pipe is broken, so we need to prevent any\n          // further writes.\n          throw new DumpappOutputBrokenException(e);\n        }\n      }\n    }\n\n    @Override\n    public void write(int oneByte) throws IOException {\n      byte[] buffer = new byte[] { (byte)oneByte };\n      write(buffer, 0, buffer.length);\n    }\n\n    @Override\n    public void write(byte[] buffer) throws IOException {\n      write(buffer, 0, buffer.length);\n    }\n\n    @Override\n    public void close() throws IOException{\n      mClosedHelper.close();\n    }\n  }\n\n  private static class ClosedHelper {\n    private volatile boolean mClosed;\n\n    public void throwIfClosed() throws IOException {\n      if (mClosed) {\n        throw new IOException(\"Stream is closed\");\n      }\n    }\n\n    public void close() {\n      mClosed = true;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/GlobalOptions.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nimport org.apache.commons.cli.Option;\nimport org.apache.commons.cli.Options;\n\npublic class GlobalOptions {\n  public final Option optionHelp = new Option(\"h\", \"help\", false, \"Print this help\");\n  public final Option optionListPlugins = new Option(\"l\", \"list\", false, \"List available plugins\");\n\n  /**\n   * Special option used to inject it into help but is otherwise not processed within the app.\n   * Instead, the dumpapp shell script interprets this and figures out which port to send the\n   * request to.\n   */\n  public final Option optionProcess = new Option(\"p\", \"process\", true, \"Specify target process\");\n\n  public final Options options;\n\n  public GlobalOptions() {\n    options = new Options();\n    options.addOption(optionHelp);\n    options.addOption(optionListPlugins);\n    options.addOption(optionProcess);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/UnexpectedFrameException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp;\n\nclass UnexpectedFrameException extends DumpappFramingException {\n  public UnexpectedFrameException(byte expected, byte got) {\n    super(\"Expected '\" + expected + \"', got: '\" + got + \"'\");\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/CrashDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp.plugins;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.dumpapp.ArgsHelper;\nimport com.facebook.stetho.dumpapp.DumpException;\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintStream;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.InvocationTargetException;\nimport java.util.Iterator;\nimport java.util.concurrent.CountDownLatch;\n\nimport javax.annotation.Nullable;\n\n/**\n * Yes, this intentionally crashes the app.  Useful for testing crash recovery and crash reporter\n * work flows.  Three separate exit strategies are supported; see help output for more details.\n */\npublic class CrashDumperPlugin implements DumperPlugin {\n  private static final String NAME = \"crash\";\n\n  private static final String OPTION_THROW_DEFAULT = \"java.lang.Error\";\n  private static final String OPTION_KILL_DEFAULT = \"9\"; // SIGKILL\n  private static final String OPTION_EXIT_DEFAULT = \"0\"; // EXIT_SUCCESS in C\n\n  public CrashDumperPlugin() {\n  }\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpException {\n    Iterator<String> argsIter = dumpContext.getArgsAsList().iterator();\n\n    String command = ArgsHelper.nextOptionalArg(argsIter, null);\n    if (\"throw\".equals(command)) {\n      doUncaughtException(argsIter);\n    } else if (\"kill\".equals(command)) {\n      doKill(dumpContext, argsIter);\n    } else if (\"exit\".equals(command)) {\n      doSystemExit(argsIter);\n    } else {\n      doUsage(dumpContext.getStdout());\n      if (command != null) {\n        throw new DumpUsageException(\"Unsupported command: \" + command);\n      }\n    }\n  }\n\n  private void doUsage(PrintStream out) {\n    final String cmdName = \"dumpapp \" + NAME;\n\n    String usagePrefix = \"Usage: \" + cmdName + \" \";\n    String blankPrefix = \"       \" + cmdName + \" \";\n    out.println(usagePrefix + \"<command> [command-options]\");\n    out.println(usagePrefix + \"throw\");\n    out.println(blankPrefix + \"kill\");\n    out.println(blankPrefix + \"exit\");\n    out.println();\n    out.println(cmdName + \" throw: Throw an uncaught exception (simulates a program crash)\");\n    out.println(\"    <Throwable>: Throwable class to use (default: \" + OPTION_THROW_DEFAULT + \")\");\n    out.println();\n    out.println(cmdName + \" kill: Send a signal to this process (simulates the low memory killer)\");\n    out.println(\"    <SIGNAL>: Either signal name or number to send (default: \" + OPTION_KILL_DEFAULT + \")\");\n    out.println(\"              See `adb shell kill -l` for more information\");\n    out.println();\n    out.println(cmdName + \" exit: Invoke System.exit (simulates an abnormal Android exit strategy)\");\n    out.println(\"    <code>: Exit code (default: \" + OPTION_EXIT_DEFAULT + \")\");\n  }\n\n  private void doSystemExit(Iterator<String> argsIter) {\n    String exitCodeStr = ArgsHelper.nextOptionalArg(argsIter, OPTION_EXIT_DEFAULT);\n    System.exit(Integer.parseInt(exitCodeStr));\n  }\n\n  private void doKill(DumperContext dumpContext, Iterator<String> argsIter) throws DumpException {\n    String signal = ArgsHelper.nextOptionalArg(argsIter, OPTION_KILL_DEFAULT);\n    try {\n      Process kill = new ProcessBuilder()\n          .command(\"/system/bin/kill\", \"-\" + signal, String.valueOf(android.os.Process.myPid()))\n          .redirectErrorStream(true)\n          .start();\n\n      // Handle kill command output gracefully in the event that the signal delivered didn't\n      // actually take out our process...\n      try {\n        InputStream in = kill.getInputStream();\n        Util.copy(in, dumpContext.getStdout(), new byte[1024]);\n      } finally {\n        kill.destroy();\n      }\n    } catch (IOException e) {\n      throw new DumpException(\"Failed to invoke kill: \" + e);\n    }\n  }\n\n  private void doUncaughtException(Iterator<String> argsIter) throws DumpException {\n    String throwableClassString = ArgsHelper.nextOptionalArg(argsIter, OPTION_THROW_DEFAULT);\n    try {\n      Class<? extends Throwable> throwableClass =\n          (Class<? extends Throwable>)Class.forName(throwableClassString);\n      Throwable t;\n      Constructor<? extends Throwable> ctorWithMessage =\n          tryGetDeclaredConstructor(throwableClass, String.class);\n      if (ctorWithMessage != null) {\n        t = ctorWithMessage.newInstance(\"Uncaught exception triggered by Stetho\");\n      } else {\n        Constructor<? extends Throwable> ctorParameterless =\n            throwableClass.getDeclaredConstructor();\n        t = ctorParameterless.newInstance();\n      }\n\n      Thread crashThread = new Thread(new ThrowRunnable(t));\n      crashThread.start();\n\n      Util.joinUninterruptibly(crashThread);\n    } catch (\n        ClassNotFoundException |\n        ClassCastException |\n        NoSuchMethodException |\n        IllegalAccessException |\n        InstantiationException e) {\n      throw new DumpException(\"Invalid supplied Throwable class: \" + e);\n    } catch (InvocationTargetException e) {\n      // This means that the method invoked actually threw, independent of reflection.  Best\n      // reflect that as a normal unchecked exception in dumpapp output.\n      throw ExceptionUtil.propagate(e.getCause());\n    }\n  }\n\n  @Nullable\n  private static <T> Constructor<? extends T> tryGetDeclaredConstructor(\n      Class<T> clazz,\n      Class<?>... parameterTypes) {\n    try {\n      return clazz.getDeclaredConstructor(parameterTypes);\n    } catch (NoSuchMethodException e) {\n      return null;\n    }\n  }\n\n  private static class ThrowRunnable implements Runnable {\n    private final Throwable mThrowable;\n\n    public ThrowRunnable(Throwable t) {\n      mThrowable = t;\n    }\n\n    @Override\n    public void run() {\n      ExceptionUtil.<Error>sneakyThrow(mThrowable);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/FilesDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp.plugins;\n\nimport android.content.Context;\nimport android.os.Environment;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.dumpapp.ArgsHelper;\nimport com.facebook.stetho.dumpapp.DumpException;\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\nimport java.io.BufferedOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\npublic class FilesDumperPlugin implements DumperPlugin {\n  private static final String NAME = \"files\";\n\n  private final Context mContext;\n\n  public FilesDumperPlugin(Context context) {\n    mContext = context;\n  }\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpException {\n    Iterator<String> args = dumpContext.getArgsAsList().iterator();\n\n    String command = ArgsHelper.nextOptionalArg(args, \"\");\n    if (\"ls\".equals(command)) {\n      doLs(dumpContext.getStdout());\n    } else if (\"tree\".equals(command)) {\n      doTree(dumpContext.getStdout());\n    } else if (\"download\".equals(command)) {\n      doDownload(dumpContext.getStdout(), args);\n    } else {\n      doUsage(dumpContext.getStdout());\n      if (!\"\".equals(command)) {\n        throw new DumpUsageException(\"Unknown command: \" + command);\n      }\n    }\n  }\n\n  private void doLs(PrintStream writer) throws DumpUsageException {\n    File baseDir = getBaseDir(mContext);\n    if (baseDir.isDirectory()) {\n      printDirectoryText(baseDir, \"\", writer);\n    }\n  }\n\n  private void doTree(PrintStream writer) throws DumpUsageException {\n    File baseDir = getBaseDir(mContext);\n    printDirectoryVisual(baseDir, 0, writer);\n  }\n\n  private static File getBaseDir(Context context) {\n    // getFilesDir() yields /data/data/<package>/files, we want the base package dir.\n    return context.getFilesDir().getParentFile();\n  }\n\n  private static void printDirectoryText(File dir, String path, PrintStream writer) {\n    File[] listFiles = dir.listFiles();\n    for (int i = 0; i < listFiles.length; ++i) {\n      File file = listFiles[i];\n      if (file.isDirectory()) {\n        printDirectoryText(file, path + file.getName() + \"/\", writer);\n      } else {\n        writer.println(path + file.getName());\n      }\n    }\n  }\n\n  private static void printDirectoryVisual(File dir, int depth, PrintStream writer) {\n    File[] listFiles = dir.listFiles();\n    for (int i = 0; i < listFiles.length; ++i) {\n      printHeaderVisual(depth, writer);\n      File file = listFiles[i];\n      writer.print(\"+---\");\n      writer.print(file.getName());\n      writer.println();\n\n      if (file.isDirectory()) {\n        printDirectoryVisual(file, depth + 1, writer);\n      }\n    }\n  }\n\n  private static void printHeaderVisual(int depth, PrintStream writer) {\n    for (int i = 0; i < depth; ++i) {\n      writer.print(\"|   \");\n    }\n  }\n\n  private void doDownload(PrintStream writer, Iterator<String> remainingArgs)\n      throws DumpUsageException {\n    String outputPath = ArgsHelper.nextArg(remainingArgs, \"Must specify output file or '-'\");\n    ArrayList<File> selectedFiles = new ArrayList<>();\n    while (remainingArgs.hasNext()) {\n      selectedFiles.add(resolvePossibleAppStoragePath(mContext, remainingArgs.next()));\n    }\n\n    try {\n      OutputStream outputStream;\n      if (\"-\".equals(outputPath)) {\n        outputStream = writer;\n      } else {\n        outputStream = new FileOutputStream(resolvePossibleSdcardPath(outputPath));\n      }\n      ZipOutputStream output = new ZipOutputStream(new BufferedOutputStream(outputStream));\n      boolean success = false;\n      try {\n        byte[] buf = new byte[2048];\n        if (selectedFiles.size() > 0) {\n          addFiles(output, buf, selectedFiles.toArray(new File[selectedFiles.size()]));\n        } else {\n          addFiles(output, buf, getBaseDir(mContext).listFiles());\n        }\n        success = true;\n      } finally {\n        try {\n          output.close();\n        } catch (IOException e) {\n          Util.close(outputStream, !success);\n          if (success) {\n            throw e;\n          }\n        }\n      }\n    } catch (IOException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private void addFiles(ZipOutputStream output, byte[] buf, File[] files) throws IOException {\n    for (File file : files) {\n      if (file.isDirectory()) {\n        addFiles(output, buf, file.listFiles());\n      } else {\n        output.putNextEntry(\n            new ZipEntry(\n                relativizePath(\n                    getBaseDir(mContext).getParentFile(),\n                    file)));\n        FileInputStream input = new FileInputStream(file);\n        try {\n          copy(input, output, buf);\n        } finally {\n          input.close();\n        }\n      }\n    }\n  }\n\n  private static void copy(InputStream in, OutputStream out, byte[] buf) throws IOException {\n    int n;\n    while ((n = in.read(buf)) >= 0) {\n      out.write(buf, 0, n);\n    }\n  }\n\n  // Disclaimer: stupid implementation :)\n  private static String relativizePath(File base, File path) {\n    String baseStr = base.getAbsolutePath();\n    String pathStr = path.getAbsolutePath();\n\n    if (pathStr.startsWith(baseStr)) {\n      return pathStr.substring(baseStr.length() + 1);\n    } else {\n      return pathStr;\n    }\n  }\n\n  private static File resolvePossibleAppStoragePath(Context context, String path) {\n    if (path.startsWith(\"/\")) {\n      return new File(path);\n    } else {\n      return new File(getBaseDir(context), path);\n    }\n  }\n\n  private static File resolvePossibleSdcardPath(String path) {\n    if (path.startsWith(\"/\")) {\n      return new File(path);\n    } else {\n      return new File(Environment.getExternalStorageDirectory(), path);\n    }\n  }\n\n  private void doUsage(PrintStream writer) {\n    final String cmdName = \"dumpapp \" + NAME;\n\n    String usagePrefix = \"Usage: \" + cmdName + \" \";\n    String blankPrefix = \"       \" + cmdName + \" \";\n    writer.println(usagePrefix + \"<command> [command-options]\");\n    writer.println(blankPrefix + \"ls\");\n    writer.println(blankPrefix + \"tree\");\n    writer.println(blankPrefix + \"download <output.zip> [<path>...]\");\n    writer.println();\n    writer.println(cmdName + \" ls: List files similar to the ls command\");\n    writer.println();\n    writer.println(cmdName + \" tree: List files similar to the tree command\");\n    writer.println();\n    writer.println(cmdName + \" download: Fetch internal application storage\");\n    writer.println(\"    <output.zip>: Output location or '-' for stdout\");\n    writer.println(\"    <path>: Fetch only those paths named (directories fetch recursively)\");\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/HprofDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp.plugins;\n\nimport android.content.Context;\nimport android.os.Debug;\n\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.dumpapp.DumpException;\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintStream;\nimport java.util.Iterator;\n\n/**\n * Generates an hprof on the sdcard and returns to the client the absolute path to the file.\n * <p/>\n * For convenient usage, see: {@code scripts/hprof_dump.sh}\n */\npublic class HprofDumperPlugin implements DumperPlugin {\n\n  private static final String NAME = \"hprof\";\n  private final Context mContext;\n\n  public HprofDumperPlugin(Context context) {\n    mContext = context;\n  }\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpException {\n    final PrintStream output = dumpContext.getStdout();\n\n    Iterator<String> argsIter = dumpContext.getArgsAsList().iterator();\n    String outputPath = argsIter.hasNext() ? argsIter.next() : null;\n    if (outputPath == null) {\n      usage(output);\n    } else {\n      if (\"-\".equals(outputPath)) {\n        handlePipeOutput(output);\n      } else {\n        File outputFile = new File(outputPath);\n        if (!outputFile.isAbsolute()) {\n          outputFile = mContext.getFileStreamPath(outputPath);\n        }\n        writeHprof(outputFile);\n        output.println(\"Wrote to \" + outputFile);\n      }\n    }\n  }\n\n  private void handlePipeOutput(OutputStream output) throws DumpException {\n    File hprofFile = mContext.getFileStreamPath(\"hprof-dump.hprof\");\n    try {\n      writeHprof(hprofFile);\n      try {\n        InputStream input = new FileInputStream(hprofFile);\n        try {\n          Util.copy(input, output, new byte[2048]);\n        } finally {\n          input.close();\n        }\n      } catch (IOException e) {\n        throw new DumpException(\"Failure copying \" + hprofFile + \" to dumper output\");\n      }\n    } finally {\n      if (hprofFile.exists()) {\n        hprofFile.delete();\n      }\n    }\n  }\n\n  private void writeHprof(File outputPath) throws DumpException {\n    try {\n      // Test that we can write here.  dumpHprofData appears to hang if it cannot write\n      // to the target location on ART.\n      truncateAndDeleteFile(outputPath);\n      Debug.dumpHprofData(outputPath.getAbsolutePath());\n    } catch (IOException e) {\n      throw new DumpException(\"Failure writing to \" + outputPath + \": \" + e.getMessage());\n    }\n  }\n\n  private static void truncateAndDeleteFile(File file) throws IOException {\n    FileOutputStream out = new FileOutputStream(file);\n    out.close();\n    if (!file.delete()) {\n      throw new IOException(\"Failed to delete \" + file);\n    }\n  }\n\n  private void usage(PrintStream output) throws DumpUsageException {\n    output.println(\"Usage: dumpapp hprof [ path ]\");\n    output.println(\"Dump HPROF memory usage data from the running application.\");\n    output.println();\n    output.println(\"Where path can be any of:\");\n    output.println(\"  -           Output directly to stdout\");\n    output.println(\"  <path>      Full path to a writable file on the device\");\n    output.println(\"  <filename>  Relative filename that will be stored in the app internal storage\");\n    throw new DumpUsageException(\"Missing path\");\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/dumpapp/plugins/SharedPreferencesDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.dumpapp.plugins;\n\nimport android.annotation.SuppressLint;\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport android.text.TextUtils;\n\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\nimport com.facebook.stetho.inspector.domstorage.SharedPreferencesHelper;\n\nimport java.io.File;\nimport java.io.PrintStream;\nimport java.util.HashSet;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\npublic class SharedPreferencesDumperPlugin implements DumperPlugin {\n\n  private static final String XML_SUFFIX = \".xml\";\n  private static final String NAME = \"prefs\";\n  private final Context mAppContext;\n\n  public SharedPreferencesDumperPlugin(Context context) {\n    mAppContext = context.getApplicationContext();\n  }\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpUsageException {\n    PrintStream writer = dumpContext.getStdout();\n    List<String> args = dumpContext.getArgsAsList();\n\n    String commandName = args.isEmpty() ? \"\" : args.remove(0);\n\n    if (commandName.equals(\"print\")) {\n      doPrint(writer, args);\n    } else if (commandName.equals(\"write\")) {\n      doWrite(args);\n    } else {\n      doUsage(writer);\n    }\n  }\n\n  /**\n   * Executes command to update one value in the shared preferences\n   */\n  // We explicitly want commit() so that the dumper blocks while the write occurs.\n  @SuppressLint(\"CommitPrefEdits\")\n  private void doWrite(List<String> args) throws DumpUsageException {\n    String usagePrefix = \"Usage: prefs write <path> <key> <type> <value>, where type is one of: \";\n\n    Iterator<String> argsIter = args.iterator();\n    String path = nextArg(argsIter, \"Expected <path>\");\n    String key = nextArg(argsIter, \"Expected <key>\");\n    String typeName = nextArg(argsIter, \"Expected <type>\");\n\n    Type type = Type.of(typeName);\n    if (type == null) {\n      throw new DumpUsageException(\n          Type.appendNamesList(new StringBuilder(usagePrefix), \", \").toString());\n    }\n\n    SharedPreferences sharedPreferences = getSharedPreferences(path);\n    SharedPreferences.Editor editor = sharedPreferences.edit();\n\n    switch (type) {\n      case BOOLEAN:\n        editor.putBoolean(key, Boolean.valueOf(nextArgValue(argsIter)));\n        break;\n      case INT:\n        editor.putInt(key, Integer.valueOf(nextArgValue(argsIter)));\n        break;\n      case LONG:\n        editor.putLong(key, Long.valueOf(nextArgValue(argsIter)));\n        break;\n      case FLOAT:\n        editor.putFloat(key, Float.valueOf(nextArgValue(argsIter)));\n        break;\n      case STRING:\n        editor.putString(key, nextArgValue(argsIter));\n        break;\n      case SET:\n        putStringSet(editor, key, argsIter);\n        break;\n    }\n\n    editor.commit();\n  }\n\n  @Nonnull\n  private static String nextArg(Iterator<String> iter, String messageIfNotPresent)\n      throws DumpUsageException {\n    if (!iter.hasNext()) {\n      throw new DumpUsageException(messageIfNotPresent);\n    }\n    return iter.next();\n  }\n\n  @Nonnull\n  private static String nextArgValue(Iterator<String> iter) throws DumpUsageException {\n    return nextArg(iter, \"Expected <value>\");\n  }\n\n  private static void putStringSet(\n      SharedPreferences.Editor editor,\n      String key,\n      Iterator<String> remainingArgs) {\n    HashSet<String> set = new HashSet<String>();\n    while (remainingArgs.hasNext()) {\n      set.add(remainingArgs.next());\n    }\n    editor.putStringSet(key, set);\n  }\n\n  /**\n   * Execute command to print all keys and values stored in the shared preferences which match\n   * the optional given prefix\n   */\n  private void doPrint(PrintStream writer, List<String> args) {\n    String rootPath = mAppContext.getApplicationInfo().dataDir + \"/shared_prefs\";\n    String offsetPrefix = args.isEmpty() ? \"\" : args.get(0);\n    String keyPrefix = (args.size() > 1) ? args.get(1) : \"\";\n\n    printRecursive(writer, rootPath, \"\", offsetPrefix, keyPrefix);\n  }\n\n  private void printRecursive(\n      PrintStream writer,\n      String rootPath,\n      String offsetPath,\n      String pathPrefix,\n      String keyPrefix) {\n    File file = new File(rootPath, offsetPath);\n    if (file.isFile()) {\n      if (offsetPath.endsWith(XML_SUFFIX)) {\n        int suffixLength = XML_SUFFIX.length();\n        String prefsName = offsetPath.substring(0, offsetPath.length() - suffixLength);\n        printFile(writer, prefsName, keyPrefix);\n      }\n    } else if (file.isDirectory()) {\n      String[] children = file.list();\n      if (children != null) {\n        for (int i = 0; i < children.length; i++) {\n          String childOffsetPath = TextUtils.isEmpty(offsetPath)\n              ? children[i]\n              : (offsetPath + File.separator + children[i]);\n          if (childOffsetPath.startsWith(pathPrefix)) {\n            printRecursive(writer, rootPath, childOffsetPath, pathPrefix, keyPrefix);\n          }\n        }\n      }\n    }\n  }\n\n  private void printFile(PrintStream writer, String prefsName, String keyPrefix) {\n    writer.println(prefsName + \":\");\n    SharedPreferences preferences = getSharedPreferences(prefsName);\n    for (Map.Entry<String, ?> entry : SharedPreferencesHelper.getSharedPreferenceEntriesSorted(preferences)) {\n      if (entry.getKey().startsWith(keyPrefix)) {\n        writer.println(\"  \" + entry.getKey() + \" = \" + entry.getValue());\n      }\n    }\n  }\n\n  private void doUsage(PrintStream writer) {\n    final String cmdName = \"dumpapp \" + NAME;\n\n    String usagePrefix = \"Usage: \" + cmdName + \" \";\n    String blankPrefix = \"       \" + cmdName + \" \";\n    writer.println(usagePrefix + \"<command> [command-options]\");\n    writer.println(usagePrefix + \"print [pathPrefix [keyPrefix]]\");\n    writer.println(\n        Type.appendNamesList(\n            new StringBuilder(blankPrefix).append(\"write <path> <key> <\"),\n            \"|\")\n            .append(\"> <value>\"));\n    writer.println();\n    writer.println(cmdName + \" print: Print all matching values from the shared preferences\");\n    writer.println();\n    writer.println(cmdName + \" write: Writes a value to the shared preferences\");\n  }\n\n  private SharedPreferences getSharedPreferences(String name) {\n    return mAppContext.getSharedPreferences(name, Context.MODE_MULTI_PROCESS);\n  }\n\n  private enum Type {\n    BOOLEAN(\"boolean\"),\n    INT(\"int\"),\n    LONG(\"long\"),\n    FLOAT(\"float\"),\n    STRING(\"string\"),\n    SET(\"set\");\n\n    private final String name;\n\n    private Type(String name) {\n      this.name = name;\n    }\n\n    public static @Nullable Type of(String name) {\n      for (Type type : values()) {\n        if (type.name.equals(name)) {\n          return type;\n        }\n      }\n      return null;\n    }\n\n    public static StringBuilder appendNamesList(StringBuilder builder, String separator) {\n      boolean isFirst = true;\n      for (Type type : values()) {\n        if (isFirst) {\n          isFirst = false;\n        } else {\n          builder.append(separator);\n        }\n        builder.append(type.name);\n      }\n      return builder;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/ChromeDevtoolsServer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport android.util.Log;\n\nimport com.facebook.stetho.common.LogRedirector;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.PendingRequest;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcRequest;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcResponse;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.websocket.CloseCodes;\nimport com.facebook.stetho.websocket.SimpleEndpoint;\nimport com.facebook.stetho.websocket.SimpleSession;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * Implements a limited version of the Chrome Debugger WebSocket protocol (using JSON-RPC 2.0).\n * The most up-to-date documentation can be found in the Blink source code:\n * <a href=\"https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/devtools/protocol.json&q=protocol.json&sq=package:chromium&type=cs\">protocol.json</a>\n */\npublic class ChromeDevtoolsServer implements SimpleEndpoint {\n  private static final String TAG = \"ChromeDevtoolsServer\";\n\n  public static final String PATH = \"/inspector\";\n\n  private final ObjectMapper mObjectMapper;\n  private final MethodDispatcher mMethodDispatcher;\n  private final Map<SimpleSession, JsonRpcPeer> mPeers =\n      Collections.synchronizedMap(\n          new HashMap<SimpleSession, JsonRpcPeer>());\n\n  public ChromeDevtoolsServer(Iterable<ChromeDevtoolsDomain> domainModules) {\n    mObjectMapper = new ObjectMapper();\n    mMethodDispatcher = new MethodDispatcher(mObjectMapper, domainModules);\n  }\n\n  @Override\n  public void onOpen(SimpleSession session) {\n    LogRedirector.d(TAG, \"onOpen\");\n    mPeers.put(session, new JsonRpcPeer(mObjectMapper, session));\n  }\n\n  @Override\n  public void onClose(SimpleSession session, int code, String reasonPhrase) {\n    LogRedirector.d(TAG, \"onClose: reason=\" + code + \" \" + reasonPhrase);\n\n    JsonRpcPeer peer = mPeers.remove(session);\n    if (peer != null) {\n      peer.invokeDisconnectReceivers();\n    }\n  }\n\n  @Override\n  public void onMessage(SimpleSession session, byte[] message, int messageLen) {\n    LogRedirector.d(TAG, \"Ignoring binary message of length \" + messageLen);\n  }\n\n  @Override\n  public void onMessage(SimpleSession session, String message) {\n    if (LogRedirector.isLoggable(TAG, Log.VERBOSE)) {\n      LogRedirector.v(TAG, \"onMessage: message=\" + message);\n    }\n    try {\n      JsonRpcPeer peer = mPeers.get(session);\n      Util.throwIfNull(peer);\n\n      handleRemoteMessage(peer, message);\n    } catch (IOException e) {\n      if (LogRedirector.isLoggable(TAG, Log.VERBOSE)) {\n        LogRedirector.v(TAG, \"Unexpected I/O exception processing message: \" + e);\n      }\n      closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());\n    } catch (MessageHandlingException e) {\n      LogRedirector.i(TAG, \"Message could not be processed by implementation: \" + e);\n      closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());\n    } catch (JSONException e) {\n      LogRedirector.v(TAG, \"Unexpected JSON exception processing message\", e);\n      closeSafely(session, CloseCodes.UNEXPECTED_CONDITION, e.getClass().getSimpleName());\n    }\n  }\n\n  private void closeSafely(SimpleSession session, int code, String reasonPhrase) {\n    session.close(code, reasonPhrase);\n  }\n\n  private void handleRemoteMessage(JsonRpcPeer peer, String message)\n      throws IOException, MessageHandlingException, JSONException {\n    // Parse as a generic JSONObject first since we don't know if this is a request or response.\n    JSONObject messageNode = new JSONObject(message);\n    if (messageNode.has(\"method\")) {\n      handleRemoteRequest(peer, messageNode);\n    } else if (messageNode.has(\"result\")) {\n      handleRemoteResponse(peer, messageNode);\n    } else {\n      throw new MessageHandlingException(\"Improper JSON-RPC message: \" + message);\n    }\n  }\n\n  private void handleRemoteRequest(JsonRpcPeer peer, JSONObject requestNode)\n      throws MessageHandlingException {\n    JsonRpcRequest request;\n    request = mObjectMapper.convertValue(\n        requestNode,\n        JsonRpcRequest.class);\n\n    JSONObject result = null;\n    JSONObject error = null;\n    try {\n      result = mMethodDispatcher.dispatch(peer,\n          request.method,\n          request.params);\n    } catch (JsonRpcException e) {\n      logDispatchException(e);\n      error = mObjectMapper.convertValue(e.getErrorMessage(), JSONObject.class);\n    }\n    if (request.id != null) {\n      JsonRpcResponse response = new JsonRpcResponse();\n      response.id = request.id;\n      response.result = result;\n      response.error = error;\n      JSONObject jsonObject = mObjectMapper.convertValue(response, JSONObject.class);\n      String responseString;\n      try {\n        responseString = jsonObject.toString();\n      } catch (OutOfMemoryError e) {\n        // JSONStringer can cause an OOM when the Json to handle is too big.\n        response.result = null;\n        response.error = mObjectMapper.convertValue(e.getMessage(), JSONObject.class);\n        jsonObject = mObjectMapper.convertValue(response, JSONObject.class);\n        responseString = jsonObject.toString();\n      }\n      peer.getWebSocket().sendText(responseString);\n    }\n  }\n\n  private static void logDispatchException(JsonRpcException e) {\n    JsonRpcError errorMessage = e.getErrorMessage();\n    switch (errorMessage.code) {\n      case METHOD_NOT_FOUND:\n        LogRedirector.d(TAG, \"Method not implemented: \" + errorMessage.message);\n        break;\n      default:\n        LogRedirector.w(TAG, \"Error processing remote message\", e);\n    }\n  }\n\n  private void handleRemoteResponse(JsonRpcPeer peer, JSONObject responseNode)\n      throws MismatchedResponseException {\n    JsonRpcResponse response = mObjectMapper.convertValue(\n        responseNode,\n        JsonRpcResponse.class);\n    PendingRequest pendingRequest = peer.getAndRemovePendingRequest(response.id);\n    if (pendingRequest == null) {\n      throw new MismatchedResponseException(response.id);\n    }\n    if (pendingRequest.callback != null) {\n      pendingRequest.callback.onResponse(peer, response);\n    }\n  }\n\n  @Override\n  public void onError(SimpleSession session, Throwable ex) {\n    LogRedirector.e(TAG, \"onError: ex=\" + ex.toString());\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/ChromeDiscoveryHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\nimport android.content.Context;\nimport android.content.pm.PackageInfo;\nimport android.content.pm.PackageManager;\nimport android.net.Uri;\nimport com.facebook.stetho.common.ProcessUtil;\nimport com.facebook.stetho.server.http.ExactPathMatcher;\nimport com.facebook.stetho.server.http.HandlerRegistry;\nimport com.facebook.stetho.server.http.HttpHandler;\nimport com.facebook.stetho.server.http.HttpStatus;\nimport com.facebook.stetho.server.SocketLike;\nimport com.facebook.stetho.server.http.LightHttpBody;\nimport com.facebook.stetho.server.http.LightHttpRequest;\nimport com.facebook.stetho.server.http.LightHttpResponse;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport javax.annotation.Nullable;\n\n/**\n * Provides sufficient responses to convince Chrome's {@code chrome://inspect/devices} that we're\n * \"one of them\".  Note that we are being discovered automatically by the name of our socket\n * as defined in {@link LocalSocketHttpServer}.  After discovery, we're required to provide\n * some context on how exactly to display and inspect what we have.\n */\npublic class ChromeDiscoveryHandler implements HttpHandler {\n  private static final String PAGE_ID = \"1\";\n\n  private static final String PATH_PAGE_LIST = \"/json\";\n  private static final String PATH_PAGE_LIST1 = \"/json/list\";\n  private static final String PATH_VERSION = \"/json/version\";\n  private static final String PATH_ACTIVATE = \"/json/activate/\" + PAGE_ID;\n\n  /**\n   * Latest version of the WebKit Inspector UI that we've tested again (ideally).\n   */\n  private static final String WEBKIT_REV = \"@cfede9db1d154de0468cb0538479f34c0755a0f4\";\n  private static final String WEBKIT_VERSION = \"537.36 (\" + WEBKIT_REV + \")\";\n\n  private static final String USER_AGENT = \"Stetho\";\n\n  /**\n   * Structured version of the WebKit Inspector protocol that we understand.\n   */\n  private static final String PROTOCOL_VERSION = \"1.3\";\n\n  private final Context mContext;\n  private final String mInspectorPath;\n\n  @Nullable private LightHttpBody mVersionResponse;\n  @Nullable private LightHttpBody mPageListResponse;\n\n  public ChromeDiscoveryHandler(Context context, String inspectorPath) {\n    mContext = context;\n    mInspectorPath = inspectorPath;\n  }\n\n  public void register(HandlerRegistry registry) {\n    registry.register(new ExactPathMatcher(PATH_PAGE_LIST), this);\n    registry.register(new ExactPathMatcher(PATH_PAGE_LIST1), this);\n    registry.register(new ExactPathMatcher(PATH_VERSION), this);\n    registry.register(new ExactPathMatcher(PATH_ACTIVATE), this);\n  }\n\n  @Override\n  public boolean handleRequest(SocketLike socket, LightHttpRequest request, LightHttpResponse response) {\n    String path = request.uri.getPath();\n    try {\n      if (PATH_VERSION.equals(path)) {\n        handleVersion(response);\n      } else if (PATH_PAGE_LIST.equals(path) || PATH_PAGE_LIST1.equals(path)) {\n        handlePageList(response);\n      } else if (PATH_ACTIVATE.equals(path)) {\n        handleActivate(response);\n      } else {\n        response.code = HttpStatus.HTTP_NOT_IMPLEMENTED;\n        response.reasonPhrase = \"Not implemented\";\n        response.body = LightHttpBody.create(\"No support for \" + path + \"\\n\", \"text/plain\");\n      }\n    } catch (JSONException e) {\n      response.code = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;\n      response.reasonPhrase = \"Internal server error\";\n      response.body = LightHttpBody.create(e.toString() + \"\\n\", \"text/plain\");\n    }\n    return true;\n  }\n\n  private void handleVersion(LightHttpResponse response)\n      throws JSONException {\n    if (mVersionResponse == null) {\n      JSONObject reply = new JSONObject();\n      reply.put(\"WebKit-Version\", WEBKIT_VERSION);\n      reply.put(\"User-Agent\", USER_AGENT);\n      reply.put(\"Protocol-Version\", PROTOCOL_VERSION);\n      reply.put(\"Browser\", getAppLabelAndVersion());\n      reply.put(\"Android-Package\", mContext.getPackageName());\n      mVersionResponse = LightHttpBody.create(reply.toString(), \"application/json\");\n    }\n    setSuccessfulResponse(response, mVersionResponse);\n  }\n\n  private void handlePageList(LightHttpResponse response)\n      throws JSONException {\n    if (mPageListResponse == null) {\n      JSONArray reply = new JSONArray();\n      JSONObject page = new JSONObject();\n      page.put(\"type\", \"app\");\n      page.put(\"title\", makeTitle());\n      page.put(\"id\", PAGE_ID);\n      page.put(\"description\", \"\");\n\n      page.put(\"webSocketDebuggerUrl\", \"ws://\" + mInspectorPath);\n      Uri chromeFrontendUrl = new Uri.Builder()\n          .scheme(\"http\")\n          .authority(\"chrome-devtools-frontend.appspot.com\")\n          .appendEncodedPath(\"serve_rev\")\n          .appendEncodedPath(WEBKIT_REV)\n          .appendEncodedPath(\"inspector.html\")\n          .appendQueryParameter(\"ws\", mInspectorPath)\n          .build();\n      page.put(\"devtoolsFrontendUrl\", chromeFrontendUrl.toString());\n\n      reply.put(page);\n      mPageListResponse = LightHttpBody.create(reply.toString(), \"application/json\");\n    }\n    setSuccessfulResponse(response, mPageListResponse);\n  }\n\n  private String makeTitle() {\n    StringBuilder b = new StringBuilder();\n    b.append(getAppLabel());\n\n    b.append(\" (powered by Stetho)\");\n\n    String processName = ProcessUtil.getProcessName();\n    int colonIndex = processName.indexOf(':');\n    if (colonIndex >= 0) {\n      String nonDefaultProcessName = processName.substring(colonIndex);\n      b.append(nonDefaultProcessName);\n    }\n\n    return b.toString();\n  }\n\n  private void handleActivate(LightHttpResponse response) {\n    // Arbitrary response seem acceptable :)\n    setSuccessfulResponse(\n        response,\n        LightHttpBody.create(\"Target activation ignored\\n\", \"text/plain\"));\n  }\n\n  private static void setSuccessfulResponse(\n      LightHttpResponse response,\n      LightHttpBody body) {\n    response.code = HttpStatus.HTTP_OK;\n    response.reasonPhrase = \"OK\";\n    response.body = body;\n  }\n\n  private String getAppLabelAndVersion() {\n    StringBuilder b = new StringBuilder();\n    PackageManager pm = mContext.getPackageManager();\n    b.append(getAppLabel());\n    b.append('/');\n    try {\n      PackageInfo info = pm.getPackageInfo(mContext.getPackageName(), 0 /* flags */);\n      b.append(info.versionName);\n    } catch (PackageManager.NameNotFoundException e) {\n      throw new RuntimeException(e);\n    }\n    return b.toString();\n  }\n\n  private CharSequence getAppLabel() {\n    PackageManager pm = mContext.getPackageManager();\n    return pm.getApplicationLabel(mContext.getApplicationInfo());\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/DevtoolsSocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\nimport android.content.Context;\nimport android.net.LocalSocket;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.server.SecureSocketHandler;\nimport com.facebook.stetho.server.SocketLike;\nimport com.facebook.stetho.server.SocketLikeHandler;\nimport com.facebook.stetho.server.http.ExactPathMatcher;\nimport com.facebook.stetho.server.http.HandlerRegistry;\nimport com.facebook.stetho.server.http.LightHttpServer;\nimport com.facebook.stetho.websocket.WebSocketHandler;\n\nimport java.io.IOException;\n\npublic class DevtoolsSocketHandler implements SocketLikeHandler {\n  private final Context mContext;\n  private final Iterable<ChromeDevtoolsDomain> mModules;\n  private final LightHttpServer mServer;\n\n  public DevtoolsSocketHandler(Context context, Iterable<ChromeDevtoolsDomain> modules) {\n    mContext = context;\n    mModules = modules;\n    mServer = createServer();\n  }\n\n  private LightHttpServer createServer() {\n    HandlerRegistry registry = new HandlerRegistry();\n    ChromeDiscoveryHandler discoveryHandler =\n        new ChromeDiscoveryHandler(\n            mContext,\n            ChromeDevtoolsServer.PATH);\n    discoveryHandler.register(registry);\n    registry.register(\n        new ExactPathMatcher(ChromeDevtoolsServer.PATH),\n        new WebSocketHandler(new ChromeDevtoolsServer(mModules)));\n\n    return new LightHttpServer(registry);\n  }\n\n  @Override\n  public void onAccepted(SocketLike socket) throws IOException {\n    mServer.serve(socket);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/MessageHandlingException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\npublic class MessageHandlingException extends Exception {\n  public MessageHandlingException(Throwable cause) {\n    super(cause);\n  }\n\n  public MessageHandlingException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/MethodDispatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\nimport javax.annotation.concurrent.ThreadSafe;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.EmptyResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.ObjectMapper;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n@ThreadSafe\npublic class MethodDispatcher {\n  @GuardedBy(\"this\")\n  private Map<String, MethodDispatchHelper> mMethods;\n\n  private final ObjectMapper mObjectMapper;\n  private final Iterable<ChromeDevtoolsDomain> mDomainHandlers;\n\n  public MethodDispatcher(\n      ObjectMapper objectMapper,\n      Iterable<ChromeDevtoolsDomain> domainHandlers) {\n    mObjectMapper = objectMapper;\n    mDomainHandlers = domainHandlers;\n  }\n\n  private synchronized MethodDispatchHelper findMethodDispatcher(String methodName) {\n    if (mMethods == null) {\n      mMethods = buildDispatchTable(mObjectMapper, mDomainHandlers);\n    }\n    return mMethods.get(methodName);\n  }\n\n  public JSONObject dispatch(JsonRpcPeer peer, String methodName, @Nullable JSONObject params)\n      throws JsonRpcException {\n    MethodDispatchHelper dispatchHelper = findMethodDispatcher(methodName);\n    if (dispatchHelper == null) {\n      throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.METHOD_NOT_FOUND,\n          \"Not implemented: \" + methodName,\n          null /* data */));\n    }\n    try {\n      return dispatchHelper.invoke(peer, params);\n    } catch (InvocationTargetException e) {\n      Throwable cause = e.getCause();\n      ExceptionUtil.propagateIfInstanceOf(cause, JsonRpcException.class);\n      throw ExceptionUtil.propagate(cause);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    } catch (JSONException e) {\n      throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR,\n          e.toString(),\n          null /* data */));\n    }\n  }\n\n  private static class MethodDispatchHelper {\n    private final ObjectMapper mObjectMapper;\n    private final ChromeDevtoolsDomain mInstance;\n    private final Method mMethod;\n\n    public MethodDispatchHelper(ObjectMapper objectMapper,\n        ChromeDevtoolsDomain instance,\n        Method method) {\n      mObjectMapper = objectMapper;\n      mInstance = instance;\n      mMethod = method;\n    }\n\n    public JSONObject invoke(JsonRpcPeer peer, @Nullable JSONObject params)\n        throws InvocationTargetException, IllegalAccessException, JSONException, JsonRpcException {\n      Object internalResult = mMethod.invoke(mInstance, peer, params);\n      if (internalResult == null || internalResult instanceof EmptyResult) {\n        return new JSONObject();\n      } else {\n        JsonRpcResult convertableResult = (JsonRpcResult)internalResult;\n        return mObjectMapper.convertValue(convertableResult, JSONObject.class);\n      }\n    }\n  }\n\n  private static Map<String, MethodDispatchHelper> buildDispatchTable(\n      ObjectMapper objectMapper,\n      Iterable<ChromeDevtoolsDomain> domainHandlers) {\n    Util.throwIfNull(objectMapper);\n    HashMap<String, MethodDispatchHelper> methods = new HashMap<String, MethodDispatchHelper>();\n    for (ChromeDevtoolsDomain domainHandler : Util.throwIfNull(domainHandlers)) {\n      Class<?> handlerClass = domainHandler.getClass();\n      String domainName = handlerClass.getSimpleName();\n\n      for (Method method : handlerClass.getMethods()) {\n        if (isDevtoolsMethod(method)) {\n          MethodDispatchHelper dispatchHelper = new MethodDispatchHelper(\n              objectMapper,\n              domainHandler,\n              method);\n          methods.put(domainName + \".\" + method.getName(), dispatchHelper);\n        }\n      }\n    }\n    return Collections.unmodifiableMap(methods);\n  }\n\n  /**\n   * Determines if the method is a {@link ChromeDevtoolsMethod}, and validates accordingly\n   * if it is.\n   *\n   * @throws IllegalArgumentException Thrown if it is a {@link ChromeDevtoolsMethod} but\n   *     it otherwise fails to satisfy requirements.\n   */\n  private static boolean isDevtoolsMethod(Method method) throws IllegalArgumentException {\n    if (!method.isAnnotationPresent(ChromeDevtoolsMethod.class)) {\n      return false;\n    } else {\n      Class<?> args[] = method.getParameterTypes();\n      String methodName = method.getDeclaringClass().getSimpleName() + \".\" + method.getName();\n      Util.throwIfNot(args.length == 2,\n          \"%s: expected 2 args, got %s\",\n          methodName,\n          args.length);\n      Util.throwIfNot(args[0].equals(JsonRpcPeer.class),\n          \"%s: expected 1st arg of JsonRpcPeer, got %s\",\n          methodName,\n          args[0].getName());\n      Util.throwIfNot(args[1].equals(JSONObject.class),\n          \"%s: expected 2nd arg of JSONObject, got %s\",\n          methodName,\n          args[1].getName());\n\n      Class<?> returnType = method.getReturnType();\n      if (!returnType.equals(void.class)) {\n        Util.throwIfNot(JsonRpcResult.class.isAssignableFrom(returnType),\n            \"%s: expected JsonRpcResult return type, got %s\",\n            methodName,\n            returnType.getName());\n      }\n      return true;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/MismatchedResponseException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector;\n\npublic class MismatchedResponseException extends MessageHandlingException {\n  public long mRequestId;\n\n  public MismatchedResponseException(long requestId) {\n    super(\"Response for request id \" + requestId + \", but no such request is pending\");\n    mRequestId = requestId;\n  }\n\n  public long getRequestId() {\n    return mRequestId;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/console/CLog.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.console;\n\nimport com.facebook.stetho.common.LogRedirector;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.protocol.module.Console;\n\nimport javax.annotation.Nonnull;\n\n/**\n * Utility for reporting an event to the console\n */\npublic class CLog {\n  private static final String TAG = \"CLog\";\n\n  public static void writeToConsole(\n      ChromePeerManager chromePeerManager,\n      Console.MessageLevel logLevel,\n      Console.MessageSource messageSource,\n      String messageText) {\n    // Send to logcat to increase the chances that a developer will notice :)\n    LogRedirector.d(TAG, messageText);\n\n    Console.ConsoleMessage message = new Console.ConsoleMessage();\n    message.source = messageSource;\n    message.level = logLevel;\n    message.text = messageText;\n    Console.MessageAddedRequest messageAddedRequest = new Console.MessageAddedRequest();\n    messageAddedRequest.message = message;\n    chromePeerManager.sendNotificationToPeers(\"Console.messageAdded\", messageAddedRequest);\n  }\n\n  public static void writeToConsole(\n      Console.MessageLevel logLevel,\n      Console.MessageSource messageSource,\n      String messageText\n  ) {\n    ConsolePeerManager peerManager = ConsolePeerManager.getInstanceOrNull();\n    if (peerManager == null) {\n      return;\n    }\n\n    writeToConsole(peerManager, logLevel, messageSource, messageText);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/console/ConsolePeerManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.console;\n\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\n\nimport javax.annotation.Nullable;\n\npublic class ConsolePeerManager extends ChromePeerManager {\n\n  private static ConsolePeerManager sInstance;\n\n  private ConsolePeerManager() {\n    super();\n  }\n\n  @Nullable\n  public static synchronized ConsolePeerManager getInstanceOrNull() {\n    return sInstance;\n  }\n\n  public static synchronized ConsolePeerManager getOrCreateInstance() {\n    if (sInstance == null) {\n      sInstance = new ConsolePeerManager();\n    }\n    return sInstance;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/console/RuntimeRepl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.console;\n\npublic interface RuntimeRepl {\n  Object evaluate(String expression) throws Throwable;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/console/RuntimeReplFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.console;\n\n/**\n * Allows callers to specify their own Console tab REPL for the DevTools UI.  This is part of\n * early support for a possible optionally included default implementation for Android.\n * <p />\n * A new {@link RuntimeRepl} instances is created for each unique peer such that memory\n * can be garbage collected when the peer disconnects.\n * <p />\n * This is provided as part of an experimental API.  Depend on it at your own risk...\n */\npublic interface RuntimeReplFactory {\n  RuntimeRepl newInstance();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/ContentProviderDatabaseDriver.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.content.ContentResolver;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteException;\n\nimport com.facebook.stetho.inspector.protocol.module.Database;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDescriptor;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDriver2;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport javax.annotation.concurrent.ThreadSafe;\n\n@ThreadSafe\npublic class ContentProviderDatabaseDriver\n    extends DatabaseDriver2<ContentProviderDatabaseDriver.ContentProviderDatabaseDescriptor> {\n\n  private final static String sDatabaseName = \"content-providers\";\n\n  private final ContentProviderSchema[] mContentProviderSchemas;\n  private List<String> mTableNames;\n\n  public ContentProviderDatabaseDriver(\n      Context context,\n      ContentProviderSchema... contentProviderSchemas) {\n    super(context);\n    mContentProviderSchemas = contentProviderSchemas;\n  }\n\n  @Override\n  public List<ContentProviderDatabaseDescriptor> getDatabaseNames() {\n    return Collections.singletonList(new ContentProviderDatabaseDescriptor());\n  }\n\n  @Override\n  public List<String> getTableNames(ContentProviderDatabaseDescriptor databaseDesc) {\n    if (mTableNames == null) {\n      mTableNames = new ArrayList<>();\n      for (ContentProviderSchema schema : mContentProviderSchemas) {\n        mTableNames.add(schema.getTableName());\n      }\n    }\n    return mTableNames;\n  }\n\n  @Override\n  public Database.ExecuteSQLResponse executeSQL(\n      ContentProviderDatabaseDescriptor databaseDesc,\n      String query,\n      ExecuteResultHandler<Database.ExecuteSQLResponse> handler) throws SQLiteException {\n\n    // resolve table name from query\n    String tableName = fetchTableName(query);\n\n    // find the right ContentProviderSchema\n    int index = mTableNames.indexOf(tableName);\n    ContentProviderSchema contentProviderSchema = mContentProviderSchemas[index];\n\n    // execute the query\n    ContentResolver contentResolver = mContext.getContentResolver();\n    Cursor cursor = contentResolver.query(\n        contentProviderSchema.getUri(),\n        contentProviderSchema.getProjection(),\n        null,\n        null,\n        null);\n    try {\n      return handler.handleSelect(cursor);\n    } finally {\n      cursor.close();\n    }\n  }\n\n  /**\n   * Fetch the table name from query\n   */\n  private String fetchTableName(String query) {\n    for (String tableName : mTableNames) {\n      if (query.contains(tableName)) {\n        return tableName;\n      }\n    }\n    return \"\";\n  }\n\n  static class ContentProviderDatabaseDescriptor implements DatabaseDescriptor {\n    public ContentProviderDatabaseDescriptor() {\n    }\n\n    @Override\n    public String name() {\n      // Hmm, this probably should be each unique URI or authority instead of treating all\n      // content provider instances as one.\n      return sDatabaseName;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/ContentProviderSchema.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.net.Uri;\n\npublic class ContentProviderSchema {\n\n  private final String mTableName;\n  private final Uri mUri;\n  private final String[] mProjection;\n\n  private ContentProviderSchema(Builder builder) {\n    mTableName = builder.mTable.mTableName;\n    mUri = builder.mTable.mUri;\n    mProjection = builder.mTable.mProjection;\n  }\n\n  public String getTableName() {\n    return mTableName;\n  }\n\n  public Uri getUri() {\n    return mUri;\n  }\n\n  public String[] getProjection() {\n    return mProjection;\n  }\n\n  public static class Builder {\n\n    private Table mTable;\n\n    public Builder table(Table table) {\n      mTable = table;\n      return this;\n    }\n\n    public ContentProviderSchema build() {\n      return new ContentProviderSchema(this);\n    }\n\n  }\n\n  public static class Table {\n\n    private Uri mUri;\n    private String[] mProjection;\n    private String mTableName;\n\n    private Table(Builder builder) {\n      mUri = builder.mUri;\n      mProjection = builder.mProjection;\n      mTableName = builder.mTableName;\n      if (mTableName == null) {\n        mTableName = mUri.getLastPathSegment();\n      }\n    }\n\n    public static class Builder {\n\n      private Uri mUri;\n      private String[] mProjection;\n      private String mTableName;\n\n      public Builder uri(Uri contentUri) {\n        mUri = contentUri;\n        return this;\n      }\n\n      // optional\n      public Builder projection(String[] columns) {\n        mProjection = columns;\n        return this;\n      }\n\n      // optional, if not set, last segment of URI will be used as table name\n      public Builder name(String tableName) {\n        mTableName = tableName;\n        return this;\n      }\n\n      public Table build() {\n        return new Table(this);\n      }\n\n    }\n\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/DatabaseConnectionProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteException;\n\nimport java.io.File;\n\n/**\n * Provides a {@link SQLiteDatabase} connection for the specified database.  For use with\n * {@link SqliteDatabaseDriver}.\n */\npublic interface DatabaseConnectionProvider {\n  /**\n   * @param databaseFile Full path to the database file.\n   * @return a connection for the specified databaseName.\n   * @throws SQLiteException if there is an error opening the specified database\n   */\n  SQLiteDatabase openDatabase(File databaseFile) throws SQLiteException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/DatabaseDriver2Adapter.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.database.sqlite.SQLiteException;\n\nimport com.facebook.stetho.inspector.protocol.module.Database;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDescriptor;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDriver2;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * @deprecated Use {@link DatabaseDriver2} directly.  This is provided only for legacy\n * drivers to be adapted internally within Stetho.\n */\n@Deprecated\npublic class DatabaseDriver2Adapter\n    extends DatabaseDriver2<DatabaseDriver2Adapter.StringDatabaseDescriptor> {\n  private final Database.DatabaseDriver mLegacy;\n\n  public DatabaseDriver2Adapter(Database.DatabaseDriver legacy) {\n    super(legacy.getContext());\n    mLegacy = legacy;\n  }\n\n  @Override\n  public List<StringDatabaseDescriptor> getDatabaseNames() {\n    List<?> names = mLegacy.getDatabaseNames();\n    List<StringDatabaseDescriptor> descriptors = new ArrayList<>(names.size());\n    for (Object name : names) {\n      descriptors.add(new StringDatabaseDescriptor(name.toString()));\n    }\n    return descriptors;\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public List<String> getTableNames(StringDatabaseDescriptor database) {\n    return mLegacy.getTableNames(database.name);\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  public Database.ExecuteSQLResponse executeSQL(\n      StringDatabaseDescriptor database,\n      String query,\n      ExecuteResultHandler handler) throws SQLiteException {\n    return mLegacy.executeSQL(database.name, query, handler);\n  }\n\n  static class StringDatabaseDescriptor implements DatabaseDescriptor {\n    public final String name;\n\n    public StringDatabaseDescriptor(String name) {\n      this.name = name;\n    }\n\n    @Override\n    public String name() {\n      return name;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/DatabaseFilesProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport java.io.File;\nimport java.util.List;\n\n/**\n * Provides a {@link List} of database files.\n */\npublic interface DatabaseFilesProvider {\n\n  /**\n   * Returns a {@link List} of database files.\n   */\n  List<File> getDatabaseFiles();\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/DefaultDatabaseConnectionProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteException;\n\nimport com.facebook.stetho.inspector.database.SQLiteDatabaseCompat.SQLiteOpenOptions;\n\nimport java.io.File;\n\n/**\n * Opens the requested database using\n * {@link SQLiteDatabase#openDatabase(String, SQLiteDatabase.CursorFactory, int)} directly.\n *\n * <p>It is intended that this class be subclassed to enable/disable features via\n * {@link #determineOpenOptions(File)}</p>\n */\npublic class DefaultDatabaseConnectionProvider implements DatabaseConnectionProvider {\n  public DefaultDatabaseConnectionProvider() {\n  }\n\n  @Override\n  public SQLiteDatabase openDatabase(File databaseFile) throws SQLiteException {\n    return performOpen(\n        databaseFile,\n        determineOpenOptions(databaseFile));\n  }\n\n  /**\n   * Subclassing this function is intended to provide custom open behaviour on a per-file basis.\n   */\n  protected @SQLiteOpenOptions int determineOpenOptions(File databaseFile) {\n    @SQLiteOpenOptions int flags = 0;\n\n    // Try to guess if we should be using write-ahead logging.  If this heuristic fails\n    // developers are expected to subclass this provider and explicitly assert the connection.\n    File walFile = new File(databaseFile.getParent(), databaseFile.getName() + \"-wal\");\n    if (walFile.exists()) {\n      flags |= SQLiteDatabaseCompat.ENABLE_WRITE_AHEAD_LOGGING;\n    }\n\n    return flags;\n  }\n\n  /**\n   * Perform the open per the options provided in {@link #determineOpenOptions(File)}.\n   * Subclassing is supported however this typically indicates a missing feature of some kind\n   * in {@link SQLiteDatabaseCompat} that should be patched in Stetho itself.\n   */\n  protected SQLiteDatabase performOpen(File databaseFile, @SQLiteOpenOptions int options) {\n    int flags = SQLiteDatabase.OPEN_READWRITE;\n\n    SQLiteDatabaseCompat compatInstance = SQLiteDatabaseCompat.getInstance();\n    flags |= compatInstance.provideOpenFlags(options);\n\n    SQLiteDatabase db = SQLiteDatabase.openDatabase(\n        databaseFile.getAbsolutePath(),\n        null /* cursorFactory */,\n        flags);\n    compatInstance.enableFeatures(options, db);\n    return db;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/DefaultDatabaseFilesProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.content.Context;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Provides the results of {@link Context#databaseList()} for {@link SqliteDatabaseDriver}.\n */\npublic final class DefaultDatabaseFilesProvider implements DatabaseFilesProvider {\n  private final Context mContext;\n\n  public DefaultDatabaseFilesProvider(Context context) {\n    mContext = context;\n  }\n\n  @Override\n  public List<File> getDatabaseFiles() {\n    List<File> databaseFiles = new ArrayList<File>();\n    for (String databaseName : mContext.databaseList()) {\n      databaseFiles.add(mContext.getDatabasePath(databaseName));\n    }\n    return databaseFiles;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/SQLiteDatabaseCompat.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.annotation.TargetApi;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.os.Build;\n\nimport androidx.annotation.IntDef;\n\n/**\n * Compatibility layer which supports opening databases with WAL and foreign key support\n * where supported.\n *\n * <p>For simplicity of implementation, all options are <em>ignored</em> prior to Honeycomb.</p>\n */\npublic abstract class SQLiteDatabaseCompat {\n  public static final int ENABLE_WRITE_AHEAD_LOGGING = 0x1;\n  public static final int ENABLE_FOREIGN_KEY_CONSTRAINTS = 0x2;\n  @IntDef(\n      value = { ENABLE_WRITE_AHEAD_LOGGING, ENABLE_FOREIGN_KEY_CONSTRAINTS },\n      flag = true)\n  public @interface SQLiteOpenOptions {}\n\n  private static final SQLiteDatabaseCompat sInstance;\n\n  static {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {\n      sInstance = new JellyBeanAndBeyondImpl();\n    } else {\n      sInstance = new IceCreamSandwichImpl();\n    }\n  }\n\n  public static SQLiteDatabaseCompat getInstance() {\n    return sInstance;\n  }\n\n  public abstract int provideOpenFlags(@SQLiteOpenOptions int openOptions);\n  public abstract void enableFeatures(@SQLiteOpenOptions int openOptions, SQLiteDatabase db);\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN)\n  private static class JellyBeanAndBeyondImpl extends SQLiteDatabaseCompat {\n    @Override\n    public int provideOpenFlags(@SQLiteOpenOptions int openOptions) {\n      int openFlags = 0;\n      if ((openOptions & ENABLE_WRITE_AHEAD_LOGGING) != 0) {\n        openFlags |= SQLiteDatabase.ENABLE_WRITE_AHEAD_LOGGING;\n      }\n      return openFlags;\n    }\n\n    @Override\n    public void enableFeatures(@SQLiteOpenOptions int openOptions, SQLiteDatabase db) {\n      if ((openOptions & ENABLE_FOREIGN_KEY_CONSTRAINTS) != 0) {\n        db.setForeignKeyConstraintsEnabled(true);\n      }\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n  private static class IceCreamSandwichImpl extends SQLiteDatabaseCompat {\n    @Override\n    public int provideOpenFlags(@SQLiteOpenOptions int openOptions) {\n      return 0;\n    }\n\n    @Override\n    public void enableFeatures(@SQLiteOpenOptions int openOptions, SQLiteDatabase db) {\n      if ((openOptions & ENABLE_WRITE_AHEAD_LOGGING) != 0) {\n        db.enableWriteAheadLogging();\n      }\n\n      if ((openOptions & ENABLE_FOREIGN_KEY_CONSTRAINTS) != 0) {\n        db.execSQL(\"PRAGMA foreign_keys = ON\");\n      }\n    }\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/database/SqliteDatabaseDriver.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteException;\nimport android.database.sqlite.SQLiteStatement;\n\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.protocol.module.Database;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseConstants;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDescriptor;\nimport com.facebook.stetho.inspector.protocol.module.DatabaseDriver2;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\nimport javax.annotation.concurrent.ThreadSafe;\n\n@ThreadSafe\npublic class SqliteDatabaseDriver\n    extends DatabaseDriver2<SqliteDatabaseDriver.SqliteDatabaseDescriptor> {\n  private static final String[] UNINTERESTING_FILENAME_SUFFIXES = new String[]{\n      \"-journal\",\n      \"-shm\",\n      \"-uid\",\n      \"-wal\"\n  };\n\n  private final DatabaseFilesProvider mDatabaseFilesProvider;\n  private final DatabaseConnectionProvider mDatabaseConnectionProvider;\n\n  /**\n   * Constructs the object with a {@link DatabaseFilesProvider} that supplies the database files\n   * from {@link Context#databaseList()}.\n   *\n   * @param context the context\n   * @deprecated use {@link SqliteDatabaseDriver#SqliteDatabaseDriver(Context, String, DatabaseFilesProvider, DatabaseConnectionProvider)}\n   */\n  @Deprecated\n  public SqliteDatabaseDriver(Context context) {\n    this(\n        context,\n        new DefaultDatabaseFilesProvider(context),\n        new DefaultDatabaseConnectionProvider());\n  }\n\n  /**\n   * @deprecated use {@link SqliteDatabaseDriver#SqliteDatabaseDriver(Context, String, DatabaseFilesProvider, DatabaseConnectionProvider)}\n   */\n  @Deprecated\n  public SqliteDatabaseDriver(\n      Context context,\n      DatabaseFilesProvider databaseFilesProvider) {\n    this(\n        context,\n        databaseFilesProvider,\n        new DefaultDatabaseConnectionProvider());\n  }\n\n  /**\n   * @param context the context\n   * @param namespace label to apply to the driver when it appears in the UI\n   * @param databaseFilesProvider a database file name provider\n   * @param databaseConnectionProvider a database connection provider\n   */\n  public SqliteDatabaseDriver(\n      Context context,\n      DatabaseFilesProvider databaseFilesProvider,\n      DatabaseConnectionProvider databaseConnectionProvider) {\n    super(context);\n    mDatabaseFilesProvider = databaseFilesProvider;\n    mDatabaseConnectionProvider = databaseConnectionProvider;\n  }\n\n  @Override\n  public List<SqliteDatabaseDescriptor> getDatabaseNames() {\n    ArrayList<SqliteDatabaseDescriptor> databases = new ArrayList<>();\n    List<File> potentialDatabaseFiles = mDatabaseFilesProvider.getDatabaseFiles();\n    Collections.sort(potentialDatabaseFiles);\n    Iterable<File> tidiedList = tidyDatabaseList(potentialDatabaseFiles);\n    for (File database : tidiedList) {\n      databases.add(new SqliteDatabaseDescriptor(database));\n    }\n    return databases;\n  }\n\n  /**\n   * Attempt to smartly eliminate uninteresting shadow databases such as -journal and -uid.  Note\n   * that this only removes the database if it is true that it shadows another database lacking\n   * the uninteresting suffix.\n   *\n   * @param databaseFiles Raw list of database files.\n   * @return Tidied list with shadow databases removed.\n   */\n  // @VisibleForTesting\n  static List<File> tidyDatabaseList(List<File> databaseFiles) {\n    Set<File> originalAsSet = new HashSet<File>(databaseFiles);\n    List<File> tidiedList = new ArrayList<File>();\n    for (File databaseFile : databaseFiles) {\n      String databaseFilename = databaseFile.getPath();\n      String sansSuffix = removeSuffix(databaseFilename, UNINTERESTING_FILENAME_SUFFIXES);\n      if (sansSuffix.equals(databaseFilename) || !originalAsSet.contains(new File(sansSuffix))) {\n        tidiedList.add(databaseFile);\n      }\n    }\n    return tidiedList;\n  }\n\n  private static String removeSuffix(String str, String[] suffixesToRemove) {\n    for (String suffix : suffixesToRemove) {\n      if (str.endsWith(suffix)) {\n        return str.substring(0, str.length() - suffix.length());\n      }\n    }\n    return str;\n  }\n\n  public List<String> getTableNames(SqliteDatabaseDescriptor databaseDesc)\n      throws SQLiteException {\n    SQLiteDatabase database = openDatabase(databaseDesc);\n    try {\n      Cursor cursor = database.rawQuery(\"SELECT name FROM sqlite_master WHERE type IN (?, ?)\",\n          new String[] { \"table\", \"view\" });\n      try {\n        List<String> tableNames = new ArrayList<String>();\n        while (cursor.moveToNext()) {\n          tableNames.add(cursor.getString(0));\n        }\n        return tableNames;\n      } finally {\n        cursor.close();\n      }\n    } finally {\n      database.close();\n    }\n  }\n\n  public Database.ExecuteSQLResponse executeSQL(\n      SqliteDatabaseDescriptor databaseDesc,\n      String query,\n      ExecuteResultHandler<Database.ExecuteSQLResponse> handler)\n          throws SQLiteException {\n    Util.throwIfNull(query);\n    Util.throwIfNull(handler);\n    SQLiteDatabase database = openDatabase(databaseDesc);\n    try {\n      String firstWordUpperCase = getFirstWord(query).toUpperCase();\n      switch (firstWordUpperCase) {\n        case \"UPDATE\":\n        case \"DELETE\":\n          return executeUpdateDelete(database, query, handler);\n        case \"INSERT\":\n          return executeInsert(database, query, handler);\n        case \"SELECT\":\n        case \"PRAGMA\":\n        case \"EXPLAIN\":\n          return executeSelect(database, query, handler);\n        default:\n          return executeRawQuery(database, query, handler);\n      }\n    } finally {\n      database.close();\n    }\n  }\n\n  private static String getFirstWord(String s) {\n    s = s.trim();\n    int firstSpace = s.indexOf(' ');\n    return firstSpace >= 0 ? s.substring(0, firstSpace) : s;\n  }\n\n  @TargetApi(DatabaseConstants.MIN_API_LEVEL)\n  private <T> T executeUpdateDelete(\n      SQLiteDatabase database,\n      String query,\n      ExecuteResultHandler<T> handler) {\n    SQLiteStatement statement = database.compileStatement(query);\n    int count = statement.executeUpdateDelete();\n    return handler.handleUpdateDelete(count);\n  }\n\n  private <T> T executeInsert(\n      SQLiteDatabase database,\n      String query,\n      ExecuteResultHandler<T> handler) {\n    SQLiteStatement statement = database.compileStatement(query);\n    long count = statement.executeInsert();\n    return handler.handleInsert(count);\n  }\n\n  private <T> T executeSelect(\n      SQLiteDatabase database,\n      String query,\n      ExecuteResultHandler<T> handler) {\n    Cursor cursor = database.rawQuery(query, null);\n    try {\n      return handler.handleSelect(cursor);\n    } finally {\n      cursor.close();\n    }\n  }\n\n  private <T> T executeRawQuery(\n      SQLiteDatabase database,\n      String query,\n      ExecuteResultHandler<T> handler) {\n    database.execSQL(query);\n    return handler.handleRawQuery();\n  }\n\n  private SQLiteDatabase openDatabase(\n      SqliteDatabaseDescriptor databaseDesc)\n      throws SQLiteException {\n    Util.throwIfNull(databaseDesc);\n    return mDatabaseConnectionProvider.openDatabase(databaseDesc.file);\n  }\n\n  static class SqliteDatabaseDescriptor implements DatabaseDescriptor {\n    public final File file;\n\n    public SqliteDatabaseDescriptor(File file) {\n      this.file = file;\n    }\n\n    @Override\n    public String name() {\n      return file.getName();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/domstorage/DOMStoragePeerManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.domstorage;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.helper.PeerRegistrationListener;\nimport com.facebook.stetho.inspector.helper.PeersRegisteredListener;\nimport com.facebook.stetho.inspector.protocol.module.Console;\nimport com.facebook.stetho.inspector.protocol.module.DOMStorage;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class DOMStoragePeerManager extends ChromePeerManager {\n  private final Context mContext;\n\n  public DOMStoragePeerManager(Context context) {\n    mContext = context;\n    setListener(mPeerListener);\n  }\n\n  public void signalItemRemoved(DOMStorage.StorageId storageId, String key) {\n    DOMStorage.DomStorageItemRemovedParams params =\n        new DOMStorage.DomStorageItemRemovedParams();\n    params.storageId = storageId;\n    params.key = key;\n    sendNotificationToPeers(\"DOMStorage.domStorageItemRemoved\", params);\n  }\n\n  public void signalItemAdded(DOMStorage.StorageId storageId, String key, String value) {\n    DOMStorage.DomStorageItemAddedParams params =\n        new DOMStorage.DomStorageItemAddedParams();\n    params.storageId = storageId;\n    params.key = key;\n    params.newValue = value;\n    sendNotificationToPeers(\"DOMStorage.domStorageItemAdded\", params);\n  }\n\n  public void signalItemUpdated(\n      DOMStorage.StorageId storageId,\n      String key,\n      String oldValue,\n      String newValue) {\n    DOMStorage.DomStorageItemUpdatedParams params =\n        new DOMStorage.DomStorageItemUpdatedParams();\n    params.storageId = storageId;\n    params.key = key;\n    params.oldValue = oldValue;\n    params.newValue = newValue;\n    sendNotificationToPeers(\"DOMStorage.domStorageItemUpdated\", params);\n  }\n\n  private final PeerRegistrationListener mPeerListener = new PeersRegisteredListener() {\n    private final List<DevToolsSharedPreferencesListener> mPrefsListeners =\n        new ArrayList<DevToolsSharedPreferencesListener>();\n\n    @Override\n    protected synchronized void onFirstPeerRegistered() {\n      // TODO: We list the tags in Page.getResourceTree as well and those are the real fixed\n      // tags that will be observed by the peer.  We can fix this by making the page frames\n      // dynamically update in response to DOMStorage events.  This would also allow us to\n      // add new SharedPreferences tags as we observe them being created by way of\n      // android.os.FileObserver.\n      List<String> tags = SharedPreferencesHelper.getSharedPreferenceTags(mContext);\n      for (String tag : tags) {\n        SharedPreferences prefs = mContext.getSharedPreferences(tag, Context.MODE_PRIVATE);\n        DevToolsSharedPreferencesListener listener =\n            new DevToolsSharedPreferencesListener(prefs, tag);\n        prefs.registerOnSharedPreferenceChangeListener(listener);\n        mPrefsListeners.add(listener);\n      }\n    }\n\n    @Override\n    protected synchronized void onLastPeerUnregistered() {\n      for (DevToolsSharedPreferencesListener prefsListener : mPrefsListeners) {\n        prefsListener.unregister();\n      }\n      mPrefsListeners.clear();\n    }\n  };\n\n  private class DevToolsSharedPreferencesListener\n      implements SharedPreferences.OnSharedPreferenceChangeListener {\n    private final SharedPreferences mPrefs;\n    private final DOMStorage.StorageId mStorageId;\n\n    /**\n     * Maintains a copy of the prefs data structure so that we can invoke\n     * {@code DOMStorage.domStorageItemUpdated}.  This method requires that we know the old\n     * value to perform updates.  Using {@code domStorageItemRemoved}/{@code Added} causes a UI\n     * glitch where the item is moved to the end of the list, unfortunately.\n     */\n    private final Map<String, Object> mCopy;\n\n    public DevToolsSharedPreferencesListener(SharedPreferences prefs, String tag) {\n      mPrefs = prefs;\n      mStorageId = new DOMStorage.StorageId();\n      mStorageId.securityOrigin = tag;\n      mStorageId.isLocalStorage = true;\n      mCopy = prefsCopy(prefs.getAll());\n    }\n\n    public void unregister() {\n      mPrefs.unregisterOnSharedPreferenceChangeListener(this);\n    }\n\n    @Override\n    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\n      Map<String, ?> entries = sharedPreferences.getAll();\n      boolean existedBefore = mCopy.containsKey(key);\n      boolean existsNow = entries.containsKey(key);\n      Object newValue = existsNow ? entries.get(key) : null;\n      if (existedBefore && existsNow) {\n        signalItemUpdated(\n            mStorageId,\n            key,\n            SharedPreferencesHelper.valueToString(mCopy.get(key)),\n            SharedPreferencesHelper.valueToString(newValue));\n        mCopy.put(key, newValue);\n      } else if (existedBefore) {\n        signalItemRemoved(mStorageId, key);\n        mCopy.remove(key);\n      } else if (existsNow) {\n        signalItemAdded(\n            mStorageId,\n            key,\n            SharedPreferencesHelper.valueToString(newValue));\n        mCopy.put(key, newValue);\n      } else {\n        // This can happen due to the async nature of the onSharedPreferenceChanged callback.  A\n        // rapid put/remove as two separate commits on a background thread would cause this.\n        LogUtil.i(\"Detected rapid put/remove of %s\", key);\n      }\n    }\n  }\n\n  private static Map<String, Object> prefsCopy(Map<String, ?> src) {\n    HashMap<String, Object> dst = new HashMap<String, Object>(src.size());\n    for (Map.Entry<String, ?> entry : src.entrySet()) {\n      String key = entry.getKey();\n      Object value = entry.getValue();\n      if (value instanceof Set) {\n        dst.put(key, shallowCopy((Set<String>)value));\n      } else {\n        dst.put(key, value);\n      }\n    }\n    return dst;\n  }\n\n  private static <T> Set<T> shallowCopy(Set<T> src) {\n    HashSet<T> dst = new HashSet<T>();\n    for (T item : src) {\n      dst.add(item);\n    }\n    return dst;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/domstorage/SharedPreferencesHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.domstorage;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\n\nimport javax.annotation.Nullable;\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map.Entry;\nimport java.util.Set;\nimport java.util.TreeSet;\n\npublic class SharedPreferencesHelper {\n  private static final String PREFS_SUFFIX = \".xml\";\n\n  private SharedPreferencesHelper() {\n  }\n\n  public static List<String> getSharedPreferenceTags(Context context) {\n    ArrayList<String> tags = new ArrayList<String>();\n\n    String rootPath = context.getApplicationInfo().dataDir + \"/shared_prefs\";\n    File root = new File(rootPath);\n    if (root.exists()) {\n      for (File file : root.listFiles()) {\n        String fileName = file.getName();\n        if (fileName.endsWith(PREFS_SUFFIX)) {\n          tags.add(fileName.substring(0, fileName.length() - PREFS_SUFFIX.length()));\n        }\n      }\n    }\n\n    Collections.sort(tags);\n\n    return tags;\n  }\n\n  public static Set<Entry<String, ?>> getSharedPreferenceEntriesSorted(SharedPreferences preferences) {\n    TreeSet<Entry<String, ?>> entries = new TreeSet<>(new Comparator<Entry<String, ?>>() {\n      @Override\n      public int compare(Entry<String, ?> lhs, Entry<String, ?> rhs) {\n        return lhs.getKey().compareTo(rhs.getKey());\n      }\n    });\n    entries.addAll(preferences.getAll().entrySet());\n    return entries;\n  }\n\n  public static String valueToString(Object value) {\n    if (value != null) {\n      if (value instanceof Set) {\n        JSONArray array = new JSONArray();\n        for (String entry : (Set<String>)value) {\n          array.put(entry);\n        }\n        return array.toString();\n      } else {\n        return value.toString();\n      }\n    } else {\n      return null;\n    }\n  }\n\n  @Nullable\n  public static Object valueFromString(String newValue, Object existingValue)\n      throws IllegalArgumentException {\n    if (existingValue instanceof Integer) {\n      return Integer.parseInt(newValue);\n    } else if (existingValue instanceof Long) {\n      return Long.parseLong(newValue);\n    } else if (existingValue instanceof Float) {\n      return Float.parseFloat(newValue);\n    } else if (existingValue instanceof Boolean) {\n      return parseBoolean(newValue);\n    } else if (existingValue instanceof String) {\n      return newValue;\n    } else if (existingValue instanceof Set) {\n      try {\n        JSONArray obj = new JSONArray(newValue);\n        int objN = obj.length();\n        HashSet<String> set = new HashSet<String>(objN);\n        for (int i = 0; i < objN; i++) {\n          set.add(obj.getString(i));\n        }\n        return set;\n      } catch (JSONException e) {\n        throw new IllegalArgumentException(e);\n      }\n    } else {\n      throw new IllegalArgumentException(\n          \"Unsupported type: \" + existingValue.getClass().getName());\n    }\n  }\n\n  private static Boolean parseBoolean(String s) throws IllegalArgumentException {\n    if (\"1\".equals(s) || \"true\".equalsIgnoreCase(s)) {\n      return Boolean.TRUE;\n    } else if (\"0\".equals(s) || \"false\".equalsIgnoreCase(s)) {\n      return Boolean.FALSE;\n    }\n    throw new IllegalArgumentException(\"Expected boolean, got \" + s);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/AbstractChainedDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.ThreadBound;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.protocol.module.DOM;\n\nimport javax.annotation.Nullable;\n\n/**\n * This class derives from {@link Descriptor} and provides a canonical implementation of\n * {@link ChainedDescriptor}.<p/>\n *\n * This class implements the thread safety enforcement policy prescribed by\n * {@link ThreadBound}. Namely, that {@link #verifyThreadAccess()}} needs to be called in the\n * prologue for every method. Your derived class SHOULD NOT call {@link #verifyThreadAccess()}} in\n * any of its on___() methods.<p/>\n *\n * (NOTE: As an optimization, {@link #verifyThreadAccess()} is not actually called in the\n * prologue for every method. Instead, we rely on {@link DocumentProvider#getNodeDescriptor(Object)}\n * calling it in order to get most of our enforcement coverage. We still call\n * {@link #verifyThreadAccess()} in a few important methods such as {@link #hook(Object)} and\n * {@link #unhook(Object)} (anything that writes or is potentially really dangerous if misused).<p/>\n *\n * @param <E> the class that this descriptor will be describing for {@link DocumentProvider},\n * {@link Document}, and ultimately {@link DOM}.\n */\npublic abstract class AbstractChainedDescriptor<E>\n    extends Descriptor<E> implements ChainedDescriptor<E> {\n\n  private Descriptor<? super E> mSuper;\n\n  @Override\n  public void setSuper(Descriptor<? super E> superDescriptor) {\n    Util.throwIfNull(superDescriptor);\n\n    if (superDescriptor != mSuper) {\n      if (mSuper != null) {\n        throw new IllegalStateException();\n      }\n      mSuper = superDescriptor;\n    }\n  }\n\n  final Descriptor<? super E> getSuper() {\n    return mSuper;\n  }\n\n  @Override\n  public final void hook(E element) {\n    verifyThreadAccess();\n    mSuper.hook(element);\n    onHook(element);\n  }\n\n  protected void onHook(E element) {\n  }\n\n  @Override\n  public final void unhook(E element) {\n    verifyThreadAccess();\n    onUnhook(element);\n    mSuper.unhook(element);\n  }\n\n  protected void onUnhook(E element) {\n  }\n\n  @Override\n  public final NodeType getNodeType(E element) {\n    return onGetNodeType(element);\n  }\n\n  protected NodeType onGetNodeType(E element) {\n    return mSuper.getNodeType(element);\n  }\n\n  @Override\n  public final String getNodeName(E element) {\n    return onGetNodeName(element);\n  }\n\n  protected String onGetNodeName(E element) {\n    return mSuper.getNodeName(element);\n  }\n\n  @Override\n  public final String getLocalName(E element) {\n    return onGetLocalName(element);\n  }\n\n  protected String onGetLocalName(E element) {\n    return mSuper.getLocalName(element);\n  }\n\n  @Override\n  public final String getNodeValue(E element) {\n    return onGetNodeValue(element);\n  }\n\n  @Nullable\n  public String onGetNodeValue(E element) {\n    return mSuper.getNodeValue(element);\n  }\n\n  @Override\n  public final void getChildren(E element, Accumulator<Object> children) {\n    mSuper.getChildren(element, children);\n    onGetChildren(element, children);\n  }\n\n  protected void onGetChildren(E element, Accumulator<Object> children) {\n  }\n\n  @Override\n  public final void getAttributes(E element, AttributeAccumulator attributes) {\n    mSuper.getAttributes(element, attributes);\n    onGetAttributes(element, attributes);\n  }\n\n  protected void onGetAttributes(E element, AttributeAccumulator attributes) {\n  }\n\n  @Override\n  public final void setAttributesAsText(E element, String text) {\n    onSetAttributesAsText(element, text);\n  }\n\n  protected void onSetAttributesAsText(E element, String text) {\n    mSuper.setAttributesAsText(element, text);\n  }\n\n  @Override\n  public final void getStyleRuleNames(E element, StyleRuleNameAccumulator accumulator) {\n    mSuper.getStyleRuleNames(element, accumulator);\n    onGetStyleRuleNames(element, accumulator);\n  }\n\n  protected void onGetStyleRuleNames(E element, StyleRuleNameAccumulator accumulator) {\n  }\n\n  @Override\n  public final void getStyles(E element, String ruleName, StyleAccumulator accumulator) {\n    mSuper.getStyles(element, ruleName, accumulator);\n    onGetStyles(element, ruleName, accumulator);\n  }\n\n  protected void onGetStyles(E element, String ruleName, StyleAccumulator accumulator) {\n  }\n\n  @Override\n  public final void setStyle(E element, String ruleName, String name, String value) {\n    mSuper.setStyle(element, ruleName, name, value);\n    onSetStyle(element, ruleName, name, value);\n  }\n\n  protected void onSetStyle(E element, String ruleName, String name, String value) {\n  }\n\n  @Override\n  public void getComputedStyles(E element, ComputedStyleAccumulator accumulator) {\n    mSuper.getComputedStyles(element, accumulator);\n    onGetComputedStyles(element, accumulator);\n  }\n\n  protected void onGetComputedStyles(E element, ComputedStyleAccumulator accumulator) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/AttributeAccumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface AttributeAccumulator {\n  void store(String name, String value);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/ChainedDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.AbsListView;\nimport android.widget.ListView;\nimport com.facebook.stetho.common.Accumulator;\n\n/**\n * This interface marks a {@link Descriptor} in a way that is specially understood by\n * {@link DescriptorMap}. When registered for a particular class 'E', a {@link Descriptor} that\n * implements this interface will be chained (via {@link ChainedDescriptor#setSuper(Descriptor)}) to\n * the {@link Descriptor} that is registered for the super class of E. If the super class of E\n * doesn't have a registration, then the super-super class will be used (and so on). This allows you\n * to implement {@link Descriptor} for any class in an inheritance hierarchy without having to\n * couple it (via direct inheritance) to the super-class' {@link Descriptor}.<p/>\n *\n * To understand why this is useful, let's say you wanted to write a {@link Descriptor} for\n * {@link ListView}. You have three options:<p/>\n *\n * The first option is to derive directly from {@link Descriptor} and write code to describe\n * everything about instances of {@link ListView}, including details that are exposed by super\n * classes such as {@link ViewGroup}, {@link View}, and even {@link Object}. This isn't generally\n * a very good choice because it would require a lot of duplicated code amongst many descriptor\n * implementations.<p/>\n *\n * The second option is to derive your 'ListViewDescriptor' from\n * {@link com.facebook.stetho.inspector.elements.android.ViewGroupDescriptor} and only implement\n * code to describe how {@link ListView} differs from {@link ViewGroup}. This will result in a class\n * hierarchy that is parallel to the one that you are describing, but is also not a good choice for\n * two reasons (let's assume for the moment that\n * {@link com.facebook.stetho.inspector.elements.android.ViewGroupDescriptor} is deriving from\n * {@link com.facebook.stetho.inspector.elements.android.ViewDescriptor}). The first problem is that\n * you will need to write code for aggregating results from the super-class in methods such as\n * {@link Descriptor#getChildren(Object, Accumulator)} and\n * {@link Descriptor#getAttributes(Object, AttributeAccumulator)}. The second problem is that you'd\n * end up with a log of fragility if you ever want to implement a descriptor for classes that are\n * in-between {@link ViewGroup} and {@link ListView}, e.g. {@link AbsListView}. Any descriptor that\n * derived from {@link com.facebook.stetho.inspector.elements.android.ViewGroupDescriptor} and\n * described a class deriving from {@link AbsListView} would have to be modified to now derive from\n * 'AbsListViewDescriptor'.<p/>\n *\n * The third option is to implement {@link ChainedDescriptor} (e.g. by deriving from\n * {@link AbstractChainedDescriptor}) which solves all of these issues for you.<p/>\n */\npublic interface ChainedDescriptor<E> {\n  void setSuper(Descriptor<? super E> superDescriptor);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/ComputedStyleAccumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface ComputedStyleAccumulator {\n  void store(String name, String value);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/Descriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.ThreadBound;\nimport com.facebook.stetho.common.UncheckedCallable;\nimport com.facebook.stetho.common.Util;\n\nimport javax.annotation.Nullable;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic abstract class Descriptor<E> implements NodeDescriptor<E> {\n  private Host mHost;\n\n  protected Descriptor() {\n  }\n\n  final void initialize(Host host) {\n    Util.throwIfNull(host);\n    Util.throwIfNotNull(mHost);\n    mHost = host;\n  }\n\n  final boolean isInitialized() {\n    return mHost != null;\n  }\n\n  protected final Host getHost() {\n    return mHost;\n  }\n\n  @Override\n  public final boolean checkThreadAccess() {\n    return getHost().checkThreadAccess();\n  }\n\n  @Override\n  public final void verifyThreadAccess() {\n    getHost().verifyThreadAccess();\n  }\n\n  @Override\n  public final <V> V postAndWait(UncheckedCallable<V> c) {\n    return getHost().postAndWait(c);\n  }\n\n  @Override\n  public final void postAndWait(Runnable r) {\n    getHost().postAndWait(r);\n  }\n\n  @Override\n  public final void postDelayed(Runnable r, long delayMillis) {\n    getHost().postDelayed(r, delayMillis);\n  }\n\n  @Override\n  public final void removeCallbacks(Runnable r) {\n    getHost().removeCallbacks(r);\n  }\n\n  /**\n   * Parses the text argument text from DOM.setAttributeAsText()\n   * Text will be in the format \"attribute1=\\\"Value 1\\\" attribute2=\\\"Value2\\\"\"\n   * @param text the text argument to be parsed\n   * @return a map of attributes to their respective values to be set.\n   */\n  protected static Map<String, String> parseSetAttributesAsTextArg(String text) {\n    String value = \"\";\n    String key = \"\";\n    StringBuilder buffer = new StringBuilder();\n    Map<String, String> keyValuePairs = new HashMap<>();\n    boolean isInsideQuotes = false;\n    for (int i = 0, N = text.length(); i < N; ++i) {\n      final char c = text.charAt(i);\n      if (c == '=') {\n        key = buffer.toString();\n        buffer.setLength(0);\n      } else if (c == '\\\"') {\n        if (isInsideQuotes) {\n          value = buffer.toString();\n          buffer.setLength(0);\n        }\n        isInsideQuotes = !isInsideQuotes;\n      } else if (c == ' ' && !isInsideQuotes) {\n        keyValuePairs.put(key, value);\n      } else {\n        buffer.append(c);\n      }\n    }\n    if (!key.isEmpty() && !value.isEmpty()) {\n      keyValuePairs.put(key, value);\n    }\n    return keyValuePairs;\n  }\n\n  public interface Host extends ThreadBound {\n    @Nullable\n    public Descriptor<?> getDescriptor(@Nullable Object element);\n\n    public void onAttributeModified(\n        Object element,\n        String name,\n        String value);\n\n    public void onAttributeRemoved(\n        Object element,\n        String name);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DescriptorMap.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.Util;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\nimport javax.annotation.Nullable;\n\npublic final class DescriptorMap implements DescriptorRegistrar {\n  private final Map<Class<?>, Descriptor> mMap = new IdentityHashMap<>();\n  private boolean mIsInitializing;\n  private Descriptor.Host mHost;\n\n  public DescriptorMap beginInit() {\n    Util.throwIf(mIsInitializing);\n    mIsInitializing = true;\n    return this;\n  }\n\n  @Override\n  public DescriptorMap registerDescriptor(Class<?> elementClass, Descriptor descriptor) {\n    Util.throwIfNull(elementClass);\n    Util.throwIfNull(descriptor);\n    Util.throwIf(descriptor.isInitialized());\n    Util.throwIfNot(mIsInitializing);\n\n    // Cannot register two descriptors for one class\n    if (mMap.containsKey(elementClass)) {\n      throw new UnsupportedOperationException();\n    }\n\n    // Cannot reuse one descriptor for two classes\n    if (mMap.containsValue(descriptor)) {\n      throw new UnsupportedOperationException();\n    }\n\n    mMap.put(elementClass, descriptor);\n    return this;\n  }\n\n  public DescriptorMap setHost(Descriptor.Host host) {\n    Util.throwIfNull(host);\n    Util.throwIfNot(mIsInitializing);\n    Util.throwIfNotNull(mHost);\n\n    mHost = host;\n    return this;\n  }\n\n  public DescriptorMap endInit() {\n    Util.throwIfNot(mIsInitializing);\n    Util.throwIfNull(mHost);\n\n    mIsInitializing = false;\n\n    for (final Class<?> elementClass : mMap.keySet()) {\n      final Descriptor descriptor = mMap.get(elementClass);\n\n      if (descriptor instanceof ChainedDescriptor) {\n        final ChainedDescriptor chainedDescriptor = (ChainedDescriptor) descriptor;\n        Class<?> superClass = elementClass.getSuperclass();\n        Descriptor superDescriptor = getImpl(superClass);\n        chainedDescriptor.setSuper(superDescriptor);\n      }\n\n      descriptor.initialize(mHost);\n    }\n\n    return this;\n  }\n\n  @Nullable\n  public Descriptor get(Class<?> elementClass) {\n    Util.throwIfNull(elementClass);\n    Util.throwIf(mIsInitializing);\n    return getImpl(elementClass);\n  }\n\n  @Nullable\n  private Descriptor getImpl(final Class<?> elementClass) {\n    Class<?> theClass = elementClass;\n    while (theClass != null) {\n      Descriptor descriptor = mMap.get(theClass);\n      if (descriptor != null) {\n        return descriptor;\n      }\n      theClass = theClass.getSuperclass();\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DescriptorProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface DescriptorProvider {\n  void registerDescriptor(DescriptorRegistrar registrar);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DescriptorRegistrar.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface DescriptorRegistrar {\n  DescriptorRegistrar registerDescriptor(Class<?> elementClass, Descriptor descriptor);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/Document.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport android.os.SystemClock;\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.ArrayListAccumulator;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.helper.ObjectIdMapper;\nimport com.facebook.stetho.inspector.helper.ThreadBoundProxy;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\nimport java.util.ArrayDeque;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Queue;\nimport java.util.regex.Pattern;\n\npublic final class Document extends ThreadBoundProxy {\n  private final DocumentProviderFactory mFactory;\n  private final ObjectIdMapper mObjectIdMapper;\n  private final Queue<Object> mCachedUpdateQueue;\n\n  private DocumentProvider mDocumentProvider;\n  private ShadowDocument mShadowDocument;\n  private UpdateListenerCollection mUpdateListeners;\n  private ChildEventingList mCachedChildEventingList;\n  private ArrayListAccumulator<Object> mCachedChildrenAccumulator;\n  private AttributeListAccumulator mCachedAttributeAccumulator;\n\n  @GuardedBy(\"this\")\n  private int mReferenceCounter;\n\n  public Document(DocumentProviderFactory factory) {\n    super(factory);\n\n    mFactory = factory;\n    mObjectIdMapper = new DocumentObjectIdMapper();\n    mReferenceCounter = 0;\n    mUpdateListeners = new UpdateListenerCollection();\n    mCachedUpdateQueue = new ArrayDeque<>();\n  }\n\n  public synchronized void addRef() {\n    if (mReferenceCounter++ == 0) {\n      init();\n    }\n  }\n\n  public synchronized void release() {\n    if (mReferenceCounter > 0) {\n      if (--mReferenceCounter == 0) {\n        cleanUp();\n      }\n    }\n  }\n\n  private void init() {\n    mDocumentProvider = mFactory.create();\n\n    mDocumentProvider.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        mShadowDocument = new ShadowDocument(mDocumentProvider.getRootElement());\n        createShadowDocumentUpdate().commit();\n        mDocumentProvider.setListener(new ProviderListener());\n      }\n    });\n\n  }\n\n  private void cleanUp() {\n    mDocumentProvider.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        mDocumentProvider.setListener(null);\n        mShadowDocument = null;\n        mObjectIdMapper.clear();\n        mDocumentProvider.dispose();\n        mDocumentProvider = null;\n      }\n    });\n\n    mUpdateListeners.clear();\n  }\n\n  public void addUpdateListener(UpdateListener updateListener) {\n    mUpdateListeners.add(updateListener);\n  }\n\n  public void removeUpdateListener(UpdateListener updateListener) {\n    mUpdateListeners.remove(updateListener);\n  }\n\n  public @Nullable NodeDescriptor getNodeDescriptor(Object element) {\n    verifyThreadAccess();\n    return mDocumentProvider.getNodeDescriptor(element);\n  }\n\n  public void highlightElement(Object element, int color) {\n    verifyThreadAccess();\n    mDocumentProvider.highlightElement(element, color);\n  }\n\n  public void hideHighlight() {\n    verifyThreadAccess();\n    mDocumentProvider.hideHighlight();\n  }\n\n  public void setInspectModeEnabled(boolean enabled) {\n    verifyThreadAccess();\n    mDocumentProvider.setInspectModeEnabled(enabled);\n  }\n\n  public @Nullable Integer getNodeIdForElement(Object element) {\n    // We don't actually call verifyThreadAccess() for performance.\n    //verifyThreadAccess();\n    return mObjectIdMapper.getIdForObject(element);\n  }\n\n  public @Nullable Object getElementForNodeId(int id) {\n    // We don't actually call verifyThreadAccess() for performance.\n    //verifyThreadAccess();\n    return mObjectIdMapper.getObjectForId(id);\n  }\n\n  public void setAttributesAsText(Object element, String text) {\n    verifyThreadAccess();\n    mDocumentProvider.setAttributesAsText(element, text);\n  }\n\n  public void getElementStyleRuleNames(Object element, StyleRuleNameAccumulator accumulator) {\n    NodeDescriptor nodeDescriptor = getNodeDescriptor(element);\n\n    nodeDescriptor.getStyleRuleNames(element, accumulator);\n  }\n\n  public void getElementStyles(Object element, String ruleName, StyleAccumulator accumulator) {\n    NodeDescriptor nodeDescriptor = getNodeDescriptor(element);\n\n    nodeDescriptor.getStyles(element, ruleName, accumulator);\n  }\n\n  public void setElementStyle(Object element, String ruleName, String name, String value) {\n    NodeDescriptor nodeDescriptor = getNodeDescriptor(element);\n\n    nodeDescriptor.setStyle(element, ruleName, name, value);\n  }\n\n  public void getElementComputedStyles(Object element, ComputedStyleAccumulator styleAccumulator) {\n    NodeDescriptor nodeDescriptor = getNodeDescriptor(element);\n\n    nodeDescriptor.getComputedStyles(element, styleAccumulator);\n  }\n\n  public DocumentView getDocumentView() {\n    verifyThreadAccess();\n    return mShadowDocument;\n  }\n\n  public Object getRootElement() {\n    verifyThreadAccess();\n\n    Object rootElement = mDocumentProvider.getRootElement();\n    if (rootElement == null) {\n      // null for rootElement is not allowed. We could support it, but our current\n      // implementation won't ever run into this, so let's punt on it for now.\n      throw new IllegalStateException();\n    }\n\n    if (rootElement != mShadowDocument.getRootElement()) {\n      // We don't support changing the root element. This is handled differently by the\n      // protocol than updates to an existing DOM, and we don't have any case in our\n      // current implementation that causes this to happen, so let's punt on it for now.\n      throw new IllegalStateException();\n    }\n\n    return rootElement;\n  }\n\n  public void findMatchingElements(String query, Accumulator<Integer> matchedIds) {\n    verifyThreadAccess();\n\n    final Pattern queryPattern = Pattern.compile(Pattern.quote(query), Pattern.CASE_INSENSITIVE);\n    final Object rootElement = mDocumentProvider.getRootElement();\n\n    findMatches(rootElement, queryPattern, matchedIds);\n  }\n\n  private void findMatches(Object element, Pattern queryPattern, Accumulator<Integer> matchedIds) {\n    final ElementInfo info = mShadowDocument.getElementInfo(element);\n\n    for (int i = 0, size = info.children.size(); i < size; i++) {\n      final Object childElement = info.children.get(i);\n\n      if (doesElementMatch(childElement, queryPattern)) {\n        matchedIds.store(mObjectIdMapper.getIdForObject(childElement));\n      }\n\n      findMatches(childElement, queryPattern, matchedIds);\n    }\n  }\n\n  private boolean doesElementMatch(Object element, Pattern queryPattern) {\n    AttributeListAccumulator accumulator = acquireCachedAttributeAccumulator();\n    NodeDescriptor descriptor = mDocumentProvider.getNodeDescriptor(element);\n\n    descriptor.getAttributes(element, accumulator);\n\n    for (int i = 0, N = accumulator.size(); i < N; i++) {\n      if (queryPattern.matcher(accumulator.get(i)).find()) {\n        releaseCachedAttributeAccumulator(accumulator);\n        return true;\n      }\n    }\n\n    releaseCachedAttributeAccumulator(accumulator);\n    return queryPattern.matcher(descriptor.getNodeName(element)).find();\n  }\n\n  private ChildEventingList acquireChildEventingList(\n      Object parentElement,\n      DocumentView documentView) {\n    ChildEventingList childEventingList = mCachedChildEventingList;\n\n    if (childEventingList == null) {\n      childEventingList = new ChildEventingList();\n    }\n\n    mCachedChildEventingList = null;\n\n    childEventingList.acquire(parentElement, documentView);\n    return childEventingList;\n  }\n\n  private void releaseChildEventingList(ChildEventingList childEventingList) {\n    childEventingList.release();\n    if (mCachedChildEventingList == null) {\n      mCachedChildEventingList = childEventingList;\n    }\n  }\n\n  private AttributeListAccumulator acquireCachedAttributeAccumulator() {\n    AttributeListAccumulator accumulator = mCachedAttributeAccumulator;\n\n    if (accumulator == null) {\n      accumulator = new AttributeListAccumulator();\n    }\n\n    mCachedChildrenAccumulator = null;\n\n    return accumulator;\n  }\n\n  private void releaseCachedAttributeAccumulator(AttributeListAccumulator accumulator) {\n    accumulator.clear();\n\n    if (mCachedAttributeAccumulator == null) {\n      mCachedAttributeAccumulator = accumulator;\n    }\n  }\n\n  private ArrayListAccumulator<Object> acquireChildrenAccumulator() {\n    ArrayListAccumulator<Object> accumulator = mCachedChildrenAccumulator;\n    if (accumulator == null) {\n      accumulator = new ArrayListAccumulator<>();\n    }\n    mCachedChildrenAccumulator = null;\n    return accumulator;\n  }\n\n  private void releaseChildrenAccumulator(ArrayListAccumulator<Object> accumulator) {\n    accumulator.clear();\n    if (mCachedChildrenAccumulator == null) {\n      mCachedChildrenAccumulator = accumulator;\n    }\n  }\n\n  private ShadowDocument.Update createShadowDocumentUpdate() {\n    verifyThreadAccess();\n\n    if (mDocumentProvider.getRootElement() != mShadowDocument.getRootElement()) {\n      throw new IllegalStateException();\n    }\n\n    ArrayListAccumulator<Object> childrenAccumulator = acquireChildrenAccumulator();\n\n    ShadowDocument.UpdateBuilder updateBuilder = mShadowDocument.beginUpdate();\n    mCachedUpdateQueue.add(mDocumentProvider.getRootElement());\n\n    while (!mCachedUpdateQueue.isEmpty()) {\n      final Object element = mCachedUpdateQueue.remove();\n      NodeDescriptor descriptor = mDocumentProvider.getNodeDescriptor(element);\n      mObjectIdMapper.putObject(element);\n      descriptor.getChildren(element, childrenAccumulator);\n\n      for (int i = 0, size = childrenAccumulator.size(); i < size; ++i) {\n        Object child = childrenAccumulator.get(i);\n        if (child != null) {\n          mCachedUpdateQueue.add(child);\n        } else {\n          // This could be indicative of a bug in Stetho code, but could also be caused by a\n          // custom element of some kind, e.g. ViewGroup. Let's not allow it to kill the hosting\n          // app.\n          LogUtil.e(\n              \"%s.getChildren() emitted a null child at position %s for element %s\",\n              descriptor.getClass().getName(),\n              Integer.toString(i),\n              element);\n\n          childrenAccumulator.remove(i);\n          --i;\n          --size;\n        }\n      }\n\n      updateBuilder.setElementChildren(element, childrenAccumulator);\n      childrenAccumulator.clear();\n    }\n\n    releaseChildrenAccumulator(childrenAccumulator);\n\n    return updateBuilder.build();\n  }\n\n  private void updateTree() {\n    long startTimeMs = SystemClock.elapsedRealtime();\n\n    ShadowDocument.Update docUpdate = createShadowDocumentUpdate();\n    boolean isEmpty = docUpdate.isEmpty();\n    if (isEmpty) {\n      docUpdate.abandon();\n    } else {\n      applyDocumentUpdate(docUpdate);\n    }\n\n    long deltaMs = SystemClock.elapsedRealtime() - startTimeMs;\n    LogUtil.d(\n        \"Document.updateTree() completed in %s ms%s\",\n        Long.toString(deltaMs),\n        isEmpty ? \" (no changes)\" : \"\");\n  }\n\n  private void applyDocumentUpdate(final ShadowDocument.Update docUpdate) {\n    // TODO: it'd be nice if we could delegate our calls into mPeerManager.sendNotificationToPeers()\n    //       to a background thread so as to offload the UI from JSON serialization stuff\n\n    // Applying the ShadowDocument.Update is done in five stages:\n\n    // Stage 1: any elements that have been disconnected from the tree, and any elements in those\n    // sub-trees which have not been reconnected to the tree, should be garbage collected. For now\n    // we gather a list of garbage element IDs which we use in stages 2 to test a changed element\n    // to see if it's also garbage. Then during stage 3 we use this list to unhook all of the\n    // garbage elements.\n\n    // This is used to collect the garbage element IDs in stage 1. It is sorted before stage 2 so\n    // that it can use a binary search as a quick \"contains()\" method.\n    // Note that this could be accomplished in a simpler way by employing a HashSet<Object> and\n    // storing the element Objects. However, HashSet wraps HashMap and we would have a lot more\n    // allocations (Map.Entry, iterator during stage 3) and thus GC pressure.\n    // Using SparseArray wouldn't be good because it ensures sorted ordering as you go, but we don't\n    // need that during stage 1. Using ArrayList with int boxing is fine because the Integers are\n    // already boxed inside of mObjectIdMapper and we make sure to reuse that allocation.\n    final ArrayList<Integer> garbageElementIds = new ArrayList<>();\n\n    docUpdate.getGarbageElements(new Accumulator<Object>() {\n      @Override\n      public void store(Object element) {\n        Integer nodeId = Util.throwIfNull(mObjectIdMapper.getIdForObject(element));\n        ElementInfo newElementInfo = docUpdate.getElementInfo(element);\n\n        // Only raise onChildNodeRemoved for the root of a disconnected tree. The remainder of the\n        // sub-tree is included automatically, so we don't need to send events for those.\n        if (newElementInfo.parentElement == null) {\n          ElementInfo oldElementInfo = mShadowDocument.getElementInfo(element);\n          int parentNodeId = mObjectIdMapper.getIdForObject(oldElementInfo.parentElement);\n          mUpdateListeners.onChildNodeRemoved(parentNodeId, nodeId);\n        }\n\n        garbageElementIds.add(nodeId);\n      }\n    });\n\n    Collections.sort(garbageElementIds);\n\n    // Stage 2: remove all elements that have been reparented. Otherwise we get into trouble if we\n    // transmit an event to insert under the new parent before we've transmitted an event to remove\n    // it from the old parent. The removal event is ignored because the parent doesn't match the\n    // listener's expectations, so we get ghost elements that are stuck and can't be exorcised.\n    docUpdate.getChangedElements(new Accumulator<Object>() {\n      @Override\n      public void store(Object element) {\n        Integer nodeId = Util.throwIfNull(mObjectIdMapper.getIdForObject(element));\n\n        // Skip garbage elements\n        if (Collections.binarySearch(garbageElementIds, nodeId) >= 0) {\n          return;\n        }\n\n        // Skip new elements\n        final ElementInfo oldElementInfo = mShadowDocument.getElementInfo(element);\n        if (oldElementInfo == null) {\n          return;\n        }\n\n        final ElementInfo newElementInfo = docUpdate.getElementInfo(element);\n        if (newElementInfo.parentElement != oldElementInfo.parentElement) {\n          int parentNodeId = mObjectIdMapper.getIdForObject(oldElementInfo.parentElement);\n          mUpdateListeners.onChildNodeRemoved(parentNodeId, nodeId);\n        }\n      }\n    });\n\n    // Stage 3: unhook garbage elements\n    for (int i = 0, N = garbageElementIds.size(); i < N; ++i) {\n      mObjectIdMapper.removeObjectById(garbageElementIds.get(i));\n    }\n\n    // Stage 4: transmit all other changes to our listener. This includes inserting reparented\n    // elements that we removed in the 2nd stage.\n    docUpdate.getChangedElements(new Accumulator<Object>() {\n      private final HashSet<Object> listenerInsertedElements = new HashSet<>();\n\n      private Accumulator<Object> insertedElements = new Accumulator<Object>() {\n        @Override\n        public void store(Object element) {\n          if (docUpdate.isElementChanged(element)) {\n            // We only need to track changed elements because unchanged elements will never be\n            // encountered by the code below, in store(), which uses this Set to skip elements that\n            // don't need to be processed.\n            listenerInsertedElements.add(element);\n          }\n        }\n      };\n\n      @Override\n      public void store(Object element) {\n        if (!mObjectIdMapper.containsObject(element)) {\n          // The element was garbage and has already been removed. At this stage that's okay and we\n          // just skip it and continue forward with the algorithm.\n          return;\n        }\n\n        if (listenerInsertedElements.contains(element)) {\n          // This element was already transmitted in its entirety by an onChildNodeInserted event.\n          // Trying to send any further updates about it is both unnecessary and incorrect (we'd\n          // end up with duplicated elements and really bad performance).\n          return;\n        }\n\n        final ElementInfo oldElementInfo = mShadowDocument.getElementInfo(element);\n        final ElementInfo newElementInfo = docUpdate.getElementInfo(element);\n\n        final List<Object> oldChildren = (oldElementInfo != null)\n            ? oldElementInfo.children\n            : Collections.emptyList();\n\n        final List<Object> newChildren = newElementInfo.children;\n\n        // This list is representative of our listener's view of the Document (ultimately, this\n        // means Chrome DevTools). We need to sync it up with newChildren.\n        ChildEventingList listenerChildren = acquireChildEventingList(element, docUpdate);\n        for (int i = 0, N = oldChildren.size(); i < N; ++i) {\n          final Object childElement = oldChildren.get(i);\n          if (mObjectIdMapper.containsObject(childElement)) {\n            final ElementInfo newChildElementInfo = docUpdate.getElementInfo(childElement);\n            if (newChildElementInfo != null &&\n                newChildElementInfo.parentElement != element) {\n              // This element was reparented, so we already told our listener to remove it.\n            } else {\n              listenerChildren.add(childElement);\n            }\n          }\n        }\n\n        updateListenerChildren(listenerChildren, newChildren, insertedElements);\n        releaseChildEventingList(listenerChildren);\n      }\n    });\n\n    // Stage 5: Finally, commit the update to the ShadowDocument.\n    docUpdate.commit();\n  }\n\n  private static void updateListenerChildren(\n      ChildEventingList listenerChildren,\n      List<Object> newChildren,\n      Accumulator<Object> insertedElements) {\n    int index = 0;\n    while (index <= listenerChildren.size()) {\n      // Insert new items that were added to the end of the list\n      if (index == listenerChildren.size()) {\n        if (index == newChildren.size()) {\n          break;\n        }\n\n        final Object newElement = newChildren.get(index);\n        listenerChildren.addWithEvent(index, newElement, insertedElements);\n        ++index;\n        continue;\n      }\n\n      // Remove old items that were removed from the end of the list\n      if (index == newChildren.size()) {\n        listenerChildren.removeWithEvent(index);\n        continue;\n      }\n\n      final Object listenerElement = listenerChildren.get(index);\n      final Object newElement = newChildren.get(index);\n\n      // This slot has exactly what we need to have here.\n      if (listenerElement == newElement) {\n        ++index;\n        continue;\n      }\n\n      int newElementListenerIndex = listenerChildren.indexOf(newElement);\n      if (newElementListenerIndex == -1) {\n        listenerChildren.addWithEvent(index, newElement, insertedElements);\n        ++index;\n        continue;\n      }\n\n      // TODO: use longest common substring to decide whether to\n      //       1) remove(newElementListenerIndex)-then-add(index), or\n      //       2) remove(index) and let a subsequent loop iteration do add() (that is, when index\n      //          catches up the current value of newElementListenerIndex)\n      //       Neither one of these is the best strategy -- it depends on context.\n\n      listenerChildren.removeWithEvent(newElementListenerIndex);\n      listenerChildren.addWithEvent(index, newElement, insertedElements);\n\n      ++index;\n    }\n  }\n\n  /**\n   * A private implementation of {@link List} that transmits our changes to our listener (and,\n   * ultimately, to the DevTools client).\n   */\n  private final class ChildEventingList extends ArrayList<Object> {\n    private Object mParentElement = null;\n    private int mParentNodeId = -1;\n    private DocumentView mDocumentView;\n\n    public void acquire(Object parentElement, DocumentView documentView) {\n      mParentElement = parentElement;\n\n      mParentNodeId = (mParentElement == null)\n          ? -1\n          : mObjectIdMapper.getIdForObject(mParentElement);\n\n      mDocumentView = documentView;\n    }\n\n    public void release() {\n      clear();\n\n      mParentElement = null;\n      mParentNodeId = -1;\n      mDocumentView = null;\n    }\n\n    public void addWithEvent(int index, Object element, Accumulator<Object> insertedElements) {\n      Object previousElement = (index == 0) ? null : get(index - 1);\n\n      int previousNodeId = (previousElement == null)\n          ? -1\n          : mObjectIdMapper.getIdForObject(previousElement);\n\n      add(index, element);\n\n      mUpdateListeners.onChildNodeInserted(\n          mDocumentView,\n          element,\n          mParentNodeId,\n          previousNodeId,\n          insertedElements);\n    }\n\n    public void removeWithEvent(int index) {\n      Object element = remove(index);\n      int nodeId = mObjectIdMapper.getIdForObject(element);\n      mUpdateListeners.onChildNodeRemoved(mParentNodeId, nodeId);\n    }\n  }\n\n  private class UpdateListenerCollection implements UpdateListener {\n    private final List<UpdateListener> mListeners;\n    private volatile UpdateListener[] mListenersSnapshot;\n\n    public UpdateListenerCollection() {\n      mListeners = new ArrayList<>();\n    }\n\n    public synchronized void add(UpdateListener listener) {\n      mListeners.add(listener);\n      mListenersSnapshot = null;\n    }\n\n    public synchronized void remove(UpdateListener listener) {\n      mListeners.remove(listener);\n      mListenersSnapshot = null;\n    }\n\n    public synchronized void clear() {\n      mListeners.clear();\n      mListenersSnapshot = null;\n    }\n\n    private UpdateListener[] getListenersSnapshot() {\n      while (true) {\n        final UpdateListener[] listenersSnapshot = mListenersSnapshot;\n        if (listenersSnapshot != null) {\n          return listenersSnapshot;\n        }\n        synchronized (this) {\n          if (mListenersSnapshot == null) {\n            mListenersSnapshot = mListeners.toArray(new UpdateListener[mListeners.size()]);\n            return mListenersSnapshot;\n          }\n        }\n      }\n    }\n\n    @Override\n    public void onAttributeModified(Object element, String name, String value) {\n      for (UpdateListener listener : getListenersSnapshot()) {\n        listener.onAttributeModified(element, name, value);\n      }\n    }\n\n    @Override\n    public void onAttributeRemoved(Object element, String name) {\n      for (UpdateListener listener : getListenersSnapshot()) {\n        listener.onAttributeRemoved(element, name);\n      }\n    }\n\n    @Override\n    public void onInspectRequested(Object element) {\n      for (UpdateListener listener : getListenersSnapshot()) {\n        listener.onInspectRequested(element);\n      }\n    }\n\n    @Override\n    public void onChildNodeRemoved(int parentNodeId, int nodeId) {\n      for (UpdateListener listener : getListenersSnapshot()) {\n        listener.onChildNodeRemoved(parentNodeId, nodeId);\n      }\n    }\n\n    @Override\n    public void onChildNodeInserted(\n        DocumentView view,\n        Object element,\n        int parentNodeId,\n        int previousNodeId,\n        Accumulator<Object> insertedItems) {\n      for (UpdateListener listener : getListenersSnapshot()) {\n        listener.onChildNodeInserted(view, element, parentNodeId, previousNodeId, insertedItems);\n      }\n    }\n  }\n\n  public interface UpdateListener {\n    void onAttributeModified(Object element, String name, String value);\n\n    void onAttributeRemoved(Object element, String name);\n\n    void onInspectRequested(Object element);\n\n    void onChildNodeRemoved(\n        int parentNodeId,\n        int nodeId);\n\n    void onChildNodeInserted(\n        DocumentView view,\n        Object element,\n        int parentNodeId,\n        int previousNodeId,\n        Accumulator<Object> insertedItems);\n  }\n\n  private final class DocumentObjectIdMapper extends ObjectIdMapper {\n    @Override\n    protected void onMapped(Object object, int id) {\n      verifyThreadAccess();\n\n      NodeDescriptor descriptor = mDocumentProvider.getNodeDescriptor(object);\n      descriptor.hook(object);\n    }\n\n    @Override\n    protected void onUnmapped(Object object, int id) {\n      verifyThreadAccess();\n\n      NodeDescriptor descriptor = mDocumentProvider.getNodeDescriptor(object);\n      descriptor.unhook(object);\n    }\n  }\n\n  private final class ProviderListener implements DocumentProviderListener {\n    @Override\n    public void onPossiblyChanged() {\n      updateTree();\n    }\n\n    @Override\n    public void onAttributeModified(Object element, String name, String value) {\n      verifyThreadAccess();\n      mUpdateListeners.onAttributeModified(element, name, value);\n    }\n\n    @Override\n    public void onAttributeRemoved(Object element, String name) {\n      verifyThreadAccess();\n      mUpdateListeners.onAttributeRemoved(element, name);\n    }\n\n    @Override\n    public void onInspectRequested(Object element) {\n      verifyThreadAccess();\n      mUpdateListeners.onInspectRequested(element);\n    }\n  }\n\n  public static final class AttributeListAccumulator\n    extends ArrayList<String> implements AttributeAccumulator {\n\n    @Override\n    public void store(String name, String value) {\n      add(name);\n      add(value);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DocumentProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.ThreadBound;\n\nimport javax.annotation.Nullable;\n\n/**\n * Provides a document that can be rendered in Chrome's Elements tab (conforming loosely to the\n * W3C DOM to the degree specified in this API).\n *\n * @see DocumentProviderFactory\n */\npublic interface DocumentProvider extends ThreadBound {\n  void setListener(DocumentProviderListener listener);\n\n  void dispose();\n\n  @Nullable\n  Object getRootElement();\n\n  @Nullable\n  NodeDescriptor getNodeDescriptor(@Nullable Object element);\n\n  void highlightElement(Object element, int color);\n\n  void hideHighlight();\n\n  void setInspectModeEnabled(boolean enabled);\n\n  void setAttributesAsText(Object element, String text);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DocumentProviderFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.ThreadBound;\n\n/**\n * Factory mechanism to dynamically construct the document provider.  This allows for lazy\n * initialization and memory cleanup when DevTools instances disconnect.\n */\npublic interface DocumentProviderFactory extends ThreadBound {\n  DocumentProvider create();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DocumentProviderListener.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface DocumentProviderListener {\n  void onPossiblyChanged();\n\n  void onAttributeModified(\n      Object element,\n      String name,\n      String value);\n\n  void onAttributeRemoved(\n      Object element,\n      String name);\n\n  void onInspectRequested(\n      Object element);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/DocumentView.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface DocumentView {\n  Object getRootElement();\n\n  ElementInfo getElementInfo(Object element);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/ElementInfo.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.ListUtil;\nimport com.facebook.stetho.common.Util;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.Immutable;\n\nimport java.util.List;\n\n@Immutable\npublic final class ElementInfo {\n  public final Object element;\n  public @Nullable final Object parentElement;\n  public final List<Object> children;\n\n  ElementInfo(\n      Object element,\n      @Nullable Object parentElement,\n      List<Object> children) {\n    this.element = Util.throwIfNull(element);\n    this.parentElement = parentElement;\n    this.children = ListUtil.copyToImmutableList(children);\n  }\n\n  @Override\n  public boolean equals(Object o) {\n    if (o == this) {\n      return true;\n    }\n\n    if (o instanceof ElementInfo) {\n      ElementInfo other = (ElementInfo) o;\n      return this.element == other.element\n          && this.parentElement == other.parentElement\n          && ListUtil.identityEquals(this.children, other.children);\n    }\n\n    return false;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/NodeDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.ThreadBound;\n\nimport javax.annotation.Nullable;\n\npublic interface NodeDescriptor<E> extends ThreadBound {\n  void hook(E element);\n\n  void unhook(E element);\n\n  NodeType getNodeType(E element);\n\n  String getNodeName(E element);\n\n  String getLocalName(E element);\n\n  @Nullable\n  String getNodeValue(E element);\n\n  void getChildren(E element, Accumulator<Object> children);\n\n  void getAttributes(E element, AttributeAccumulator attributes);\n\n  void setAttributesAsText(E element, String text);\n\n  void getStyleRuleNames(E element, StyleRuleNameAccumulator accumulator);\n\n  void getStyles(E element, String ruleName, StyleAccumulator accumulator);\n\n  void setStyle(E element, String ruleName, String name, String value);\n\n  void getComputedStyles(E element, ComputedStyleAccumulator accumulator);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/NodeType.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.json.annotation.JsonValue;\n\npublic enum NodeType {\n  ELEMENT_NODE(1),\n  TEXT_NODE(3),\n  PROCESSING_INSTRUCTION_NODE(7),\n  COMMENT_NODE(8),\n  DOCUMENT_NODE(9),\n  DOCUMENT_TYPE_NODE(10),\n  DOCUMENT_FRAGMENT_NODE(11);\n\n  private final int mValue;\n\n  private NodeType(int value) {\n    mValue = value;\n  }\n\n  @JsonValue\n  public int getProtocolValue() {\n    return mValue;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/ObjectDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.common.Accumulator;\n\npublic final class ObjectDescriptor extends Descriptor<Object> {\n  @Override\n  public void hook(Object element) {\n  }\n\n  @Override\n  public void unhook(Object element) {\n  }\n\n  @Override\n  public NodeType getNodeType(Object element) {\n    return NodeType.ELEMENT_NODE;\n  }\n\n  @Override\n  public String getNodeName(Object element) {\n    return element.getClass().getName();\n  }\n\n  @Override\n  public String getLocalName(Object element) {\n    return getNodeName(element);\n  }\n\n  @Override\n  public String getNodeValue(Object element) {\n    return null;\n  }\n\n  @Override\n  public void getChildren(Object element, Accumulator<Object> children) {\n  }\n\n  @Override\n  public void getAttributes(Object element, AttributeAccumulator attributes) {\n  }\n\n  @Override\n  public void setAttributesAsText(Object element, String text) {\n  }\n\n  @Override\n  public void getStyleRuleNames(Object element, StyleRuleNameAccumulator accumulator) {\n  }\n\n  @Override\n  public void getStyles(Object element, String ruleName, StyleAccumulator accumulator) {\n  }\n\n  @Override\n  public void setStyle(Object element, String ruleName, String name, String value) {\n  }\n\n  @Override\n  public void getComputedStyles(Object element, ComputedStyleAccumulator accumulator) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/Origin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport com.facebook.stetho.json.annotation.JsonValue;\n\npublic enum Origin {\n  INJECTED(\"injected\"),\n  USER_AGENT(\"user-agent\"),\n  INSPECTOR(\"inspector\"),\n  REGULAR(\"regular\");\n\n  private final String mValue;\n\n  Origin(String value) {\n    mValue = value;\n  }\n\n  @JsonValue\n  public String getProtocolValue() {\n    return mValue;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/ShadowDocument.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\nimport android.app.Activity;\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.ListUtil;\nimport com.facebook.stetho.common.Util;\n\nimport java.util.ArrayDeque;\nimport java.util.Collections;\nimport java.util.HashSet;\nimport java.util.IdentityHashMap;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.Set;\n\npublic final class ShadowDocument implements DocumentView {\n  private final Object mRootElement;\n  private final IdentityHashMap<Object, ElementInfo> mElementToInfoMap = new IdentityHashMap<>();\n  private boolean mIsUpdating;\n\n  public ShadowDocument(Object rootElement) {\n    mRootElement = Util.throwIfNull(rootElement);\n  }\n\n  @Override\n  public Object getRootElement() {\n    return mRootElement;\n  }\n\n  @Override\n  public ElementInfo getElementInfo(Object element) {\n    return mElementToInfoMap.get(element);\n  }\n\n  public UpdateBuilder beginUpdate() {\n    if (mIsUpdating) {\n      throw new IllegalStateException();\n    }\n\n    mIsUpdating = true;\n\n    return new UpdateBuilder();\n  }\n\n  public final class UpdateBuilder {\n    /**\n     * We use a {@link LinkedHashMap} to preserve ordering between\n     * {@link UpdateBuilder#setElementChildren(Object, List)} and\n     * {@link Update#getChangedElements(Accumulator)}. This isn't needed for correctness but it\n     * significantly improves performance.<p/>\n     *\n     * Transmitting DOM updates to Chrome works best if we can do it in top-down order because it\n     * allows us to skip processing (and, more importantly, transmission) of an element that was\n     * already transmitted in a previous DOM.childNodeInserted event (i.o.w. we can skip\n     * transmission of E2 if it was already bundled up in E1's event, where E2 is any element in\n     * E1's sub-tree). DOM.childNodeInserted transmits the node being inserted by-value, so it takes\n     * time and space proportional to the size of that node's sub-tree. This means the difference\n     * between O(n^2) and O(n) time for transmitting updates to Chrome.<p/>\n     *\n     * We currently only have one implementation of {@link DocumentProvider},\n     * {@link com.facebook.stetho.inspector.elements.android.AndroidDocumentProvider}, and it\n     * already supplies element changes in top-down order. Because of this, we can just use\n     * {@link LinkedHashMap} instead of adding some kind of post-process sorting of the elements to\n     * put them in that order. If we reach a point where we can't or shouldn't rely on elements\n     * being forwarded to us in top-down order, then we should change this field to an\n     * {@link IdentityHashMap} and sort them before relaying them via\n     * {@link Update#getChangedElements(Accumulator)}.<p/>\n     *\n     * When a large sub-tree is added (e.g. starting a new {@link Activity}), the use of\n     * {@link LinkedHashMap} instead of {@link IdentityHashMap} can mean the difference between an\n     * update taking 500ms versus taking more than 30 seconds.<p/>\n     *\n     * Technically we actually want something like a LinkedIdentityHashMap because we do want\n     * to key off of object identity instead of allowing for the possibility of value identity.\n     * Given the difference in performance, however, the risk of potential protocol abuse seems\n     * reasonable.<p/>\n     */\n    private final Map<Object, ElementInfo> mElementToInfoChangesMap = new LinkedHashMap<>();\n\n    /**\n     * This contains every element in {@link #mElementToInfoChangesMap} whose\n     * {@link ElementInfo#parentElement} is null. {@link ShadowDocument} provides access to a tree, which\n     * means it has a single root (only one element with a null parent). During an update, however,\n     * the DOM can be conceptually thought of as being a forest. The true root is identified by\n     * {@link #mRootElement}, and all other roots identify disconnected trees full of elements that\n     * must be garbage collected.\n     */\n    private final HashSet<Object> mRootElementChanges = new HashSet<>();\n\n    /**\n     * This is used during {@link #setElementChildren}. We allocate 1 on-demand and reuse it.\n     */\n    private HashSet<Object> mCachedNotNewChildrenSet;\n\n    public void setElementChildren(Object element, List<Object> children) {\n      // If we receive redundant information, then nothing needs to be done.\n      ElementInfo changesElementInfo = mElementToInfoChangesMap.get(element);\n      if (changesElementInfo != null &&\n          ListUtil.identityEquals(children, changesElementInfo.children)) {\n        return;\n      }\n\n      ElementInfo oldElementInfo = mElementToInfoMap.get(element);\n      if (changesElementInfo == null &&\n          oldElementInfo != null &&\n          ListUtil.identityEquals(children, oldElementInfo.children)) {\n        return;\n      }\n\n      ElementInfo newElementInfo;\n      if (changesElementInfo != null &&\n          oldElementInfo != null &&\n          oldElementInfo.parentElement == changesElementInfo.parentElement &&\n          ListUtil.identityEquals(children, oldElementInfo.children)) {\n        // setElementChildren() was already called for element with changes during this\n        // transaction, but now we're being told that the children should match the old view.\n        // So we should actually remove the change entry.\n        newElementInfo = mElementToInfoMap.get(element);\n        mElementToInfoChangesMap.remove(element);\n      } else {\n        Object parentElement = (changesElementInfo != null)\n            ? changesElementInfo.parentElement\n            : (oldElementInfo != null)\n            ? oldElementInfo.parentElement\n            : null;\n\n        newElementInfo = new ElementInfo(element, parentElement, children);\n\n        mElementToInfoChangesMap.put(element, newElementInfo);\n      }\n\n      // At this point, newElementInfo is either equal to oldElementInfo because we've reverted\n      // back to the same data that's in the old view of the tree, or it's a brand new object with\n      // brand new changes (it's different than both of oldElementInfo and changesElementInfo).\n\n      // Next, set the parentElement to null for child elements that have been removed from\n      // element's children. We must be careful not to set a parentElement to null if that child has\n      // already been moved to be the child of a different element. e.g.,\n      //     setElementChildren(E, { A, B, C})\n      //     ...\n      //     setElementChildren(F, { A })\n      //     setElementChildren(E, { B, C })    (don't mark A's parent as null in this case)\n\n      // notNewChildrenSet = (oldChildren + changesChildren) - newChildren\n      HashSet<Object> notNewChildrenSet = acquireNotNewChildrenHashSet();\n\n      if (oldElementInfo != null &&\n          oldElementInfo.children != newElementInfo.children) {\n        for (int i = 0, N = oldElementInfo.children.size(); i < N; ++i) {\n          final Object childElement = oldElementInfo.children.get(i);\n          notNewChildrenSet.add(childElement);\n        }\n      }\n\n      if (changesElementInfo != null &&\n          changesElementInfo.children != newElementInfo.children) {\n        for (int i = 0, N = changesElementInfo.children.size(); i < N; ++i) {\n          final Object childElement = changesElementInfo.children.get(i);\n          notNewChildrenSet.add(childElement);\n        }\n      }\n\n      for (int i = 0, N = newElementInfo.children.size(); i < N; ++i) {\n        final Object childElement = newElementInfo.children.get(i);\n        setElementParent(childElement, element);\n        notNewChildrenSet.remove(childElement);\n      }\n\n      for (Object childElement : notNewChildrenSet) {\n        final ElementInfo childChangesElementInfo = mElementToInfoChangesMap.get(childElement);\n        if (childChangesElementInfo != null &&\n            childChangesElementInfo.parentElement != element) {\n          // do nothing. this childElement was moved to be the child of another element.\n          continue;\n        }\n\n        final ElementInfo oldChangesElementInfo = mElementToInfoMap.get(childElement);\n        if (oldChangesElementInfo != null &&\n            oldChangesElementInfo.parentElement == element) {\n          setElementParent(childElement, null);\n        }\n      }\n\n      releaseNotNewChildrenHashSet(notNewChildrenSet);\n    }\n\n    private void setElementParent(Object element, Object parentElement) {\n      ElementInfo changesElementInfo = mElementToInfoChangesMap.get(element);\n      if (changesElementInfo != null &&\n          parentElement == changesElementInfo.parentElement) {\n        return;\n      }\n\n      ElementInfo oldElementInfo = mElementToInfoMap.get(element);\n      if (changesElementInfo == null &&\n          oldElementInfo != null &&\n          parentElement == oldElementInfo.parentElement) {\n        return;\n      }\n\n      if (changesElementInfo != null &&\n          oldElementInfo != null &&\n          parentElement == oldElementInfo.parentElement &&\n          ListUtil.identityEquals(oldElementInfo.children, changesElementInfo.children)) {\n        mElementToInfoChangesMap.remove(element);\n\n        if (parentElement == null) {\n          mRootElementChanges.remove(element);\n        }\n\n        return;\n      }\n\n      List<Object> children = (changesElementInfo != null)\n          ? changesElementInfo.children\n          : (oldElementInfo != null)\n          ? oldElementInfo.children\n          : Collections.emptyList();\n\n      ElementInfo newElementInfo = new ElementInfo(element, parentElement, children);\n      mElementToInfoChangesMap.put(element, newElementInfo);\n\n      if (parentElement == null) {\n        mRootElementChanges.add(element);\n      } else {\n        mRootElementChanges.remove(element);\n      }\n    }\n\n    public Update build() {\n      return new Update(mElementToInfoChangesMap, mRootElementChanges);\n    }\n\n    private HashSet<Object> acquireNotNewChildrenHashSet() {\n      HashSet<Object> notNewChildrenHashSet = mCachedNotNewChildrenSet;\n      if (notNewChildrenHashSet == null) {\n        notNewChildrenHashSet = new HashSet<>();\n      }\n      mCachedNotNewChildrenSet = null;\n      return notNewChildrenHashSet;\n    }\n\n    private void releaseNotNewChildrenHashSet(HashSet<Object> notNewChildrenHashSet) {\n      notNewChildrenHashSet.clear();\n      if (mCachedNotNewChildrenSet == null) {\n        mCachedNotNewChildrenSet = notNewChildrenHashSet;\n      }\n    }\n  }\n\n  public final class Update implements DocumentView {\n    private final Map<Object, ElementInfo> mElementToInfoChangesMap;\n    private final Set<Object> mRootElementChangesSet;\n\n    public Update(\n        Map<Object, ElementInfo> elementToInfoChangesMap,\n        Set<Object> rootElementChangesSet) {\n      mElementToInfoChangesMap = elementToInfoChangesMap;\n      mRootElementChangesSet = rootElementChangesSet;\n    }\n\n    public boolean isEmpty() {\n      return mElementToInfoChangesMap.isEmpty();\n    }\n\n    public boolean isElementChanged(Object element) {\n      return mElementToInfoChangesMap.containsKey(element);\n    }\n\n    public Object getRootElement() {\n      return ShadowDocument.this.getRootElement();\n    }\n\n    public ElementInfo getElementInfo(Object element) {\n      // Return ElementInfo for the new (albeit uncommitted and pre-garbage collected) view of the\n      // Document. If element is garbage then you'll still get its info (feature, not a bug :)).\n      ElementInfo elementInfo = mElementToInfoChangesMap.get(element);\n      if (elementInfo != null) {\n        return elementInfo;\n      }\n      return mElementToInfoMap.get(element);\n    }\n\n    public void getChangedElements(Accumulator<Object> accumulator) {\n      for (Object element : mElementToInfoChangesMap.keySet()) {\n        accumulator.store(element);\n      }\n    }\n\n    public void getGarbageElements(Accumulator<Object> accumulator) {\n      // This queue stores pairs of elements, [element, expectedParent]\n      // When we dequeue, we look at element's parentElement in the new view to see if it matches\n      // expectedParent. If it does, then it's garbage. For enqueueing roots, whose parents are\n      // null, since we can't enqueue null we instead enqueue the element twice.\n      Queue<Object> queue = new ArrayDeque<>();\n\n      // Initialize the queue with all disconnected tree roots (parentElement == null) which\n      // aren't the DOM root.\n      for (Object element : mRootElementChangesSet) {\n        ElementInfo newElementInfo = getElementInfo(element);\n        if (element != mRootElement && newElementInfo.parentElement == null) {\n          queue.add(element);\n          queue.add(element);\n        }\n      }\n\n      // BFS traversal from those elements in the old view of the tree and test each element\n      // to see if it's still within a disconnected sub-tree. We can tell if it's garbage if its\n      // parent element in the new view of the tree hasn't changed.\n      while (!queue.isEmpty()) {\n        final Object element = queue.remove();\n        final Object expectedParent0 = queue.remove();\n        final Object expectedParent = (element == expectedParent0) ? null : expectedParent0;\n        final ElementInfo newElementInfo = getElementInfo(element);\n\n        if (newElementInfo.parentElement == expectedParent) {\n          accumulator.store(element);\n\n          ElementInfo oldElementInfo = ShadowDocument.this.getElementInfo(element);\n          if (oldElementInfo != null) {\n            for (int i = 0, N = oldElementInfo.children.size(); i < N; ++i) {\n              queue.add(oldElementInfo.children.get(i));\n              queue.add(element);\n            }\n          }\n        }\n      }\n    }\n\n    public void abandon() {\n      if (!mIsUpdating) {\n        throw new IllegalStateException();\n      }\n\n      mIsUpdating = false;\n    }\n\n    public void commit() {\n      if (!mIsUpdating) {\n        throw new IllegalStateException();\n      }\n\n      // Apply the changes to the tree\n      mElementToInfoMap.putAll(mElementToInfoChangesMap);\n\n      // Remove garbage elements: those that have a null parent (other than mRootElement), and\n      // their entire sub-trees, but excluding reparented elements.\n      for (Object element : mRootElementChangesSet) {\n        removeGarbageSubTree(mElementToInfoMap, element);\n      }\n\n      mIsUpdating = false;\n\n      // Not usually enabled because it's expensive. Very useful for debugging.\n      //validateTree(mElementToInfoMap);\n    }\n\n    private void removeGarbageSubTree(\n        Map<Object, ElementInfo> elementToInfoMap,\n        Object element) {\n      final ElementInfo elementInfo = elementToInfoMap.get(element);\n\n      // If this element has a parent (it's not a root), and that parent is still in the tree after\n      // changes have been applied and after our caller (removeGarbageSubTree) removed another\n      // element that claims this element as its child, then that means this element should not be\n      // removed. It has been reparented, and recursion stops here.\n      if (elementInfo.parentElement != null &&\n          elementToInfoMap.containsKey(elementInfo.parentElement)) {\n        return;\n      }\n\n      elementToInfoMap.remove(element);\n\n      for (int i = 0, N = elementInfo.children.size(); i < N; ++i) {\n        removeGarbageSubTree(elementToInfoMap, elementInfo.children.get(i));\n      }\n    }\n\n    // This method is intended for use during debugging. Put a breakpoint on each throw statement in\n    // order to catch structural problems in the tree. This method should only be called at the very\n    // end of commit().\n    private void validateTree(Map<Object, ElementInfo> elementToInfoMap) {\n      // We need a tree, not a forest.\n      HashSet<Object> rootElements = new HashSet<>();\n\n      for (Map.Entry<Object, ElementInfo> entry : elementToInfoMap.entrySet()) {\n        final Object element = entry.getKey();\n        final ElementInfo elementInfo = entry.getValue();\n\n        if (element != elementInfo.element) {\n          // should not be possible\n          throw new IllegalStateException(\"element != elementInfo.element\");\n        }\n\n        // Verify children\n        for (int i = 0, N = elementInfo.children.size(); i < N; ++i) {\n          final Object childElement = elementInfo.children.get(i);\n          final ElementInfo childElementInfo = elementToInfoMap.get(childElement);\n\n          if (childElementInfo == null) {\n            throw new IllegalStateException(String.format(\n                \"elementInfo.get(elementInfo.children.get(%s)) == null\",\n                i));\n          }\n\n          if (childElementInfo.parentElement != element) {\n            throw new IllegalStateException(\"childElementInfo.parentElement != element\");\n          }\n        }\n\n        // Verify parent\n        if (elementInfo.parentElement == null) {\n          rootElements.add(element);\n        } else {\n          final ElementInfo parentElementInfo = elementToInfoMap.get(elementInfo.parentElement);\n          if (parentElementInfo == null) {\n            throw new IllegalStateException(\n                \"elementToInfoMap.get(elementInfo.parentElementInfo) == NULL\");\n          }\n\n          if (elementInfo.parentElement != parentElementInfo.element) {\n            // should not be possible\n            throw new IllegalStateException(\n                \"elementInfo.parentElementInfo != parentElementInfo.parent\");\n          }\n\n          if (!parentElementInfo.children.contains(element)) {\n            throw new IllegalStateException(\n                \"parentElementInfo.children.contains(element) == FALSE\");\n          }\n        }\n      }\n\n      if (rootElements.size() != 1) {\n        throw new IllegalStateException(\n            \"elementToInfoMap is a forest, not a tree. rootElements.size() != 1\");\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/StyleAccumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface StyleAccumulator {\n  void store(String name, String value, boolean isDefault);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/StyleRuleNameAccumulator.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements;\n\npublic interface StyleRuleNameAccumulator {\n  void store(String ruleName, boolean editable);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AccessibilityNodeInfoWrapper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewParent;\nimport android.widget.EditText;\n\nimport com.facebook.stetho.common.android.AccessibilityUtil;\n\nimport androidx.annotation.Nullable;\nimport androidx.core.view.ViewCompat;\nimport androidx.core.view.accessibility.AccessibilityNodeInfoCompat;\nimport androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat;\n\npublic final class AccessibilityNodeInfoWrapper {\n\n  public AccessibilityNodeInfoWrapper() {\n  }\n\n  public static AccessibilityNodeInfoCompat createNodeInfoFromView(View view) {\n    AccessibilityNodeInfoCompat nodeInfo = AccessibilityNodeInfoCompat.obtain();\n    ViewCompat.onInitializeAccessibilityNodeInfo(view, nodeInfo);\n    return nodeInfo;\n  }\n\n  public static boolean getIsAccessibilityFocused(View view) {\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    boolean isAccessibilityFocused = node.isAccessibilityFocused();\n    node.recycle();\n\n    return isAccessibilityFocused;\n  }\n\n  public static boolean getIgnored(View view) {\n    int important = ViewCompat.getImportantForAccessibility(view);\n    if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO ||\n        important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {\n      return true;\n    }\n\n    // Go all the way up the tree to make sure no parent has hidden its descendants\n    ViewParent parent = view.getParent();\n    while (parent instanceof View) {\n      if (ViewCompat.getImportantForAccessibility((View) parent)\n          == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {\n        return true;\n      }\n      parent = parent.getParent();\n    }\n\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    try {\n      if (!node.isVisibleToUser()) {\n        return true;\n      }\n\n      if (AccessibilityUtil.isAccessibilityFocusable(node, view)) {\n        if (node.getChildCount() <= 0) {\n          // Leaves that are accessibility focusable are never ignored, even if they don't have a\n          // speakable description\n          return false;\n        } else if (AccessibilityUtil.isSpeakingNode(node, view)) {\n          // Node is focusable and has something to speak\n          return false;\n        }\n\n        // Node is focusable and has nothing to speak\n        return true;\n      }\n\n      // If this node has no focusable ancestors, but it still has text,\n      // then it should receive focus from navigation and be read aloud.\n      if (!AccessibilityUtil.hasFocusableAncestor(node, view) && AccessibilityUtil.hasText(node)) {\n        return false;\n      }\n\n      return true;\n    } finally {\n      node.recycle();\n    }\n  }\n\n  public static String getIgnoredReasons(View view) {\n    int important = ViewCompat.getImportantForAccessibility(view);\n\n    if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO) {\n      return \"View has importantForAccessibility set to 'NO'.\";\n    }\n\n    if (important == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {\n      return \"View has importantForAccessibility set to 'NO_HIDE_DESCENDANTS'.\";\n    }\n\n    ViewParent parent = view.getParent();\n    while (parent instanceof View) {\n      if (ViewCompat.getImportantForAccessibility((View) parent)\n              == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) {\n        return \"An ancestor View has importantForAccessibility set to 'NO_HIDE_DESCENDANTS'.\";\n      }\n      parent = parent.getParent();\n    }\n\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    try {\n      if (!node.isVisibleToUser()) {\n        return \"View is not visible.\";\n      }\n\n      if (AccessibilityUtil.isAccessibilityFocusable(node, view)) {\n        return \"View is actionable, but has no description.\";\n      }\n\n      if (AccessibilityUtil.hasText(node)) {\n        return \"View is not actionable, and an ancestor View has co-opted its description.\";\n      }\n\n      return \"View is not actionable and has no description.\";\n    } finally {\n      node.recycle();\n    }\n  }\n\n  @Nullable\n  public static String getFocusableReasons(View view) {\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    try {\n      boolean hasText = AccessibilityUtil.hasText(node);\n      boolean isCheckable = node.isCheckable();\n      boolean hasNonActionableSpeakingDescendants =\n          AccessibilityUtil.hasNonActionableSpeakingDescendants(node, view);\n\n      if (AccessibilityUtil.isActionableForAccessibility(node)) {\n        if (node.getChildCount() <= 0) {\n          return \"View is actionable and has no children.\";\n        } else if (hasText) {\n          return \"View is actionable and has a description.\";\n        } else if (isCheckable) {\n          return \"View is actionable and checkable.\";\n        } else if (hasNonActionableSpeakingDescendants) {\n          return \"View is actionable and has non-actionable descendants with descriptions.\";\n        }\n      }\n\n      if (AccessibilityUtil.isTopLevelScrollItem(node, view)) {\n        if (hasText) {\n          return \"View is a direct child of a scrollable container and has a description.\";\n        } else if (isCheckable) {\n          return \"View is a direct child of a scrollable container and is checkable.\";\n        } else if (hasNonActionableSpeakingDescendants) {\n          return\n              \"View is a direct child of a scrollable container and has non-actionable \" +\n              \"descendants with descriptions.\";\n        }\n      }\n\n      if (hasText) {\n        return \"View has a description and is not actionable, but has no actionable ancestor.\";\n      }\n\n      return null;\n    } finally {\n      node.recycle();\n    }\n  }\n\n  @Nullable\n  public static String getActions(View view) {\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    try {\n      final StringBuilder actionLabels = new StringBuilder();\n      final String separator = \", \";\n\n      for (AccessibilityActionCompat action : node.getActionList()) {\n        if (actionLabels.length() > 0) {\n          actionLabels.append(separator);\n        }\n        switch (action.getId()) {\n          case AccessibilityNodeInfoCompat.ACTION_FOCUS:\n            actionLabels.append(\"focus\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:\n            actionLabels.append(\"clear-focus\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_SELECT:\n            actionLabels.append(\"select\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_CLEAR_SELECTION:\n            actionLabels.append(\"clear-selection\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_CLICK:\n            actionLabels.append(\"click\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_LONG_CLICK:\n            actionLabels.append(\"long-click\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:\n            actionLabels.append(\"accessibility-focus\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:\n            actionLabels.append(\"clear-accessibility-focus\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_NEXT_AT_MOVEMENT_GRANULARITY:\n            actionLabels.append(\"next-at-movement-granularity\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY:\n            actionLabels.append(\"previous-at-movement-granularity\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_NEXT_HTML_ELEMENT:\n            actionLabels.append(\"next-html-element\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_PREVIOUS_HTML_ELEMENT:\n            actionLabels.append(\"previous-html-element\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD:\n            actionLabels.append(\"scroll-forward\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD:\n            actionLabels.append(\"scroll-backward\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_CUT:\n            actionLabels.append(\"cut\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_COPY:\n            actionLabels.append(\"copy\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_PASTE:\n            actionLabels.append(\"paste\");\n            break;\n          case AccessibilityNodeInfoCompat.ACTION_SET_SELECTION:\n            actionLabels.append(\"set-selection\");\n            break;\n          default:\n            CharSequence label = action.getLabel();\n            if (label != null) {\n              actionLabels.append(label);\n            } else {\n              actionLabels.append(\"unknown\");\n            }\n            break;\n        }\n      }\n\n      return actionLabels.length() > 0 ? actionLabels.toString() : null;\n    } finally {\n      node.recycle();\n    }\n  }\n\n  @Nullable\n  public static CharSequence getDescription(View view) {\n    AccessibilityNodeInfoCompat node = createNodeInfoFromView(view);\n    try {\n      CharSequence contentDescription = node.getContentDescription();\n      CharSequence nodeText = node.getText();\n\n      boolean hasNodeText = !TextUtils.isEmpty(nodeText);\n      boolean isEditText = view instanceof EditText;\n\n      // EditText's prioritize their own text content over a contentDescription\n      if (!TextUtils.isEmpty(contentDescription) && (!isEditText || !hasNodeText)) {\n        return contentDescription;\n      }\n\n      if (hasNodeText) {\n        return nodeText;\n      }\n\n      // If there are child views and no contentDescription the text of all non-focusable children,\n      // comma separated, becomes the description.\n      if (view instanceof ViewGroup) {\n        final StringBuilder concatChildDescription = new StringBuilder();\n        final String separator = \", \";\n        ViewGroup viewGroup = (ViewGroup) view;\n\n        for (int i = 0, count = viewGroup.getChildCount(); i < count; i++) {\n          final View child = viewGroup.getChildAt(i);\n\n          AccessibilityNodeInfoCompat childNodeInfo = AccessibilityNodeInfoCompat.obtain();\n          ViewCompat.onInitializeAccessibilityNodeInfo(child, childNodeInfo);\n\n          CharSequence childNodeDescription = null;\n          if (AccessibilityUtil.isSpeakingNode(childNodeInfo, child) &&\n              !AccessibilityUtil.isAccessibilityFocusable(childNodeInfo, child)) {\n            childNodeDescription = getDescription(child);\n          }\n\n          if (!TextUtils.isEmpty(childNodeDescription)) {\n            if (concatChildDescription.length() > 0) {\n              concatChildDescription.append(separator);\n            }\n            concatChildDescription.append(childNodeDescription);\n          }\n          childNodeInfo.recycle();\n        }\n\n        return concatChildDescription.length() > 0 ? concatChildDescription.toString() : null;\n      }\n\n      return null;\n    } finally {\n      node.recycle();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ActivityDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Activity;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.Window;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.StringUtil;\nimport com.facebook.stetho.common.android.FragmentActivityAccessor;\nimport com.facebook.stetho.common.android.FragmentCompat;\nimport com.facebook.stetho.common.android.FragmentManagerAccessor;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.Descriptor;\n\nimport javax.annotation.Nullable;\n\nimport java.util.List;\n\nfinal class ActivityDescriptor\n    extends AbstractChainedDescriptor<Activity> implements HighlightableDescriptor<Activity> {\n  @Override\n  protected String onGetNodeName(Activity element) {\n    String className = element.getClass().getName();\n    return StringUtil.removePrefix(className, \"android.app.\");\n  }\n\n  @Override\n  protected void onGetChildren(Activity element, Accumulator<Object> children) {\n    getDialogFragments(FragmentCompat.getSupportLibInstance(), element, children);\n    getDialogFragments(FragmentCompat.getFrameworkInstance(), element, children);\n\n    Window window = element.getWindow();\n    if (window != null) {\n      children.store(window);\n    }\n  }\n\n  @Nullable\n  @Override\n  public View getViewAndBoundsForHighlighting(Activity element, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Window window = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      window = element.getWindow();\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(window);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getViewAndBoundsForHighlighting(window, bounds);\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(Activity element, int x, int y, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Window window = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      window = element.getWindow();\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(window);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getElementToHighlightAtPosition(window, x, y, bounds);\n  }\n\n  private static void getDialogFragments(\n      @Nullable FragmentCompat compat,\n      Activity activity,\n      Accumulator<Object> accumulator) {\n    if (compat == null || !compat.getFragmentActivityClass().isInstance(activity)) {\n      return;\n    }\n\n    FragmentActivityAccessor activityAccessor = compat.forFragmentActivity();\n    Object fragmentManager = activityAccessor.getFragmentManager(activity);\n    if (fragmentManager == null) {\n      return;\n    }\n\n    FragmentManagerAccessor fragmentManagerAccessor = compat.forFragmentManager();\n    List<Object> addedFragments = fragmentManagerAccessor.getAddedFragments(fragmentManager);\n    if (addedFragments == null) {\n      return;\n    }\n\n    for (int i = 0, N = addedFragments.size(); i < N; ++i) {\n      final Object fragment = addedFragments.get(i);\n      if (compat.getDialogFragmentClass().isInstance(fragment)) {\n        accumulator.store(fragment);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ActivityTracker.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.annotation.TargetApi;\nimport android.app.Activity;\nimport android.app.Application;\nimport android.os.Build;\nimport android.os.Bundle;\nimport android.os.Looper;\n\nimport androidx.annotation.NonNull;\n\nimport com.facebook.stetho.common.Util;\n\nimport java.lang.ref.WeakReference;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\nimport javax.annotation.concurrent.NotThreadSafe;\n\n/**\n * Tracks which {@link Activity} instances have been created and not yet destroyed in creation\n * order for use by Stetho features.  Note that automatic tracking is not available for\n * all versions of Android but it is possible to manually track activities using the {@link #add}\n * and {@link #remove} methods exposed below.  Be aware that this is an easy opportunity to\n * cause serious memory leaks in your application however.  Use with caution.\n * <p/>\n * Most callers can and should ignore this class, though it is necessary if you are implementing\n * Activity tracking pre-ICS.\n */\n@NotThreadSafe\npublic final class ActivityTracker {\n  private static final ActivityTracker sInstance = new ActivityTracker();\n\n  /**\n   * Use {@link WeakReference} here to silence a false positive from LeakCanary:\n   *   https://github.com/facebook/stetho/issues/319#issuecomment-285699813\n   */\n  @GuardedBy(\"Looper.getMainLooper()\")\n  private final ArrayList<WeakReference<Activity>> mActivities = new ArrayList<>();\n  private final List<WeakReference<Activity>> mActivitiesUnmodifiable =\n      Collections.unmodifiableList(mActivities);\n\n  private final List<Listener> mListeners = new CopyOnWriteArrayList<>();\n\n  @Nullable\n  private AutomaticTracker mAutomaticTracker;\n\n  public static ActivityTracker get() {\n    return sInstance;\n  }\n\n  public void registerListener(Listener listener) {\n    mListeners.add(listener);\n  }\n\n  public void unregisterListener(Listener listener) {\n    mListeners.remove(listener);\n  }\n\n  /**\n   * Start automatic tracking if we are running on ICS+.\n   *\n   * @return Automatic tracking has been started.  No need to manually invoke {@link #add} or\n   *     {@link #remove} methods.\n   */\n  public boolean beginTrackingIfPossible(Application application) {\n    if (mAutomaticTracker == null) {\n      AutomaticTracker automaticTracker =\n          AutomaticTracker.newInstance(application, this /* tracker */);\n        automaticTracker.register();\n        mAutomaticTracker = automaticTracker;\n        return true;\n    }\n    return false;\n  }\n\n  public boolean endTracking() {\n    if (mAutomaticTracker != null) {\n      mAutomaticTracker.unregister();\n      mAutomaticTracker = null;\n      return true;\n    }\n    return false;\n  }\n\n  public void add(Activity activity) {\n    Util.throwIfNull(activity);\n    Util.throwIfNot(Looper.myLooper() == Looper.getMainLooper());\n    mActivities.add(new WeakReference<>(activity));\n    for (Listener listener : mListeners) {\n      listener.onActivityAdded(activity);\n    }\n  }\n\n  public void remove(Activity activity) {\n    Util.throwIfNull(activity);\n    Util.throwIfNot(Looper.myLooper() == Looper.getMainLooper());\n    if (removeFromWeakList(mActivities, activity)) {\n      for (Listener listener : mListeners) {\n        listener.onActivityRemoved(activity);\n      }\n    }\n  }\n\n  private static <T> boolean removeFromWeakList(ArrayList<WeakReference<T>> haystack, T needle) {\n    for (int i = 0, N = haystack.size(); i < N; i++) {\n      T hay = haystack.get(i).get();\n      if (hay == needle) {\n        haystack.remove(i);\n        return true;\n      }\n    }\n    return false;\n  }\n\n  public List<WeakReference<Activity>> getActivitiesView() {\n    return mActivitiesUnmodifiable;\n  }\n\n  @Nullable\n  public Activity tryGetTopActivity() {\n    if (mActivitiesUnmodifiable.isEmpty()) {\n      return null;\n    }\n    for (int i = mActivitiesUnmodifiable.size() - 1; i >= 0; i--) {\n      Activity activity = mActivitiesUnmodifiable.get(i).get();\n      if (activity != null) {\n        return activity;\n      }\n    }\n    return null;\n  }\n\n  public interface Listener {\n    public void onActivityAdded(Activity activity);\n    public void onActivityRemoved(Activity activity);\n  }\n\n  private static abstract class AutomaticTracker {\n    @NonNull\n    public static AutomaticTracker newInstance(\n            Application application,\n            ActivityTracker tracker) {\n      return new AutomaticTrackerICSAndBeyond(application, tracker);\n    }\n\n    public abstract void register();\n    public abstract void unregister();\n\n    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n    private static class AutomaticTrackerICSAndBeyond extends AutomaticTracker {\n      private final Application mApplication;\n      private final ActivityTracker mTracker;\n\n      public AutomaticTrackerICSAndBeyond(Application application, ActivityTracker tracker) {\n        mApplication = application;\n        mTracker = tracker;\n      }\n\n      public void register() {\n        mApplication.registerActivityLifecycleCallbacks(mLifecycleCallbacks);\n      }\n\n      public void unregister() {\n        mApplication.unregisterActivityLifecycleCallbacks(mLifecycleCallbacks);\n      }\n\n      private final Application.ActivityLifecycleCallbacks mLifecycleCallbacks =\n          new Application.ActivityLifecycleCallbacks() {\n        @Override\n        public void onActivityCreated(Activity activity, Bundle savedInstanceState) {\n          mTracker.add(activity);\n        }\n\n        @Override\n        public void onActivityStarted(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityResumed(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityPaused(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivityStopped(Activity activity) {\n\n        }\n\n        @Override\n        public void onActivitySaveInstanceState(Activity activity, Bundle outState) {\n\n        }\n\n        @Override\n        public void onActivityDestroyed(Activity activity) {\n          mTracker.remove(activity);\n        }\n      };\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AndroidDescriptorHost.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport com.facebook.stetho.inspector.elements.Descriptor;\n\nimport javax.annotation.Nullable;\n\ninterface AndroidDescriptorHost extends Descriptor.Host {\n  @Nullable\n  HighlightableDescriptor getHighlightableDescriptor(@Nullable Object element);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AndroidDocumentConstants.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.os.Build;\n\npublic interface AndroidDocumentConstants {\n  /**\n   * Minimum API version required to make effective use of AndroidDocumentProvider. This can be\n   * moved back significantly through manual APIs to discover {@link android.app.Activity}\n   * instances.\n   */\n  int MIN_API_LEVEL = Build.VERSION_CODES.ICE_CREAM_SANDWICH;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AndroidDocumentProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.app.Dialog;\nimport android.content.Context;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.view.MotionEvent;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.Window;\nimport android.view.WindowManager;\nimport android.widget.TextView;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.Predicate;\nimport com.facebook.stetho.common.ThreadBound;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.DocumentProvider;\nimport com.facebook.stetho.inspector.elements.Descriptor;\nimport com.facebook.stetho.inspector.elements.DescriptorProvider;\nimport com.facebook.stetho.inspector.elements.DescriptorMap;\nimport com.facebook.stetho.inspector.elements.DocumentProviderListener;\nimport com.facebook.stetho.inspector.elements.NodeDescriptor;\nimport com.facebook.stetho.inspector.elements.ObjectDescriptor;\nimport com.facebook.stetho.inspector.helper.ThreadBoundProxy;\n\nimport javax.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nfinal class AndroidDocumentProvider extends ThreadBoundProxy\n    implements DocumentProvider, AndroidDescriptorHost {\n  private static final int INSPECT_OVERLAY_COLOR = 0x40FFFFFF;\n  private static final int INSPECT_HOVER_COLOR = 0x404040ff;\n\n  private final Rect mHighlightingBoundsRect = new Rect();\n  private final Rect mHitRect = new Rect();\n\n  private final Application mApplication;\n  private final DescriptorMap mDescriptorMap;\n  private final AndroidDocumentRoot mDocumentRoot;\n  private final ViewHighlighter mHighlighter;\n  private final InspectModeHandler mInspectModeHandler;\n  private @Nullable DocumentProviderListener mListener;\n\n  // We don't yet have an an implementation for reliably detecting fine-grained changes in the\n  // View tree. So, for now at least, we have a timer that runs every so often and just reports\n  // that we changed. Our listener will then read the entire Document from us and transmit the\n  // changes to Chrome. Detecting, reporting, and traversing fine-grained changes is a future work\n  // item (see Issue #210).\n  private static final long REPORT_CHANGED_INTERVAL_MS = 1000;\n  private boolean mIsReportChangesTimerPosted = false;\n  private final Runnable mReportChangesTimer = new Runnable() {\n    @Override\n    public void run() {\n      mIsReportChangesTimerPosted = false;\n\n      if (mListener != null) {\n        mListener.onPossiblyChanged();\n        mIsReportChangesTimerPosted = true;\n        postDelayed(this, REPORT_CHANGED_INTERVAL_MS);\n      }\n    }\n  };\n\n  public AndroidDocumentProvider(\n      Application application,\n      List<DescriptorProvider> descriptorProviders,\n      ThreadBound enforcer) {\n    super(enforcer);\n\n    mApplication = Util.throwIfNull(application);\n    mDocumentRoot = new AndroidDocumentRoot(application);\n\n    mDescriptorMap = new DescriptorMap()\n        .beginInit()\n        .registerDescriptor(Activity.class, new ActivityDescriptor())\n        .registerDescriptor(AndroidDocumentRoot.class, mDocumentRoot)\n        .registerDescriptor(Application.class, new ApplicationDescriptor())\n        .registerDescriptor(Dialog.class, new DialogDescriptor())\n        .registerDescriptor(Object.class, new ObjectDescriptor())\n        .registerDescriptor(TextView.class, new TextViewDescriptor())\n        .registerDescriptor(View.class, new ViewDescriptor())\n        .registerDescriptor(ViewGroup.class, new ViewGroupDescriptor())\n        .registerDescriptor(Window.class, new WindowDescriptor());\n\n    DialogFragmentDescriptor.register(mDescriptorMap);\n    FragmentDescriptor.register(mDescriptorMap);\n\n    for (int i = 0, size = descriptorProviders.size(); i < size; ++i) {\n      final DescriptorProvider descriptorProvider = descriptorProviders.get(i);\n      descriptorProvider.registerDescriptor(mDescriptorMap);\n    }\n\n    mDescriptorMap.setHost(this).endInit();\n\n    mHighlighter = ViewHighlighter.newInstance();\n    mInspectModeHandler = new InspectModeHandler();\n  }\n\n  @Override\n  public void dispose() {\n    verifyThreadAccess();\n\n    mHighlighter.clearHighlight();\n    mInspectModeHandler.disable();\n    removeCallbacks(mReportChangesTimer);\n    mIsReportChangesTimerPosted = false;\n    mListener = null;\n  }\n\n  @Override\n  public void setListener(DocumentProviderListener listener) {\n    verifyThreadAccess();\n\n    mListener = listener;\n    if (mListener == null && mIsReportChangesTimerPosted) {\n      mIsReportChangesTimerPosted = false;\n      removeCallbacks(mReportChangesTimer);\n    } else if (mListener != null && !mIsReportChangesTimerPosted) {\n      mIsReportChangesTimerPosted = true;\n      postDelayed(mReportChangesTimer, REPORT_CHANGED_INTERVAL_MS);\n    }\n  }\n\n  @Override\n  public Object getRootElement() {\n    verifyThreadAccess();\n    return mDocumentRoot;\n  }\n\n  @Override\n  public NodeDescriptor getNodeDescriptor(Object element) {\n    verifyThreadAccess();\n    return getDescriptor(element);\n  }\n\n  @Override\n  public void highlightElement(Object element, int color) {\n    verifyThreadAccess();\n\n    final HighlightableDescriptor descriptor = getHighlightableDescriptor(element);\n    if (descriptor == null) {\n      mHighlighter.clearHighlight();\n      return;\n    }\n\n    mHighlightingBoundsRect.setEmpty();\n    final View highlightingView =\n        descriptor.getViewAndBoundsForHighlighting(element, mHighlightingBoundsRect);\n    if (highlightingView == null) {\n      mHighlighter.clearHighlight();\n      return;\n    }\n\n    mHighlighter.setHighlightedView(\n        highlightingView,\n        mHighlightingBoundsRect,\n        color);\n  }\n\n  @Override\n  public void hideHighlight() {\n    verifyThreadAccess();\n\n    mHighlighter.clearHighlight();\n  }\n\n  @Override\n  public void setInspectModeEnabled(boolean enabled) {\n    verifyThreadAccess();\n\n    if (enabled) {\n      mInspectModeHandler.enable();\n    } else {\n      mInspectModeHandler.disable();\n    }\n  }\n\n  @Override\n  public void setAttributesAsText(Object element, String text) {\n    verifyThreadAccess();\n\n    Descriptor descriptor = mDescriptorMap.get(element.getClass());\n    if (descriptor != null) {\n      descriptor.setAttributesAsText(element, text);\n    }\n  }\n\n  // Descriptor.Host implementation\n  @Override\n  public Descriptor getDescriptor(Object element) {\n    return (element == null) ? null : mDescriptorMap.get(element.getClass());\n  }\n\n  @Override\n  public void onAttributeModified(Object element, String name, String value) {\n    if (mListener != null) {\n      mListener.onAttributeModified(element, name, value);\n    }\n  }\n\n  @Override\n  public void onAttributeRemoved(Object element, String name) {\n    if (mListener != null) {\n      mListener.onAttributeRemoved(element, name);\n    }\n  }\n\n  // AndroidDescriptorHost implementation\n  @Override\n  @Nullable\n  public HighlightableDescriptor getHighlightableDescriptor(@Nullable Object element) {\n    if (element == null) {\n      return null;\n    }\n\n    HighlightableDescriptor highlightableDescriptor = null;\n    Class<?> theClass = element.getClass();\n    Descriptor lastDescriptor = null;\n    while (highlightableDescriptor == null && theClass != null) {\n      Descriptor descriptor = mDescriptorMap.get(theClass);\n      if (descriptor == null) {\n        return null;\n      }\n\n      if (descriptor != lastDescriptor && descriptor instanceof HighlightableDescriptor) {\n        highlightableDescriptor = ((HighlightableDescriptor) descriptor);\n      }\n\n      lastDescriptor = descriptor;\n      theClass = theClass.getSuperclass();\n    }\n\n    return highlightableDescriptor;\n  }\n\n  private void getWindows(final Accumulator<Window> accumulator) {\n    Descriptor appDescriptor = getDescriptor(mApplication);\n    if (appDescriptor != null) {\n      Accumulator<Object> elementAccumulator = new Accumulator<Object>() {\n        @Override\n        public void store(Object element) {\n          if (element instanceof Window) {\n            // Store the Window and do not recurse into its children.\n            accumulator.store((Window) element);\n          } else {\n            // Recursively scan this element's children in search of more Windows.\n            Descriptor elementDescriptor = getDescriptor(element);\n            if (elementDescriptor != null) {\n              elementDescriptor.getChildren(element, this);\n            }\n          }\n        }\n      };\n\n      appDescriptor.getChildren(mApplication, elementAccumulator);\n    }\n  }\n\n  private final class InspectModeHandler {\n    private final Predicate<View> mViewSelector = new Predicate<View>() {\n      @Override\n      public boolean apply(View view) {\n        return !(view instanceof DocumentHiddenView);\n      }\n    };\n\n    private List<View> mOverlays;\n\n    public void enable() {\n      verifyThreadAccess();\n\n      if (mOverlays != null) {\n        disable();\n      }\n\n      mOverlays = new ArrayList<>();\n\n      getWindows(new Accumulator<Window>() {\n        @Override\n        public void store(Window object) {\n          if (object.peekDecorView() instanceof ViewGroup) {\n            final ViewGroup decorView = (ViewGroup) object.peekDecorView();\n\n            OverlayView overlayView = new OverlayView(mApplication);\n\n            WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();\n            layoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;\n            layoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;\n\n            decorView.addView(overlayView, layoutParams);\n            decorView.bringChildToFront(overlayView);\n\n            mOverlays.add(overlayView);\n          }\n        }\n      });\n    }\n\n    public void disable() {\n      verifyThreadAccess();\n\n      if (mOverlays == null) {\n        return;\n      }\n\n      for (int i = 0; i < mOverlays.size(); ++i) {\n        final View overlayView = mOverlays.get(i);\n        ViewGroup decorViewGroup = (ViewGroup)overlayView.getParent();\n        decorViewGroup.removeView(overlayView);\n      }\n\n      mOverlays = null;\n    }\n\n    private final class OverlayView extends DocumentHiddenView {\n      public OverlayView(Context context) {\n        super(context);\n      }\n\n      @Override\n      protected void onDraw(Canvas canvas) {\n        canvas.drawColor(INSPECT_OVERLAY_COLOR);\n        super.onDraw(canvas);\n      }\n\n      @Override\n      public boolean onTouchEvent(MotionEvent event) {\n        int x = (int) event.getX();\n        int y = (int) event.getY();\n        Object elementToHighlight = getParent();\n        while (true) {\n          final HighlightableDescriptor descriptor =\n              getHighlightableDescriptor(elementToHighlight);\n\n          if (descriptor == null) {\n            break;\n          }\n\n          mHitRect.setEmpty();\n          final Object element =\n              descriptor.getElementToHighlightAtPosition(elementToHighlight, x, y, mHitRect);\n\n          x -= mHitRect.left;\n          y -= mHitRect.top;\n\n          if (element == elementToHighlight) {\n            break;\n          }\n\n          elementToHighlight = element;\n        }\n\n        if (elementToHighlight != null) {\n          final HighlightableDescriptor descriptor =\n              getHighlightableDescriptor(elementToHighlight);\n\n          if (descriptor != null) {\n            final View viewToHighlight =\n                descriptor.getViewAndBoundsForHighlighting(\n                    elementToHighlight,\n                    mHighlightingBoundsRect);\n\n            if (event.getAction() != MotionEvent.ACTION_CANCEL) {\n              if (viewToHighlight != null) {\n                mHighlighter.setHighlightedView(\n                    viewToHighlight,\n                    mHighlightingBoundsRect,\n                    INSPECT_HOVER_COLOR);\n\n                if (event.getAction() == MotionEvent.ACTION_UP) {\n                  if (mListener != null) {\n                    mListener.onInspectRequested(elementToHighlight);\n                  }\n                }\n              }\n            }\n          }\n        }\n\n        return true;\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AndroidDocumentProviderFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Application;\n\nimport android.os.Handler;\nimport android.os.Looper;\nimport com.facebook.stetho.common.ThreadBound;\nimport com.facebook.stetho.common.UncheckedCallable;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.common.android.HandlerUtil;\nimport com.facebook.stetho.inspector.elements.DescriptorProvider;\nimport com.facebook.stetho.inspector.elements.DocumentProvider;\nimport com.facebook.stetho.inspector.elements.DocumentProviderFactory;\n\nimport java.util.List;\n\npublic final class AndroidDocumentProviderFactory\n    implements DocumentProviderFactory, ThreadBound {\n  private final Application mApplication;\n  private final List<DescriptorProvider> mDescriptorProviders;\n  private final Handler mHandler;\n\n  public AndroidDocumentProviderFactory(\n      Application application,\n      List<DescriptorProvider> descriptorProviders) {\n    mApplication = Util.throwIfNull(application);\n    mDescriptorProviders = Util.throwIfNull(descriptorProviders);\n    mHandler = new Handler(Looper.getMainLooper());\n  }\n\n  @Override\n  public DocumentProvider create() {\n    return new AndroidDocumentProvider(mApplication, mDescriptorProviders, this);\n  }\n\n  // ThreadBound implementation\n  @Override\n  public boolean checkThreadAccess() {\n    return HandlerUtil.checkThreadAccess(mHandler);\n  }\n\n  @Override\n  public void verifyThreadAccess() {\n    HandlerUtil.verifyThreadAccess(mHandler);\n  }\n\n  @Override\n  public <V> V postAndWait(UncheckedCallable<V> c) {\n    return HandlerUtil.postAndWait(mHandler, c);\n  }\n\n  @Override\n  public void postAndWait(Runnable r) {\n    HandlerUtil.postAndWait(mHandler, r);\n  }\n\n  @Override\n  public void postDelayed(Runnable r, long delayMillis) {\n    if (!mHandler.postDelayed(r, delayMillis)) {\n      throw new RuntimeException(\"Handler.postDelayed() returned false\");\n    }\n  }\n\n  @Override\n  public void removeCallbacks(Runnable r) {\n    mHandler.removeCallbacks(r);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/AndroidDocumentRoot.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Application;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.NodeType;\n\n// For the root, we use 1 object for both element and descriptor.\n\nfinal class AndroidDocumentRoot extends AbstractChainedDescriptor<AndroidDocumentRoot> {\n  private final Application mApplication;\n\n  public AndroidDocumentRoot(Application application) {\n    mApplication = Util.throwIfNull(application);\n  }\n\n  @Override\n  protected NodeType onGetNodeType(AndroidDocumentRoot element) {\n    return NodeType.DOCUMENT_NODE;\n  }\n\n  @Override\n  protected String onGetNodeName(AndroidDocumentRoot element) {\n    return \"root\";\n  }\n\n  @Override\n  protected void onGetChildren(AndroidDocumentRoot element, Accumulator<Object> children) {\n    children.store(mApplication);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ApplicationDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Activity;\nimport android.app.Application;\nimport android.view.View;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.NodeType;\nimport com.facebook.stetho.inspector.elements.android.window.WindowRootViewCompat;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nfinal class ApplicationDescriptor extends AbstractChainedDescriptor<Application> {\n  private final Map<Application, ElementContext> mElementToContextMap =\n      Collections.synchronizedMap(new IdentityHashMap<Application, ElementContext>());\n\n  private final ActivityTracker mActivityTracker = ActivityTracker.get();\n\n  private ElementContext getContext(Application element) {\n    return mElementToContextMap.get(element);\n  }\n\n  @Override\n  protected void onHook(Application element) {\n    ElementContext context = new ElementContext();\n    context.hook(element);\n    mElementToContextMap.put(element, context);\n  }\n\n  @Override\n  protected void onUnhook(Application element) {\n    ElementContext context = mElementToContextMap.remove(element);\n    context.unhook();\n  }\n\n  @Override\n  protected NodeType onGetNodeType(Application element) {\n    return NodeType.ELEMENT_NODE;\n  }\n\n  @Override\n  protected void onGetChildren(Application element, Accumulator<Object> children) {\n    ElementContext context = getContext(element);\n    List<WeakReference<Activity>> activities = context.getActivitiesList();\n    // We report these in reverse order so that the newer ones show up on top\n    for (int i = activities.size() - 1; i >= 0; --i) {\n      Activity activity = activities.get(i).get();\n      if (activity != null) {\n        children.store(activity);\n      }\n    }\n    storeWindowIfNeeded(element, children, activities);\n  }\n\n  private void storeWindowIfNeeded(Application application, Accumulator<Object> children, List<WeakReference<Activity>> activities) {\n    List<View> rootViews = WindowRootViewCompat.get(application).getRootViews();\n    for (View view : rootViews) {\n      if (!isDecorViewOfActivity(view, activities)) {\n        children.store(view);\n      }\n    }\n  }\n\n  private static boolean isDecorViewOfActivity(View view, List<WeakReference<Activity>> references) {\n    Util.throwIfNull(references);\n    for (WeakReference<Activity> reference : references) {\n      Activity activity = reference.get();\n      if (activity == null) {\n        continue;\n      }\n      if (activity.getWindow().getDecorView() == view) {\n        return true;\n      }\n    }\n    return false;\n  }\n\n  private class ElementContext {\n    private Application mElement;\n\n    public ElementContext() {\n    }\n\n    public void hook(Application element) {\n      mElement = element;\n      mActivityTracker.registerListener(mListener);\n    }\n\n    public void unhook() {\n      mActivityTracker.unregisterListener(mListener);\n      mElement = null;\n    }\n\n    public List<WeakReference<Activity>> getActivitiesList() {\n      return mActivityTracker.getActivitiesView();\n    }\n\n    private final ActivityTracker.Listener mListener = new ActivityTracker.Listener() {\n      @Override\n      public void onActivityAdded(Activity activity) {\n        // TODO: once we have the ability to report fine-grained updates, do that here\n      }\n\n      @Override\n      public void onActivityRemoved(Activity activity) {\n        // TODO: once we have the ability to report fine-grained updates, do that here\n      }\n    };\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/DialogDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Dialog;\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.Window;\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.Descriptor;\n\nimport javax.annotation.Nullable;\n\nfinal class DialogDescriptor\n    extends AbstractChainedDescriptor<Dialog> implements HighlightableDescriptor<Dialog> {\n  @Override\n  protected void onGetChildren(Dialog element, Accumulator<Object> children) {\n    Window window = element.getWindow();\n    if (window != null) {\n      children.store(window);\n    }\n  }\n\n  @Nullable\n  @Override\n  public View getViewAndBoundsForHighlighting(Dialog element, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Window window = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      window = element.getWindow();\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(window);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getViewAndBoundsForHighlighting(window, bounds);\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(Dialog element, int x, int y, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Window window = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      window = element.getWindow();\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(window);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getElementToHighlightAtPosition(window, x, y, bounds);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/DialogFragmentDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Dialog;\nimport android.graphics.Rect;\nimport android.view.View;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.common.android.DialogFragmentAccessor;\nimport com.facebook.stetho.common.android.FragmentCompat;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.AttributeAccumulator;\nimport com.facebook.stetho.inspector.elements.ChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.ComputedStyleAccumulator;\nimport com.facebook.stetho.inspector.elements.Descriptor;\nimport com.facebook.stetho.inspector.elements.DescriptorMap;\nimport com.facebook.stetho.inspector.elements.NodeType;\nimport com.facebook.stetho.inspector.elements.StyleAccumulator;\nimport com.facebook.stetho.inspector.elements.StyleRuleNameAccumulator;\n\nimport javax.annotation.Nullable;\n\nfinal class DialogFragmentDescriptor\n    extends Descriptor<Object>\n    implements ChainedDescriptor<Object>, HighlightableDescriptor<Object> {\n  private final DialogFragmentAccessor mAccessor;\n  private Descriptor<? super Object> mSuper;\n\n  public static DescriptorMap register(DescriptorMap map) {\n    maybeRegister(map, FragmentCompat.getSupportLibInstance());\n    maybeRegister(map, FragmentCompat.getFrameworkInstance());\n    return map;\n  }\n\n  private static void maybeRegister(DescriptorMap map, @Nullable FragmentCompat compat) {\n    if (compat != null) {\n      Class<?> dialogFragmentClass = compat.getDialogFragmentClass();\n      LogUtil.d(\"Adding support for %s\", dialogFragmentClass);\n      map.registerDescriptor(dialogFragmentClass, new DialogFragmentDescriptor(compat));\n    }\n  }\n\n  private DialogFragmentDescriptor(FragmentCompat compat) {\n    mAccessor = compat.forDialogFragment();\n  }\n\n  @Override\n  public void setSuper(Descriptor<? super Object> superDescriptor) {\n    Util.throwIfNull(superDescriptor);\n\n    if (superDescriptor != mSuper) {\n      if (mSuper != null) {\n        throw new IllegalStateException();\n      }\n      mSuper = superDescriptor;\n    }\n  }\n\n  @Override\n  public void hook(Object element) {\n    mSuper.hook(element);\n  }\n\n  @Override\n  public void unhook(Object element) {\n    mSuper.unhook(element);\n  }\n\n  @Override\n  public NodeType getNodeType(Object element) {\n    return mSuper.getNodeType(element);\n  }\n\n  @Override\n  public String getNodeName(Object element) {\n    return mSuper.getNodeName(element);\n  }\n\n  @Override\n  public String getLocalName(Object element) {\n    return mSuper.getLocalName(element);\n  }\n\n  @Nullable\n  @Override\n  public String getNodeValue(Object element) {\n    return mSuper.getNodeValue(element);\n  }\n\n  @Override\n  public void getChildren(Object element, Accumulator<Object> children) {\n    /**\n     * We do NOT want the children from our super-{@link Descriptor}, which is probably\n     * {@link FragmentDescriptor}. We only want to emit the {@link Dialog}, not the {@link View}.\n     * Therefore, we don't call mSuper.getChildren(), and this is the reason why we don't derive\n     * from {@link AbstractChainedDescriptor} (it doesn't allow a non-chained implementation of\n     * {@link Descriptor#getChildren(Object, Accumulator)}).\n     */\n    children.store(mAccessor.getDialog(element));\n  }\n\n  @Override\n  public void getAttributes(Object element, AttributeAccumulator attributes) {\n    mSuper.getAttributes(element, attributes);\n  }\n\n  @Override\n  public void setAttributesAsText(Object element, String text) {\n    mSuper.setAttributesAsText(element, text);\n  }\n\n  @Nullable\n  @Override\n  public View getViewAndBoundsForHighlighting(Object element, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Dialog dialog = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      dialog = mAccessor.getDialog(element);\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(dialog);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getViewAndBoundsForHighlighting(dialog, bounds);\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(Object element, int x, int y, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    Dialog dialog = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      dialog = mAccessor.getDialog(element);\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(dialog);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getElementToHighlightAtPosition(dialog, x, y, bounds);\n  }\n\n  @Override\n  public void getStyleRuleNames(Object element, StyleRuleNameAccumulator accumulator) {\n  }\n\n  @Override\n  public void getStyles(Object element, String ruleName, StyleAccumulator accumulator) {\n  }\n\n  @Override\n  public void setStyle(Object element, String ruleName, String name, String value) {\n  }\n\n  @Override\n  public void getComputedStyles(Object element, ComputedStyleAccumulator styles) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/DocumentHiddenView.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.content.Context;\nimport android.view.View;\n\nclass DocumentHiddenView extends View {\n  public DocumentHiddenView(Context context) {\n    super(context);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/FragmentDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.graphics.Rect;\nimport android.view.View;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.android.FragmentAccessor;\nimport com.facebook.stetho.common.android.FragmentCompat;\nimport com.facebook.stetho.common.android.ResourcesUtil;\nimport com.facebook.stetho.inspector.elements.AttributeAccumulator;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.Descriptor;\nimport com.facebook.stetho.inspector.elements.DescriptorMap;\n\nimport javax.annotation.Nullable;\n\nfinal class FragmentDescriptor\n    extends AbstractChainedDescriptor<Object> implements HighlightableDescriptor<Object> {\n  private static final String ID_ATTRIBUTE_NAME = \"id\";\n  private static final String TAG_ATTRIBUTE_NAME = \"tag\";\n\n  private final FragmentAccessor mAccessor;\n\n  public static DescriptorMap register(DescriptorMap map) {\n    maybeRegister(map, FragmentCompat.getSupportLibInstance());\n    maybeRegister(map, FragmentCompat.getFrameworkInstance());\n    return map;\n  }\n\n  private static void maybeRegister(DescriptorMap map, @Nullable FragmentCompat compat) {\n    if (compat != null) {\n      Class<?> fragmentClass = compat.getFragmentClass();\n      LogUtil.d(\"Adding support for %s\", fragmentClass.getName());\n      map.registerDescriptor(fragmentClass, new FragmentDescriptor(compat));\n    }\n  }\n\n  private FragmentDescriptor(FragmentCompat compat) {\n    mAccessor = compat.forFragment();\n  }\n\n  @Override\n  protected void onGetAttributes(Object element, AttributeAccumulator attributes) {\n    int id = mAccessor.getId(element);\n    if (id != FragmentAccessor.NO_ID) {\n      String value = ResourcesUtil.getIdStringQuietly(\n          element,\n          mAccessor.getResources(element),\n          id);\n      attributes.store(ID_ATTRIBUTE_NAME, value);\n    }\n\n    String tag = mAccessor.getTag(element);\n    if (tag != null && tag.length() > 0) {\n      attributes.store(TAG_ATTRIBUTE_NAME, tag);\n    }\n  }\n\n  @Override\n  protected void onGetChildren(Object element, Accumulator<Object> children) {\n    View view = mAccessor.getView(element);\n    if (view != null) {\n      children.store(view);\n    }\n  }\n\n  @Override\n  @Nullable\n  public View getViewAndBoundsForHighlighting(Object element, Rect bounds) {\n    return mAccessor.getView(element);\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(Object element, int x, int y, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    View view = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      view = mAccessor.getView(element);\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(view);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getElementToHighlightAtPosition(view, x, y, bounds);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/HighlightableDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.graphics.Rect;\nimport android.view.View;\n\nimport javax.annotation.Nullable;\n\npublic interface HighlightableDescriptor<E> {\n\n  /**\n   * Return the {@link View} to highlight or null if this element cannot be highlighted.\n   * If the element does not span the full bounds of the returned {@link View} you can set\n   * the bounds of the passed in Rect. By default the passed in bounds are empty which means\n   * highlight the full bounds of the {@link View}.\n   */\n  @Nullable\n  View getViewAndBoundsForHighlighting(E element, Rect bounds);\n\n  /**\n   * Used when activating find by touch feature to figure out which element to focus / highlight.\n   *\n   * @param element the element\n   * @param y coordinate in local coordinate space\n   * @param y coordinate in local coordinate space\n   * @param bounds The bounds of the returned element. Used to offset the coordinates for next call.\n   * @return A child element or self if this coordinate falls within self and not a child.\n   */\n  @Nullable\n  Object getElementToHighlightAtPosition(E element, int x, int y, Rect bounds);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/MethodInvoker.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Tries to arbitrarily invoke single argument methods by name on an object instance by trying out\n * different argument types.\n */\npublic class MethodInvoker {\n\n  private static final List<TypedMethodInvoker<?>> invokers = Arrays.asList(\n      new StringMethodInvoker(), new CharSequenceMethodInvoker(), new IntegerMethodInvoker(),\n      new FloatMethodInvoker(), new BooleanMethodInvoker());\n\n  /**\n   * Tries to invoke a method on receiver with a single argument by trying out different types\n   * for arg until it finds one that matches (or not). No exceptions are thrown on failure.\n   *\n   * @param methodName The method name to be invoked\n   * @param argument The single argument to be provided to the method\n   */\n  public void invoke(Object receiver, String methodName, String argument) {\n    Util.throwIfNull(receiver, methodName, argument);\n    int size = invokers.size();\n    for (int i = 0; i < size; ++i) {\n      final TypedMethodInvoker<?> invoker = invokers.get(i);\n      if (invoker.invoke(receiver, methodName, argument)) {\n        return;\n      }\n    }\n    LogUtil.w(\"Method with name \" + methodName +\n              \" not found for any of the MethodInvoker supported argument types.\");\n  }\n\n  private static abstract class TypedMethodInvoker<T> {\n    private final Class<T> mArgType;\n\n    TypedMethodInvoker(Class<T> argType) {\n      mArgType = argType;\n    }\n\n    boolean invoke(Object receiver, String methodName, String argument) {\n      try {\n        Method method = receiver.getClass().getMethod(methodName, mArgType);\n        method.invoke(receiver, convertArgument(argument));\n        return true;\n      } catch (NoSuchMethodException ignored) {\n        // ignore\n      } catch (InvocationTargetException e) {\n        LogUtil.w(\"InvocationTargetException: \" + e.getMessage());\n      } catch (IllegalAccessException e) {\n        LogUtil.w(\"IllegalAccessException: \" + e.getMessage());\n      } catch (IllegalArgumentException e) {\n        LogUtil.w(\"IllegalArgumentException: \" + e.getMessage());\n      }\n      return false;\n    }\n\n    abstract T convertArgument(String argument);\n  }\n\n  private static class StringMethodInvoker extends TypedMethodInvoker<String> {\n    StringMethodInvoker() {\n      super(String.class);\n    }\n\n    @Override\n    String convertArgument(String argument) {\n      return argument;\n    }\n  }\n\n  private static class CharSequenceMethodInvoker extends TypedMethodInvoker<CharSequence> {\n    CharSequenceMethodInvoker() {\n      super(CharSequence.class);\n    }\n\n    @Override\n    CharSequence convertArgument(String argument) {\n      return argument;\n    }\n  }\n\n  private static class IntegerMethodInvoker extends TypedMethodInvoker<Integer> {\n    IntegerMethodInvoker() {\n      super(int.class);\n    }\n\n    @Override\n    Integer convertArgument(String argument) {\n      return Integer.parseInt(argument);\n    }\n  }\n\n  private static class FloatMethodInvoker extends TypedMethodInvoker<Float> {\n    FloatMethodInvoker() {\n      super(float.class);\n    }\n\n    @Override\n    Float convertArgument(String argument) {\n      return Float.parseFloat(argument);\n    }\n  }\n\n  private static class BooleanMethodInvoker extends TypedMethodInvoker<Boolean> {\n    BooleanMethodInvoker() {\n      super(boolean.class);\n    }\n\n    @Override\n    Boolean convertArgument(String argument) {\n      return Boolean.parseBoolean(argument);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/TextViewDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.text.Editable;\nimport android.text.TextWatcher;\nimport android.widget.TextView;\n\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.AttributeAccumulator;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\n\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\nfinal class TextViewDescriptor extends AbstractChainedDescriptor<TextView> {\n  private static final String TEXT_ATTRIBUTE_NAME = \"text\";\n\n  private final Map<TextView, ElementContext> mElementToContextMap =\n      Collections.synchronizedMap(new IdentityHashMap<TextView, ElementContext>());\n\n  @Override\n  protected void onHook(final TextView element) {\n    ElementContext context = new ElementContext();\n    context.hook(element);\n    mElementToContextMap.put(element, context);\n  }\n\n  protected void onUnhook(TextView element) {\n    ElementContext context = mElementToContextMap.remove(element);\n    context.unhook();\n  }\n\n  @Override\n  protected void onGetAttributes(TextView element, AttributeAccumulator attributes) {\n    CharSequence text = element.getText();\n    if (text != null && text.length() != 0) {\n      attributes.store(TEXT_ATTRIBUTE_NAME, text.toString());\n    }\n  }\n\n  private final class ElementContext implements TextWatcher {\n    private TextView mElement;\n\n    public void hook(TextView element) {\n      mElement = Util.throwIfNull(element);\n      mElement.addTextChangedListener(this);\n    }\n\n    public void unhook() {\n      if (mElement != null) {\n        mElement.removeTextChangedListener(this);\n        mElement = null;\n      }\n    }\n\n    @Override\n    public void beforeTextChanged(CharSequence s, int start, int count, int after) {\n    }\n\n    @Override\n    public void onTextChanged(CharSequence s, int start, int before, int count) {\n    }\n\n    @Override\n    public void afterTextChanged(Editable s) {\n      if (s.length() == 0) {\n        getHost().onAttributeRemoved(mElement, TEXT_ATTRIBUTE_NAME);\n      } else {\n        getHost().onAttributeModified(mElement, TEXT_ATTRIBUTE_NAME, s.toString());\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ViewDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.ViewDebug;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.ReflectionUtil;\nimport com.facebook.stetho.common.StringUtil;\nimport com.facebook.stetho.common.android.ResourcesUtil;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.AttributeAccumulator;\nimport com.facebook.stetho.inspector.elements.ComputedStyleAccumulator;\nimport com.facebook.stetho.inspector.elements.StyleAccumulator;\nimport com.facebook.stetho.inspector.elements.StyleRuleNameAccumulator;\nimport com.facebook.stetho.inspector.helper.IntegerFormatter;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\nfinal class ViewDescriptor extends AbstractChainedDescriptor<View>\n    implements HighlightableDescriptor<View> {\n  private static final String ID_NAME = \"id\";\n  private static final String NONE_VALUE = \"(none)\";\n  private static final String NONE_MAPPING = \"<no mapping>\";\n  private static final String VIEW_STYLE_RULE_NAME = \"<this_view>\";\n  private static final String ACCESSIBILITY_STYLE_RULE_NAME = \"Accessibility Properties\";\n\n  private final MethodInvoker mMethodInvoker;\n\n  private static final boolean sHasSupportNodeInfo;\n\n  static {\n    sHasSupportNodeInfo = ReflectionUtil.tryGetClassForName(\n        \"androidx.core.view.accessibility.AccessibilityNodeInfoCompat\") != null;\n  }\n\n  /**\n   * NOTE: Only access this via {@link #getWordBoundaryPattern}.\n   */\n  @Nullable\n  private Pattern mWordBoundaryPattern;\n\n  /**\n   * NOTE: Only access this via {@link #getViewProperties}.\n   */\n  @Nullable\n  @GuardedBy(\"this\")\n  private volatile List<ViewCSSProperty> mViewProperties;\n\n  private Pattern getWordBoundaryPattern() {\n    if (mWordBoundaryPattern == null) {\n      mWordBoundaryPattern = Pattern.compile(\"(?<=\\\\p{Lower})(?=\\\\p{Upper})\");\n    }\n\n    return mWordBoundaryPattern;\n  }\n\n  private List<ViewCSSProperty> getViewProperties() {\n    if (mViewProperties == null) {\n      synchronized (this) {\n        if (mViewProperties == null) {\n          List<ViewCSSProperty> props = new ArrayList<>();\n\n          for (final Method method : View.class.getDeclaredMethods()) {\n            ViewDebug.ExportedProperty annotation =\n                method.getAnnotation(\n                    ViewDebug.ExportedProperty.class);\n\n            if (annotation != null) {\n              props.add(new MethodBackedCSSProperty(\n                  method,\n                  convertViewPropertyNameToCSSName(method.getName()),\n                  annotation));\n            }\n          }\n\n          for (final Field field : View.class.getDeclaredFields()) {\n            ViewDebug.ExportedProperty annotation =\n                field.getAnnotation(\n                    ViewDebug.ExportedProperty.class);\n\n            if (annotation != null) {\n              props.add(new FieldBackedCSSProperty(\n                  field,\n                  convertViewPropertyNameToCSSName(field.getName()),\n                  annotation));\n            }\n          }\n\n          Collections.sort(props, new Comparator<ViewCSSProperty>() {\n            @Override\n            public int compare(ViewCSSProperty lhs, ViewCSSProperty rhs) {\n              return lhs.getCSSName().compareTo(rhs.getCSSName());\n            }\n          });\n          mViewProperties = Collections.unmodifiableList(props);\n        }\n      }\n    }\n\n    return mViewProperties;\n  }\n\n  public ViewDescriptor() {\n    this(new MethodInvoker());\n  }\n\n  public ViewDescriptor(MethodInvoker methodInvoker) {\n    mMethodInvoker = methodInvoker;\n  }\n\n  @Override\n  protected String onGetNodeName(View element) {\n    String className = element.getClass().getName();\n\n    return\n        StringUtil.removePrefix(className, \"android.view.\",\n        StringUtil.removePrefix(className, \"android.widget.\"));\n  }\n\n  @Override\n  protected void onGetAttributes(View element, AttributeAccumulator attributes) {\n    String id = getIdAttribute(element);\n    if (id != null) {\n      attributes.store(ID_NAME, id);\n    }\n  }\n\n  @Override\n  protected void onSetAttributesAsText(View element, String text) {\n    Map<String, String> attributeToValueMap = parseSetAttributesAsTextArg(text);\n    for (Map.Entry<String, String> entry : attributeToValueMap.entrySet()) {\n      String methodName = \"set\" + capitalize(entry.getKey());\n      String propertyValue = entry.getValue();\n      mMethodInvoker.invoke(element, methodName, propertyValue);\n    }\n  }\n\n  @Nullable\n  private static String getIdAttribute(View element) {\n    int id = element.getId();\n    if (id == View.NO_ID) {\n      return null;\n    }\n    return ResourcesUtil.getIdStringQuietly(element, element.getResources(), id);\n  }\n\n  @Override\n  @Nullable\n  public View getViewAndBoundsForHighlighting(View element, Rect bounds) {\n    return element;\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(View element, int x, int y, Rect bounds) {\n    bounds.set(0, 0, element.getWidth(), element.getHeight());\n    return element;\n  }\n\n  @Override\n  protected void onGetStyleRuleNames(View element, StyleRuleNameAccumulator accumulator) {\n    accumulator.store(VIEW_STYLE_RULE_NAME, false);\n    if (sHasSupportNodeInfo) {\n      accumulator.store(ACCESSIBILITY_STYLE_RULE_NAME, false);\n    }\n  }\n\n  @Override\n  protected void onGetStyles(View element, String ruleName, StyleAccumulator accumulator) {\n    if (VIEW_STYLE_RULE_NAME.equals(ruleName)) {\n      List<ViewCSSProperty> properties = getViewProperties();\n      for (int i = 0, size = properties.size(); i < size; i++) {\n        ViewCSSProperty property = properties.get(i);\n        try {\n          getStyleFromValue(\n              element,\n              property.getCSSName(),\n              property.getValue(element),\n              property.getAnnotation(),\n              accumulator);\n        } catch (Exception e) {\n          if (e instanceof IllegalAccessException || e instanceof InvocationTargetException) {\n            LogUtil.e(e, \"failed to get style property \" + property.getCSSName() +\n                    \" of element= \" + element.toString());\n          } else {\n            throw ExceptionUtil.propagate(e);\n          }\n        }\n      }\n    } else if (ACCESSIBILITY_STYLE_RULE_NAME.equals(ruleName)) {\n      if (sHasSupportNodeInfo) {\n        boolean ignored = AccessibilityNodeInfoWrapper.getIgnored(element);\n        getStyleFromValue(\n            element,\n            \"ignored\",\n            ignored,\n            null,\n            accumulator);\n\n        if (ignored) {\n          getStyleFromValue(\n              element,\n              \"ignored-reasons\",\n              AccessibilityNodeInfoWrapper.getIgnoredReasons(element),\n              null,\n              accumulator);\n        }\n\n        getStyleFromValue(\n            element,\n            \"focusable\",\n            !ignored,\n            null,\n            accumulator);\n\n        if (!ignored) {\n          getStyleFromValue(\n              element,\n              \"focusable-reasons\",\n              AccessibilityNodeInfoWrapper.getFocusableReasons(element),\n              null,\n              accumulator);\n\n          getStyleFromValue(\n              element,\n              \"focused\",\n              AccessibilityNodeInfoWrapper.getIsAccessibilityFocused(element),\n              null,\n              accumulator);\n\n          getStyleFromValue(\n              element,\n              \"description\",\n              AccessibilityNodeInfoWrapper.getDescription(element),\n              null,\n              accumulator);\n\n          getStyleFromValue(\n              element,\n              \"actions\",\n              AccessibilityNodeInfoWrapper.getActions(element),\n              null,\n              accumulator);\n        }\n      }\n    }\n  }\n\n  @Override\n  protected void onGetComputedStyles(View element, ComputedStyleAccumulator styles) {\n    styles.store(\"left\", Integer.toString(element.getLeft()));\n    styles.store(\"top\", Integer.toString(element.getTop()));\n    styles.store(\"right\", Integer.toString(element.getRight()));\n    styles.store(\"bottom\", Integer.toString(element.getBottom()));\n  }\n\n  private static boolean canIntBeMappedToString(@Nullable ViewDebug.ExportedProperty annotation) {\n    return annotation != null\n        && annotation.mapping() != null\n        && annotation.mapping().length > 0;\n  }\n\n  private static String mapIntToStringUsingAnnotation(\n      int value,\n      @Nullable ViewDebug.ExportedProperty annotation) {\n    if (!canIntBeMappedToString(annotation)) {\n      throw new IllegalStateException(\"Cannot map using this annotation\");\n    }\n\n    for (ViewDebug.IntToString map : annotation.mapping()) {\n      if (map.from() == value) {\n        return map.to();\n      }\n    }\n\n    // no mapping was found even though one was expected ):\n    return NONE_MAPPING;\n  }\n\n  private static boolean canFlagsBeMappedToString(@Nullable ViewDebug.ExportedProperty annotation) {\n    return annotation != null\n        && annotation.flagMapping() != null\n        && annotation.flagMapping().length > 0;\n  }\n\n  private static String mapFlagsToStringUsingAnnotation(\n      int value,\n      @Nullable ViewDebug.ExportedProperty annotation) {\n    if (!canFlagsBeMappedToString(annotation)) {\n      throw new IllegalStateException(\"Cannot map using this annotation\");\n    }\n\n    StringBuilder stringBuilder = null;\n    boolean atLeastOneFlag = false;\n\n    for (ViewDebug.FlagToString flagToString : annotation.flagMapping()) {\n      if (flagToString.outputIf() == ((value & flagToString.mask()) == flagToString.equals())) {\n        if (stringBuilder == null) {\n          stringBuilder = new StringBuilder();\n        }\n\n        if (atLeastOneFlag) {\n          stringBuilder.append(\" | \");\n        }\n\n        stringBuilder.append(flagToString.name());\n        atLeastOneFlag = true;\n      }\n    }\n\n    if (atLeastOneFlag) {\n      return stringBuilder.toString();\n    } else {\n      return NONE_MAPPING;\n    }\n  }\n\n  private String convertViewPropertyNameToCSSName(String getterName) {\n    // Split string by uppercase characters. Thankfully since\n    // this is the android source we don't have to worry about\n    // internationalization funk.\n\n    String[] words = getWordBoundaryPattern().split(getterName);\n\n    StringBuilder result = new StringBuilder();\n\n    for (int i = 0; i < words.length; i++) {\n      if (words[i].equals(\"get\") || words[i].equals(\"m\")) {\n        continue;\n      }\n\n      result.append(words[i].toLowerCase());\n\n      if (i < words.length - 1) {\n        result.append('-');\n      }\n    }\n\n    return result.toString();\n  }\n\n  private void getStyleFromValue(\n      View element,\n      String name,\n      Object value,\n      @Nullable ViewDebug.ExportedProperty annotation,\n      StyleAccumulator styles) {\n\n    if (name.equals(ID_NAME)) {\n      getIdStyle(element, styles);\n    } else if (value instanceof Integer) {\n      getStyleFromInteger(name, (Integer) value, annotation, styles);\n    } else if (value instanceof Float) {\n      styles.store(name, String.valueOf(value), ((Float) value) == 0.0f);\n    } else if (value instanceof Boolean) {\n      styles.store(name, String.valueOf(value), false);\n    } else if (value instanceof Short) {\n      styles.store(name, String.valueOf(value), ((Short) value) == 0);\n    } else if (value instanceof Long) {\n      styles.store(name, String.valueOf(value), ((Long) value) == 0);\n    } else if (value instanceof Double) {\n      styles.store(name, String.valueOf(value), ((Double) value) == 0.0d);\n    } else if (value instanceof Byte) {\n      styles.store(name, String.valueOf(value), ((Byte) value) == 0);\n    } else if (value instanceof Character) {\n      styles.store(name, String.valueOf(value), ((Character) value) == Character.MIN_VALUE);\n    } else if (value instanceof CharSequence) {\n      styles.store(name, String.valueOf(value), ((CharSequence) value).length() == 0);\n    } else {\n      getStylesFromObject(element, name, value, annotation, styles);\n    }\n  }\n\n  private void getIdStyle(\n      View element,\n      StyleAccumulator styles) {\n\n    @Nullable String id = getIdAttribute(element);\n\n    if (id == null) {\n      styles.store(ID_NAME, NONE_VALUE, false);\n    } else {\n      styles.store(ID_NAME, id, false);\n    }\n  }\n\n  private void getStyleFromInteger(\n      String name,\n      Integer value,\n      @Nullable ViewDebug.ExportedProperty annotation,\n      StyleAccumulator styles) {\n\n    String intValueStr = IntegerFormatter.getInstance().format(value, annotation);\n\n    if (canIntBeMappedToString(annotation)) {\n      styles.store(\n          name,\n          intValueStr + \" (\" + mapIntToStringUsingAnnotation(value, annotation) + \")\",\n          false);\n    } else if (canFlagsBeMappedToString(annotation)) {\n      styles.store(\n          name,\n          intValueStr + \" (\" + mapFlagsToStringUsingAnnotation(value, annotation) + \")\",\n          false);\n    } else {\n      Boolean defaultValue = true;\n      // Mappable ints should always be shown, because enums don't necessarily have\n      // logical \"default\" values. Thus we mark all of them as not default, so that they\n      // show up in the inspector.\n      if (value != 0 ||\n          canFlagsBeMappedToString(annotation) ||\n          canIntBeMappedToString(annotation)) {\n        defaultValue = false;\n      }\n      styles.store(name, intValueStr, defaultValue);\n    }\n  }\n\n  private void getStylesFromObject(\n      View view,\n      String name,\n      Object value,\n      @Nullable ViewDebug.ExportedProperty annotation,\n      StyleAccumulator styles) {\n    if (annotation == null || !annotation.deepExport() || value == null) {\n      return;\n    }\n\n    Field[] fields = value.getClass().getFields();\n\n    for (Field field : fields) {\n      int modifiers = field.getModifiers();\n      if (Modifier.isStatic(modifiers)) {\n        continue;\n      }\n\n      Object propertyValue;\n      try {\n          field.setAccessible(true);\n          propertyValue = field.get(value);\n      } catch (IllegalAccessException e) {\n        LogUtil.e(\n            e,\n            \"failed to get property of name: \\\"\" + name + \"\\\" of object: \" + String.valueOf(value));\n        return;\n      }\n\n      String propertyName = field.getName();\n\n      switch (propertyName) {\n        case \"bottomMargin\":\n          propertyName = \"margin-bottom\";\n          break;\n        case \"topMargin\":\n          propertyName = \"margin-top\";\n          break;\n        case \"leftMargin\":\n          propertyName = \"margin-left\";\n          break;\n        case \"rightMargin\":\n          propertyName = \"margin-right\";\n          break;\n        default:\n          String annotationPrefix = annotation.prefix();\n          propertyName = convertViewPropertyNameToCSSName(\n              (annotationPrefix == null) ? propertyName : (annotationPrefix + propertyName));\n          break;\n      }\n\n      ViewDebug.ExportedProperty subAnnotation =\n          field.getAnnotation(ViewDebug.ExportedProperty.class);\n\n      getStyleFromValue(\n          view,\n          propertyName,\n          propertyValue,\n          subAnnotation,\n          styles);\n    }\n  }\n\n  private static String capitalize(String str) {\n    if (str == null || str.length() == 0 || Character.isTitleCase(str.charAt(0))) {\n      return str;\n    }\n    StringBuilder buffer = new StringBuilder(str);\n    buffer.setCharAt(0, Character.toTitleCase(buffer.charAt(0)));\n    return buffer.toString();\n  }\n\n  private final class FieldBackedCSSProperty extends ViewCSSProperty {\n    private final Field mField;\n\n    public FieldBackedCSSProperty(\n        Field field,\n        String cssName,\n        @Nullable ViewDebug.ExportedProperty annotation) {\n      super(cssName, annotation);\n      mField = field;\n      mField.setAccessible(true);\n    }\n\n    @Override\n    public Object getValue(View view) throws InvocationTargetException, IllegalAccessException {\n      return mField.get(view);\n    }\n  }\n\n  private final class MethodBackedCSSProperty extends ViewCSSProperty {\n    private final Method mMethod;\n\n    public MethodBackedCSSProperty(\n        Method method,\n        String cssName,\n        @Nullable ViewDebug.ExportedProperty annotation) {\n      super(cssName, annotation);\n      mMethod = method;\n      mMethod.setAccessible(true);\n    }\n\n    @Override\n    public Object getValue(View view) throws InvocationTargetException, IllegalAccessException {\n      return mMethod.invoke(view);\n    }\n  }\n\n  private abstract class ViewCSSProperty {\n    private final String mCSSName;\n    private final ViewDebug.ExportedProperty mAnnotation;\n\n    public ViewCSSProperty(String cssName, @Nullable ViewDebug.ExportedProperty annotation) {\n      mCSSName = cssName;\n      mAnnotation = annotation;\n    }\n\n    public final String getCSSName() {\n      return mCSSName;\n    }\n\n    public abstract Object getValue(View view)\n        throws InvocationTargetException, IllegalAccessException;\n\n    public final @Nullable ViewDebug.ExportedProperty getAnnotation() {\n      return mAnnotation;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ViewGroupDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.ViewGroup;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.android.FragmentCompatUtil;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\n\nimport java.lang.ref.WeakReference;\nimport java.util.Collections;\nimport java.util.Map;\nimport java.util.WeakHashMap;\n\nimport javax.annotation.Nullable;\n\nfinal class ViewGroupDescriptor extends AbstractChainedDescriptor<ViewGroup>\n    implements HighlightableDescriptor<ViewGroup> {\n\n  /**\n   * This is a cache that maps from a View to the Fragment that contains it. If the View isn't\n   * contained by a Fragment, then this maps the View to itself. For Views contained by Fragments,\n   * we emit the Fragment instead, and then let the Fragment's descriptor emit the View as its sole\n   * child. This allows us to see Fragments in the inspector as part of the UI tree.\n   */\n  private final Map<View, Object> mViewToElementMap =\n      Collections.synchronizedMap(new WeakHashMap<View, Object>());\n\n  public ViewGroupDescriptor() {\n  }\n\n  @Override\n  protected void onGetChildren(ViewGroup element, Accumulator<Object> children) {\n    for (int i = 0, N = element.getChildCount(); i < N; ++i) {\n      final View childView = element.getChildAt(i);\n      if (isChildVisible(childView)) {\n        final Object childElement = getElementForView(element, childView);\n        children.store(childElement);\n      }\n    }\n  }\n\n  private boolean isChildVisible(View child) {\n    return !(child instanceof DocumentHiddenView);\n  }\n\n  private Object getElementForView(ViewGroup parentView, View childView) {\n    Object value = mViewToElementMap.get(childView);\n    if (value != null) {\n      Object element = getElement(childView, value);\n\n      // The parent of a View may have changed since we stashed it into the cache.\n      // If that's the case then we can't use the cache's answer.\n      if (element != null && childView.getParent() == parentView) {\n        return element;\n      }\n      mViewToElementMap.remove(childView);\n    }\n\n    /**\n     * Note that we do NOT emit DialogFragments. Those get emitted via ActivityDescriptor.\n     * We do the check here so that we can also cache the cost of calling\n     * {@link FragmentCompatUtil#isDialogFragment(Object)}.\n     */\n\n    Object fragment = FragmentCompatUtil.findFragmentForView(childView);\n    if (fragment != null && !FragmentCompatUtil.isDialogFragment(fragment)) {\n      mViewToElementMap.put(childView, new WeakReference<>(fragment));\n      return fragment;\n    } else {\n      // No need to store a strong reference to the childView in the value. We'll just store this\n      // object and when pull the value out of the map we'll check for this object and just use the\n      // key instead.\n      mViewToElementMap.put(childView, this);\n      return childView;\n    }\n  }\n\n  @SuppressWarnings(\"unchecked\")\n  private Object getElement(View childView, Object value) {\n    if (value == this) {\n      return childView;\n    } else {\n      return ((WeakReference<Object>) value).get();\n    }\n  }\n\n  @Override\n  @Nullable\n  public View getViewAndBoundsForHighlighting(ViewGroup element, Rect bounds) {\n    return element;\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(ViewGroup element, int x, int y, Rect bounds) {\n    View hitChild = null;\n    for (int i = element.getChildCount() - 1; i >= 0; --i) {\n      final View childView = element.getChildAt(i);\n      if (isChildVisible(childView) &&\n          childView.getVisibility() == View.VISIBLE) {\n        childView.getHitRect(bounds);\n        if (bounds.contains(x, y)) {\n          hitChild = childView;\n          break;\n        }\n      }\n    }\n\n    if (hitChild != null) {\n      return hitChild;\n    } else {\n      bounds.set(0, 0, element.getWidth(), element.getHeight());\n      return element;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ViewHighlightOverlays.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Canvas;\nimport android.graphics.Rect;\nimport android.graphics.Region;\nimport android.graphics.drawable.ColorDrawable;\nimport android.os.Build;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.view.ViewGroup.MarginLayoutParams;\n\nabstract class ViewHighlightOverlays {\n\n  abstract void highlightView(View view, Rect bounds, int mainColor);\n\n  abstract void removeHighlight(View view);\n\n  static ViewHighlightOverlays newInstance() {\n    // This may not be needed since ViewHighlighter.newInstance() is already instantiating a\n    // NoopHighlighter for SDK_INT < JELLY_BEAN_MR2, but just to make sure...\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n      return new ViewHighlightOverlaysJellybeanMR2();\n    }\n    return new NoOpViewHighlightOverlays();\n  }\n\n  private static class NoOpViewHighlightOverlays extends ViewHighlightOverlays {\n\n    @Override\n    void highlightView(View view, Rect bounds, int mainColor) {\n    }\n\n    @Override\n    void removeHighlight(View view) {\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n  private static class ViewHighlightOverlaysJellybeanMR2 extends ViewHighlightOverlays {\n    private static final int MARGIN_OVERLAY_COLOR = 0xaaf4ca9e;\n    private static final int PADDING_OVERLAY_COLOR = 0xaabedab6;\n    private final MainHighlightDrawable mMainHighlightDrawable = new MainHighlightDrawable();\n    private final HighlightDrawable[] mHighlightDrawables = {\n        mMainHighlightDrawable,\n        new PaddingTopHighlightDrawable(),\n        new PaddingBottomHighlightDrawable(),\n        new PaddingRightHighlightDrawable(),\n        new PaddingLeftHighlightDrawable(),\n        new MarginTopHighlightDrawable(),\n        new MarginBottomHighlightDrawable(),\n        new MarginRightHighlightDrawable(),\n        new MarginLeftHighlightDrawable()\n    };\n\n    ViewHighlightOverlaysJellybeanMR2() {\n    }\n\n    @Override\n    void highlightView(View view, Rect bounds, int mainColor) {\n      mMainHighlightDrawable.setColor(mainColor);\n\n      if (bounds.isEmpty()) {\n        mMainHighlightDrawable.setBounds(0, 0, view.getWidth(), view.getHeight());\n      } else {\n        mMainHighlightDrawable.setBounds(bounds);\n      }\n\n      int total = mHighlightDrawables.length;\n      for (int i = 0; i < total; i++) {\n        HighlightDrawable drawable = mHighlightDrawables[i];\n        drawable.highlightView(view);\n        view.getOverlay().add(drawable);\n      }\n    }\n\n    @Override\n    void removeHighlight(View view) {\n      for (ColorDrawable drawable : mHighlightDrawables) {\n        view.getOverlay().remove(drawable);\n      }\n    }\n\n    static abstract class HighlightDrawable extends ColorDrawable {\n\n      protected final Rect mMargins = new Rect();\n      protected final Rect mPaddings = new Rect();\n\n      HighlightDrawable(int color) {\n        super(color);\n      }\n\n      public HighlightDrawable() {\n      }\n\n      void highlightView(View view) {\n        ViewGroup.LayoutParams layoutParams = view.getLayoutParams();\n        if (layoutParams instanceof MarginLayoutParams) {\n          MarginLayoutParams marginLayoutParams = (MarginLayoutParams) layoutParams;\n          mMargins.left = marginLayoutParams.leftMargin;\n          mMargins.top = marginLayoutParams.topMargin;\n          mMargins.right = marginLayoutParams.rightMargin;\n          mMargins.bottom = marginLayoutParams.bottomMargin;\n        } else {\n          mMargins.left = 0;\n          mMargins.top = 0;\n          mMargins.right = 0;\n          mMargins.bottom = 0;\n        }\n        mPaddings.left = view.getPaddingLeft();\n        mPaddings.top = view.getPaddingTop();\n        mPaddings.right = view.getPaddingRight();\n        mPaddings.bottom = view.getPaddingBottom();\n      }\n    }\n\n    static class MainHighlightDrawable extends HighlightDrawable {\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n      }\n\n      @Override\n      public void draw(Canvas canvas) {\n        // We don't have access to the OverlayViewGroup instance directly, but we can manipulate\n        // its Canvas via the Drawables' draw(). This allows us to draw outside the View bounds,\n        // so we can position the margin overlays correctly.\n        Rect newRect = canvas.getClipBounds();\n        // Make the Canvas Rect bigger according to the View margins.\n        newRect.inset(-(mMargins.right + mMargins.left), -(mMargins.top + mMargins.bottom));\n\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {\n          canvas.clipRect(newRect, Region.Op.REPLACE);\n        } else {\n          canvas.clipOutRect(newRect);\n        }\n        super.draw(canvas);\n      }\n    }\n\n    static class PaddingTopHighlightDrawable extends HighlightDrawable {\n      PaddingTopHighlightDrawable() {\n        super(PADDING_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(mPaddings.left, 0, view.getWidth() - mPaddings.right, mPaddings.top);\n      }\n    }\n\n    static class PaddingBottomHighlightDrawable extends HighlightDrawable {\n      PaddingBottomHighlightDrawable() {\n        super(PADDING_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(mPaddings.left, view.getHeight() - mPaddings.bottom,\n            view.getWidth() - mPaddings.right, view.getHeight());\n      }\n    }\n\n    static class PaddingRightHighlightDrawable extends HighlightDrawable {\n      PaddingRightHighlightDrawable() {\n        super(PADDING_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(view.getWidth() - mPaddings.right, 0, view.getWidth(), view.getHeight());\n      }\n    }\n\n    static class PaddingLeftHighlightDrawable extends HighlightDrawable {\n      PaddingLeftHighlightDrawable() {\n        super(PADDING_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(0, 0, mPaddings.left, view.getHeight());\n      }\n    }\n\n    static class MarginTopHighlightDrawable extends HighlightDrawable {\n\n      MarginTopHighlightDrawable() {\n        super(MARGIN_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(0, 0, view.getWidth(), mMargins.top);\n      }\n\n      @Override\n      public void draw(Canvas canvas) {\n        canvas.translate(0, -mMargins.top);\n        super.draw(canvas);\n      }\n    }\n\n    static class MarginBottomHighlightDrawable extends HighlightDrawable {\n\n      MarginBottomHighlightDrawable() {\n        super(MARGIN_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(0, view.getHeight() - mMargins.bottom, view.getWidth(), view.getHeight());\n      }\n\n      @Override\n      public void draw(Canvas canvas) {\n        canvas.translate(0, mMargins.bottom + mMargins.top);\n        super.draw(canvas);\n      }\n    }\n\n    static class MarginRightHighlightDrawable extends HighlightDrawable {\n\n      MarginRightHighlightDrawable() {\n        super(MARGIN_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(view.getWidth() - mMargins.right, 0, view.getWidth(),\n            view.getHeight() + mMargins.top + mMargins.bottom);\n\n      }\n\n      @Override\n      public void draw(Canvas canvas) {\n        canvas.translate(mMargins.right, -(mMargins.top + mMargins.bottom));\n        super.draw(canvas);\n      }\n    }\n\n    static class MarginLeftHighlightDrawable extends HighlightDrawable {\n\n      MarginLeftHighlightDrawable() {\n        super(MARGIN_OVERLAY_COLOR);\n      }\n\n      @Override\n      void highlightView(View view) {\n        super.highlightView(view);\n        setBounds(0, 0, mMargins.left, view.getHeight() + mMargins.top + mMargins.bottom);\n      }\n\n      @Override\n      public void draw(Canvas canvas) {\n        canvas.translate(-(mMargins.left + mMargins.right), 0);\n        super.draw(canvas);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/ViewHighlighter.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.annotation.TargetApi;\nimport android.graphics.Rect;\nimport android.os.Build;\nimport android.os.Handler;\nimport android.os.Looper;\nimport android.view.View;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\n\nimport javax.annotation.Nullable;\nimport java.util.concurrent.atomic.AtomicInteger;\nimport java.util.concurrent.atomic.AtomicReference;\n\nabstract class ViewHighlighter {\n\n  public static ViewHighlighter newInstance() {\n    // TODO: find ways to do highlighting on older versions too\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {\n      return new OverlayHighlighter();\n    } else {\n      LogUtil.w(\"Running on pre-JBMR2: View highlighting is not supported\");\n      return new NoopHighlighter();\n    }\n  }\n\n  protected ViewHighlighter() {\n  }\n\n  public abstract void clearHighlight();\n\n  public abstract void setHighlightedView(View view, @Nullable Rect bounds, int color);\n\n  private static final class NoopHighlighter extends ViewHighlighter {\n    @Override\n    public void clearHighlight() {\n    }\n\n    @Override\n    public void setHighlightedView(View view, @Nullable Rect bounds, int color) {\n    }\n  }\n\n  @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)\n  private static final class OverlayHighlighter extends ViewHighlighter {\n    // TODO: use the top-level ViewGroupOverlay instead of ViewOverlay so that we don't end up\n    //       causing every single view to allocate a ViewOverlay\n\n    private final Handler mHandler;\n    private final ViewHighlightOverlays mHighlightOverlays = ViewHighlightOverlays.newInstance();\n\n    // Only assigned on the UI thread\n    private View mHighlightedView;\n    private final Rect mHighlightedBounds = new Rect();\n    private final Rect mEmptyRect = new Rect();\n\n    private AtomicReference<View> mViewToHighlight = new AtomicReference<View>();\n    private AtomicReference<Rect> mBoundsToHighlight = new AtomicReference<Rect>();\n    private AtomicInteger mContentColor = new AtomicInteger();\n\n    private final Runnable mHighlightViewOnUiThreadRunnable = new Runnable() {\n      @Override\n      public void run() {\n        highlightViewOnUiThread();\n      }\n    };\n\n    public OverlayHighlighter() {\n      mHandler = new Handler(Looper.getMainLooper());\n    }\n\n    @Override\n    public void clearHighlight() {\n      setHighlightedViewImpl(null, null, 0);\n    }\n\n    @Override\n    public void setHighlightedView(View view, @Nullable Rect bounds, int color) {\n      setHighlightedViewImpl(Util.throwIfNull(view), bounds, color);\n    }\n\n    private void setHighlightedViewImpl(@Nullable View view, @Nullable Rect bounds, int color) {\n      mHandler.removeCallbacks(mHighlightViewOnUiThreadRunnable);\n      mViewToHighlight.set(view);\n      mBoundsToHighlight.set(bounds);\n      mContentColor.set(color);\n      mHandler.postDelayed(mHighlightViewOnUiThreadRunnable, 100);\n    }\n\n    private void highlightViewOnUiThread() {\n      final View viewToHighlight = mViewToHighlight.getAndSet(null);\n      Rect boundsToHighlight = mBoundsToHighlight.getAndSet(null);\n      if (boundsToHighlight == null) {\n        boundsToHighlight = mEmptyRect;\n      }\n\n      if (viewToHighlight == mHighlightedView && mHighlightedBounds.equals(boundsToHighlight)) {\n        return;\n      }\n\n      if (mHighlightedView != null) {\n        mHighlightOverlays.removeHighlight(mHighlightedView);\n      }\n\n      if (viewToHighlight != null) {\n        mHighlightOverlays.highlightView(\n            viewToHighlight,\n            boundsToHighlight,\n            mContentColor.get());\n      }\n\n      mHighlightedView = viewToHighlight;\n\n      if (boundsToHighlight == null) {\n        mHighlightedBounds.setEmpty();\n      } else {\n        mHighlightedBounds.set(boundsToHighlight);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/WindowDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.graphics.Rect;\nimport android.view.View;\nimport android.view.Window;\n\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.inspector.elements.AbstractChainedDescriptor;\nimport com.facebook.stetho.inspector.elements.Descriptor;\n\nimport javax.annotation.Nullable;\n\nfinal class WindowDescriptor extends AbstractChainedDescriptor<Window>\n    implements HighlightableDescriptor<Window> {\n  @Override\n  protected void onGetChildren(Window element, Accumulator<Object> children) {\n    View decorView = element.peekDecorView();\n    if (decorView != null) {\n      children.store(decorView);\n    }\n  }\n\n  @Override\n  @Nullable\n  public View getViewAndBoundsForHighlighting(Window element, Rect bounds) {\n    return element.peekDecorView();\n  }\n\n  @Nullable\n  @Override\n  public Object getElementToHighlightAtPosition(Window element, int x, int y, Rect bounds) {\n    final Descriptor.Host host = getHost();\n    View view = null;\n    HighlightableDescriptor descriptor = null;\n\n    if (host instanceof AndroidDescriptorHost) {\n      view = element.peekDecorView();\n      descriptor = ((AndroidDescriptorHost) host).getHighlightableDescriptor(view);\n    }\n\n    return descriptor == null\n        ? null\n        : descriptor.getElementToHighlightAtPosition(view, x, y, bounds);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/window/WindowRootViewCompactV16Impl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android.window;\n\nimport android.content.Context;\nimport android.view.View;\nimport android.view.WindowManager;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport androidx.annotation.NonNull;\n\nclass WindowRootViewCompactV16Impl extends WindowRootViewCompat {\n  private Context mContext;\n\n  WindowRootViewCompactV16Impl(Context context) {\n    this.mContext = context;\n  }\n\n  @NonNull\n  @Override\n  public List<View> getRootViews() {\n    WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);\n    Object wm = getOuter(windowManager);\n    return getWindowViews(wm);\n  }\n\n  private static Object getOuter(Object innerWM) {\n    try {\n      Field parentField = innerWM.getClass().getDeclaredField(\"mWindowManager\");\n      parentField.setAccessible(true);\n      Object outerWM = parentField.get(innerWM);\n      parentField.setAccessible(false);\n      return outerWM;\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  private static List<View> getWindowViews(final Object windowManager) {\n    try {\n      Class clz = windowManager.getClass();\n      Field field = clz.getDeclaredField(\"mViews\");\n      field.setAccessible(true);\n      return Collections.unmodifiableList(Arrays.asList((View[]) field.get(windowManager)));\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/window/WindowRootViewCompactV18Impl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android.window;\n\nimport android.view.View;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport androidx.annotation.NonNull;\n\nclass WindowRootViewCompactV18Impl extends WindowRootViewCompat {\n\n  private Field mViewsField;\n  private Object mWindowManagerGlobal;\n\n  WindowRootViewCompactV18Impl() {\n    try {\n      Class wmClz = Class.forName(\"android.view.WindowManagerGlobal\");\n      Method getInstanceMethod = wmClz.getDeclaredMethod(\"getInstance\");\n      mWindowManagerGlobal = getInstanceMethod.invoke(wmClz);\n      mViewsField = wmClz.getDeclaredField(\"mViews\");\n      mViewsField.setAccessible(true);\n    } catch (ClassNotFoundException e) {\n      throw new RuntimeException(e);\n    } catch (NoSuchMethodException e) {\n      throw new RuntimeException(e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    } catch (InvocationTargetException e) {\n      throw new RuntimeException(e);\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(e);\n    }\n\n  }\n\n  @NonNull\n  @Override\n  public List<View> getRootViews() {\n    try {\n      return Collections.unmodifiableList(Arrays.asList((View[]) mViewsField.get(mWindowManagerGlobal)));\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/window/WindowRootViewCompactV19Impl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android.window;\n\nimport android.view.View;\n\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.util.Collections;\nimport java.util.List;\n\nimport androidx.annotation.NonNull;\n\nclass WindowRootViewCompactV19Impl extends WindowRootViewCompat {\n\n  private List<View> mRootViews;\n\n  WindowRootViewCompactV19Impl() {\n    try {\n      Class wmClz = Class.forName(\"android.view.WindowManagerGlobal\");\n      Method getInstanceMethod = wmClz.getDeclaredMethod(\"getInstance\");\n      Object managerGlobal = getInstanceMethod.invoke(wmClz);\n      Field mViewsField = wmClz.getDeclaredField(\"mViews\");\n      mViewsField.setAccessible(true);\n      mRootViews = (List<View>) mViewsField.get(managerGlobal);\n      mViewsField.setAccessible(false);\n    } catch (ClassNotFoundException e) {\n      throw new RuntimeException(e);\n    } catch (NoSuchMethodException e) {\n      throw new RuntimeException(e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    } catch (InvocationTargetException e) {\n      throw new RuntimeException(e);\n    } catch (NoSuchFieldException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @NonNull\n  @Override\n  public List<View> getRootViews() {\n    return Collections.unmodifiableList(mRootViews);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/elements/android/window/WindowRootViewCompat.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android.window;\n\nimport android.content.Context;\nimport android.os.Build;\nimport android.view.View;\n\nimport com.facebook.stetho.common.Util;\n\nimport java.util.List;\n\nimport androidx.annotation.NonNull;\n\n/**\n * get the root view of all windows\n * <p>\n * when you add view by windowManager.addView(), the root view may not be a DecorView\n * <p>\n * <p>\n * There are some differences when you want to get the root view\n * <p>\n * 4.0.3_r1    WindowManagerImpl      private View[] mViews;\n * <p>\n * 4.0.4       WindowManagerImpl      private View[] mViews;\n * <p>\n * 4.1.1       WindowManagerImpl      private View[] mViews;\n * <p>\n * 4.1.2       WindowManagerImpl      private View[] mViews;\n * <p>\n * 4.2_r1      WindowManagerGlobal    private View[] mViews\n * <p>\n * 4.2.2 r1    WindowManagerGlobal    private View[] mViews\n * <p>\n * 4.3_r2.1    WindowManagerGlobal    private View[] mViews;\n * <p>\n * 4.4_r1      WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n * <p>\n * 4.4.2_r1    WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n * <p>\n * 5.0.0_r2    WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n * <p>\n * 6.0.0_r1    WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n * <p>\n * 7.0.0_r1    WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n * <p>\n * 8.0.0_r4    WindowManagerGlobal    private final ArrayList&lt;View&gt; mViews\n */\npublic abstract class WindowRootViewCompat {\n\n  private static WindowRootViewCompat sInstance;\n\n  public static WindowRootViewCompat get(Context context) {\n    if (sInstance != null) {\n      return sInstance;\n    }\n\n    Util.throwIfNull(context);\n\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {\n      sInstance = new WindowRootViewCompactV19Impl();\n    } else if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR1\n      || Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN_MR2) {\n      sInstance = new WindowRootViewCompactV18Impl();\n    } else {\n      sInstance = new WindowRootViewCompactV16Impl(context.getApplicationContext());\n    }\n    return sInstance;\n  }\n\n  @NonNull\n  public abstract List<View> getRootViews();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/ChromePeerManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\nimport java.nio.channels.NotYetConnectedException;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport com.facebook.stetho.common.LogRedirector;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.jsonrpc.DisconnectReceiver;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.PendingRequestCallback;\n\n/**\n * Interface glue that allows a particular domain to manage the enabled peers.  The way the\n * WebKit inspector protocol works is that each functionality domain has an enable/disable JSON-RPC\n * method call which alerts the server (that's us) that we can now begin sending local events\n * to the peer to have them appear in the inspector UI.  This class simplifies managing those\n * enabled peers for each functionality domain.\n */\npublic class ChromePeerManager {\n  private static final String TAG = \"ChromePeerManager\";\n\n  /**\n   * Set of registered peers, mapped to the disconnect receiver for automatic unregistration\n   * purposes.\n   */\n  @GuardedBy(\"this\")\n  private final Map<JsonRpcPeer, DisconnectReceiver> mReceivingPeers = new HashMap<>();\n\n  /**\n   * This should be set to null anytime mReceivingPeers is changed. It should always be\n   * retrieved by calling getReceivingPeersSnapshot().\n   */\n  @GuardedBy(\"this\")\n  private JsonRpcPeer[] mReceivingPeersSnapshot;\n\n  @GuardedBy(\"this\")\n  private PeerRegistrationListener mListener;\n\n  public ChromePeerManager() {\n  }\n\n  /**\n   * Set a listener which can receive notifications of unique registration event (see\n   * {@link #addPeer} and {@link #removePeer}).\n   *\n   * @param listener\n   */\n  public synchronized void setListener(PeerRegistrationListener listener) {\n    mListener = listener;\n  }\n\n  /**\n   * Register a new peer, adding them to an internal list of receivers.\n   *\n   * @param peer\n   * @return True if this is a newly registered peer; false if it was already registered.\n   */\n  public synchronized boolean addPeer(JsonRpcPeer peer) {\n    if (mReceivingPeers.containsKey(peer)) {\n      return false;\n    }\n\n    DisconnectReceiver disconnectReceiver = new UnregisterOnDisconnect(peer);\n    peer.registerDisconnectReceiver(disconnectReceiver);\n    mReceivingPeers.put(peer, disconnectReceiver);\n    mReceivingPeersSnapshot = null;\n    if (mListener != null) {\n      mListener.onPeerRegistered(peer);\n    }\n    return true;\n  }\n\n  /**\n   * Unregister an existing peer.\n   *\n   * @param peer\n   */\n  public synchronized void removePeer(JsonRpcPeer peer) {\n    if (mReceivingPeers.remove(peer) != null) {\n      mReceivingPeersSnapshot = null;\n      if (mListener != null) {\n        mListener.onPeerUnregistered(peer);\n      }\n    }\n  }\n\n  public synchronized boolean hasRegisteredPeers() {\n    return !mReceivingPeers.isEmpty();\n  }\n\n  private synchronized JsonRpcPeer[] getReceivingPeersSnapshot() {\n    if (mReceivingPeersSnapshot == null) {\n      mReceivingPeersSnapshot = mReceivingPeers.keySet().toArray(\n          new JsonRpcPeer[mReceivingPeers.size()]);\n    }\n    return mReceivingPeersSnapshot;\n  }\n\n  public void sendNotificationToPeers(String method,\n      Object params) {\n    sendMessageToPeers(method, params, null /* callback */);\n  }\n\n  public void invokeMethodOnPeers(String method,\n      Object params,\n      PendingRequestCallback callback) {\n    Util.throwIfNull(callback);\n    sendMessageToPeers(method, params, callback);\n  }\n\n  private void sendMessageToPeers(String method,\n      Object params,\n      @Nullable PendingRequestCallback callback) {\n    JsonRpcPeer[] peers = getReceivingPeersSnapshot();\n    for (JsonRpcPeer peer : peers) {\n      try {\n        peer.invokeMethod(method, params, callback);\n      } catch (NotYetConnectedException e) {\n        LogRedirector.e(TAG, \"Error delivering data to Chrome\", e);\n      }\n    }\n  }\n\n  private class UnregisterOnDisconnect implements DisconnectReceiver {\n    private final JsonRpcPeer mPeer;\n\n    public UnregisterOnDisconnect(JsonRpcPeer peer) {\n      mPeer = peer;\n    }\n\n    @Override\n    public void onDisconnect() {\n      removePeer(mPeer);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/IntegerFormatter.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport android.annotation.TargetApi;\nimport android.os.Build;\nimport android.view.ViewDebug;\n\nimport androidx.annotation.Nullable;\n\npublic class IntegerFormatter {\n  private static IntegerFormatter cachedFormatter;\n\n  public static IntegerFormatter getInstance() {\n    if (cachedFormatter == null) {\n      synchronized (IntegerFormatter.class) {\n        if (cachedFormatter == null) {\n          if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {\n            cachedFormatter = new IntegerFormatterWithHex();\n          } else {\n            cachedFormatter = new IntegerFormatter();\n          }\n        }\n      }\n    }\n\n    return cachedFormatter;\n  }\n\n  private IntegerFormatter() {\n  }\n\n  public String format(Integer integer, @Nullable ViewDebug.ExportedProperty annotation) {\n    return String.valueOf(integer);\n  }\n\n  private static class IntegerFormatterWithHex extends IntegerFormatter {\n    @Override\n    @TargetApi(Build.VERSION_CODES.LOLLIPOP)\n    public String format(Integer integer, @Nullable ViewDebug.ExportedProperty annotation) {\n      if (annotation != null && annotation.formatToHexString()) {\n        return \"0x\" + Integer.toHexString(integer);\n      }\n\n      return super.format(integer, annotation);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/ObjectIdMapper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport android.util.SparseArray;\n\nimport java.util.IdentityHashMap;\nimport java.util.Map;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\npublic class ObjectIdMapper {\n  protected final Object mSync = new Object();\n\n  @GuardedBy(\"mSync\")\n  private int mNextId = 1;\n\n  @GuardedBy(\"mSync\")\n  private final Map<Object, Integer> mObjectToIdMap = new IdentityHashMap<Object, Integer>();\n\n  @GuardedBy(\"mSync\")\n  private SparseArray<Object> mIdToObjectMap = new SparseArray<Object>();\n\n  public void clear() {\n    SparseArray<Object> idToObjectMap;\n    synchronized (mSync) {\n      idToObjectMap = mIdToObjectMap;\n      mObjectToIdMap.clear();\n      mIdToObjectMap = new SparseArray<Object>();\n    }\n\n    int size = idToObjectMap.size();\n    for (int i = 0; i < size; ++i) {\n      int id = idToObjectMap.keyAt(i);\n      Object object = idToObjectMap.valueAt(i);\n      onUnmapped(object, id);\n    }\n  }\n\n  public boolean containsId(int id) {\n    synchronized (mSync) {\n      return mIdToObjectMap.get(id) != null;\n    }\n  }\n\n  public boolean containsObject(Object object) {\n    synchronized (mSync) {\n      return mObjectToIdMap.containsKey(object);\n    }\n  }\n\n  @Nullable\n  public Object getObjectForId(int id) {\n    synchronized (mSync) {\n      return mIdToObjectMap.get(id);\n    }\n  }\n\n  @Nullable\n  public Integer getIdForObject(Object object) {\n    synchronized (mSync) {\n      return mObjectToIdMap.get(object);\n    }\n  }\n\n  public int putObject(Object object) {\n    Integer id;\n\n    synchronized (mSync) {\n      id = mObjectToIdMap.get(object);\n      if (id != null) {\n        return id;\n      }\n\n      id = mNextId++;\n      mObjectToIdMap.put(object, id);\n      mIdToObjectMap.put(id, object);\n    }\n\n    onMapped(object, id);\n    return id;\n  }\n\n  @Nullable\n  public Object removeObjectById(int id) {\n    Object object;\n\n    synchronized (mSync) {\n      object = mIdToObjectMap.get(id);\n      if (object == null) {\n        return null;\n      }\n\n      mIdToObjectMap.remove(id);\n      mObjectToIdMap.remove(object);\n    }\n\n    onUnmapped(object, id);\n    return object;\n  }\n\n  @Nullable\n  public Integer removeObject(Object object) {\n    Integer id;\n\n    synchronized (mSync) {\n      id = mObjectToIdMap.remove(object);\n      if (id == null) {\n        return null;\n      }\n\n      mIdToObjectMap.remove(id);\n    }\n\n    onUnmapped(object, id);\n    return id;\n  }\n\n  public int size() {\n    synchronized (mSync) {\n      return mObjectToIdMap.size();\n    }\n  }\n\n  protected void onMapped(Object object, int id) {\n  }\n\n  protected void onUnmapped(Object object, int id) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/PeerRegistrationListener.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\n\npublic interface PeerRegistrationListener {\n  void onPeerRegistered(JsonRpcPeer peer);\n  void onPeerUnregistered(JsonRpcPeer peer);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/PeersRegisteredListener.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport java.util.concurrent.atomic.AtomicInteger;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\n\npublic abstract class PeersRegisteredListener implements PeerRegistrationListener {\n  private AtomicInteger mPeers = new AtomicInteger(0);\n\n  @Override\n  public final void onPeerRegistered(JsonRpcPeer peer) {\n    if (mPeers.incrementAndGet() == 1) {\n      onFirstPeerRegistered();\n    }\n    onPeerAdded(peer);\n  }\n\n  @Override\n  public final void onPeerUnregistered(JsonRpcPeer peer) {\n    if (mPeers.decrementAndGet() == 0) {\n      onLastPeerUnregistered();\n    }\n    onPeerRemoved(peer);\n  }\n\n  protected void onPeerAdded(JsonRpcPeer peer) {}\n  protected void onPeerRemoved(JsonRpcPeer peer) {}\n\n  protected abstract void onFirstPeerRegistered();\n  protected abstract void onLastPeerUnregistered();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/helper/ThreadBoundProxy.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.helper;\n\nimport android.os.Handler;\nimport com.facebook.stetho.common.ThreadBound;\nimport com.facebook.stetho.common.UncheckedCallable;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.common.android.HandlerUtil;\n\n/**\n * This class is for those cases when a class' threading\n * policy is determined by one of its member variables.\n */\npublic abstract class ThreadBoundProxy implements ThreadBound {\n  private final ThreadBound mEnforcer;\n\n  public ThreadBoundProxy(ThreadBound enforcer) {\n    mEnforcer = Util.throwIfNull(enforcer);\n  }\n\n  @Override\n  public final boolean checkThreadAccess() {\n    return mEnforcer.checkThreadAccess();\n  }\n\n  @Override\n  public final void verifyThreadAccess() {\n    mEnforcer.verifyThreadAccess();\n  }\n\n  @Override\n  public final <V> V postAndWait(UncheckedCallable<V> c) {\n    return mEnforcer.postAndWait(c);\n  }\n\n  @Override\n  public final void postAndWait(Runnable r) {\n    mEnforcer.postAndWait(r);\n  }\n\n  @Override\n  public final void postDelayed(Runnable r, long delayMillis) {\n    mEnforcer.postDelayed(r, delayMillis);\n  }\n\n  @Override\n  public final void removeCallbacks(Runnable r) {\n    mEnforcer.removeCallbacks(r);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/DisconnectReceiver.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\n/**\n * @see JsonRpcPeer#registerDisconnectReceiver(DisconnectReceiver)\n */\npublic interface DisconnectReceiver {\n  /**\n   * Invoked when a WebSocket peer disconnects.\n   */\n  void onDisconnect();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/JsonRpcException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.common.Util;\n\npublic class JsonRpcException extends Exception {\n  private final JsonRpcError mErrorMessage;\n\n  public JsonRpcException(JsonRpcError errorMessage) {\n    super(errorMessage.code + \": \" + errorMessage.message);\n    mErrorMessage = Util.throwIfNull(errorMessage);\n  }\n\n  public JsonRpcError getErrorMessage() {\n    return mErrorMessage;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/JsonRpcPeer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\nimport javax.annotation.concurrent.ThreadSafe;\n\nimport java.nio.channels.NotYetConnectedException;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport android.database.Observable;\n\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcRequest;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.websocket.SimpleSession;\n\nimport org.json.JSONObject;\n\n@ThreadSafe\npublic class JsonRpcPeer {\n  private final SimpleSession mPeer;\n  private final ObjectMapper mObjectMapper;\n\n  @GuardedBy(\"this\")\n  private long mNextRequestId;\n\n  @GuardedBy(\"this\")\n  private final Map<Long, PendingRequest> mPendingRequests = new HashMap<>();\n\n  private final DisconnectObservable mDisconnectObservable = new DisconnectObservable();\n\n  public JsonRpcPeer(ObjectMapper objectMapper, SimpleSession peer) {\n    mObjectMapper = objectMapper;\n    mPeer = Util.throwIfNull(peer);\n  }\n\n  public SimpleSession getWebSocket() {\n    return mPeer;\n  }\n\n  public void invokeMethod(String method, Object paramsObject,\n      @Nullable PendingRequestCallback callback)\n      throws NotYetConnectedException {\n    Util.throwIfNull(method);\n\n    Long requestId = (callback != null) ? preparePendingRequest(callback) : null;\n\n    // magic, can basically convert anything for some amount of runtime overhead...\n    JSONObject params = mObjectMapper.convertValue(paramsObject, JSONObject.class);\n\n    JsonRpcRequest message = new JsonRpcRequest(requestId, method, params);\n    String requestString;\n    JSONObject jsonObject = mObjectMapper.convertValue(message, JSONObject.class);\n    requestString = jsonObject.toString();\n    mPeer.sendText(requestString);\n  }\n\n  public void registerDisconnectReceiver(DisconnectReceiver callback) {\n    mDisconnectObservable.registerObserver(callback);\n  }\n\n  public void unregisterDisconnectReceiver(DisconnectReceiver callback) {\n    mDisconnectObservable.unregisterObserver(callback);\n  }\n\n  public void invokeDisconnectReceivers() {\n    mDisconnectObservable.onDisconnect();\n  }\n\n  private synchronized long preparePendingRequest(PendingRequestCallback callback) {\n    long requestId = mNextRequestId++;\n    mPendingRequests.put(requestId, new PendingRequest(requestId, callback));\n    return requestId;\n  }\n\n  public synchronized PendingRequest getAndRemovePendingRequest(long requestId) {\n    return mPendingRequests.remove(requestId);\n  }\n\n  private static class DisconnectObservable extends Observable<DisconnectReceiver> {\n    public void onDisconnect() {\n      for (int i = 0, N = mObservers.size(); i < N; ++i) {\n        final DisconnectReceiver observer = mObservers.get(i);\n        observer.onDisconnect();\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/JsonRpcResult.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcResponse;\n\n/**\n * Marker interface used to denote a JSON-RPC result.  After conversion from Jackson,\n * this will be placed into {@link JsonRpcResponse#result}.\n */\npublic interface JsonRpcResult {\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/PendingRequest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\nimport javax.annotation.Nullable;\n\n/**\n * Represents an outstanding request to the peer (issued by us).  This callback will be\n * fired when the server responds.  Note that with JSON-RPC, there is a special kind of\n * request called a notification which does not require a callback (and thus won't use\n * this class).\n */\npublic class PendingRequest {\n  public final long requestId;\n  public final @Nullable PendingRequestCallback callback;\n\n  public PendingRequest(long requestId, @Nullable PendingRequestCallback callback) {\n    this.requestId = requestId;\n    this.callback = callback;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/PendingRequestCallback.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc;\n\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcResponse;\n\npublic interface PendingRequestCallback {\n  void onResponse(JsonRpcPeer peer, JsonRpcResponse response);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/protocol/EmptyResult.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc.protocol;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\n\npublic class EmptyResult implements JsonRpcResult {\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/protocol/JsonRpcError.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc.protocol;\n\nimport javax.annotation.Nullable;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\nimport org.json.JSONObject;\n\n@SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\npublic class JsonRpcError {\n  @JsonProperty(required = true)\n  public ErrorCode code;\n\n  @JsonProperty(required = true)\n  public String message;\n\n  @JsonProperty\n  public JSONObject data;\n\n  public JsonRpcError() {\n  }\n\n  public JsonRpcError(ErrorCode code, String message, @Nullable JSONObject data) {\n    this.code = code;\n    this.message = message;\n    this.data = data;\n  }\n\n  public enum ErrorCode {\n    PARSER_ERROR(-32700),\n    INVALID_REQUEST(-32600),\n    METHOD_NOT_FOUND(-32601),\n    INVALID_PARAMS(-32602),\n    INTERNAL_ERROR(-32603);\n\n    private final int mProtocolValue;\n\n    private ErrorCode(int protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public int getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/protocol/JsonRpcEvent.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc.protocol;\n\nimport javax.annotation.Nullable;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\n@SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\npublic class JsonRpcEvent {\n  @JsonProperty(required = true)\n  public String method;\n\n  @JsonProperty\n  public JSONObject params;\n\n  public JsonRpcEvent() {\n  }\n\n  public JsonRpcEvent(String method, @Nullable JSONObject params) {\n    this.method = method;\n    this.params = params;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/protocol/JsonRpcRequest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc.protocol;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\n@SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\npublic class JsonRpcRequest {\n  /**\n   * This field is not required so that we can support JSON-RPC \"notification\" requests.\n   */\n  @JsonProperty\n  public Long id;\n\n  @JsonProperty(required = true)\n  public String method;\n\n  @JsonProperty\n  public JSONObject params;\n\n  public JsonRpcRequest() {\n  }\n\n  public JsonRpcRequest(Long id, String method, JSONObject params) {\n    this.id = id;\n    this.method = method;\n    this.params = params;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/jsonrpc/protocol/JsonRpcResponse.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.jsonrpc.protocol;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\n@SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\npublic class JsonRpcResponse {\n  @JsonProperty(required = true)\n  public long id;\n\n  @JsonProperty\n  public JSONObject result;\n\n  @JsonProperty\n  public JSONObject error;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/AsyncPrettyPrinter.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\n\n/**\n * Interface that callers need to implement in order to pretty print binary payload received by Stetho\n */\npublic interface AsyncPrettyPrinter {\n  /**\n   * Prints the prettified version of payload to output. This method can block\n   * for a certain period of time. Note that Stetho may impose arbitrary\n   * time out on this method.\n   *\n   * @param output Writes the prettified version of payload\n   * @param payload Response stream that has the raw data to be prettified\n   * @throws IOException\n   */\n  public void printTo(PrintWriter output, InputStream payload) throws IOException;\n\n  /**\n   * Specifies the type of pretty printed content. Note that this method is called\n   * before the content is actually pretty printed. Stetho uses this\n   * method to make a hopeful guess of the type of prettified content\n   *\n   * @return an enum defined by PrettyPrinterDisplayType class\n   */\n  public PrettyPrinterDisplayType getPrettifiedType();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/AsyncPrettyPrinterExecutorHolder.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\n\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\n/**\n * A holder class for the executor service used for pretty printing related tasks\n */\nfinal class AsyncPrettyPrinterExecutorHolder {\n\n  private static ExecutorService sExecutorService;\n\n  private AsyncPrettyPrinterExecutorHolder() {\n  }\n  \n  public static void ensureInitialized() {\n    if (sExecutorService == null) {\n      sExecutorService = Executors.newCachedThreadPool();\n    }\n  }\n\n  @Nullable\n  public static ExecutorService getExecutorService() {\n    return sExecutorService;\n  }\n\n  public static void shutdown() {\n    sExecutorService.shutdown();\n    sExecutorService = null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/AsyncPrettyPrinterFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.inspector.protocol.module.Page;\n\n/**\n * Interface for creating a factory for asynchronous pretty printers\n */\npublic interface AsyncPrettyPrinterFactory {\n\n  /**\n   * Creates an asynchronous pretty printer. This method must not be blocking.\n   *\n   * @param headerName header name of a response which is used to associate\n   * with an asynchronous pretty printer\n   * @param headerValue header value of a response which contains the URI for\n   * the schema data needed to pretty print the response body\n   * @return an asynchronous pretty printer to prettify the response body\n   */\n  public AsyncPrettyPrinter getInstance(String headerName, String headerValue);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/AsyncPrettyPrinterInitializer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\n/**\n * Interface that is called if AsyncPrettyPrinterRegistry is unpopulated when\n * the first peer connects to Stetho. It is responsible for registering header\n * names and their corresponding pretty printers\n */\npublic interface AsyncPrettyPrinterInitializer {\n\n  /**\n   * Populates AsyncPrettyPrinterRegistry with header names and their corresponding pretty\n   * printers. This is responsible for registering all {@link AsyncPrettyPrinter} to the\n   * provided registry.\n   * @param registry\n   */\n  void populatePrettyPrinters(AsyncPrettyPrinterRegistry registry);\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/AsyncPrettyPrinterRegistry.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.ThreadSafe;\n\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\n\n@ThreadSafe\npublic class AsyncPrettyPrinterRegistry {\n\n  private final Map<String, AsyncPrettyPrinterFactory> mRegistry = new HashMap<>();\n\n  public synchronized void register(String headerName, AsyncPrettyPrinterFactory factory) {\n    mRegistry.put(headerName, factory);\n  }\n\n  @Nullable\n  public synchronized AsyncPrettyPrinterFactory lookup(String headerName) {\n    return mRegistry.get(headerName);\n  }\n\n  public synchronized boolean unregister(String headerName) {\n    return mRegistry.remove(headerName) != null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/CountingOutputStream.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nclass CountingOutputStream extends FilterOutputStream {\n  private long mCount;\n\n  public CountingOutputStream(OutputStream out) {\n    super(out);\n  }\n\n  public long getCount() {\n    return mCount;\n  }\n\n  @Override\n  public void write(int oneByte) throws IOException {\n    out.write(oneByte);\n    mCount++;\n  }\n\n  @Override\n  public void write(byte[] buffer) throws IOException {\n    write(buffer, 0, buffer.length);\n  }\n\n  @Override\n  public void write(byte[] buffer, int offset, int length) throws IOException {\n    out.write(buffer, offset, length);\n    mCount += length;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/DecompressionHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.protocol.module.Console;\n\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.zip.InflaterOutputStream;\n\n// @VisibleForTest\npublic class DecompressionHelper {\n  static final String GZIP_ENCODING = \"gzip\";\n  static final String DEFLATE_ENCODING = \"deflate\";\n\n  public static InputStream teeInputWithDecompression(\n      NetworkPeerManager peerManager,\n      String requestId,\n      InputStream availableInputStream,\n      OutputStream decompressedOutput,\n      @Nullable String contentEncoding,\n      ResponseHandler responseHandler) throws IOException {\n    OutputStream output = decompressedOutput;\n    CountingOutputStream decompressedCounter = null;\n\n    if (contentEncoding != null) {\n      boolean gzipEncoding = GZIP_ENCODING.equals(contentEncoding);\n      boolean deflateEncoding = DEFLATE_ENCODING.equals(contentEncoding);\n\n      if (gzipEncoding || deflateEncoding) {\n        decompressedCounter = new CountingOutputStream(decompressedOutput);\n        if (gzipEncoding) {\n          output = GunzippingOutputStream.create(decompressedCounter);\n        } else if (deflateEncoding) {\n          output = new InflaterOutputStream(decompressedCounter);\n        }\n      } else {\n        CLog.writeToConsole(\n            peerManager,\n            Console.MessageLevel.WARNING,\n            Console.MessageSource.NETWORK,\n            \"Unsupported Content-Encoding in response for request #\" + requestId +\n                \": \" + contentEncoding);\n      }\n    }\n\n    return new ResponseHandlingInputStream(\n        availableInputStream,\n        requestId,\n        output,\n        decompressedCounter,\n        peerManager,\n        responseHandler);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/DefaultResponseHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.IOException;\n\n/**\n * Simple interceptor that delegates response read events to {@link NetworkEventReporter}.\n */\npublic class DefaultResponseHandler implements ResponseHandler {\n  private final NetworkEventReporter mEventReporter;\n  private final String mRequestId;\n\n  private int mBytesRead = 0;\n  private int mDecodedBytesRead = -1;\n\n  public DefaultResponseHandler(NetworkEventReporter eventReporter, String requestId) {\n    mEventReporter = eventReporter;\n    mRequestId = requestId;\n  }\n\n  @Override\n  public void onRead(int numBytes) {\n    mBytesRead += numBytes;\n  }\n\n  @Override\n  public void onReadDecoded(int numBytes) {\n    if (mDecodedBytesRead == -1) {\n      mDecodedBytesRead = 0;\n    }\n    mDecodedBytesRead += numBytes;\n  }\n\n  public void onEOF() {\n    reportDataReceived();\n    mEventReporter.responseReadFinished(mRequestId);\n  }\n\n  public void onError(IOException e) {\n    reportDataReceived();\n    mEventReporter.responseReadFailed(mRequestId, e.toString());\n  }\n\n  private void reportDataReceived() {\n    mEventReporter.dataReceived(\n        mRequestId,\n        mBytesRead,\n        mDecodedBytesRead >= 0 ? mDecodedBytesRead : mBytesRead);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/DownloadingAsyncPrettyPrinterFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.net.HttpURLConnection;\nimport java.net.MalformedURLException;\nimport java.net.URL;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.Util;\n\nimport javax.annotation.Nullable;\n\n/**\n * Abstract class for pretty printer factory that asynchronously downloads schema needed for\n * pretty printing the payload\n */\npublic abstract class DownloadingAsyncPrettyPrinterFactory implements AsyncPrettyPrinterFactory {\n\n  @Override\n  public AsyncPrettyPrinter getInstance(final String headerName, final String headerValue) {\n\n    final MatchResult result = matchAndParseHeader(headerName, headerValue);\n    if (result == null) {\n      return null;\n    }\n    String uri = result.getSchemaUri();\n    URL schemaURL = parseURL(uri);\n    if (schemaURL == null) {\n      return getErrorAsyncPrettyPrinter(headerName, headerValue);\n    } else {\n      ExecutorService  executorService = AsyncPrettyPrinterExecutorHolder.getExecutorService();\n      if (executorService == null) {\n        //last peer is unregistered...\n        return null;\n      }\n      final Future<String> response = executorService.submit(new Request(schemaURL));\n      return new AsyncPrettyPrinter() {\n        public void printTo(PrintWriter output, InputStream payload)\n            throws IOException {\n          try {\n            String schema;\n            try {\n              schema = response.get();\n            } catch (ExecutionException e) {\n              Throwable cause = e.getCause();\n              if (IOException.class.isInstance(cause)) {\n                doErrorPrint(\n                    output,\n                    payload,\n                    \"Cannot successfully download schema: \"\n                        + e.getMessage());\n                return;\n              } else {\n                throw e;\n              }\n            }\n            doPrint(output, payload, schema);\n          } catch (InterruptedException e) {\n            doErrorPrint(\n                output,\n                payload,\n                \"Encountered spurious interrupt while downloading schema for pretty printing: \"\n                    + e.getMessage());\n          } catch (ExecutionException e) {\n            Throwable cause = e.getCause();\n            throw ExceptionUtil.propagate(cause);\n          }\n        }\n\n        public PrettyPrinterDisplayType getPrettifiedType() {\n          return result.getDisplayType();\n        }\n      };\n    }\n  }\n\n  /**\n   * Match the correct header that contains information about the schema uri\n   * @param headerName header name of a response that needs to be pretty printed\n   * @param headerValue header value which contains the URI for\n   * the schema data needed to pretty print the response body\n   * @return MatchResult that has the schema uri and the type of prettified result. Null\n   * if there is no correct header match.\n   */\n  @Nullable\n  protected abstract MatchResult matchAndParseHeader(String headerName, String headerValue);\n\n  /**\n   * Note that the IOException thrown by this method will be propagated all the way up and\n   * yield an error to the chrome devtools\n   */\n  protected abstract void doPrint(PrintWriter output, InputStream payload, String schema)\n      throws IOException;\n\n  @Nullable\n  private static URL parseURL(String uri) {\n    try {\n      return new URL(uri);\n    } catch (MalformedURLException e) {\n      return null;\n    }\n  }\n\n  private static void doErrorPrint(PrintWriter output, InputStream payload, String errorMessage)\n      throws IOException {\n    output.print(errorMessage + \"\\n\" + Util.readAsUTF8(payload));\n  }\n\n  private static AsyncPrettyPrinter getErrorAsyncPrettyPrinter(\n      final String headerName,\n      final String headerValue) {\n    return new AsyncPrettyPrinter() {\n      @Override\n      public void printTo(PrintWriter output, InputStream payload) throws IOException {\n        String errorMessage = \"[Failed to parse header: \"\n            + headerName + \" : \" + headerValue + \" ]\";\n        doErrorPrint(output, payload, errorMessage);\n      }\n\n      @Override\n      public PrettyPrinterDisplayType getPrettifiedType() {\n        return PrettyPrinterDisplayType.TEXT;\n      }\n    };\n  }\n\n\n  protected class MatchResult {\n    private final String mSchemaUri;\n    private final PrettyPrinterDisplayType mDisplayType;\n\n    public MatchResult(String schemaUri, PrettyPrinterDisplayType displayType) {\n      mSchemaUri = schemaUri;\n      mDisplayType = displayType;\n    }\n\n    public String getSchemaUri() {\n      return mSchemaUri;\n    }\n\n    public PrettyPrinterDisplayType getDisplayType() {\n      return mDisplayType;\n    }\n  }\n\n  private static class Request implements Callable<String> {\n    private URL url;\n\n    public Request(URL url) {\n      this.url = url;\n    }\n\n    @Override\n    public String call() throws IOException {\n      HttpURLConnection connection = (HttpURLConnection)url.openConnection();\n      int statusCode = connection.getResponseCode();\n      if (statusCode != 200) {\n        throw new IOException(\"Got status code: \" + statusCode + \" while downloading \" +\n            \"schema with url: \" + url.toString());\n      }\n      InputStream urlStream = connection.getInputStream();\n      try {\n        return Util.readAsUTF8(urlStream);\n      } finally {\n        urlStream.close();\n      }\n    }\n  }\n}\n\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/GunzippingOutputStream.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.Util;\n\nimport java.io.FilterOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PipedInputStream;\nimport java.io.PipedOutputStream;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\nimport java.util.concurrent.Future;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * An {@link OutputStream} filter which decompresses gzip data before it is written to the\n * specified destination output stream.  This is functionally equivalent to\n * {@link java.util.zip.InflaterOutputStream} but provides gzip header awareness.  The\n * implementation however is very different to avoid actually interpreting the gzip header.\n */\nclass GunzippingOutputStream extends FilterOutputStream {\n  private final Future<Void> mCopyFuture;\n\n  private static final ExecutorService sExecutor = Executors.newCachedThreadPool();\n\n  public static GunzippingOutputStream create(OutputStream finalOut) throws IOException {\n    PipedInputStream pipeIn = new PipedInputStream();\n    PipedOutputStream pipeOut = new PipedOutputStream(pipeIn);\n\n    Future<Void> copyFuture = sExecutor.submit(\n        new GunzippingCallable(pipeIn, finalOut));\n\n    return new GunzippingOutputStream(pipeOut, copyFuture);\n  }\n\n  private GunzippingOutputStream(OutputStream out, Future<Void> copyFuture) throws IOException {\n    super(out);\n    mCopyFuture = copyFuture;\n  }\n\n  @Override\n  public void close() throws IOException {\n    boolean success = false;\n    try {\n      super.close();\n      success = true;\n    } finally {\n      try {\n        getAndRethrow(mCopyFuture);\n      } catch (IOException e) {\n        if (success) {\n          throw e;\n        }\n      }\n    }\n  }\n\n  private static <T> T getAndRethrow(Future<T> future) throws IOException {\n    while (true) {\n      try {\n        return future.get();\n      } catch (InterruptedException e) {\n        // Continue...\n      } catch (ExecutionException e) {\n        Throwable cause = e.getCause();\n        ExceptionUtil.propagateIfInstanceOf(cause, IOException.class);\n        ExceptionUtil.propagate(cause);\n      }\n    }\n  }\n\n  private static class GunzippingCallable implements Callable<Void> {\n    private final InputStream mIn;\n    private final OutputStream mOut;\n\n    public GunzippingCallable(InputStream in, OutputStream out) {\n      mIn = in;\n      mOut = out;\n    }\n\n    @Override\n    public Void call() throws IOException {\n      GZIPInputStream in = new GZIPInputStream(mIn);\n      try {\n        Util.copy(in, mOut, new byte[1024]);\n      } finally {\n        in.close();\n        mOut.close();\n      }\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/MimeMatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\n\nimport java.util.ArrayList;\n\nimport android.annotation.SuppressLint;\n\npublic class MimeMatcher<T> {\n  private final ArrayList<MimeMatcherRule> mRuleMap = new ArrayList<MimeMatcherRule>();\n\n  /**\n   * Add a matching rule in the canonical MIME T form such as \"image/*\" or a MIME T\n   * literal such as \"text/html\".\n   *\n   * @param ruleExpression Expression to match, in the order it was added.\n   * @param resultIfMatched Result if this expression matches.\n   */\n  public void addRule(String ruleExpression, T resultIfMatched) {\n    mRuleMap.add(new MimeMatcherRule(ruleExpression, resultIfMatched));\n  }\n\n  public void clear() {\n    mRuleMap.clear();\n  }\n\n  @Nullable\n  public T match(String mimeT) {\n    int ruleMapN = mRuleMap.size();\n    for (int i = 0; i < ruleMapN; i++) {\n      MimeMatcherRule rule = mRuleMap.get(i);\n      if (rule.match(mimeT)) {\n        return rule.getResultIfMatched();\n      }\n    }\n    return null;\n  }\n\n  @SuppressLint(\"BadMethodUse-java.lang.String.length\")\n  private class MimeMatcherRule {\n    private final boolean mHasWildcard;\n    private final String mMatchPrefix;\n    private final T mResultIfMatched;\n\n    public MimeMatcherRule(String ruleExpression, T resultIfMatched) {\n      if (ruleExpression.endsWith(\"*\")) {\n        mHasWildcard = true;\n        mMatchPrefix = ruleExpression.substring(0, ruleExpression.length() - 1);\n      } else {\n        mHasWildcard = false;\n        mMatchPrefix = ruleExpression;\n      }\n      if (mMatchPrefix.contains(\"*\")) {\n        throw new IllegalArgumentException(\"Multiple wildcards present in rule expression \" +\n            ruleExpression);\n      }\n      mResultIfMatched = resultIfMatched;\n    }\n\n    public boolean match(String mimeType) {\n      // Make sure the string literal matches.\n      if (!mimeType.startsWith(mMatchPrefix)) {\n        return false;\n      }\n\n      // If we have a wildcard and the prefix matches, then we've matched; otherwise if the\n      // string literal and the mime T are the same length then we must have a match.\n      return (mHasWildcard || mimeType.length() == mMatchPrefix.length());\n    }\n\n    public T getResultIfMatched() {\n      return mResultIfMatched;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/NetworkEventReporter.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * Interface that callers must invoke in order to supply data to the Network tab in\n * the WebKit Inspector.  For HTTP specific traffic, the following call flow must be met:\n *\n * <pre>\n * requestWillBeSent +---> responseHeadersReceived +---> interpretResponseStream\n *                   |           |                 |\n *                   |           `---> dataSent    |\n *                   |                             |\n *                   `-----------------------------`--------> httpExchangeFailed\n * </pre>\n *\n * <p>Note that {@link #interpretResponseStream} combined with {@link DefaultResponseHandler}\n * will automatically invoke {@link #dataReceived}, {@link #responseReadFailed} and\n * {@link #responseReadFinished}.  If you use your own custom {@link ResponseHandler} you\n * must be sure to invoke these methods manually.</p>\n *\n * <p>For arbitrary sockets or explicitly for WebSockets, the following call flow must be met:</p>\n *\n * <pre>\n * webSocketCreated +---> webSocketWillSendHandshakeRequest ----> webSocketHandshakeResponseReceived\n *                  |                                                              |\n *                  |                     ,----------------------+-----------------+--------,\n *                  |                     v                      v                          |\n *                  +---------> [ webSocketFrameSent | webSocketFrameReceived ] ---,        |\n *                  |                     ^                      ^                 |        |\n *                  |                     `----------------------+-----------------+        |\n *                  |                                                              |        |\n *                  `---------> webSocketClosed <----------------------------------'        |\n *                                     ^                                                    |\n *                                     `----------------------------------------------------'\n * </pre>\n *\n * <p>Explicitly worth nothing is that regular sockets in an Android app can be treated as\n * WebSockets for the purpose of arbitrary socket inspection and can skip\n * {@link #webSocketWillSendHandshakeRequest} and {@link #webSocketHandshakeResponseReceived}\n * which are only used for the WebSocket-specific HTTP upgrade.</p>\n */\npublic interface NetworkEventReporter {\n  /**\n   * Returns true if there is at least one peer listening for network events; false otherwise.\n   * This value is provided as an optimization to avoid expensive work when the WebKit Inspector is\n   * not being used.  It is otherwise safe to invoke methods defined in this interface when\n   * the value is false.\n   */\n  boolean isEnabled();\n\n  /**\n   * Indicates that a request is about to be sent, but has not yet been delivered over the wire.\n   *\n   * @param request Request descriptor.\n   */\n  void requestWillBeSent(InspectorRequest request);\n\n  /**\n   * Indicates that a response message was just received from the network, but the body\n   * has not yet been read.\n   *\n   * @param response Response descriptor.\n   */\n  void responseHeadersReceived(InspectorResponse response);\n\n /**\n  * Indicates that communication with the server has failed. You are expected to call this for any\n  * exception before you call {@link #interpretResponseStream}. After\n  * {@link #interpretResponseStream} is called we will reporting any\n  * {@link IOException} during reading from the {@link InputStream}.\n  *\n  * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}\n  * @param errorText Text to report for the error; using {@link IOException#toString()} is\n  *     recommended.\n   */\n  void httpExchangeFailed(String requestId, String errorText);\n\n  /**\n   * Intercept the stream as given by the underlying HTTP library that contains the body of the\n   * response. In order to have the response show up in inspector (and to have the request be\n   * completed successfully) you need to call this AND read until exhaustion/EOF of the returned\n   * stream.\n   *\n   * <p>\n   * We will internally signal a failure if there is an {@link IOException} received while reading\n   * from the stream.\n   *\n   * <p>\n   * Do not invoke {@link #httpExchangeFailed(String, String)} after calling this method.\n   *\n   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}\n   * @param contentType The {@code Content-Type} header value that was specified in\n   *     {@link InspectorResponse}.  This header is used to determine the appropriate\n   *     storage format for the body.  For instance, {@code image/*} is necessary to cause\n   *     images to appear in the Inspector UI.\n   * @param contentEncoding The {@code Content-Encoding} header value that was specified in\n   *     {@link InspectorResponse}.  This header is used to determine what type of decompression\n   *     is to be applied when delivering the raw response stream to the debugging interface.\n   *     If null, no decompression will be used.\n   * @param inputStream Response stream if applicable (\"HEAD\" for instance does not have a body).\n   *     {@code null} otherwise.\n   * @param responseHandler Callback to forward stream events back to the relevant event reporter\n   *     methods.  Recommend using {@link DefaultResponseHandler} for most callers.\n   *\n   * @return {@link InputStream} that has been intercepted if WebkitInspector is active and enabled\n   *     otherwise it will return {@code inputStream}\n   */\n  @Nullable\n  InputStream interpretResponseStream(\n      String requestId,\n      @Nullable String contentType,\n      @Nullable String contentEncoding,\n      @Nullable InputStream inputStream,\n      ResponseHandler responseHandler);\n\n  /**\n   * Indicates that there was a failure while reading from response stream.  If you use\n   * {@link #interpretResponseStream} with {@link DefaultResponseHandler} (as is recommended),\n   * this method will be invoked automatically for you.\n   *\n   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}\n   * @param errorText Text to report for the error; using {@link IOException#toString()} is\n   *     recommended.\n   */\n  void responseReadFailed(String requestId, String errorText);\n\n  /**\n   * Indicates that the response stream has been fully exhausted and the request is now\n   * complete.  If you use {@link #interpretResponseStream} with {@link DefaultResponseHandler}\n   * (as is recommended), this method will be invoked automatically for you.\n   *\n   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}\n   */\n  void responseReadFinished(String requestId);\n\n  /**\n   * Indicates that raw data was sent over the network.  It is permissible to invoke this\n   * method just once after the full size of the request is known.\n   * <p>\n   * Invoking this method is optional and merely provides additional timing metrics and actual\n   * payload sizes to the Inspector UI.\n   *\n   * @param requestId Unique identifier for the request as per {@link InspectorRequest#id()}\n   * @param dataLength Uncompressed data segment length\n   * @param encodedDataLength Compressed data segment length\n   */\n  void dataSent(String requestId, int dataLength, int encodedDataLength);\n\n  /**\n   * Indicates that raw data was received from the network.\n   *\n   * @see #dataSent\n   */\n  void dataReceived(String requestId, int dataLength, int encodedDataLength);\n\n  /**\n   * Provides unique request id for {@link InspectorRequest#id()}.\n   */\n  String nextRequestId();\n\n  /**\n   * Invoked when a socket is created and implicitly being connected (but not necessarily connected\n   * yet).  If a websocket is being used, proceed to {@link #webSocketWillSendHandshakeRequest}.\n   * Otherwise you may proceed next to {@code webSocketFrame*} methods.\n   */\n  void webSocketCreated(String requestId, String url);\n\n  /**\n   * Socket has been closed for unknown reasons.  Consider first invoking\n   * {@link #webSocketFrameError} even for standard sockets to provide context.\n   */\n  void webSocketClosed(String requestId);\n\n  /**\n   * Invoked specifically for websockets to communicate the WebSocket upgrade messages.  Not\n   * necessary to call for standard sockets.\n   */\n  void webSocketWillSendHandshakeRequest(InspectorWebSocketRequest request);\n\n  /**\n   * Delivers the reply from the peer in response to the WebSocket upgrade request.\n   */\n  void webSocketHandshakeResponseReceived(InspectorWebSocketResponse response);\n\n  /**\n   * Send a \"websocket\" frame from our app to the remote peer.  Standard sockets can simply emulate\n   * this by capturing each socket send as a frame of either\n   * {@link InspectorWebSocketFrame#OPCODE_BINARY} or\n   * {@link InspectorWebSocketFrame#OPCODE_TEXT}.  Note that binary\n   * payloads are not visualized in Chrome but can be sent by simply assuming the data is UTF-8\n   * encoded (yes, really:\n   * https://chromium.googlesource.com/chromium/blink/+/master/Source/core/inspector/InspectorResourceAgent.cpp#850).\n   */\n  void webSocketFrameSent(InspectorWebSocketFrame frame);\n\n  /**\n   * The receive side of {@link #webSocketFrameSent}.\n   */\n  void webSocketFrameReceived(InspectorWebSocketFrame frame);\n\n  /**\n   * Indicates a web socket (or standard socket) error has occurred though this doesn't\n   * explicitly close the socket (see {@link #webSocketClosed}) but it does let the UI\n   * know that it should denote the closure as forceful or as a failure in some way.\n   */\n  void webSocketFrameError(String requestId, String errorMessage);\n\n  /**\n   * Represents the request that will be sent over HTTP.  Note that for many implementations\n   * of HTTP the request constructed may differ from the request actually sent over the wire.\n   * For instance, additional headers like {@code Host}, {@code User-Agent}, {@code Content-Type},\n   * etc may not be part of this request but should be injected if necessary.  Some stacks offer\n   * inspection of the raw request about to be sent to the server which is preferable.\n   */\n  interface InspectorRequest extends InspectorRequestCommon {\n    /**\n     * Provide an extra integer to decorate the {@link #friendlyName()}.  This shows up next to\n     * it in the WebKit Inspector UI and can be used to indicate things like request priority.\n     */\n    @Nullable\n    Integer friendlyNameExtra();\n\n    String url();\n\n    /**\n     * HTTP method (\"GET\", \"POST\", \"DELETE\", etc).\n     */\n    String method();\n\n    /**\n     * Provide the body if part of an entity-enclosing request (like \"POST\" or \"PUT\").  May\n     * return null otherwise.\n     */\n    @Nullable\n    byte[] body() throws IOException;\n  }\n\n  interface InspectorResponse extends InspectorResponseCommon {\n    String url();\n\n    /**\n     * True if the response was furnished on a re-used socket; false otherwise or if unknown.\n     */\n    boolean connectionReused();\n\n    /**\n     * Unique connection identifier representing the socket that was used to furnish the response.\n     */\n    int connectionId();\n\n    /**\n     * True if the response was furnished by disk cache; false otherwise or if unknown.\n     */\n    boolean fromDiskCache();\n  }\n\n  interface InspectorWebSocketRequest extends InspectorRequestCommon {\n  }\n\n  interface InspectorWebSocketResponse extends InspectorResponseCommon {\n    /**\n     * Optional and redundant set of headers from {@link InspectorWebSocketRequest} that are for\n     * some mysterious reason are included here in the response by Chrome.\n     */\n    @Nullable\n    InspectorHeaders requestHeaders();\n  }\n\n  interface InspectorWebSocketFrame {\n    String requestId();\n\n    int OPCODE_CONTINUATION = 0;\n    int OPCODE_TEXT = 1;\n    int OPCODE_BINARY = 2;\n    int OPCODE_CONNECTION_CLOSE = 8;\n    int OPCODE_PING = 9;\n    int OPCODE_PONG = 10;\n\n    int opcode();\n    boolean mask();\n    String payloadData();\n  }\n\n  interface InspectorRequestCommon extends InspectorHeaders {\n    /**\n     * Unique identifier for this request.  This identifier must be used in all other network\n     * events corresponding to this request.  Identifiers may be re-used for HTTP requests  or\n     * WebSockets that have exhuasted the state machine to its final closed/finished state.\n     */\n    String id();\n\n    /**\n     * Arbitrary debug-friendly name of the request.\n     */\n    String friendlyName();\n  }\n\n  interface InspectorResponseCommon extends InspectorHeaders {\n    /** @see InspectorRequest#id() */\n    String requestId();\n\n    int statusCode();\n\n    String reasonPhrase();\n  }\n\n  interface InspectorHeaders {\n    int headerCount();\n    String headerName(int index);\n    String headerValue(int index);\n\n    @Nullable\n    String firstHeaderValue(String name);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/NetworkEventReporterImpl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport android.os.SystemClock;\nimport com.facebook.stetho.common.Utf8Charset;\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.protocol.module.Console;\nimport com.facebook.stetho.inspector.protocol.module.Network;\nimport com.facebook.stetho.inspector.protocol.module.Page;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.ArrayList;\nimport java.util.concurrent.atomic.AtomicInteger;\n\n/**\n * Implementation of {@link NetworkEventReporter} which allows callers to inform the Stetho\n * system of network traffic.  Callers can safely eagerly access this class and store a\n * reference if they wish.  When WebKit Inspector clients are connected, the internal\n * implementation will be automatically wired up to them.\n */\npublic class NetworkEventReporterImpl implements NetworkEventReporter {\n  private final AtomicInteger mNextRequestId = new AtomicInteger(0);\n  @Nullable\n  private ResourceTypeHelper mResourceTypeHelper;\n\n  private static NetworkEventReporter sInstance;\n\n  private NetworkEventReporterImpl() {\n  }\n\n  /**\n   * Static accessor allowing callers to easily hook into the WebKit Inspector system without\n   * creating dependencies on the main Stetho initialization code path.\n   */\n  public static synchronized NetworkEventReporter get() {\n    if (sInstance == null) {\n      sInstance = new NetworkEventReporterImpl();\n    }\n    return sInstance;\n  }\n\n  @Override\n  public boolean isEnabled() {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    return peerManager != null;\n  }\n\n  @Nullable\n  private NetworkPeerManager getPeerManagerIfEnabled() {\n    NetworkPeerManager peerManager = NetworkPeerManager.getInstanceOrNull();\n    if (peerManager != null && peerManager.hasRegisteredPeers()) {\n      return peerManager;\n    }\n    return null;\n  }\n\n  @Override\n  public void requestWillBeSent(InspectorRequest request) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.Request requestJSON = new Network.Request();\n      requestJSON.url = request.url();\n      requestJSON.method = request.method();\n      requestJSON.headers = formatHeadersAsJSON(request);\n      requestJSON.postData = readBodyAsString(peerManager, request);\n\n      // Hack to use the initiator of SCRIPT to generate a fake call stack that includes\n      // the request's \"friendly\" name.\n      String requestFriendlyName = request.friendlyName();\n      Integer requestPriority = request.friendlyNameExtra();\n      Network.Initiator initiatorJSON = new Network.Initiator();\n      initiatorJSON.type = Network.InitiatorType.SCRIPT;\n      initiatorJSON.stackTrace = new ArrayList<Console.CallFrame>();\n      initiatorJSON.stackTrace.add(new Console.CallFrame(requestFriendlyName,\n          requestFriendlyName,\n          requestPriority != null ? requestPriority : 0 /* lineNumber */,\n          0 /* columnNumber */));\n\n      Network.RequestWillBeSentParams params = new Network.RequestWillBeSentParams();\n      params.requestId = request.id();\n      params.frameId = \"1\";\n      params.loaderId = \"1\";\n      params.documentURL = request.url();\n      params.request = requestJSON;\n      params.timestamp = stethoNow() / 1000.0;\n      params.initiator = initiatorJSON;\n      params.redirectResponse = null;\n\n      // Type is now required as of at least WebKit Inspector rev @188492.  If you don't send\n      // it, Chrome will refuse to draw the row in the Network tab until the response is\n      // received (providing the type).  This delay is very noticable on slow networks.\n      params.type = Page.ResourceType.OTHER;\n\n      peerManager.sendNotificationToPeers(\"Network.requestWillBeSent\", params);\n    }\n  }\n\n  @Nullable\n  private static String readBodyAsString(\n      NetworkPeerManager peerManager,\n      InspectorRequest request) {\n    try {\n      byte[] body = request.body();\n      if (body != null) {\n        return new String(body, Utf8Charset.INSTANCE);\n      }\n    } catch (IOException | OutOfMemoryError e) {\n      CLog.writeToConsole(\n          peerManager,\n          Console.MessageLevel.WARNING,\n          Console.MessageSource.NETWORK,\n          \"Could not reproduce POST body: \" + e);\n    }\n    return null;\n  }\n\n  @Override\n  public void responseHeadersReceived(InspectorResponse response) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.Response responseJSON = new Network.Response();\n      responseJSON.url = response.url();\n      responseJSON.status = response.statusCode();\n      responseJSON.statusText = response.reasonPhrase();\n      responseJSON.headers = formatHeadersAsJSON(response);\n      String contentType = getContentType(response);\n      responseJSON.mimeType = contentType != null ?\n          getResourceTypeHelper().stripContentExtras(contentType) :\n          \"application/octet-stream\";\n      responseJSON.connectionReused = response.connectionReused();\n      responseJSON.connectionId = response.connectionId();\n      responseJSON.fromDiskCache = response.fromDiskCache();\n      Network.ResponseReceivedParams receivedParams = new Network.ResponseReceivedParams();\n      receivedParams.requestId = response.requestId();\n      receivedParams.frameId = \"1\";\n      receivedParams.loaderId = \"1\";\n      receivedParams.timestamp = stethoNow() / 1000.0;\n      receivedParams.response = responseJSON;\n      AsyncPrettyPrinter asyncPrettyPrinter =\n          initAsyncPrettyPrinterForResponse(response, peerManager);\n      receivedParams.type =\n          determineResourceType(asyncPrettyPrinter, contentType, getResourceTypeHelper());\n      peerManager.sendNotificationToPeers(\"Network.responseReceived\", receivedParams);\n    }\n  }\n\n  @Nullable\n  private static AsyncPrettyPrinter initAsyncPrettyPrinterForResponse(\n      InspectorResponse response,\n      NetworkPeerManager peerManager) {\n    AsyncPrettyPrinterRegistry registry = peerManager.getAsyncPrettyPrinterRegistry();\n    AsyncPrettyPrinter asyncPrettyPrinter = createPrettyPrinterForResponse(response, registry);\n    if (asyncPrettyPrinter != null) {\n      peerManager.getResponseBodyFileManager().associateAsyncPrettyPrinterWithId(\n          response.requestId(),\n          asyncPrettyPrinter);\n    }\n     return asyncPrettyPrinter;\n  }\n\n  private static Page.ResourceType determineResourceType(\n      AsyncPrettyPrinter asyncPrettyPrinter,\n      String contentType,\n      ResourceTypeHelper resourceTypeHelper) {\n    if (asyncPrettyPrinter != null) {\n      return asyncPrettyPrinter.getPrettifiedType().getResourceType();\n    } else {\n      return contentType != null ?\n          resourceTypeHelper.determineResourceType(contentType) :\n          Page.ResourceType.OTHER;\n    }\n  }\n\n  //@VisibleForTesting\n  @Nullable\n  static AsyncPrettyPrinter createPrettyPrinterForResponse(\n      InspectorResponse response,\n      @Nullable AsyncPrettyPrinterRegistry registry) {\n    if (registry != null) {\n      for (int i = 0, count = response.headerCount(); i < count; i++) {\n        AsyncPrettyPrinterFactory factory = registry.lookup(response.headerName(i));\n        if (factory != null) {\n          AsyncPrettyPrinter asyncPrettyPrinter = factory.getInstance(\n              response.headerName(i),\n              response.headerValue(i));\n          return asyncPrettyPrinter;\n        }\n      }\n    }\n    return null;\n  }\n\n  @Override\n  public InputStream interpretResponseStream(\n      String requestId,\n      @Nullable String contentType,\n      @Nullable String contentEncoding,\n      @Nullable InputStream availableInputStream,\n      ResponseHandler responseHandler) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      if (availableInputStream == null) {\n        responseHandler.onEOF();\n        return null;\n      }\n      Page.ResourceType resourceType =\n          contentType != null ?\n              getResourceTypeHelper().determineResourceType(contentType) :\n              null;\n\n      // There's this weird logic at play that only knows how to base64 decode certain kinds of\n      // resources.\n      boolean base64Encode = false;\n      if (resourceType != null && resourceType == Page.ResourceType.IMAGE) {\n        base64Encode = true;\n      }\n\n      try {\n        OutputStream fileOutputStream =\n            peerManager.getResponseBodyFileManager().openResponseBodyFile(\n                requestId,\n                base64Encode);\n        return DecompressionHelper.teeInputWithDecompression(\n            peerManager,\n            requestId,\n            availableInputStream,\n            fileOutputStream,\n            contentEncoding,\n            responseHandler);\n      } catch (IOException e) {\n        CLog.writeToConsole(\n            peerManager,\n            Console.MessageLevel.ERROR,\n            Console.MessageSource.NETWORK,\n            \"Error writing response body data for request #\" + requestId);\n      }\n    }\n    return availableInputStream;\n  }\n\n  @Override\n  public void httpExchangeFailed(String requestId, String errorText) {\n    loadingFailed(requestId, errorText);\n  }\n\n  @Override\n  public void responseReadFinished(String requestId) {\n    loadingFinished(requestId);\n  }\n\n  private void loadingFinished(String requestId) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.LoadingFinishedParams finishedParams = new Network.LoadingFinishedParams();\n      finishedParams.requestId = requestId;\n      finishedParams.timestamp = stethoNow() / 1000.0;\n      peerManager.sendNotificationToPeers(\"Network.loadingFinished\", finishedParams);\n    }\n  }\n\n  @Override\n  public void responseReadFailed(String requestId, String errorText) {\n    loadingFailed(requestId, errorText);\n  }\n\n  private void loadingFailed(String requestId, String errorText) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.LoadingFailedParams failedParams = new Network.LoadingFailedParams();\n      failedParams.requestId = requestId;\n      failedParams.timestamp = stethoNow() / 1000.0;\n      failedParams.errorText = errorText;\n      failedParams.type = Page.ResourceType.OTHER;\n      peerManager.sendNotificationToPeers(\"Network.loadingFailed\", failedParams);\n    }\n  }\n\n  @Override\n  public void dataSent(\n      String requestId,\n      int dataLength,\n      int encodedDataLength) {\n    // The inspector protocol only gives us the dataReceived event, but we can happily combine\n    // upstream and downstream data into this to visualize the real size of the request, not\n    // strictly the size of the \"content\" as reported in the UI.\n    dataReceived(requestId, dataLength, encodedDataLength);\n  }\n\n  @Override\n  public void dataReceived(\n      String requestId,\n      int dataLength,\n      int encodedDataLength) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.DataReceivedParams dataReceivedParams = new Network.DataReceivedParams();\n      dataReceivedParams.requestId = requestId;\n      dataReceivedParams.timestamp = stethoNow() / 1000.0;\n      dataReceivedParams.dataLength = dataLength;\n      dataReceivedParams.encodedDataLength = encodedDataLength;\n      peerManager.sendNotificationToPeers(\"Network.dataReceived\", dataReceivedParams);\n    }\n  }\n\n  @Override\n  public String nextRequestId() {\n    return String.valueOf(mNextRequestId.getAndIncrement());\n  }\n\n  @Nullable\n  private String getContentType(InspectorHeaders headers) {\n    // This may need to change in the future depending on how cumbersome header simulation\n    // is for the various hooks we expose.\n    return headers.firstHeaderValue(\"Content-Type\");\n  }\n\n  @Override\n  public void webSocketCreated(String requestId, String url) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketCreatedParams params = new Network.WebSocketCreatedParams();\n      params.requestId = requestId;\n      params.url = url;\n      peerManager.sendNotificationToPeers(\"Network.webSocketCreated\", params);\n    }\n  }\n\n  @Override\n  public void webSocketClosed(String requestId) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketClosedParams params = new Network.WebSocketClosedParams();\n      params.requestId = requestId;\n      params.timestamp = stethoNow() / 1000.0;\n      peerManager.sendNotificationToPeers(\"Network.webSocketClosed\", params);\n    }\n  }\n\n  @Override\n  public void webSocketWillSendHandshakeRequest(InspectorWebSocketRequest request) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketWillSendHandshakeRequestParams params =\n          new Network.WebSocketWillSendHandshakeRequestParams();\n      params.requestId = request.id();\n      params.timestamp = stethoNow() / 1000.0;\n      params.wallTime = System.currentTimeMillis() / 1000.0;\n      Network.WebSocketRequest requestJSON = new Network.WebSocketRequest();\n      requestJSON.headers = formatHeadersAsJSON(request);\n      params.request = requestJSON;\n      peerManager.sendNotificationToPeers(\"Network.webSocketWillSendHandshakeRequest\", params);\n    }\n  }\n\n  @Override\n  public void webSocketHandshakeResponseReceived(InspectorWebSocketResponse response) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketHandshakeResponseReceivedParams params =\n          new Network.WebSocketHandshakeResponseReceivedParams();\n      params.requestId = response.requestId();\n      params.timestamp = stethoNow() / 1000.0;\n      Network.WebSocketResponse responseJSON = new Network.WebSocketResponse();\n      responseJSON.headers = formatHeadersAsJSON(response);\n      responseJSON.headersText = null;\n      if (response.requestHeaders() != null) {\n        responseJSON.requestHeaders = formatHeadersAsJSON(response.requestHeaders());\n        responseJSON.requestHeadersText = null;\n      }\n      responseJSON.status = response.statusCode();\n      responseJSON.statusText = response.reasonPhrase();\n      params.response = responseJSON;\n      peerManager.sendNotificationToPeers(\"Network.webSocketHandshakeResponseReceived\", params);\n    }\n  }\n\n  @Override\n  public void webSocketFrameSent(InspectorWebSocketFrame frame) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketFrameSentParams params = new Network.WebSocketFrameSentParams();\n      params.requestId = frame.requestId();\n      params.timestamp = stethoNow() / 1000.0;\n      params.response = convertFrame(frame);\n      peerManager.sendNotificationToPeers(\"Network.webSocketFrameSent\", params);\n    }\n  }\n\n  @Override\n  public void webSocketFrameReceived(InspectorWebSocketFrame frame) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketFrameReceivedParams params = new Network.WebSocketFrameReceivedParams();\n      params.requestId = frame.requestId();\n      params.timestamp = stethoNow() / 1000.0;\n      params.response = convertFrame(frame);\n      peerManager.sendNotificationToPeers(\"Network.webSocketFrameReceived\", params);\n    }\n  }\n\n  private static Network.WebSocketFrame convertFrame(InspectorWebSocketFrame in) {\n    Network.WebSocketFrame out = new Network.WebSocketFrame();\n    out.opcode = in.opcode();\n    out.mask = in.mask();\n    out.payloadData = in.payloadData();\n    return out;\n  }\n\n  @Override\n  public void webSocketFrameError(String requestId, String errorMessage) {\n    NetworkPeerManager peerManager = getPeerManagerIfEnabled();\n    if (peerManager != null) {\n      Network.WebSocketFrameErrorParams params = new Network.WebSocketFrameErrorParams();\n      params.requestId = requestId;\n      params.timestamp = stethoNow() / 1000.0;\n      params.errorMessage = errorMessage;\n      peerManager.sendNotificationToPeers(\"Network.webSocketFrameError\", params);\n    }\n  }\n\n  private static JSONObject formatHeadersAsJSON(InspectorHeaders headers) {\n    JSONObject json = new JSONObject();\n    for (int i = 0; i < headers.headerCount(); i++) {\n      String name = headers.headerName(i);\n      String value = headers.headerValue(i);\n      try {\n        if (json.has(name)) {\n          // Multiple headers are separated with a new line.\n          json.put(name, json.getString(name) + \"\\n\" + value);\n        } else {\n          json.put(name, value);\n        }\n      } catch (JSONException e) {\n        throw new RuntimeException(e);\n      }\n    }\n    return json;\n  }\n\n  @Nonnull\n  private ResourceTypeHelper getResourceTypeHelper() {\n    if (mResourceTypeHelper == null) {\n      mResourceTypeHelper = new ResourceTypeHelper();\n    }\n    return mResourceTypeHelper;\n  }\n\n  private static long stethoNow() {\n    return SystemClock.elapsedRealtime();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/NetworkPeerManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\n\nimport android.content.Context;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.helper.PeersRegisteredListener;\n\npublic class NetworkPeerManager extends ChromePeerManager {\n  private static NetworkPeerManager sInstance;\n\n  private final ResponseBodyFileManager mResponseBodyFileManager;\n  private AsyncPrettyPrinterInitializer mPrettyPrinterInitializer;\n  private AsyncPrettyPrinterRegistry mAsyncPrettyPrinterRegistry;\n\n  @Nullable\n  public static synchronized NetworkPeerManager getInstanceOrNull() {\n    return sInstance;\n  }\n\n  public static synchronized NetworkPeerManager getOrCreateInstance(Context context) {\n    if (sInstance == null) {\n      sInstance = new NetworkPeerManager(\n          new ResponseBodyFileManager(\n              context.getApplicationContext()));\n    }\n    return sInstance;\n  }\n\n  public NetworkPeerManager(\n      ResponseBodyFileManager responseBodyFileManager) {\n    mResponseBodyFileManager = responseBodyFileManager;\n    setListener(mTempFileCleanup);\n  }\n\n  public ResponseBodyFileManager getResponseBodyFileManager() {\n    return mResponseBodyFileManager;\n  }\n\n  @Nullable\n  public AsyncPrettyPrinterRegistry getAsyncPrettyPrinterRegistry() {\n    return mAsyncPrettyPrinterRegistry;\n  }\n\n  public void setPrettyPrinterInitializer(AsyncPrettyPrinterInitializer initializer) {\n    Util.throwIfNotNull(mPrettyPrinterInitializer);\n    mPrettyPrinterInitializer = Util.throwIfNull(initializer);\n  }\n\n  private final PeersRegisteredListener mTempFileCleanup = new PeersRegisteredListener() {\n    @Override\n    protected void onFirstPeerRegistered() {\n      AsyncPrettyPrinterExecutorHolder.ensureInitialized();\n      if (mAsyncPrettyPrinterRegistry == null && mPrettyPrinterInitializer != null) {\n        mAsyncPrettyPrinterRegistry = new AsyncPrettyPrinterRegistry();\n        mPrettyPrinterInitializer.populatePrettyPrinters(mAsyncPrettyPrinterRegistry);\n      }\n      mResponseBodyFileManager.cleanupFiles();\n    }\n\n    @Override\n    protected void onLastPeerUnregistered() {\n      mResponseBodyFileManager.cleanupFiles();\n      AsyncPrettyPrinterExecutorHolder.shutdown();\n    }\n  };\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/PrettyPrinterDisplayType.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.inspector.protocol.module.Page;\n\npublic enum PrettyPrinterDisplayType {\n  JSON(Page.ResourceType.XHR),\n  HTML(Page.ResourceType.DOCUMENT),\n  TEXT(Page.ResourceType.DOCUMENT);\n\n  private final Page.ResourceType mResourceType;\n\n  private PrettyPrinterDisplayType(Page.ResourceType resourceType) {\n    mResourceType = resourceType;\n  }\n\n  /**\n   * Converts PrettyPrinterDisplayType values to the appropriate\n   *  {@link Page.ResourceType} values that Stetho understands\n   */\n  public Page.ResourceType getResourceType() {\n    return mResourceType;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/RequestBodyHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport javax.annotation.Nullable;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.InflaterOutputStream;\n\n/**\n * Helper which manages provides computed request sizes as well as transparent decompression.\n * Note that request compression is not officially part of the HTTP standard however it is\n * commonly in use and can be conveniently supported here.\n * <p />\n * To use, invoke {@link #createBodySink} to prepare an output stream where the raw body can be\n * written.  Then invoke {@link #getDisplayBody()} to retrieve the possibly decoded body.\n * Finally, {@link #reportDataSent()} can be called to report to Stetho the raw and decompressed\n * payload sizes.\n */\npublic class RequestBodyHelper {\n  private final NetworkEventReporter mEventReporter;\n  private final String mRequestId;\n\n  private ByteArrayOutputStream mDeflatedOutput;\n  private CountingOutputStream mDeflatingOutput;\n\n  public RequestBodyHelper(NetworkEventReporter eventReporter, String requestId) {\n    mEventReporter = eventReporter;\n    mRequestId = requestId;\n  }\n\n  public OutputStream createBodySink(@Nullable String contentEncoding) throws IOException {\n    OutputStream deflatingOutput;\n    ByteArrayOutputStream deflatedOutput = new ByteArrayOutputStream();\n    if (DecompressionHelper.GZIP_ENCODING.equals(contentEncoding)) {\n      deflatingOutput = GunzippingOutputStream.create(deflatedOutput);\n    } else if (DecompressionHelper.DEFLATE_ENCODING.equals(contentEncoding)) {\n      deflatingOutput = new InflaterOutputStream(deflatedOutput);\n    } else {\n      deflatingOutput = deflatedOutput;\n    }\n\n    mDeflatingOutput = new CountingOutputStream(deflatingOutput);\n    mDeflatedOutput = deflatedOutput;\n\n    return mDeflatingOutput;\n  }\n\n  public byte[] getDisplayBody() {\n    throwIfNoBody();\n    return mDeflatedOutput.toByteArray();\n  }\n\n  public boolean hasBody() {\n    return mDeflatedOutput != null;\n  }\n\n  public void reportDataSent() {\n    throwIfNoBody();\n    mEventReporter.dataSent(\n        mRequestId,\n        mDeflatedOutput.size(),\n        (int)mDeflatingOutput.getCount());\n  }\n\n  private void throwIfNoBody() {\n    if (!hasBody()) {\n      throw new IllegalStateException(\"No body found; has createBodySink been called?\");\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/ResourceTypeHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.inspector.protocol.module.Page;\n\npublic class ResourceTypeHelper {\n  private final MimeMatcher<Page.ResourceType> mMimeMatcher;\n\n  public ResourceTypeHelper() {\n    mMimeMatcher = new MimeMatcher<Page.ResourceType>();\n    mMimeMatcher.addRule(\"text/css\", Page.ResourceType.STYLESHEET);\n    mMimeMatcher.addRule(\"image/*\", Page.ResourceType.IMAGE);\n    mMimeMatcher.addRule(\"application/x-javascript\", Page.ResourceType.SCRIPT);\n\n    // This is apparently important to allow the WebKit inspector to do JSON preview.  I don't\n    // know exactly why, but whatever.\n    mMimeMatcher.addRule(\"text/javascript\", Page.ResourceType.XHR);\n    mMimeMatcher.addRule(\"application/json\", Page.ResourceType.XHR);\n\n    // Everything else gets a lame, unformatted blob.\n    mMimeMatcher.addRule(\"text/*\", Page.ResourceType.DOCUMENT);\n\n    // I think this disables preview.  Perhaps that's not what we want as the default but we'll\n    // need some time to soak in real data to see for sure.\n    mMimeMatcher.addRule(\"*\", Page.ResourceType.OTHER);\n  }\n\n  public Page.ResourceType determineResourceType(String contentType) {\n    String mimeType = stripContentExtras(contentType);\n    return mMimeMatcher.match(mimeType);\n  }\n\n  /**\n   * Strip out any extra data following the semicolon (e.g. \\\"text/javascript; charset=UTF-8\").\n   *\n   * @return MIME type with content extras stripped out (if there were any).\n   */\n  public String stripContentExtras(String contentType) {\n    int index = contentType.indexOf(';');\n    return (index >= 0) ? contentType.substring(0, index) : contentType;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/ResponseBodyData.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\n/**\n * Special file data necessary to comply with the Chrome DevTools instance which doesn't let\n * us just naively base64 encode everything.\n */\npublic class ResponseBodyData {\n  public String data;\n  public boolean base64Encoded;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/ResponseBodyFileManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.concurrent.Callable;\nimport java.util.concurrent.ExecutionException;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Future;\nimport java.util.concurrent.TimeUnit;\nimport java.util.concurrent.TimeoutException;\n\nimport android.content.Context;\nimport android.util.Base64;\nimport android.util.Base64OutputStream;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.common.LogRedirector;\nimport com.facebook.stetho.common.Util;\n\n/**\n * Manages temporary files created by {@link ChromeHttpFlowObserver} to serve request bodies.\n */\npublic class ResponseBodyFileManager {\n  private static final String TAG = \"ResponseBodyFileManager\";\n  private static final String FILENAME_PREFIX = \"network-response-body-\";\n  private static final int PRETTY_PRINT_TIMEOUT_SEC = 10;\n\n  private final Context mContext;\n  private final Map<String, AsyncPrettyPrinter> mRequestIdMap = Collections.synchronizedMap(\n      new HashMap<String, AsyncPrettyPrinter>());\n\n  public ResponseBodyFileManager(Context context) {\n    mContext = context;\n  }\n\n  public void cleanupFiles() {\n    for (File file : mContext.getFilesDir().listFiles()) {\n      if (file.getName().startsWith(FILENAME_PREFIX)) {\n        if (!file.delete()) {\n          LogRedirector.w(TAG, \"Failed to delete \" + file.getAbsolutePath());\n        }\n      }\n    }\n    LogRedirector.i(TAG, \"Cleaned up temporary network files.\");\n  }\n\n  public ResponseBodyData readFile(String requestId) throws IOException {\n    InputStream in = mContext.openFileInput(getFilename(requestId));\n    try {\n      int firstByte = in.read();\n      if (firstByte == -1) {\n        throw new EOFException(\"Failed to read base64Encode byte\");\n      }\n      ResponseBodyData bodyData = new ResponseBodyData();\n      bodyData.base64Encoded = firstByte != 0;\n\n      AsyncPrettyPrinter asyncPrettyPrinter = mRequestIdMap.get(requestId);\n      if (asyncPrettyPrinter != null) {\n        // TODO: this line blocks for up to 10 seconds and create problems as described\n        // in issue #243 allow asynchronous dispatch for MethodDispatcher\n        bodyData.data = prettyPrintContentWithTimeOut(asyncPrettyPrinter, in);\n      } else {\n        bodyData.data = Util.readAsUTF8(in);\n      }\n      return bodyData;\n\n    } finally {\n      in.close();\n    }\n  }\n\n  private String prettyPrintContentWithTimeOut(\n      AsyncPrettyPrinter asyncPrettyPrinter,\n      InputStream in) throws IOException {\n    AsyncPrettyPrintingCallable prettyPrintingCallable = new AsyncPrettyPrintingCallable(\n        in,\n        asyncPrettyPrinter);\n    ExecutorService executorService = AsyncPrettyPrinterExecutorHolder.getExecutorService();\n    if (executorService == null) {\n      //last peer is unregistered...\n      return null;\n    }\n    Future<String> future = executorService.submit(prettyPrintingCallable);\n    try {\n      return Util.getUninterruptibly(future, PRETTY_PRINT_TIMEOUT_SEC, TimeUnit.SECONDS);\n    } catch (TimeoutException e) {\n      future.cancel(true);\n      return \"Time out after \" + PRETTY_PRINT_TIMEOUT_SEC +\n          \" seconds of attempting to pretty print\\n\" + Util.readAsUTF8(in);\n    }  catch (ExecutionException e) {\n      Throwable cause = e.getCause();\n      ExceptionUtil.propagateIfInstanceOf(cause, IOException.class);\n      throw ExceptionUtil.propagate(cause);\n    }\n  }\n\n  public OutputStream openResponseBodyFile(String requestId, boolean base64Encode)\n      throws IOException {\n    OutputStream out = mContext.openFileOutput(getFilename(requestId), Context.MODE_PRIVATE);\n    out.write(base64Encode ? 1 : 0);\n    if (base64Encode) {\n      return new Base64OutputStream(out, Base64.DEFAULT);\n    } else {\n      return out;\n    }\n  }\n\n  private static String getFilename(String requestId) {\n    return FILENAME_PREFIX + requestId;\n  }\n\n  /**\n   * Associates an asynchronous pretty printer with a response request id\n   * The pretty printer will be used to pretty print the response body that has\n   * the particular request id\n   *\n   * @param requestId Unique identifier for the response\n   * as per {@link NetworkEventReporter.InspectorResponse#requestId()}\n   * @param asyncPrettyPrinter Asynchronous Pretty Printer to pretty print the response body\n   */\n  public void associateAsyncPrettyPrinterWithId(\n      String requestId,\n      AsyncPrettyPrinter asyncPrettyPrinter) {\n    if (mRequestIdMap.put(requestId, asyncPrettyPrinter) != null) {\n      throw new IllegalArgumentException(\"cannot associate different \" +\n          \"pretty printers with the same request id: \"+requestId);\n    }\n  }\n\n  private class AsyncPrettyPrintingCallable implements Callable<String> {\n    private final InputStream mInputStream;\n    private final AsyncPrettyPrinter mAsyncPrettyPrinter;\n\n    public AsyncPrettyPrintingCallable(\n        InputStream in,\n        AsyncPrettyPrinter asyncPrettyPrinter) {\n      mInputStream = in;\n      mAsyncPrettyPrinter = asyncPrettyPrinter;\n    }\n\n    @Override\n    public String call() throws IOException {\n      return prettyPrintContent(mInputStream, mAsyncPrettyPrinter);\n    }\n\n    private String prettyPrintContent(InputStream in, AsyncPrettyPrinter asyncPrettyPrinter)\n        throws IOException {\n      ByteArrayOutputStream out = new ByteArrayOutputStream();\n      PrintWriter writer = new PrintWriter(out);\n      asyncPrettyPrinter.printTo(writer, in);\n      writer.flush();\n      return out.toString(\"UTF-8\");\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/ResponseHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.IOException;\n\n/**\n * Custom hook to intercept read events delivered by\n * {@link NetworkEventReporter#interpretResponseStream}.\n */\npublic interface ResponseHandler {\n  /**\n   * Signal that data has been read from the response stream.\n   *\n   * @param numBytes Bytes read from the network stack's stream as established by\n   *     {@link NetworkEventReporter#interpretResponseStream}.\n   */\n  void onRead(int numBytes);\n\n  /**\n   * Signal that data has been decoded (reversing the response's {@code Content-Encoding}) while\n   * reading a raw stream.  This method is only called when the stream is known to have\n   * a supported encoding.  Note that for HTTP, content encoding almost always is used for\n   * some form of response compression.\n   *\n   * @param numBytes Bytes yielded after decoding bytes received from the network stack's\n   *     stream.\n   */\n  void onReadDecoded(int numBytes);\n\n  /**\n   * Signals that EOF has been reached reading the response stream from the network\n   * stack.\n   */\n  void onEOF();\n\n  /**\n   * Signals that an error occurred while reading the response stream.\n   */\n  void onError(IOException e);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/ResponseHandlingInputStream.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.protocol.module.Console;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\n/**\n * {@link InputStream} that caches the data as the data is read, and writes them to the given\n * {@link OutputStream}. This also guarantees that we will attempt to reach EOF on the\n * {@link InputStream} passing all data to the {@link OutputStream}.\n * This is done to allow us to guarantee all responses are represented in the webkit inspector.\n */\n// @VisibleForTest\npublic final class ResponseHandlingInputStream extends FilterInputStream {\n\n  public static final String TAG = \"ResponseHandlingInputStream\";\n\n  private static final int BUFFER_SIZE = 1024;\n\n  private final String mRequestId;\n  private final OutputStream mOutputStream;\n  @Nullable private final CountingOutputStream mDecompressedCounter;\n  private final ChromePeerManager mNetworkPeerManager;\n  private final ResponseHandler mResponseHandler;\n\n  /**\n   * This stream will no longer be usable if {@link #close()} has been called on this stream.\n   */\n  @GuardedBy(\"this\")\n  private boolean mClosed;\n\n  @GuardedBy(\"this\")\n  private boolean mEofSeen;\n\n  @Nullable\n  @GuardedBy(\"this\")\n  private byte[] mSkipBuffer;\n\n  private long mLastDecompressedCount = 0;\n\n  /**\n   * @param inputStream\n   * @param requestId the requestId to use when we call the {@link NetworkEventReporter}\n   * @param outputStream stream to write to.\n   * @param decompressedCounter Optional decompressing counting output stream which\n   *     can be queried after each write to determine the number of decompressed bytes\n   *     yielded.  Used to implement {@link ResponseHandler#onReadDecoded(int)}.\n   * @param networkPeerManager A peer manager which is used to log internal errors to the\n   *     Inspector console.\n   * @param responseHandler Special interface to intercept read events before they are sent\n   *     to peers via {@link NetworkEventReporter} methods.\n   */\n  public ResponseHandlingInputStream(\n      InputStream inputStream,\n      String requestId,\n      OutputStream outputStream,\n      @Nullable CountingOutputStream decompressedCounter,\n      ChromePeerManager networkPeerManager,\n      ResponseHandler responseHandler) {\n    super(inputStream);\n    mRequestId = requestId;\n    mOutputStream = outputStream;\n    mDecompressedCounter = decompressedCounter;\n    mNetworkPeerManager = networkPeerManager;\n    mResponseHandler = responseHandler;\n    mClosed = false;\n  }\n\n  private synchronized int checkEOF(int n) {\n    if (n == -1) {\n      closeOutputStreamQuietly();\n      mResponseHandler.onEOF();\n      mEofSeen = true;\n    }\n    return n;\n  }\n\n  @Override\n  public int read() throws IOException {\n    try {\n      int result = checkEOF(in.read());\n      if (result != -1) {\n        mResponseHandler.onRead(1);\n        writeToOutputStream(result);\n      }\n      return result;\n    } catch (IOException ex) {\n      throw handleIOException(ex);\n    }\n  }\n\n  @Override\n  public int read(byte[] b) throws IOException {\n    return this.read(b, 0, b.length);\n  }\n\n  @Override\n  public int read(byte[] b, int off, int len) throws IOException {\n    try {\n      int result = checkEOF(in.read(b, off, len));\n      if (result != -1) {\n        mResponseHandler.onRead(result);\n        writeToOutputStream(b, off, result);\n      }\n      return result;\n    } catch (IOException ex) {\n      throw handleIOException(ex);\n    }\n  }\n\n  @Override\n  public synchronized long skip(long n) throws IOException {\n    byte[] buffer = getSkipBufferLocked();\n    long total = 0;\n    while (total < n) {\n      long bytesDiff = n - total;\n      int bytesToRead = (int) Math.min((long) buffer.length, bytesDiff);\n      int result = this.read(buffer, 0, bytesToRead);\n      if (result == -1) {\n        break;\n      }\n      total += result;\n    }\n    return total;\n  }\n\n  @Nonnull\n  private byte[] getSkipBufferLocked() {\n    if (mSkipBuffer == null) {\n      mSkipBuffer = new byte[BUFFER_SIZE];\n    }\n\n    return mSkipBuffer;\n  }\n\n  @Override\n  public boolean markSupported() {\n    // this can be implemented, but isn't needed for TeedInputStream's behavior\n    return false;\n  }\n\n  @Override\n  public void mark(int readlimit) {\n    // noop -- mark is not supported\n  }\n\n  @Override\n  public void reset() throws IOException {\n    throw new UnsupportedOperationException(\"Mark not supported\");\n  }\n\n  @Override\n  public void close() throws IOException {\n    try {\n      long bytesRead = 0;\n      if (!mEofSeen) {\n        byte[] buffer = new byte[BUFFER_SIZE];\n        int count;\n        while ((count = this.read(buffer)) != -1) {\n          bytesRead += count;\n        }\n      }\n      if (bytesRead > 0) {\n        CLog.writeToConsole(\n            mNetworkPeerManager,\n            Console.MessageLevel.ERROR,\n            Console.MessageSource.NETWORK,\n            \"There were \" + String.valueOf(bytesRead) + \" bytes that were not consumed while \"\n            + \"processing request \" + mRequestId);\n      }\n    } finally {\n      super.close();\n      closeOutputStreamQuietly();\n    }\n  }\n\n  /**\n   * Attempts to close all the output stream, and swallows any exceptions.\n   */\n  private synchronized void closeOutputStreamQuietly() {\n    if (!mClosed) {\n      try {\n        mOutputStream.close();\n        reportDecodedSizeIfApplicable();\n      } catch (IOException e) {\n        CLog.writeToConsole(\n            mNetworkPeerManager,\n            Console.MessageLevel.ERROR,\n            Console.MessageSource.NETWORK,\n            \"Could not close the output stream\" + e);\n      } finally {\n        mClosed = true;\n      }\n    }\n  }\n\n  /**\n   * Handles reporting an {@link IOException}. We do this so we can centralize the logic while still\n   * maintaining the ability of the catch clause to throw.\n   * @param ex\n   * @return\n   */\n  private IOException handleIOException(IOException ex) {\n    mResponseHandler.onError(ex);\n    return ex;\n  }\n\n  private void reportDecodedSizeIfApplicable() {\n    if (mDecompressedCounter != null) {\n      long currentCount = mDecompressedCounter.getCount();\n      int delta = (int)(currentCount - mLastDecompressedCount);\n      mResponseHandler.onReadDecoded(delta);\n      mLastDecompressedCount = currentCount;\n    }\n  }\n\n  /**\n   * Writes the byte to all the output streams. If we get an exception when writing to any\n   * of the streams, we close all the streams, and then propagate the first exception that\n   * occurred when writing.\n   */\n  private synchronized void writeToOutputStream(int oneByte) {\n    if (mClosed) {\n      return;\n    }\n\n    try {\n      mOutputStream.write(oneByte);\n      reportDecodedSizeIfApplicable();\n    } catch (IOException e) {\n      handleIOExceptionWritingToStream(e);\n    }\n  }\n\n  /**\n   * Same as {@link #writeToOutputStream(int)}, but we write a buffer instead.\n   */\n  private synchronized void writeToOutputStream(byte[] b, int offset, int count) {\n    if (mClosed) {\n      return;\n    }\n\n    try {\n      mOutputStream.write(b, offset, count);\n      reportDecodedSizeIfApplicable();\n    } catch (IOException e) {\n      handleIOExceptionWritingToStream(e);\n    }\n  }\n\n  private void handleIOExceptionWritingToStream(IOException e) {\n    CLog.writeToConsole(\n        mNetworkPeerManager,\n        Console.MessageLevel.ERROR,\n        Console.MessageSource.NETWORK,\n        \"Could not write response body to the stream \" + e);\n\n    closeOutputStreamQuietly();\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/SimpleBinaryInspectorWebSocketFrame.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.UnsupportedEncodingException;\n\npublic class SimpleBinaryInspectorWebSocketFrame\n    implements NetworkEventReporter.InspectorWebSocketFrame {\n  private final String mRequestId;\n  private final byte[] mPayload;\n\n  public SimpleBinaryInspectorWebSocketFrame(String requestId, byte[] payload) {\n    mRequestId = requestId;\n    mPayload = payload;\n  }\n\n  @Override\n  public String requestId() {\n    return mRequestId;\n  }\n\n  @Override\n  public int opcode() {\n    return OPCODE_BINARY;\n  }\n\n  @Override\n  public boolean mask() {\n    return false;\n  }\n\n  @Override\n  public String payloadData() {\n    try {\n      // LOL, yes this is really how Chrome does it too...\n      return new String(mPayload, \"UTF-8\");\n    } catch (UnsupportedEncodingException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/network/SimpleTextInspectorWebSocketFrame.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\npublic class SimpleTextInspectorWebSocketFrame\n    implements NetworkEventReporter.InspectorWebSocketFrame {\n  private final String mRequestId;\n  private final String mPayload;\n\n  public SimpleTextInspectorWebSocketFrame(String requestId, String payload) {\n    mRequestId = requestId;\n    mPayload = payload;\n  }\n\n  @Override\n  public String requestId() {\n    return mRequestId;\n  }\n\n  @Override\n  public int opcode() {\n    return OPCODE_TEXT;\n  }\n\n  @Override\n  public boolean mask() {\n    return false;\n  }\n\n  @Override\n  public String payloadData() {\n    return mPayload;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/ChromeDevtoolsDomain.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol;\n\n/**\n * Marker interface that identifies implementations of subsystems in the WebKit Inspector protocol.\n */\npublic interface ChromeDevtoolsDomain {\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/ChromeDevtoolsMethod.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ChromeDevtoolsMethod {\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/BaseDatabaseDriver.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteException;\n\nimport java.util.List;\n\n/**\n * Extend {@link DatabaseDriver2} directly.  This class is provided only as a common API compatible\n * base layer for the legacy {@link Database.DatabaseDriver}.\n */\npublic abstract class BaseDatabaseDriver<DESC> {\n\n  protected Context mContext;\n\n  public BaseDatabaseDriver(Context context) {\n    mContext = context;\n  }\n\n  public Context getContext() {\n    return mContext;\n  }\n\n  /**\n   * Access a stable list of objects that describe the databases made available by this driver.\n   * The list of returned objects must not change on each invocation as this will cause\n   * a memory leak when assigning unique identifiers for the objects to remote peers.\n   */\n  public abstract List<DESC> getDatabaseNames();\n\n  /**\n   * Get or create a list of table names given a previously returned database descriptor instance\n   * from {@link #getDatabaseNames()}.\n   */\n  public abstract List<String> getTableNames(DESC database);\n\n  public abstract Database.ExecuteSQLResponse executeSQL(\n      DESC database,\n      String query,\n      ExecuteResultHandler<Database.ExecuteSQLResponse> handler)\n      throws SQLiteException;\n\n  public interface ExecuteResultHandler<RESULT> {\n    RESULT handleRawQuery() throws SQLiteException;\n\n    RESULT handleSelect(Cursor result) throws SQLiteException;\n\n    RESULT handleInsert(long insertedId) throws SQLiteException;\n\n    RESULT handleUpdateDelete(int count) throws SQLiteException;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/CSS.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.common.ListUtil;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.StringUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.ComputedStyleAccumulator;\nimport com.facebook.stetho.inspector.elements.Document;\nimport com.facebook.stetho.inspector.elements.Origin;\nimport com.facebook.stetho.inspector.elements.StyleAccumulator;\nimport com.facebook.stetho.inspector.elements.StyleRuleNameAccumulator;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.helper.PeersRegisteredListener;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\npublic class CSS implements ChromeDevtoolsDomain {\n  private final ChromePeerManager mPeerManager;\n  private final Document mDocument;\n  private final ObjectMapper mObjectMapper;\n\n  public CSS(Document document) {\n    mDocument = Util.throwIfNull(document);\n    mObjectMapper = new ObjectMapper();\n    mPeerManager = new ChromePeerManager();\n    mPeerManager.setListener(new PeerManagerListener());\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getComputedStyleForNode(JsonRpcPeer peer, JSONObject params) {\n    final GetComputedStyleForNodeRequest request = mObjectMapper.convertValue(\n        params,\n        GetComputedStyleForNodeRequest.class);\n\n    final GetComputedStyleForNodeResult result = new GetComputedStyleForNodeResult();\n    result.computedStyle = new ArrayList<>();\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        Object element = mDocument.getElementForNodeId(request.nodeId);\n\n        if (element == null) {\n          LogUtil.e(\"Tried to get the style of an element that does not exist, using nodeid=\" +\n              request.nodeId);\n\n          return;\n        }\n\n        mDocument.getElementComputedStyles(\n            element,\n            new ComputedStyleAccumulator() {\n              @Override\n              public void store(String name, String value) {\n                final CSSComputedStyleProperty property = new CSSComputedStyleProperty();\n                property.name = name;\n                property.value = value;\n                result.computedStyle.add(property);\n              }\n            });\n      }\n    });\n\n    return result;\n  }\n\n  @SuppressLint(\"DefaultLocale\")\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getMatchedStylesForNode(JsonRpcPeer peer, JSONObject params) {\n    final GetMatchedStylesForNodeRequest request = mObjectMapper.convertValue(\n        params,\n        GetMatchedStylesForNodeRequest.class);\n\n    final GetMatchedStylesForNodeResult result = new GetMatchedStylesForNodeResult();\n    result.matchedCSSRules = new ArrayList<>();\n    result.inherited = Collections.emptyList();\n    result.pseudoElements = Collections.emptyList();\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        final Object elementForNodeId = mDocument.getElementForNodeId(request.nodeId);\n\n        if (elementForNodeId == null) {\n          LogUtil.w(\"Failed to get style of an element that does not exist, nodeid=\" +\n              request.nodeId);\n          return;\n        }\n\n        mDocument.getElementStyleRuleNames(elementForNodeId, new StyleRuleNameAccumulator() {\n          @Override\n          public void store(String ruleName, boolean editable) {\n            final ArrayList<CSSProperty> properties = new ArrayList<>();\n\n            final RuleMatch match = new RuleMatch();\n            match.matchingSelectors = ListUtil.newImmutableList(0);\n\n            final Selector selector = new Selector();\n            selector.value = ruleName;\n\n            final CSSRule rule = new CSSRule();\n            rule.origin = Origin.REGULAR;\n            rule.selectorList = new SelectorList();\n            rule.selectorList.selectors = ListUtil.newImmutableList(selector);\n            rule.style = new CSSStyle();\n            rule.style.cssProperties = properties;\n            rule.style.shorthandEntries = Collections.emptyList();\n\n            if (editable) {\n              rule.style.styleSheetId = String.format(\n                  \"%s.%s\",\n                  Integer.toString(request.nodeId),\n                  selector.value);\n            }\n\n            mDocument.getElementStyles(elementForNodeId, ruleName, new StyleAccumulator() {\n              @Override\n              public void store(String name, String value, boolean isDefault) {\n                final CSSProperty property = new CSSProperty();\n                property.name = name;\n                property.value = value;\n                properties.add(property);\n              }\n            });\n\n            match.rule = rule;\n            result.matchedCSSRules.add(match);\n          }\n        });\n      }\n    });\n\n    return result;\n  }\n\n  @ChromeDevtoolsMethod\n  public SetPropertyTextResult setPropertyText(JsonRpcPeer peer, JSONObject params) {\n    final SetPropertyTextRequest request =  mObjectMapper.convertValue(\n        params,\n        SetPropertyTextRequest.class);\n\n    final String[] parts = request.styleSheetId.split(\"\\\\.\", 2);\n    final int nodeId = Integer.parseInt(parts[0]);\n    final String ruleName = parts[1];\n\n    final String value;\n    final String key;\n    if (request.text == null || !request.text.contains(\":\")) {\n      key = null;\n      value = null;\n    } else {\n      final String[] keyValue = request.text.split(\":\", 2);\n      key = keyValue[0].trim();\n      value = StringUtil.removeAll(keyValue[1], ';').trim();\n    }\n\n\n    final SetPropertyTextResult result = new SetPropertyTextResult();\n    result.style = new CSSStyle();\n    result.style.styleSheetId = request.styleSheetId;\n    result.style.cssProperties = new ArrayList<>();\n    result.style.shorthandEntries = Collections.emptyList();\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        final Object elementForNodeId = mDocument.getElementForNodeId(nodeId);\n\n        if (elementForNodeId == null) {\n          LogUtil.w(\"Failed to get style of an element that does not exist, nodeid=\" + nodeId);\n          return;\n        }\n\n        if (key != null) {\n          mDocument.setElementStyle(elementForNodeId, ruleName, key, value);\n        }\n\n        mDocument.getElementStyles(elementForNodeId, ruleName, new StyleAccumulator() {\n          @Override\n          public void store(String name, String value, boolean isDefault) {\n            final CSSProperty property = new CSSProperty();\n            property.name = name;\n            property.value = value;\n            result.style.cssProperties.add(property);\n          }\n        });\n      }\n    });\n\n    return result;\n  }\n\n  private final class PeerManagerListener extends PeersRegisteredListener {\n    @Override\n    protected synchronized void onFirstPeerRegistered() {\n      mDocument.addRef();\n    }\n\n    @Override\n    protected synchronized void onLastPeerUnregistered() {\n      mDocument.release();\n    }\n  }\n\n  private static class CSSComputedStyleProperty {\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public String value;\n  }\n\n  private static class RuleMatch {\n    @JsonProperty\n    public CSSRule rule;\n\n    @JsonProperty\n    public List<Integer> matchingSelectors;\n  }\n\n  private static class SelectorList {\n    @JsonProperty\n    public List<Selector> selectors;\n\n    @JsonProperty\n    public String text;\n  }\n\n  private static class SourceRange {\n    @JsonProperty(required = true)\n    public int startLine;\n\n    @JsonProperty(required = true)\n    public int startColumn;\n\n    @JsonProperty(required = true)\n    public int endLine;\n\n    @JsonProperty(required = true)\n    public int endColumn;\n  }\n\n  private static class Selector {\n    @JsonProperty(required = true)\n    public String value;\n\n    @JsonProperty\n    public SourceRange range;\n  }\n\n  private static class CSSRule {\n    @JsonProperty\n    public String styleSheetId;\n\n    @JsonProperty(required = true)\n    public SelectorList selectorList;\n\n    @JsonProperty\n    public Origin origin;\n\n    @JsonProperty\n    public CSSStyle style;\n  }\n\n  private static class CSSStyle {\n    @JsonProperty\n    public String styleSheetId;\n\n    @JsonProperty(required = true)\n    public List<CSSProperty> cssProperties;\n\n    @JsonProperty\n    public List<ShorthandEntry> shorthandEntries;\n\n    @JsonProperty\n    public String cssText;\n\n    @JsonProperty\n    public SourceRange range;\n  }\n\n  private static class ShorthandEntry {\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public String value;\n\n    @JsonProperty\n    public Boolean important;\n  }\n\n  private static class CSSProperty {\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public String value;\n\n    @JsonProperty\n    public Boolean important;\n\n    @JsonProperty\n    public Boolean implicit;\n\n    @JsonProperty\n    public String text;\n\n    @JsonProperty\n    public Boolean parsedOk;\n\n    @JsonProperty\n    public Boolean disabled;\n\n    @JsonProperty\n    public SourceRange range;\n  }\n\n  private static class PseudoIdMatches {\n    @JsonProperty(required = true)\n    public int pseudoId;\n\n    @JsonProperty(required = true)\n    public List<RuleMatch> matches;\n\n    public PseudoIdMatches() {\n      this.matches = new ArrayList<>();\n    }\n  }\n\n  private static class GetComputedStyleForNodeRequest {\n    @JsonProperty(required = true)\n    public int nodeId;\n  }\n\n  private static class InheritedStyleEntry {\n    @JsonProperty(required = true)\n    public CSSStyle inlineStyle;\n\n    @JsonProperty(required = true)\n    public List<RuleMatch> matchedCSSRules;\n  }\n\n  private static class GetComputedStyleForNodeResult implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<CSSComputedStyleProperty> computedStyle;\n  }\n\n  private static class GetMatchedStylesForNodeRequest implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty\n    public Boolean excludePseudo;\n\n    @JsonProperty\n    public Boolean excludeInherited;\n  }\n\n  private static class GetMatchedStylesForNodeResult implements JsonRpcResult {\n    @JsonProperty\n    public List<RuleMatch> matchedCSSRules;\n\n    @JsonProperty\n    public List<PseudoIdMatches> pseudoElements;\n\n    @JsonProperty\n    public List<InheritedStyleEntry> inherited;\n  }\n\n  private static class SetPropertyTextRequest implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public String styleSheetId;\n\n    @JsonProperty(required = true)\n    public String text;\n  }\n\n  private static class SetPropertyTextResult implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public CSSStyle style;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Console.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.annotation.SuppressLint;\n\nimport com.facebook.stetho.inspector.console.ConsolePeerManager;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\n\nimport org.json.JSONObject;\n\npublic class Console implements ChromeDevtoolsDomain {\n  public Console() {\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    ConsolePeerManager.getOrCreateInstance().addPeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n    ConsolePeerManager.getOrCreateInstance().removePeer(peer);\n  }\n\n  @SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\n  public static class MessageAddedRequest {\n    @JsonProperty(required = true)\n    public ConsoleMessage message;\n  }\n\n  @SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\n  public static class ConsoleMessage {\n    @JsonProperty(required = true)\n    public MessageSource source;\n\n    @JsonProperty(required = true)\n    public MessageLevel level;\n\n    @JsonProperty(required = true)\n    public String text;\n  }\n\n  public enum MessageSource {\n    XML(\"xml\"),\n    JAVASCRIPT(\"javascript\"),\n    NETWORK(\"network\"),\n    CONSOLE_API(\"console-api\"),\n    STORAGE(\"storage\"),\n    APPCACHE(\"appcache\"),\n    RENDERING(\"rendering\"),\n    CSS(\"css\"),\n    SECURITY(\"security\"),\n    OTHER(\"other\");\n\n    private final String mProtocolValue;\n\n    private MessageSource(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n  public enum MessageLevel {\n    LOG(\"log\"),\n    WARNING(\"warning\"),\n    ERROR(\"error\"),\n    DEBUG(\"debug\");\n\n    private final String mProtocolValue;\n\n    private MessageLevel(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n  @SuppressLint({ \"UsingDefaultJsonDeserializer\", \"EmptyJsonPropertyUse\" })\n  public static class CallFrame {\n    @JsonProperty(required = true)\n    public String functionName;\n\n    @JsonProperty(required = true)\n    public String url;\n\n    @JsonProperty(required = true)\n    public int lineNumber;\n\n    @JsonProperty(required = true)\n    public int columnNumber;\n\n    public CallFrame() {\n    }\n\n    public CallFrame(String functionName, String url, int lineNumber, int columnNumber) {\n      this.functionName = functionName;\n      this.url = url;\n      this.lineNumber = lineNumber;\n      this.columnNumber = columnNumber;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/DOM.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.graphics.Color;\nimport com.facebook.stetho.common.Accumulator;\nimport com.facebook.stetho.common.ArrayListAccumulator;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.UncheckedCallable;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.elements.DocumentView;\nimport com.facebook.stetho.inspector.elements.Document;\nimport com.facebook.stetho.inspector.elements.ElementInfo;\nimport com.facebook.stetho.inspector.elements.NodeDescriptor;\nimport com.facebook.stetho.inspector.elements.NodeType;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.helper.PeersRegisteredListener;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\nimport javax.annotation.Nullable;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class DOM implements ChromeDevtoolsDomain {\n  private final ObjectMapper mObjectMapper;\n  private final Document mDocument;\n  private final Map<String, List<Integer>> mSearchResults;\n  private final AtomicInteger mResultCounter;\n  private final ChromePeerManager mPeerManager;\n  private final DocumentUpdateListener mListener;\n\n  private ChildNodeRemovedEvent mCachedChildNodeRemovedEvent;\n  private ChildNodeInsertedEvent mCachedChildNodeInsertedEvent;\n\n  public DOM(Document document) {\n    mObjectMapper = new ObjectMapper();\n    mDocument = Util.throwIfNull(document);\n    mSearchResults = Collections.synchronizedMap(\n      new HashMap<String, List<Integer>>());\n    mResultCounter = new AtomicInteger(0);\n    mPeerManager = new ChromePeerManager();\n    mPeerManager.setListener(new PeerManagerListener());\n    mListener = new DocumentUpdateListener();\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    mPeerManager.addPeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n    mPeerManager.removePeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getDocument(JsonRpcPeer peer, JSONObject params) {\n    final GetDocumentResponse result = new GetDocumentResponse();\n\n    result.root = mDocument.postAndWait(new UncheckedCallable<Node>() {\n      @Override\n      public Node call() {\n        Object element = mDocument.getRootElement();\n        return createNodeForElement(element, mDocument.getDocumentView(), null);\n      }\n    });\n\n    return result;\n  }\n\n  @ChromeDevtoolsMethod\n  public void highlightNode(JsonRpcPeer peer, JSONObject params) {\n    final HighlightNodeRequest request =\n      mObjectMapper.convertValue(params, HighlightNodeRequest.class);\n    if (request.nodeId == null) {\n      LogUtil.w(\"DOM.highlightNode was not given a nodeId; JS objectId is not supported\");\n      return;\n    }\n\n    final RGBAColor contentColor = request.highlightConfig.contentColor;\n    if (contentColor == null) {\n      LogUtil.w(\"DOM.highlightNode was not given a color to highlight with\");\n      return;\n    }\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        Object element = mDocument.getElementForNodeId(request.nodeId);\n        if (element != null) {\n          mDocument.highlightElement(element, contentColor.getColor());\n        }\n      }\n    });\n  }\n\n  @ChromeDevtoolsMethod\n  public void hideHighlight(JsonRpcPeer peer, JSONObject params) {\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        mDocument.hideHighlight();\n      }\n    });\n  }\n\n  @ChromeDevtoolsMethod\n  public ResolveNodeResponse resolveNode(JsonRpcPeer peer, JSONObject params)\n      throws JsonRpcException {\n    final ResolveNodeRequest request = mObjectMapper.convertValue(params, ResolveNodeRequest.class);\n\n    final Object element = mDocument.postAndWait(new UncheckedCallable<Object>() {\n      @Override\n      public Object call() {\n        return mDocument.getElementForNodeId(request.nodeId);\n      }\n    });\n\n    if (element == null) {\n      throw new JsonRpcException(\n          new JsonRpcError(\n              JsonRpcError.ErrorCode.INVALID_PARAMS,\n              \"No known nodeId=\" + request.nodeId,\n              null /* data */));\n    }\n\n    int mappedObjectId = Runtime.mapObject(peer, element);\n\n    Runtime.RemoteObject remoteObject = new Runtime.RemoteObject();\n    remoteObject.type = Runtime.ObjectType.OBJECT;\n    remoteObject.subtype = Runtime.ObjectSubType.NODE;\n    remoteObject.className = element.getClass().getName();\n    remoteObject.value = null; // not a primitive\n    remoteObject.description = null; // not sure what this does...\n    remoteObject.objectId = String.valueOf(mappedObjectId);\n    ResolveNodeResponse response = new ResolveNodeResponse();\n    response.object = remoteObject;\n\n    return response;\n  }\n\n  @ChromeDevtoolsMethod\n  public void setAttributesAsText(JsonRpcPeer peer, JSONObject params) {\n    final SetAttributesAsTextRequest request = mObjectMapper.convertValue(\n        params,\n        SetAttributesAsTextRequest.class);\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        Object element = mDocument.getElementForNodeId(request.nodeId);\n        if (element != null) {\n          mDocument.setAttributesAsText(element, request.text);\n        }\n      }\n    });\n  }\n\n  @ChromeDevtoolsMethod\n  public void setInspectModeEnabled(JsonRpcPeer peer, JSONObject params) {\n    final SetInspectModeEnabledRequest request = mObjectMapper.convertValue(\n        params,\n        SetInspectModeEnabledRequest.class);\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        mDocument.setInspectModeEnabled(request.enabled);\n      }\n    });\n  }\n\n  @ChromeDevtoolsMethod\n  public PerformSearchResponse performSearch(JsonRpcPeer peer, final JSONObject params) {\n    final PerformSearchRequest request = mObjectMapper.convertValue(\n        params,\n        PerformSearchRequest.class);\n\n    final ArrayListAccumulator<Integer> resultNodeIds = new ArrayListAccumulator<>();\n\n    mDocument.postAndWait(new Runnable() {\n      @Override\n      public void run() {\n        mDocument.findMatchingElements(request.query, resultNodeIds);\n      }\n    });\n\n    // Each search action has a unique ID so that\n    // it can be queried later.\n    final String searchId = String.valueOf(mResultCounter.getAndIncrement());\n\n    mSearchResults.put(searchId, resultNodeIds);\n\n    final PerformSearchResponse response = new PerformSearchResponse();\n    response.searchId = searchId;\n    response.resultCount = resultNodeIds.size();\n\n    return response;\n  }\n\n  @ChromeDevtoolsMethod\n  public GetSearchResultsResponse getSearchResults(JsonRpcPeer peer, JSONObject params) {\n    final GetSearchResultsRequest request = mObjectMapper.convertValue(\n        params,\n        GetSearchResultsRequest.class);\n\n    if (request.searchId == null) {\n      LogUtil.w(\"searchId may not be null\");\n      return null;\n    }\n\n    final List<Integer> results = mSearchResults.get(request.searchId);\n\n    if (results == null) {\n      LogUtil.w(\"\\\"\" + request.searchId + \"\\\" is not a valid reference to a search result\");\n      return null;\n    }\n\n    final List<Integer> resultsRange = results.subList(request.fromIndex, request.toIndex);\n\n    final GetSearchResultsResponse response = new GetSearchResultsResponse();\n    response.nodeIds = resultsRange;\n\n    return response;\n  }\n\n  @ChromeDevtoolsMethod\n  public void discardSearchResults(JsonRpcPeer peer, JSONObject params) {\n    final DiscardSearchResultsRequest request = mObjectMapper.convertValue(\n      params,\n      DiscardSearchResultsRequest.class);\n\n    if (request.searchId != null) {\n      mSearchResults.remove(request.searchId);\n    }\n  }\n\n  private Node createNodeForElement(\n      Object element,\n      DocumentView view,\n      @Nullable Accumulator<Object> processedElements) {\n    if (processedElements != null) {\n      processedElements.store(element);\n    }\n\n    NodeDescriptor descriptor = mDocument.getNodeDescriptor(element);\n\n    Node node = new DOM.Node();\n    node.nodeId = mDocument.getNodeIdForElement(element);\n    node.nodeType = descriptor.getNodeType(element);\n    node.nodeName = descriptor.getNodeName(element);\n    node.localName = descriptor.getLocalName(element);\n    node.nodeValue = descriptor.getNodeValue(element);\n\n    Document.AttributeListAccumulator accumulator = new Document.AttributeListAccumulator();\n    descriptor.getAttributes(element, accumulator);\n\n    // Attributes\n    node.attributes = accumulator;\n\n    // Children\n    ElementInfo elementInfo = view.getElementInfo(element);\n    List<Node> childrenNodes = (elementInfo.children.size() == 0)\n        ? Collections.<Node>emptyList()\n        : new ArrayList<Node>(elementInfo.children.size());\n\n    for (int i = 0, N = elementInfo.children.size(); i < N; ++i) {\n      final Object childElement = elementInfo.children.get(i);\n      Node childNode = createNodeForElement(childElement, view, processedElements);\n      childrenNodes.add(childNode);\n    }\n\n    node.children = childrenNodes;\n    node.childNodeCount = childrenNodes.size();\n\n    return node;\n  }\n\n  private ChildNodeInsertedEvent acquireChildNodeInsertedEvent() {\n    ChildNodeInsertedEvent childNodeInsertedEvent = mCachedChildNodeInsertedEvent;\n    if (childNodeInsertedEvent == null) {\n      childNodeInsertedEvent = new ChildNodeInsertedEvent();\n    }\n    mCachedChildNodeInsertedEvent = null;\n    return childNodeInsertedEvent;\n  }\n\n  private void releaseChildNodeInsertedEvent(ChildNodeInsertedEvent childNodeInsertedEvent) {\n    childNodeInsertedEvent.parentNodeId = -1;\n    childNodeInsertedEvent.previousNodeId = -1;\n    childNodeInsertedEvent.node = null;\n    if (mCachedChildNodeInsertedEvent == null) {\n      mCachedChildNodeInsertedEvent = childNodeInsertedEvent;\n    }\n  }\n\n  private ChildNodeRemovedEvent acquireChildNodeRemovedEvent() {\n    ChildNodeRemovedEvent childNodeRemovedEvent = mCachedChildNodeRemovedEvent;\n    if (childNodeRemovedEvent == null) {\n      childNodeRemovedEvent = new ChildNodeRemovedEvent();\n    }\n    mCachedChildNodeRemovedEvent = null;\n    return childNodeRemovedEvent;\n  }\n\n  private void releaseChildNodeRemovedEvent(ChildNodeRemovedEvent childNodeRemovedEvent) {\n    childNodeRemovedEvent.parentNodeId = -1;\n    childNodeRemovedEvent.nodeId = -1;\n    if (mCachedChildNodeRemovedEvent == null) {\n      mCachedChildNodeRemovedEvent = childNodeRemovedEvent;\n    }\n  }\n\n  private final class DocumentUpdateListener implements Document.UpdateListener {\n    public void onAttributeModified(Object element, String name, String value) {\n      AttributeModifiedEvent message = new AttributeModifiedEvent();\n      message.nodeId = mDocument.getNodeIdForElement(element);\n      message.name = name;\n      message.value = value;\n      mPeerManager.sendNotificationToPeers(\"DOM.attributeModified\", message);\n    }\n\n    public void onAttributeRemoved(Object element, String name) {\n      AttributeRemovedEvent message = new AttributeRemovedEvent();\n      message.nodeId = mDocument.getNodeIdForElement(element);\n      message.name = name;\n      mPeerManager.sendNotificationToPeers(\"DOM.attributeRemoved\", message);\n    }\n\n    public void onInspectRequested(Object element) {\n      Integer nodeId = mDocument.getNodeIdForElement(element);\n      if (nodeId == null) {\n        LogUtil.d(\n            \"DocumentProvider.Listener.onInspectRequested() \" +\n                \"called for a non-mapped node: element=%s\",\n            element);\n      } else {\n        InspectNodeRequestedEvent message = new InspectNodeRequestedEvent();\n        message.nodeId = nodeId;\n        mPeerManager.sendNotificationToPeers(\"DOM.inspectNodeRequested\", message);\n      }\n    }\n\n    public void onChildNodeRemoved(\n        int parentNodeId,\n        int nodeId) {\n      ChildNodeRemovedEvent removedEvent = acquireChildNodeRemovedEvent();\n\n      removedEvent.parentNodeId = parentNodeId;\n      removedEvent.nodeId = nodeId;\n      mPeerManager.sendNotificationToPeers(\"DOM.childNodeRemoved\", removedEvent);\n\n      releaseChildNodeRemovedEvent(removedEvent);\n    }\n\n    public void onChildNodeInserted(\n        DocumentView view,\n        Object element,\n        int parentNodeId,\n        int previousNodeId,\n        Accumulator<Object> insertedElements) {\n      ChildNodeInsertedEvent insertedEvent = acquireChildNodeInsertedEvent();\n\n      insertedEvent.parentNodeId = parentNodeId;\n      insertedEvent.previousNodeId = previousNodeId;\n      insertedEvent.node = createNodeForElement(element, view, insertedElements);\n\n      mPeerManager.sendNotificationToPeers(\"DOM.childNodeInserted\", insertedEvent);\n\n      releaseChildNodeInsertedEvent(insertedEvent);\n    }\n  }\n\n  private final class PeerManagerListener extends PeersRegisteredListener {\n    @Override\n    protected synchronized void onFirstPeerRegistered() {\n      mDocument.addRef();\n      mDocument.addUpdateListener(mListener);\n    }\n\n    @Override\n    protected synchronized void onLastPeerUnregistered() {\n      mSearchResults.clear();\n      mDocument.removeUpdateListener(mListener);\n      mDocument.release();\n    }\n  }\n\n  private static class GetDocumentResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public Node root;\n  }\n\n  private static class Node implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty(required = true)\n    public NodeType nodeType;\n\n    @JsonProperty(required = true)\n    public String nodeName;\n\n    @JsonProperty(required = true)\n    public String localName;\n\n    @JsonProperty(required = true)\n    public String nodeValue;\n\n    @JsonProperty\n    public Integer childNodeCount;\n\n    @JsonProperty\n    public List<Node> children;\n\n    @JsonProperty\n    public List<String> attributes;\n  }\n\n  private static class AttributeModifiedEvent {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public String value;\n  }\n\n  private static class AttributeRemovedEvent {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty(required = true)\n    public String name;\n  }\n\n  private static class ChildNodeInsertedEvent {\n    @JsonProperty(required = true)\n    public int parentNodeId;\n\n    @JsonProperty(required = true)\n    public int previousNodeId;\n\n    @JsonProperty(required = true)\n    public Node node;\n  }\n\n  private static class ChildNodeRemovedEvent {\n    @JsonProperty(required = true)\n    public int parentNodeId;\n\n    @JsonProperty(required = true)\n    public int nodeId;\n  }\n\n  private static class HighlightNodeRequest {\n    @JsonProperty(required = true)\n    public HighlightConfig highlightConfig;\n\n    @JsonProperty\n    public Integer nodeId;\n\n    @JsonProperty\n    public String objectId;\n  }\n\n  private static class HighlightConfig {\n    @JsonProperty\n    public RGBAColor contentColor;\n  }\n\n  private static class InspectNodeRequestedEvent {\n    @JsonProperty\n    public int nodeId;\n  }\n\n  private static class SetInspectModeEnabledRequest {\n    @JsonProperty(required = true)\n    public boolean enabled;\n\n    @JsonProperty\n    public Boolean inspectShadowDOM;\n\n    @JsonProperty\n    public HighlightConfig highlightConfig;\n  }\n\n  private static class RGBAColor {\n    @JsonProperty(required = true)\n    public int r;\n\n    @JsonProperty(required = true)\n    public int g;\n\n    @JsonProperty(required = true)\n    public int b;\n\n    @JsonProperty\n    public Double a;\n\n    public int getColor() {\n      byte alpha;\n      if (this.a == null) {\n        alpha = (byte)255;\n      } else {\n        long aLong = Math.round(this.a * 255.0);\n        alpha = (aLong < 0) ? (byte)0 : (aLong >= 255) ? (byte)255 : (byte)aLong;\n      }\n\n      return Color.argb(alpha, this.r, this.g, this.b);\n    }\n  }\n\n  private static class ResolveNodeRequest {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty\n    public String objectGroup;\n  }\n\n  private static class SetAttributesAsTextRequest {\n    @JsonProperty(required = true)\n    public int nodeId;\n\n    @JsonProperty(required = true)\n    public String text;\n  }\n\n  private static class ResolveNodeResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public Runtime.RemoteObject object;\n  }\n\n  private static class PerformSearchRequest {\n    @JsonProperty(required = true)\n    public String query;\n\n    @JsonProperty\n    public Boolean includeUserAgentShadowDOM;\n  }\n\n  private static class PerformSearchResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public String searchId;\n\n    @JsonProperty(required = true)\n    public int resultCount;\n  }\n\n  private static class GetSearchResultsRequest {\n    @JsonProperty(required = true)\n    public String searchId;\n\n    @JsonProperty(required = true)\n    public int fromIndex;\n\n    @JsonProperty(required = true)\n    public int toIndex;\n  }\n\n  private static class GetSearchResultsResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<Integer> nodeIds;\n  }\n\n  private static class DiscardSearchResultsRequest {\n    @JsonProperty(required = true)\n    public String searchId;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/DOMStorage.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\nimport android.content.SharedPreferences;\n\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.domstorage.DOMStoragePeerManager;\nimport com.facebook.stetho.inspector.domstorage.SharedPreferencesHelper;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Locale;\nimport java.util.Map;\nimport java.util.Set;\n\npublic class DOMStorage implements ChromeDevtoolsDomain {\n  private final Context mContext;\n  private final DOMStoragePeerManager mDOMStoragePeerManager;\n  private final ObjectMapper mObjectMapper = new ObjectMapper();\n\n  public DOMStorage(Context context) {\n    mContext = context;\n    mDOMStoragePeerManager = new DOMStoragePeerManager(context);\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    mDOMStoragePeerManager.addPeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n    mDOMStoragePeerManager.removePeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getDOMStorageItems(JsonRpcPeer peer, JSONObject params)\n      throws JSONException {\n    StorageId storage = mObjectMapper.convertValue(\n        params.getJSONObject(\"storageId\"),\n        StorageId.class);\n\n    ArrayList<List<String>> entries = new ArrayList<List<String>>();\n    String prefTag = storage.securityOrigin;\n    if (storage.isLocalStorage) {\n      SharedPreferences prefs = mContext.getSharedPreferences(prefTag, Context.MODE_PRIVATE);\n      for (Map.Entry<String, ?> prefsEntry : SharedPreferencesHelper.getSharedPreferenceEntriesSorted(prefs)) {\n        ArrayList<String> entry = new ArrayList<String>(2);\n        entry.add(prefsEntry.getKey());\n        entry.add(SharedPreferencesHelper.valueToString(prefsEntry.getValue()));\n        entries.add(entry);\n      }\n    }\n\n    GetDOMStorageItemsResult result = new GetDOMStorageItemsResult();\n    result.entries = entries;\n    return result;\n  }\n\n  @ChromeDevtoolsMethod\n  public void setDOMStorageItem(JsonRpcPeer peer, JSONObject params)\n      throws JSONException, JsonRpcException {\n    StorageId storage = mObjectMapper.convertValue(\n        params.getJSONObject(\"storageId\"),\n        StorageId.class);\n    String key = params.getString(\"key\");\n    String value = params.getString(\"value\");\n\n    if (storage.isLocalStorage) {\n      SharedPreferences prefs = mContext.getSharedPreferences(\n          storage.securityOrigin,\n          Context.MODE_PRIVATE);\n      Object existingValue = prefs.getAll().get(key);\n      try {\n        if (existingValue == null) {\n          throw new DOMStorageAssignmentException(\n              \"Unsupported: cannot add new key \" + key + \" due to lack of type inference\");\n        } else {\n          SharedPreferences.Editor editor = prefs.edit();\n          try {\n            assignByType(editor, key, SharedPreferencesHelper.valueFromString(value, existingValue));\n            editor.apply();\n          } catch (IllegalArgumentException e) {\n            throw new DOMStorageAssignmentException(\n                String.format(Locale.US,\n                    \"Type mismatch setting %s to %s (expected %s)\",\n                    key,\n                    value,\n                    existingValue.getClass().getSimpleName()));\n          }\n        }\n      } catch (DOMStorageAssignmentException e) {\n        CLog.writeToConsole(\n            mDOMStoragePeerManager,\n            Console.MessageLevel.ERROR,\n            Console.MessageSource.STORAGE,\n            e.getMessage());\n\n        // Force the DevTools UI to refresh with the old value again (it assumes that the set\n        // operation succeeded).  Note that we should be able to do this by throwing\n        // JsonRpcException but the UI doesn't respect setDOMStorageItem failure.\n        if (prefs.contains(key)) {\n          mDOMStoragePeerManager.signalItemUpdated(\n              storage,\n              key,\n              value,\n              SharedPreferencesHelper.valueToString(existingValue));\n        } else {\n          mDOMStoragePeerManager.signalItemRemoved(storage, key);\n        }\n      }\n    }\n  }\n\n  @ChromeDevtoolsMethod\n  public void removeDOMStorageItem(JsonRpcPeer peer, JSONObject params) throws JSONException {\n    StorageId storage = mObjectMapper.convertValue(\n        params.getJSONObject(\"storageId\"),\n        StorageId.class);\n    String key = params.getString(\"key\");\n\n    if (storage.isLocalStorage) {\n      SharedPreferences prefs = mContext.getSharedPreferences(\n          storage.securityOrigin,\n          Context.MODE_PRIVATE);\n      prefs.edit().remove(key).apply();\n    }\n  }\n\n  private static void assignByType(\n      SharedPreferences.Editor editor,\n      String key,\n      Object value)\n      throws IllegalArgumentException {\n    if (value instanceof Integer) {\n      editor.putInt(key, (Integer)value);\n    } else if (value instanceof Long) {\n      editor.putLong(key, (Long)value);\n    } else if (value instanceof Float) {\n      editor.putFloat(key, (Float)value);\n    } else if (value instanceof Boolean) {\n      editor.putBoolean(key, (Boolean)value);\n    } else if (value instanceof String) {\n      editor.putString(key, (String)value);\n    } else if (value instanceof Set) {\n      editor.putStringSet(key, (Set<String>)value);\n    } else {\n      throw new IllegalArgumentException(\"Unsupported type=\" + value.getClass().getName());\n    }\n  }\n\n  public static class StorageId {\n    @JsonProperty(required = true)\n    public String securityOrigin;\n\n    @JsonProperty(required = true)\n    public boolean isLocalStorage;\n  }\n\n  private static class GetDOMStorageItemsResult implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<List<String>> entries;\n  }\n\n  public static class DomStorageItemsClearedParams {\n    @JsonProperty(required = true)\n    public StorageId storageId;\n  }\n\n  public static class DomStorageItemRemovedParams {\n    @JsonProperty(required = true)\n    public StorageId storageId;\n\n    @JsonProperty(required = true)\n    public String key;\n  }\n\n  public static class DomStorageItemAddedParams {\n    @JsonProperty(required = true)\n    public StorageId storageId;\n\n    @JsonProperty(required = true)\n    public String key;\n\n    @JsonProperty(required = true)\n    public String newValue;\n  }\n\n  public static class DomStorageItemUpdatedParams {\n    @JsonProperty(required = true)\n    public StorageId storageId;\n\n    @JsonProperty(required = true)\n    public String key;\n\n    @JsonProperty(required = true)\n    public String oldValue;\n\n    @JsonProperty(required = true)\n    public String newValue;\n  }\n\n  /**\n   * Exception thrown internally when we fail to honor {@link #setDOMStorageItem}.\n   */\n  private static class DOMStorageAssignmentException extends Exception {\n    public DOMStorageAssignmentException(String message) {\n      super(message);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Database.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteException;\nimport android.util.SparseArray;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.helper.ObjectIdMapper;\nimport com.facebook.stetho.inspector.helper.PeersRegisteredListener;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\n\nimport org.json.JSONObject;\n\nimport java.io.UnsupportedEncodingException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport javax.annotation.concurrent.GuardedBy;\nimport javax.annotation.concurrent.ThreadSafe;\n\npublic class Database implements ChromeDevtoolsDomain {\n  /**\n   * The protocol doesn't offer an efficient means of pagination or anything like that so\n   * we'll just cap the result list to some arbitrarily large number that I think folks will\n   * actually need in practice.\n   * <p>\n   * Note that when this limit is exceeded, a dummy row will be introduced that indicates\n   * truncation occurred.\n   */\n  private static final int MAX_EXECUTE_RESULTS = 250;\n\n  /**\n   * Maximum length of a BLOB field before we stop trying to interpret it and just\n   * return {@link #UNKNOWN_BLOB_LABEL}\n   */\n  private static final int MAX_BLOB_LENGTH = 512;\n\n  /**\n   * Label to use when a BLOB column cannot be converted to a string.\n   */\n  private static final String UNKNOWN_BLOB_LABEL = \"{blob}\";\n\n  private List<DatabaseDriver2> mDatabaseDrivers;\n  private final ChromePeerManager mChromePeerManager;\n  private final DatabasePeerRegistrationListener mPeerListener;\n  private final ObjectMapper mObjectMapper;\n\n  /**\n   * Constructs the object.\n   */\n  public Database() {\n    mDatabaseDrivers = new ArrayList<>();\n    mChromePeerManager = new ChromePeerManager();\n    mPeerListener = new DatabasePeerRegistrationListener(mDatabaseDrivers);\n    mChromePeerManager.setListener(mPeerListener);\n    mObjectMapper = new ObjectMapper();\n  }\n\n  public void add(DatabaseDriver2 databaseDriver) {\n    mDatabaseDrivers.add(databaseDriver);\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    mChromePeerManager.addPeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n    mChromePeerManager.removePeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getDatabaseTableNames(JsonRpcPeer peer, JSONObject params)\n      throws JsonRpcException {\n    GetDatabaseTableNamesRequest request = mObjectMapper.convertValue(params,\n        GetDatabaseTableNamesRequest.class);\n\n    String databaseId = request.databaseId;\n    DatabaseDescriptorHolder holder =\n        mPeerListener.getDatabaseDescriptorHolder(databaseId);\n\n    try {\n      GetDatabaseTableNamesResponse response = new GetDatabaseTableNamesResponse();\n      response.tableNames = holder.driver.getTableNames(holder.descriptor);\n      return response;\n    } catch (SQLiteException e) {\n      throw new JsonRpcException(\n          new JsonRpcError(\n              JsonRpcError.ErrorCode.INVALID_REQUEST,\n              e.toString(),\n              null /* data */));\n    }\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult executeSQL(JsonRpcPeer peer, JSONObject params) {\n    ExecuteSQLRequest request = mObjectMapper.convertValue(params,\n        ExecuteSQLRequest.class);\n\n    DatabaseDescriptorHolder holder =\n        mPeerListener.getDatabaseDescriptorHolder(request.databaseId);\n\n    try {\n      return holder.driver.executeSQL(\n          holder.descriptor,\n          request.query,\n          new DatabaseDriver.ExecuteResultHandler<ExecuteSQLResponse>() {\n        @Override\n        public ExecuteSQLResponse handleRawQuery() throws SQLiteException {\n          ExecuteSQLResponse response = new ExecuteSQLResponse();\n          // This is done because the inspector UI likes to delete rows if you give them no\n          // name/value list\n          response.columnNames = Collections.singletonList(\"success\");\n          response.values = Collections.singletonList(\"true\");\n          return response;\n        }\n\n        @Override\n        public ExecuteSQLResponse handleSelect(Cursor result) throws SQLiteException {\n          ExecuteSQLResponse response = new ExecuteSQLResponse();\n          response.columnNames = Arrays.asList(result.getColumnNames());\n          response.values = flattenRows(result, MAX_EXECUTE_RESULTS);\n          return response;\n        }\n\n        @Override\n        public ExecuteSQLResponse handleInsert(long insertedId) throws SQLiteException {\n          ExecuteSQLResponse response = new ExecuteSQLResponse();\n          response.columnNames = Collections.singletonList(\"ID of last inserted row\");\n          response.values = Collections.singletonList(String.valueOf(insertedId));\n          return response;\n        }\n\n        @Override\n        public ExecuteSQLResponse handleUpdateDelete(int count) throws SQLiteException {\n          ExecuteSQLResponse response = new ExecuteSQLResponse();\n          response.columnNames = Collections.singletonList(\"Modified rows\");\n          response.values = Collections.singletonList(String.valueOf(count));\n          return response;\n        }\n      });\n    } catch (RuntimeException e) {\n      LogUtil.e(e, \"Exception executing: %s\", request.query);\n\n      Error error = new Error();\n      error.code = 0;\n      error.message = e.getMessage();\n      ExecuteSQLResponse response = new ExecuteSQLResponse();\n      response.sqlError = error;\n      return response;\n    }\n  }\n\n  /**\n   * Flatten all columns and all rows of a cursor to a single array.  The array cannot be\n   * interpreted meaningfully without the number of columns.\n   *\n   * @param cursor\n   * @param limit Maximum number of rows to process.\n   * @return List of Java primitives matching the value type of each column, converted to\n   *      strings.\n   */\n  private static ArrayList<String> flattenRows(Cursor cursor, int limit) {\n    Util.throwIfNot(limit >= 0);\n    ArrayList<String> flatList = new ArrayList<>();\n    final int numColumns = cursor.getColumnCount();\n    for (int row = 0; row < limit && cursor.moveToNext(); row++) {\n      for (int column = 0; column < numColumns; column++) {\n        switch (cursor.getType(column)) {\n          case Cursor.FIELD_TYPE_NULL:\n            flatList.add(null);\n            break;\n          case Cursor.FIELD_TYPE_INTEGER:\n            flatList.add(String.valueOf(cursor.getLong(column)));\n            break;\n          case Cursor.FIELD_TYPE_FLOAT:\n            flatList.add(String.valueOf(cursor.getDouble(column)));\n            break;\n          case Cursor.FIELD_TYPE_BLOB:\n            flatList.add(blobToString(cursor.getBlob(column)));\n            break;\n          case Cursor.FIELD_TYPE_STRING:\n          default:\n            flatList.add(cursor.getString(column));\n            break;\n        }\n      }\n    }\n    if (!cursor.isAfterLast()) {\n      for (int column = 0; column < numColumns; column++) {\n        flatList.add(\"{truncated}\");\n      }\n    }\n    return flatList;\n  }\n\n  private static String blobToString(byte[] blob) {\n    if (blob.length <= MAX_BLOB_LENGTH) {\n      if (fastIsAscii(blob)) {\n        try {\n          return new String(blob, \"US-ASCII\");\n        } catch (UnsupportedEncodingException e) {\n          // Fall through...\n        }\n      }\n    }\n    return UNKNOWN_BLOB_LABEL;\n  }\n\n  private static boolean fastIsAscii(byte[] blob) {\n    for (byte b : blob) {\n      if ((b & ~0x7f) != 0) {\n        return false;\n      }\n    }\n    return true;\n  }\n\n  @ThreadSafe\n  private static class DatabasePeerRegistrationListener extends PeersRegisteredListener {\n    private final List<DatabaseDriver2> mDatabaseDrivers;\n\n    @GuardedBy(\"this\")\n    private final SparseArray<DatabaseDescriptorHolder> mDatabaseHolders = new SparseArray<>();\n\n    @GuardedBy(\"this\")\n    private final ObjectIdMapper mDatabaseIdMapper = new ObjectIdMapper();\n\n    private DatabasePeerRegistrationListener(List<DatabaseDriver2> databaseDrivers) {\n      mDatabaseDrivers = databaseDrivers;\n    }\n\n    public DatabaseDescriptorHolder getDatabaseDescriptorHolder(String databaseId) {\n      return mDatabaseHolders.get(Integer.parseInt(databaseId));\n    }\n\n    @Override\n    protected synchronized void onFirstPeerRegistered() {\n      for (DatabaseDriver2<?> driver : mDatabaseDrivers) {\n        for (DatabaseDescriptor desc : driver.getDatabaseNames()) {\n          Integer databaseId = mDatabaseIdMapper.getIdForObject(desc);\n          if (databaseId == null) {\n            databaseId = mDatabaseIdMapper.putObject(desc);\n            mDatabaseHolders.put(\n                databaseId,\n                new DatabaseDescriptorHolder(driver, desc));\n          }\n        }\n      }\n    }\n\n    @Override\n    protected synchronized void onLastPeerUnregistered() {\n      mDatabaseIdMapper.clear();\n      mDatabaseHolders.clear();\n    }\n\n    @Override\n    protected synchronized void onPeerAdded(JsonRpcPeer peer) {\n      for (int i = 0, N = mDatabaseHolders.size(); i < N; i++) {\n        int id = mDatabaseHolders.keyAt(i);\n        DatabaseDescriptorHolder holder = mDatabaseHolders.valueAt(i);\n\n        Database.DatabaseObject databaseParams = new Database.DatabaseObject();\n        databaseParams.id = String.valueOf(id);\n        databaseParams.name = holder.descriptor.name();\n        databaseParams.domain = holder.driver.getContext().getPackageName();\n        databaseParams.version = \"N/A\";\n        Database.AddDatabaseEvent eventParams = new Database.AddDatabaseEvent();\n        eventParams.database = databaseParams;\n        peer.invokeMethod(\"Database.addDatabase\", eventParams, null /* callback */);\n      }\n    }\n\n    @Override\n    protected synchronized void onPeerRemoved(JsonRpcPeer peer) {\n      // Nothing to do on each peer removal...\n    }\n  }\n\n  private static class DatabaseDescriptorHolder {\n    public final DatabaseDriver2 driver;\n    public final DatabaseDescriptor descriptor;\n\n    public DatabaseDescriptorHolder(DatabaseDriver2 driver, DatabaseDescriptor descriptor) {\n      this.driver = driver;\n      this.descriptor = descriptor;\n    }\n  }\n\n  private static class GetDatabaseTableNamesRequest {\n    @JsonProperty(required = true)\n    public String databaseId;\n  }\n\n  private static class GetDatabaseTableNamesResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<String> tableNames;\n  }\n\n  public static class ExecuteSQLRequest {\n    @JsonProperty(required = true)\n    public String databaseId;\n\n    @JsonProperty(required = true)\n    public String query;\n  }\n\n  public static class ExecuteSQLResponse implements JsonRpcResult {\n    @JsonProperty\n    public List<String> columnNames;\n\n    @JsonProperty\n    public List<String> values;\n\n    @JsonProperty\n    public Error sqlError;\n  }\n\n  public static class AddDatabaseEvent {\n    @JsonProperty(required = true)\n    public DatabaseObject database;\n  }\n\n  public static class DatabaseObject {\n    @JsonProperty(required = true)\n    public String id;\n\n    @JsonProperty(required = true)\n    public String domain;\n\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public String version;\n  }\n\n  public static class Error {\n    @JsonProperty(required = true)\n    public String message;\n\n    @JsonProperty(required = true)\n    public int code;\n  }\n\n  /**\n   * @deprecated Use {@link DatabaseDriver2} which allows for structured identifiers of database\n   *     objects (such as a file path instead of just a string name) which also serves as a\n   *     namespacing separation of multiple drivers.\n   */\n  @Deprecated\n  public static abstract class DatabaseDriver extends BaseDatabaseDriver<String> {\n    public DatabaseDriver(Context context) {\n      super(context);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/DatabaseConstants.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.os.Build;\n\npublic interface DatabaseConstants {\n\n  /**\n   * Minimum API version required to use the {@link Database}.\n   */\n  public static final int MIN_API_LEVEL = Build.VERSION_CODES.ICE_CREAM_SANDWICH;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/DatabaseDescriptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\npublic interface DatabaseDescriptor {\n  /**\n   * The user visible name for this database.\n   */\n  String name();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/DatabaseDriver2.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\n\n/**\n * Replaces {@link Database.DatabaseDriver} to enforce that the generic type must\n * extend {@link DatabaseDescriptor}.\n */\npublic abstract class DatabaseDriver2<DESC extends DatabaseDescriptor>\n    extends BaseDatabaseDriver<DESC> {\n  public DatabaseDriver2(Context context) {\n    super(context);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Debugger.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\n\nimport org.json.JSONObject;\n\npublic class Debugger implements ChromeDevtoolsDomain {\n  public Debugger() {\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult canSetScriptSource(JsonRpcPeer peer, JSONObject params) {\n    return new SimpleBooleanResult(false);\n  }\n\n  @ChromeDevtoolsMethod\n  public void setPauseOnExceptions(JsonRpcPeer peer, JSONObject params) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/HeapProfiler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\npublic class HeapProfiler implements ChromeDevtoolsDomain {\n  public HeapProfiler() {\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getProfileHeaders(JsonRpcPeer peer, JSONObject params) {\n    ProfileHeaderResponse response = new ProfileHeaderResponse();\n    response.headers = Collections.emptyList();\n    return response;\n  }\n\n  private static class ProfileHeaderResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<ProfileHeader> headers;\n  }\n\n  private static class ProfileHeader {\n    @JsonProperty(required = true)\n    public String title;\n\n    @JsonProperty(required = true)\n    public int uid;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Inspector.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\n\nimport org.json.JSONObject;\n\npublic class Inspector implements ChromeDevtoolsDomain {\n  public Inspector() {\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Network.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport android.content.Context;\n\nimport com.facebook.stetho.common.Util;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.network.AsyncPrettyPrinterInitializer;\nimport com.facebook.stetho.inspector.network.NetworkPeerManager;\nimport com.facebook.stetho.inspector.network.ResponseBodyData;\nimport com.facebook.stetho.inspector.network.ResponseBodyFileManager;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\npublic class Network implements ChromeDevtoolsDomain {\n  private final NetworkPeerManager mNetworkPeerManager;\n  private final ResponseBodyFileManager mResponseBodyFileManager;\n\n  public Network(Context context) {\n    mNetworkPeerManager = NetworkPeerManager.getOrCreateInstance(context);\n    mResponseBodyFileManager = mNetworkPeerManager.getResponseBodyFileManager();\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    mNetworkPeerManager.addPeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n    mNetworkPeerManager.removePeer(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void setUserAgentOverride(JsonRpcPeer peer, JSONObject params) {\n    // Not implemented...\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getResponseBody(JsonRpcPeer peer, JSONObject params)\n      throws JsonRpcException {\n    try {\n      String requestId = params.getString(\"requestId\");\n      return readResponseBody(requestId);\n    } catch (IOException e) {\n      throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR,\n          e.toString(),\n          null /* data */));\n    } catch (JSONException e) {\n      throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR,\n          e.toString(),\n          null /* data */));\n    }\n  }\n\n  private GetResponseBodyResponse readResponseBody(String requestId)\n      throws IOException, JsonRpcException {\n    GetResponseBodyResponse response = new GetResponseBodyResponse();\n    ResponseBodyData bodyData;\n    try {\n      bodyData = mResponseBodyFileManager.readFile(requestId);\n    } catch (OutOfMemoryError e) {\n      throw new JsonRpcException(new JsonRpcError(JsonRpcError.ErrorCode.INTERNAL_ERROR,\n          e.toString(),\n          null /* data */));\n    }\n    response.body = bodyData.data;\n    response.base64Encoded = bodyData.base64Encoded;\n    return response;\n  }\n\n  /**\n   * Method that allows callers to provide an {@link AsyncPrettyPrinterInitializer} that is\n   * responsible for registering all\n   * {@link com.facebook.stetho.inspector.network.AsyncPrettyPrinter}.\n   * Note that AsyncPrettyPrinterInitializer cannot be null and can only be set once.\n   * @param initializer\n   */\n  public void setPrettyPrinterInitializer(AsyncPrettyPrinterInitializer initializer) {\n    Util.throwIfNull(initializer);\n    mNetworkPeerManager.setPrettyPrinterInitializer(initializer);\n  }\n\n  private static class GetResponseBodyResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public String body;\n\n    @JsonProperty(required = true)\n    public boolean base64Encoded;\n  }\n\n  public static class RequestWillBeSentParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public String frameId;\n\n    @JsonProperty(required = true)\n    public String loaderId;\n\n    @JsonProperty(required = true)\n    public String documentURL;\n\n    @JsonProperty(required = true)\n    public Request request;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public Initiator initiator;\n\n    @JsonProperty\n    public Response redirectResponse;\n\n    @JsonProperty\n    public Page.ResourceType type;\n  }\n\n  public static class ResponseReceivedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public String frameId;\n\n    @JsonProperty(required = true)\n    public String loaderId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public Page.ResourceType type;\n\n    @JsonProperty(required = true)\n    public Response response;\n  }\n\n  public static class LoadingFinishedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n  }\n\n  public static class LoadingFailedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public String errorText;\n\n    // Chrome introduced this undocumented new addition that, if not sent, will cause the row\n    // to be removed from the UI and raise a JavaScript exception in the console.  This is\n    // clearly an upstream bug that needs to be fixed, though we can work around it by\n    // providing this new undocumented field.\n    @JsonProperty\n    public Page.ResourceType type;\n  }\n\n  public static class DataReceivedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public int dataLength;\n\n    @JsonProperty(required = true)\n    public int encodedDataLength;\n  }\n\n  public static class Request {\n    @JsonProperty(required = true)\n    public String url;\n\n    @JsonProperty(required = true)\n    public String method;\n\n    @JsonProperty(required = true)\n    public JSONObject headers;\n\n    @JsonProperty\n    public String postData;\n  }\n\n  public static class Initiator {\n    @JsonProperty(required = true)\n    public InitiatorType type;\n\n    @JsonProperty\n    public List<Console.CallFrame> stackTrace;\n  }\n\n  public enum InitiatorType {\n    PARSER(\"parser\"),\n    SCRIPT(\"script\"),\n    OTHER(\"other\");\n\n    private final String mProtocolValue;\n\n    private InitiatorType(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n  public static class Response {\n    @JsonProperty(required = true)\n    public String url;\n\n    @JsonProperty(required = true)\n    public int status;\n\n    @JsonProperty(required = true)\n    public String statusText;\n\n    @JsonProperty(required = true)\n    public JSONObject headers;\n\n    @JsonProperty\n    public String headersText;\n\n    @JsonProperty(required = true)\n    public String mimeType;\n\n    @JsonProperty\n    public JSONObject requestHeaders;\n\n    @JsonProperty\n    public String requestHeadersTest;\n\n    @JsonProperty(required = true)\n    public boolean connectionReused;\n\n    @JsonProperty(required = true)\n    public int connectionId;\n\n    @JsonProperty(required = true)\n    public Boolean fromDiskCache;\n\n    @JsonProperty\n    public ResourceTiming timing;\n  }\n\n  public static class ResourceTiming {\n    @JsonProperty(required = true)\n    public double requestTime;\n\n    @JsonProperty(required = true)\n    public double proxyStart;\n\n    @JsonProperty(required = true)\n    public double proxyEnd;\n\n    @JsonProperty(required = true)\n    public double dnsStart;\n\n    @JsonProperty(required = true)\n    public double dnsEnd;\n\n    @JsonProperty(required = true)\n    public double connectionStart;\n\n    @JsonProperty(required = true)\n    public double connectionEnd;\n\n    @JsonProperty(required = true)\n    public double sslStart;\n\n    @JsonProperty(required = true)\n    public double sslEnd;\n\n    @JsonProperty(required = true)\n    public double sendStart;\n\n    @JsonProperty(required = true)\n    public double sendEnd;\n\n    @JsonProperty(required = true)\n    public double receivedHeadersEnd;\n  }\n\n  public static class WebSocketCreatedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public String url;\n  }\n\n  public static class WebSocketClosedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n  }\n\n  public static class WebSocketWillSendHandshakeRequestParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public double wallTime;\n\n    @JsonProperty(required = true)\n    public WebSocketRequest request;\n  }\n\n  public static class WebSocketRequest {\n    @JsonProperty(required = true)\n    public JSONObject headers;\n  }\n\n  public static class WebSocketHandshakeResponseReceivedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public WebSocketResponse response;\n  }\n\n  public static class WebSocketResponse {\n    @JsonProperty(required = true)\n    public int status;\n\n    @JsonProperty(required = true)\n    public String statusText;\n\n    @JsonProperty(required = true)\n    public JSONObject headers;\n\n    @JsonProperty\n    public String headersText;\n\n    @JsonProperty\n    public JSONObject requestHeaders;\n\n    @JsonProperty\n    public String requestHeadersText;\n  }\n\n  public static class WebSocketFrameReceivedParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public WebSocketFrame response;\n  }\n\n  public static class WebSocketFrameSentParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public WebSocketFrame response;\n  }\n\n  public static class WebSocketFrame {\n    @JsonProperty(required = true)\n    public int opcode;\n\n    @JsonProperty(required = true)\n    public boolean mask;\n\n    @JsonProperty(required = true)\n    public String payloadData;\n  }\n\n  public static class WebSocketFrameErrorParams {\n    @JsonProperty(required = true)\n    public String requestId;\n\n    @JsonProperty(required = true)\n    public double timestamp;\n\n    @JsonProperty(required = true)\n    public String errorMessage;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Page.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\n\nimport com.facebook.stetho.common.ProcessUtil;\nimport com.facebook.stetho.inspector.domstorage.SharedPreferencesHelper;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.inspector.screencast.ScreencastDispatcher;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\n\nimport org.json.JSONObject;\n\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.Iterator;\nimport java.util.List;\n\nimport androidx.annotation.Nullable;\n\npublic class Page implements ChromeDevtoolsDomain {\n  public static final String BANNER = // Note: not using Android resources so we can maintain .jar distribution for now.\n  \"_____/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_______________________________________________/\\\\\\\\\\\\_______________________\\n\" +\n  \" ___/\\\\\\\\\\\\/////////\\\\\\\\\\\\____________________________________________\\\\/\\\\\\\\\\\\_______________________\\n\" +\n  \"  __\\\\//\\\\\\\\\\\\______\\\\///______/\\\\\\\\\\\\_________________________/\\\\\\\\\\\\______\\\\/\\\\\\\\\\\\_______________________\\n\" +\n  \"   ___\\\\////\\\\\\\\\\\\__________/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_____/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\___/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_\\\\/\\\\\\\\\\\\_____________/\\\\\\\\\\\\\\\\\\\\____\\n\" +\n  \"    ______\\\\////\\\\\\\\\\\\______\\\\////\\\\\\\\\\\\////____/\\\\\\\\\\\\/////\\\\\\\\\\\\_\\\\////\\\\\\\\\\\\////__\\\\/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\____/\\\\\\\\\\\\///\\\\\\\\\\\\__\\n\" +\n  \"     _________\\\\////\\\\\\\\\\\\______\\\\/\\\\\\\\\\\\_______/\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\_____\\\\/\\\\\\\\\\\\______\\\\/\\\\\\\\\\\\/////\\\\\\\\\\\\__/\\\\\\\\\\\\__\\\\//\\\\\\\\\\\\_\\n\" +\n  \"      __/\\\\\\\\\\\\______\\\\//\\\\\\\\\\\\_____\\\\/\\\\\\\\\\\\_/\\\\\\\\__\\\\//\\\\\\\\///////______\\\\/\\\\\\\\\\\\_/\\\\\\\\__\\\\/\\\\\\\\\\\\___\\\\/\\\\\\\\\\\\_\\\\//\\\\\\\\\\\\__/\\\\\\\\\\\\__\\n\" +\n  \"       _\\\\///\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\/______\\\\//\\\\\\\\\\\\\\\\\\\\____\\\\//\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\____\\\\//\\\\\\\\\\\\\\\\\\\\___\\\\/\\\\\\\\\\\\___\\\\/\\\\\\\\\\\\__\\\\///\\\\\\\\\\\\\\\\\\\\/___\\n\" +\n  \"        ___\\\\///////////_________\\\\/////______\\\\//////////______\\\\/////____\\\\///____\\\\///_____\\\\/////_____\\n\" +\n  \"         Welcome to Stetho\";\n\n\n  private final Context mContext;\n  private final String mMessage;\n  private final ObjectMapper mObjectMapper = new ObjectMapper();\n  @Nullable\n  private ScreencastDispatcher mScreencastDispatcher;\n\n  public Page(Context context) {\n    this(context, BANNER);\n  }\n\n  public Page(Context context, String message) {\n    mContext = context;\n    mMessage = message;\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n    notifyExecutionContexts(peer);\n    sendWelcomeMessage(peer);\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  private void notifyExecutionContexts(JsonRpcPeer peer) {\n    ExecutionContextDescription context = new ExecutionContextDescription();\n    context.frameId = \"1\";\n    context.id = 1;\n    ExecutionContextCreatedParams params = new ExecutionContextCreatedParams();\n    params.context = context;\n    peer.invokeMethod(\"Runtime.executionContextCreated\", params, null /* callback */);\n  }\n\n  private void sendWelcomeMessage(JsonRpcPeer peer) {\n    Console.ConsoleMessage message = new Console.ConsoleMessage();\n    message.source = Console.MessageSource.JAVASCRIPT;\n    message.level = Console.MessageLevel.LOG;\n    message.text = mMessage + \"\\n\" + \"          Attached to \" + ProcessUtil.getProcessName() + \"\\n\";\n    Console.MessageAddedRequest messageAddedRequest = new Console.MessageAddedRequest();\n    messageAddedRequest.message = message;\n    peer.invokeMethod(\"Console.messageAdded\", messageAddedRequest, null /* callback */);\n  }\n\n  // Dog science...\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getResourceTree(JsonRpcPeer peer, JSONObject params) {\n    // The DOMStorage module expects one key/value store per \"security origin\" which has a 1:1\n    // relationship with resource tree frames.\n    List<String> prefsTags = SharedPreferencesHelper.getSharedPreferenceTags(mContext);\n    Iterator<String> prefsTagsIter = prefsTags.iterator();\n\n    FrameResourceTree tree = createSimpleFrameResourceTree(\n        \"1\",\n        null /* parentId */,\n        \"Stetho\",\n        prefsTagsIter.hasNext() ? prefsTagsIter.next() : \"\");\n    if (tree.childFrames == null) {\n      tree.childFrames = new ArrayList<FrameResourceTree>();\n    }\n\n    int nextChildFrameId = 1;\n    while (prefsTagsIter.hasNext()) {\n      String frameId = \"1.\" + (nextChildFrameId++);\n      String prefsTag = prefsTagsIter.next();\n      FrameResourceTree child = createSimpleFrameResourceTree(\n          frameId,\n          \"1\",\n          \"Child #\" + frameId,\n          prefsTag);\n      tree.childFrames.add(child);\n    }\n\n    GetResourceTreeParams resultParams = new GetResourceTreeParams();\n    resultParams.frameTree = tree;\n    return resultParams;\n  }\n\n  private static FrameResourceTree createSimpleFrameResourceTree(\n      String id,\n      String parentId,\n      String name,\n      String securityOrigin) {\n    Frame frame = new Frame();\n    frame.id = id;\n    frame.parentId = parentId;\n    frame.loaderId = \"1\";\n    frame.name = name;\n    frame.url = \"\";\n    frame.securityOrigin = securityOrigin;\n    frame.mimeType = \"text/plain\";\n    FrameResourceTree tree = new FrameResourceTree();\n    tree.frame = frame;\n    tree.resources = Collections.emptyList();\n    tree.childFrames = null;\n    return tree;\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult canScreencast(JsonRpcPeer peer, JSONObject params) {\n    return new SimpleBooleanResult(true);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult hasTouchInputs(JsonRpcPeer peer, JSONObject params) {\n    return new SimpleBooleanResult(false);\n  }\n\n  @ChromeDevtoolsMethod\n  public void setDeviceMetricsOverride(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void clearDeviceOrientationOverride(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void startScreencast(final JsonRpcPeer peer, JSONObject params) {\n    final StartScreencastRequest request = mObjectMapper.convertValue(\n        params, StartScreencastRequest.class);\n    if (mScreencastDispatcher == null) {\n      mScreencastDispatcher = new ScreencastDispatcher();\n      mScreencastDispatcher.startScreencast(peer, request);\n    }\n  }\n\n  @ChromeDevtoolsMethod\n  public void stopScreencast(JsonRpcPeer peer, JSONObject params) {\n    if (mScreencastDispatcher != null) {\n      mScreencastDispatcher.stopScreencast();\n      mScreencastDispatcher = null;\n    }\n  }\n\n  @ChromeDevtoolsMethod\n  public void screencastFrameAck(JsonRpcPeer peer, JSONObject params) {\n    // Nothing to do here, just need to make sure Chrome doesn't get an error that this method\n    // isn't implemented\n  }\n\n  @ChromeDevtoolsMethod\n  public void clearGeolocationOverride(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void setTouchEmulationEnabled(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void setEmulatedMedia(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void setShowViewportSizeOnResize(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  private static class GetResourceTreeParams implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public FrameResourceTree frameTree;\n  }\n\n  private static class FrameResourceTree {\n    @JsonProperty(required = true)\n    public Frame frame;\n\n    @JsonProperty\n    public List<FrameResourceTree> childFrames;\n\n    @JsonProperty(required = true)\n    public List<Resource> resources;\n  }\n\n  private static class Frame {\n    @JsonProperty(required = true)\n    public String id;\n\n    @JsonProperty\n    public String parentId;\n\n    @JsonProperty(required = true)\n    public String loaderId;\n\n    @JsonProperty\n    public String name;\n\n    @JsonProperty(required = true)\n    public String url;\n\n    @JsonProperty(required = true)\n    public String securityOrigin;\n\n    @JsonProperty(required = true)\n    public String mimeType;\n  }\n\n  private static class Resource {\n    // Incomplete...\n  }\n\n  public enum ResourceType {\n    DOCUMENT(\"Document\"),\n    STYLESHEET(\"Stylesheet\"),\n    IMAGE(\"Image\"),\n    FONT(\"Font\"),\n    SCRIPT(\"Script\"),\n    XHR(\"XHR\"),\n    WEBSOCKET(\"WebSocket\"),\n    OTHER(\"Other\");\n\n    private final String mProtocolValue;\n\n    private ResourceType(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n  private static class ExecutionContextCreatedParams {\n    @JsonProperty(required = true)\n    public ExecutionContextDescription context;\n  }\n\n  private static class ExecutionContextDescription {\n    @JsonProperty(required = true)\n    public String frameId;\n\n    @JsonProperty(required = true)\n    public int id;\n  }\n\n  public static class ScreencastFrameEvent {\n    @JsonProperty(required = true)\n    public String data;\n\n    @JsonProperty(required = true)\n    public ScreencastFrameEventMetadata metadata;\n  }\n\n  public static class ScreencastFrameEventMetadata {\n    @JsonProperty(required = true)\n    public int pageScaleFactor;\n    @JsonProperty(required = true)\n    public int offsetTop;\n    @JsonProperty(required = true)\n    public int deviceWidth;\n    @JsonProperty(required = true)\n    public int deviceHeight;\n    @JsonProperty(required = true)\n    public int scrollOffsetX;\n    @JsonProperty(required = true)\n    public int scrollOffsetY;\n  }\n\n  public static class StartScreencastRequest {\n    @JsonProperty\n    public String format;\n    @JsonProperty\n    public int quality;\n    @JsonProperty\n    public int maxWidth;\n    @JsonProperty\n    public int maxHeight;\n  }\n\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Profiler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport java.util.Collections;\nimport java.util.List;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\n\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport org.json.JSONObject;\n\npublic class Profiler implements ChromeDevtoolsDomain {\n  public Profiler() {\n  }\n\n  @ChromeDevtoolsMethod\n  public void enable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void disable(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public void setSamplingInterval(JsonRpcPeer peer, JSONObject params) {\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getProfileHeaders(JsonRpcPeer peer, JSONObject params) {\n    ProfileHeaderResponse response = new ProfileHeaderResponse();\n    response.headers = Collections.emptyList();\n    return response;\n  }\n\n  private static class ProfileHeaderResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<ProfileHeader> headers;\n  }\n\n  private static class ProfileHeader {\n    @JsonProperty(required = true)\n    String typeId;\n\n    @JsonProperty(required = true)\n    String title;\n\n    @JsonProperty(required = true)\n    int uid;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Runtime.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport android.content.Context;\nimport com.facebook.stetho.Stetho;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.inspector.console.RuntimeRepl;\nimport com.facebook.stetho.inspector.console.RuntimeReplFactory;\nimport com.facebook.stetho.inspector.helper.ObjectIdMapper;\nimport com.facebook.stetho.inspector.jsonrpc.DisconnectReceiver;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcException;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.jsonrpc.protocol.JsonRpcError;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\nimport com.facebook.stetho.inspector.runtime.RhinoDetectingRuntimeReplFactory;\nimport com.facebook.stetho.json.ObjectMapper;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\n\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\nimport java.lang.reflect.Array;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.Modifier;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\npublic class Runtime implements ChromeDevtoolsDomain {\n  private final ObjectMapper mObjectMapper = new ObjectMapper();\n\n  private static final Map<JsonRpcPeer, Session> sSessions =\n      Collections.synchronizedMap(new HashMap<JsonRpcPeer, Session>());\n\n  private final RuntimeReplFactory mReplFactory;\n\n  /**\n   * @deprecated Provided for ABI compatibility\n   *\n   * @see #Runtime(RuntimeReplFactory)\n   * @see Stetho.DefaultInspectorModulesBuilder#runtimeRepl(RuntimeReplFactory)\n   */\n  @Deprecated\n  public Runtime() {\n    this(new RuntimeReplFactory() {\n      @Override\n      public RuntimeRepl newInstance() {\n        return new RuntimeRepl() {\n          @Override\n          public Object evaluate(String expression) throws Throwable {\n            return \"Not supported with legacy Runtime module\";\n          }\n        };\n      }\n    });\n  }\n\n  /**\n   * @deprecated This was a transitionary API that was replaced by\n   *     {@link com.facebook.stetho.Stetho.DefaultInspectorModulesBuilder#runtimeRepl}\n   */\n  public Runtime(Context context) {\n    this(new RhinoDetectingRuntimeReplFactory(context));\n  }\n\n  public Runtime(RuntimeReplFactory replFactory) {\n    mReplFactory = replFactory;\n  }\n\n  public static int mapObject(JsonRpcPeer peer, Object object) {\n    return getSession(peer).getObjects().putObject(object);\n  }\n\n  @Nonnull\n  private static synchronized Session getSession(final JsonRpcPeer peer) {\n    Session session = sSessions.get(peer);\n    if (session == null) {\n      session = new Session();\n      sSessions.put(peer, session);\n      peer.registerDisconnectReceiver(new DisconnectReceiver() {\n        @Override\n        public void onDisconnect() {\n          sSessions.remove(peer);\n        }\n      });\n    }\n    return session;\n  }\n\n  /**\n   * Removes objects from peer's session previously added by {@link #mapObject}\n   */\n  public static void releaseObject(JsonRpcPeer peer, Integer id) throws JSONException {\n    getSession(peer).getObjects().removeObjectById(id);\n  }\n\n  @ChromeDevtoolsMethod\n  public void releaseObject(JsonRpcPeer peer, JSONObject params) throws JSONException {\n    String objectId = params.getString(\"objectId\");\n    getSession(peer).getObjects().removeObjectById(Integer.parseInt(objectId));\n  }\n\n  @ChromeDevtoolsMethod\n  public void releaseObjectGroup(JsonRpcPeer peer, JSONObject params) {\n    LogUtil.w(\"Ignoring request to releaseObjectGroup: \" + params);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult callFunctionOn(JsonRpcPeer peer, JSONObject params)\n      throws JsonRpcException {\n    CallFunctionOnRequest args = mObjectMapper.convertValue(params, CallFunctionOnRequest.class);\n\n    Session session = getSession(peer);\n    Object object = session.getObjectOrThrow(args.objectId);\n\n    // The DevTools UI thinks it can run arbitrary JavaScript against us in order to figure out\n    // the class structure of an object.  That obviously won't fly, and there's no way to\n    // translate without building a crude JavaScript parser so let's just go ahead and guess\n    // what this function does by name.\n    if (!args.functionDeclaration.startsWith(\"function protoList(\")) {\n      throw new JsonRpcException(\n          new JsonRpcError(\n              JsonRpcError.ErrorCode.INTERNAL_ERROR,\n              \"Expected protoList, got: \" + args.functionDeclaration,\n              null /* data */));\n    }\n\n    // Since this is really a function call we have to create this fake object to hold the\n    // \"result\" of the function.\n    ObjectProtoContainer objectContainer = new ObjectProtoContainer(object);\n    RemoteObject result = new RemoteObject();\n    result.type = ObjectType.OBJECT;\n    result.subtype = ObjectSubType.NODE;\n    result.className = object.getClass().getName();\n    result.description = getPropertyClassName(object);\n    result.objectId = String.valueOf(session.getObjects().putObject(objectContainer));\n\n    CallFunctionOnResponse response = new CallFunctionOnResponse();\n    response.result = result;\n    response.wasThrown = false;\n\n    return response;\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult evaluate(JsonRpcPeer peer, JSONObject params) {\n    return getSession(peer).evaluate(mReplFactory, params);\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult getProperties(JsonRpcPeer peer, JSONObject params) throws JsonRpcException {\n    return getSession(peer).getProperties(params);\n  }\n\n  private static String getPropertyClassName(Object o) {\n    String name = o.getClass().getSimpleName();\n    if (name == null || name.length() == 0) {\n      // Looks better for anonymous classes.\n      name = o.getClass().getName();\n    }\n    return name;\n  }\n\n  private static class ObjectProtoContainer {\n    public final Object object;\n\n    public ObjectProtoContainer(Object object) {\n      this.object = object;\n    }\n  }\n\n  /**\n   * Object representing a session with a single client.\n   *\n   * <p>Clients inherently leak object references because they can expand any object in the UI\n   * at any time.  Grouping references by client allows us to drop them when the client\n   * disconnects.\n   */\n  private static class Session {\n    private final ObjectIdMapper mObjects = new ObjectIdMapper();\n    private final ObjectMapper mObjectMapper = new ObjectMapper();\n\n    @Nullable\n    private RuntimeRepl mRepl;\n\n    public ObjectIdMapper getObjects() {\n      return mObjects;\n    }\n\n    public Object getObjectOrThrow(String objectId) throws JsonRpcException {\n      Object object = getObjects().getObjectForId(Integer.parseInt(objectId));\n      if (object == null) {\n        throw new JsonRpcException(new JsonRpcError(\n            JsonRpcError.ErrorCode.INVALID_REQUEST,\n            \"No object found for \" + objectId,\n            null /* data */));\n      }\n      return object;\n    }\n\n    public RemoteObject objectForRemote(Object value) {\n      RemoteObject result = new RemoteObject();\n      if (value == null) {\n        result.type = ObjectType.OBJECT;\n        result.subtype = ObjectSubType.NULL;\n        result.value = JSONObject.NULL;\n      } else if (value instanceof Boolean) {\n        result.type = ObjectType.BOOLEAN;\n        result.value = value;\n      } else if (value instanceof Number) {\n        result.type = ObjectType.NUMBER;\n        result.value = value;\n      } else if (value instanceof Character) {\n        // Unclear whether we should expose these as strings, numbers, or something else.\n        result.type = ObjectType.NUMBER;\n        result.value = Integer.valueOf(((Character)value).charValue());\n      } else if (value instanceof String) {\n        result.type = ObjectType.STRING;\n        result.value = String.valueOf(value);\n      } else {\n        result.type = ObjectType.OBJECT;\n        result.className = \"What??\";  // I have no idea where this is used.\n        result.objectId = String.valueOf(mObjects.putObject(value));\n\n        if (value.getClass().isArray()) {\n          result.description = \"array\";\n        } else if (value instanceof List) {\n          result.description = \"List\";\n        } else if (value instanceof Set) {\n          result.description = \"Set\";\n        } else if (value instanceof Map) {\n          result.description = \"Map\";\n        } else {\n          result.description = getPropertyClassName(value);\n        }\n\n      }\n      return result;\n    }\n\n    public EvaluateResponse evaluate(RuntimeReplFactory replFactory, JSONObject params) {\n      EvaluateRequest request = mObjectMapper.convertValue(params, EvaluateRequest.class);\n\n      try {\n        if (!request.objectGroup.equals(\"console\")) {\n          return buildExceptionResponse(\"Not supported by FAB\");\n        }\n\n        RuntimeRepl repl = getRepl(replFactory);\n        Object result = repl.evaluate(request.expression);\n        return buildNormalResponse(result);\n      } catch (Throwable t) {\n        return buildExceptionResponse(t);\n      }\n    }\n\n    @Nonnull\n    private synchronized RuntimeRepl getRepl(RuntimeReplFactory replFactory) {\n      if (mRepl == null) {\n        mRepl = replFactory.newInstance();\n      }\n      return mRepl;\n    }\n\n    private EvaluateResponse buildNormalResponse(Object retval) {\n      EvaluateResponse response = new EvaluateResponse();\n      response.wasThrown = false;\n      response.result = objectForRemote(retval);\n      return response;\n    }\n\n    private EvaluateResponse buildExceptionResponse(Object retval) {\n      EvaluateResponse response = new EvaluateResponse();\n      response.wasThrown = true;\n      response.result = objectForRemote(retval);\n      response.exceptionDetails = new ExceptionDetails();\n      response.exceptionDetails.text = retval.toString();\n      return response;\n    }\n\n    public GetPropertiesResponse getProperties(JSONObject params) throws JsonRpcException {\n      GetPropertiesRequest request = mObjectMapper.convertValue(params, GetPropertiesRequest.class);\n\n      if (!request.ownProperties) {\n        GetPropertiesResponse response = new GetPropertiesResponse();\n        response.result = new ArrayList<>();\n        return response;\n      }\n\n      Object object = getObjectOrThrow(request.objectId);\n\n      if (object.getClass().isArray()) {\n        object = arrayToList(object);\n      }\n\n      if (object instanceof ObjectProtoContainer) {\n        return getPropertiesForProtoContainer((ObjectProtoContainer) object);\n      } else if (object instanceof List) {\n        return getPropertiesForIterable((List) object, /* enumerate */ true);\n      } else if (object instanceof Set) {\n        return getPropertiesForIterable((Set) object, /* enumerate */ false);\n      } else if (object instanceof Map) {\n        return getPropertiesForMap(object);\n      } else {\n        return getPropertiesForObject(object);\n      }\n    }\n\n    private List<?> arrayToList(Object object) {\n      Class<?> type = object.getClass();\n      if (!type.isArray()) {\n        throw new IllegalArgumentException(\"Argument must be an array.  Was \" + type);\n      }\n      Class<?> component = type.getComponentType();\n\n      if (!component.isPrimitive()) {\n        return Arrays.asList((Object[]) object);\n      }\n\n      // Loop manually for primitives.\n      int length = Array.getLength(object);\n      List<Object> ret = new ArrayList<>(length);\n      for (int i = 0; i < length; i++) {\n        ret.add(Array.get(object, i));\n      }\n      return ret;\n    }\n\n    // Normally JavaScript will return the full class hierarchy as a list.  That seems less\n    // useful for Java since it's more natural (IMO) to see all available member variables in one\n    // big list.\n    private GetPropertiesResponse getPropertiesForProtoContainer(ObjectProtoContainer proto) {\n      Object target = proto.object;\n      RemoteObject protoRemote = new RemoteObject();\n      protoRemote.type = ObjectType.OBJECT;\n      protoRemote.subtype = ObjectSubType.NODE;\n      protoRemote.className = target.getClass().getName();\n      protoRemote.description = getPropertyClassName(target);\n      protoRemote.objectId = String.valueOf(mObjects.putObject(target));\n      PropertyDescriptor descriptor = new PropertyDescriptor();\n      descriptor.name = \"1\";\n      descriptor.value = protoRemote;\n      GetPropertiesResponse response = new GetPropertiesResponse();\n      response.result = new ArrayList<>(1);\n      response.result.add(descriptor);\n      return response;\n    }\n\n    private GetPropertiesResponse getPropertiesForIterable(Iterable<?> object, boolean enumerate) {\n      GetPropertiesResponse response = new GetPropertiesResponse();\n      List<PropertyDescriptor> properties = new ArrayList<>();\n\n      int index = 0;\n      for (Object value : object) {\n        PropertyDescriptor property = new PropertyDescriptor();\n        property.name = enumerate ? String.valueOf(index++) : null;\n        property.value = objectForRemote(value);\n        properties.add(property);\n      }\n\n      response.result = properties;\n      return response;\n    }\n\n    private GetPropertiesResponse getPropertiesForMap(Object object) {\n      GetPropertiesResponse response = new GetPropertiesResponse();\n      List<PropertyDescriptor> properties = new ArrayList<>();\n\n      for (Map.Entry<?, ?> entry : ((Map<?, ?>) object).entrySet()) {\n        PropertyDescriptor property = new PropertyDescriptor();\n        property.name = String.valueOf(entry.getKey());\n        property.value = objectForRemote(entry.getValue());\n        properties.add(property);\n      }\n\n      response.result = properties;\n      return response;\n    }\n\n    private GetPropertiesResponse getPropertiesForObject(Object object) {\n      GetPropertiesResponse response = new GetPropertiesResponse();\n      List<PropertyDescriptor> properties = new ArrayList<>();\n      for (\n          Class<?> declaringClass = object.getClass();\n          declaringClass != null;\n          declaringClass = declaringClass.getSuperclass()\n          ) {\n        // Reverse the list of fields while going up the superclass chain.\n        // When we're done, we'll reverse the full list so that the superclasses\n        // appear at the top, but within each class they properties are in declared order.\n        List<Field> fields =\n            new ArrayList<Field>(Arrays.asList(declaringClass.getDeclaredFields()));\n        Collections.reverse(fields);\n        String prefix = declaringClass == object.getClass()\n            ? \"\"\n            : declaringClass.getSimpleName() + \".\";\n        for (Field field : fields) {\n          if (Modifier.isStatic(field.getModifiers())) {\n            continue;\n          }\n          field.setAccessible(true);\n          try {\n            Object fieldValue = field.get(object);\n            PropertyDescriptor property = new PropertyDescriptor();\n            property.name = prefix + field.getName();\n            property.value = objectForRemote(fieldValue);\n            properties.add(property);\n          } catch (IllegalAccessException e) {\n            throw new RuntimeException(e);\n          }\n        }\n      }\n      Collections.reverse(properties);\n      response.result = properties;\n      return response;\n    }\n  }\n\n  private static class CallFunctionOnRequest {\n    @JsonProperty\n    public String objectId;\n\n    @JsonProperty\n    public String functionDeclaration;\n\n    @JsonProperty\n    public List<CallArgument> arguments;\n\n    @JsonProperty(required = false)\n    public Boolean doNotPauseOnExceptionsAndMuteConsole;\n\n    @JsonProperty(required = false)\n    public Boolean returnByValue;\n\n    @JsonProperty(required = false)\n    public Boolean generatePreview;\n  }\n\n  private static class CallFunctionOnResponse implements JsonRpcResult {\n    @JsonProperty\n    public RemoteObject result;\n\n    @JsonProperty(required = false)\n    public Boolean wasThrown;\n  }\n\n  private static class CallArgument {\n    @JsonProperty(required = false)\n    public Object value;\n\n    @JsonProperty(required = false)\n    public String objectId;\n\n    @JsonProperty(required = false)\n    public ObjectType type;\n  }\n\n  private static class GetPropertiesRequest implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public boolean ownProperties;\n\n    @JsonProperty(required = true)\n    public String objectId;\n  }\n\n  private static class GetPropertiesResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public List<PropertyDescriptor> result;\n  }\n\n  private static class EvaluateRequest implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public String objectGroup;\n\n    @JsonProperty(required = true)\n    public String expression;\n  }\n\n  private static class EvaluateResponse implements JsonRpcResult {\n    @JsonProperty(required = true)\n    public RemoteObject result;\n\n    @JsonProperty(required = true)\n    public boolean wasThrown;\n\n    @JsonProperty\n    public ExceptionDetails exceptionDetails;\n  }\n\n  private static class ExceptionDetails {\n    @JsonProperty(required = true)\n    public String text;\n  }\n\n  public static class RemoteObject {\n    @JsonProperty(required = true)\n    public ObjectType type;\n\n    @JsonProperty\n    public ObjectSubType subtype;\n\n    @JsonProperty\n    public Object value;\n\n    @JsonProperty\n    public String className;\n\n    @JsonProperty\n    public String description;\n\n    @JsonProperty\n    public String objectId;\n  }\n\n  private static class PropertyDescriptor {\n    @JsonProperty(required = true)\n    public String name;\n\n    @JsonProperty(required = true)\n    public RemoteObject value;\n\n    @JsonProperty(required = true)\n    public final boolean isOwn = true;\n\n    @JsonProperty(required = true)\n    public final boolean configurable = false;\n\n    @JsonProperty(required = true)\n    public final boolean enumerable = true;\n\n    @JsonProperty(required = true)\n    public final boolean writable = false;\n  }\n\n  public static enum ObjectType {\n    OBJECT(\"object\"),\n    FUNCTION(\"function\"),\n    UNDEFINED(\"undefined\"),\n    STRING(\"string\"),\n    NUMBER(\"number\"),\n    BOOLEAN(\"boolean\"),\n    SYMBOL(\"symbol\");\n\n    private final String mProtocolValue;\n\n    private ObjectType(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n  public static enum ObjectSubType {\n    ARRAY(\"array\"),\n    NULL(\"null\"),\n    NODE(\"node\"),\n    REGEXP(\"regexp\"),\n    DATE(\"date\"),\n    MAP(\"map\"),\n    SET(\"set\"),\n    ITERATOR(\"iterator\"),\n    GENERATOR(\"generator\"),\n    ERROR(\"error\");\n\n    private final String mProtocolValue;\n\n    private ObjectSubType(String protocolValue) {\n      mProtocolValue = protocolValue;\n    }\n\n    @JsonValue\n    public String getProtocolValue() {\n      return mProtocolValue;\n    }\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/SimpleBooleanResult.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.json.annotation.JsonProperty;\n\npublic class SimpleBooleanResult implements JsonRpcResult {\n  @JsonProperty(required = true)\n  public boolean result;\n\n  public SimpleBooleanResult() {\n  }\n\n  public SimpleBooleanResult(boolean result) {\n    this.result = result;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/protocol/module/Worker.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.protocol.module;\n\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcResult;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsMethod;\n\nimport org.json.JSONObject;\n\npublic class Worker implements ChromeDevtoolsDomain {\n  public Worker() {\n  }\n\n  @ChromeDevtoolsMethod\n  public JsonRpcResult canInspectWorkers(JsonRpcPeer peer, JSONObject params) {\n    return new SimpleBooleanResult(true);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/runtime/RhinoDetectingRuntimeReplFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.runtime;\n\nimport android.content.Context;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.inspector.console.RuntimeRepl;\nimport com.facebook.stetho.inspector.console.RuntimeReplFactory;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\n\nimport androidx.annotation.Nullable;\n\n/**\n * Attempts to locate stetho-js-rhino in the classpath and use it if available.  Otherwise falls\n * back to a no-op version which informs folks that they can include stetho-js-rhino for more\n * advanced functionality.\n * <p />\n * Eventually we should develop a kind of service locator somehow to make this more discoverable\n * and generalized.  For now with only one official implementation however it seems like overkill.\n */\npublic class RhinoDetectingRuntimeReplFactory implements RuntimeReplFactory {\n  private final Context mContext;\n\n  private boolean mSearchedForRhinoJs;\n  private RuntimeReplFactory mRhinoReplFactory;\n  private RuntimeException mRhinoJsUnexpectedError;\n\n  public RhinoDetectingRuntimeReplFactory(Context context) {\n    mContext = context;\n  }\n\n  @Override\n  public RuntimeRepl newInstance() {\n    if (!mSearchedForRhinoJs) {\n      mSearchedForRhinoJs = true;\n      try {\n        mRhinoReplFactory = findRhinoReplFactory(mContext);\n      } catch (RuntimeException e) {\n        mRhinoJsUnexpectedError = e;\n      }\n    }\n    if (mRhinoReplFactory != null) {\n      return mRhinoReplFactory.newInstance();\n    } else {\n      return new RuntimeRepl() {\n        @Override\n        public Object evaluate(String expression) throws Exception {\n          if (mRhinoJsUnexpectedError != null) {\n            return \"stetho-js-rhino threw: \" + mRhinoJsUnexpectedError.toString();\n          } else {\n            return \"Not supported without stetho-js-rhino dependency\";\n          }\n        }\n      };\n    }\n  }\n\n  @Nullable\n  private static RuntimeReplFactory findRhinoReplFactory(Context context) throws RuntimeException {\n    try {\n      Class<?> jsRuntimeReplFactory =\n          Class.forName(\"com.facebook.stetho.rhino.JsRuntimeReplFactoryBuilder\");\n      Method defaultFactoryMethod =\n          jsRuntimeReplFactory.getDeclaredMethod(\"defaultFactory\", Context.class);\n      return (RuntimeReplFactory) defaultFactoryMethod.invoke(null, context);\n    } catch (ClassNotFoundException e) {\n      LogUtil.i(\"Error finding stetho-js-rhino, cannot enable console evaluation!\");\n      return null;\n    } catch (NoSuchMethodException e) {\n      throw new RuntimeException(e);\n    } catch (InvocationTargetException e) {\n      throw new RuntimeException(e);\n    } catch (IllegalAccessException e) {\n      throw new RuntimeException(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/inspector/screencast/ScreencastDispatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.screencast;\n\nimport android.app.Activity;\nimport android.graphics.Bitmap;\nimport android.graphics.Canvas;\nimport android.graphics.Matrix;\nimport android.graphics.RectF;\nimport android.os.Handler;\nimport android.os.HandlerThread;\nimport android.os.Looper;\nimport android.util.Base64;\nimport android.util.Base64OutputStream;\nimport android.view.View;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.inspector.elements.android.ActivityTracker;\nimport com.facebook.stetho.inspector.jsonrpc.JsonRpcPeer;\nimport com.facebook.stetho.inspector.protocol.module.Page;\n\nimport java.io.ByteArrayOutputStream;\n\npublic final class ScreencastDispatcher {\n  private static final long FRAME_DELAY = 200l;\n\n  private final Handler mMainHandler = new Handler(Looper.getMainLooper());\n  private final BitmapFetchRunnable mBitmapFetchRunnable = new BitmapFetchRunnable();\n  private final ActivityTracker mActivityTracker = ActivityTracker.get();\n  private final EventDispatchRunnable mEventDispatchRunnable = new EventDispatchRunnable();\n  private final RectF mTempSrc = new RectF();\n  private final RectF mTempDst = new RectF();\n\n  private boolean mIsRunning;\n  private Handler mBackgroundHandler;\n  private JsonRpcPeer mPeer;\n  private HandlerThread mHandlerThread;\n  private Bitmap mBitmap;\n  private Canvas mCanvas;\n  private Page.StartScreencastRequest mRequest;\n  private ByteArrayOutputStream mStream;\n  private Page.ScreencastFrameEvent mEvent = new Page.ScreencastFrameEvent();\n  private Page.ScreencastFrameEventMetadata mMetadata = new Page.ScreencastFrameEventMetadata();\n\n  public ScreencastDispatcher() {\n  }\n\n  public void startScreencast(JsonRpcPeer peer, Page.StartScreencastRequest request) {\n    LogUtil.d(\"Starting screencast\");\n    mRequest = request;\n    mHandlerThread = new HandlerThread(\"Screencast Thread\");\n    mHandlerThread.start();\n    mPeer = peer;\n    mIsRunning = true;\n    mStream = new ByteArrayOutputStream();\n    mBackgroundHandler = new Handler(mHandlerThread.getLooper());\n    mMainHandler.postDelayed(mBitmapFetchRunnable, FRAME_DELAY);\n  }\n\n  public void stopScreencast() {\n    LogUtil.d(\"Stopping screencast\");\n    mBackgroundHandler.post(new CancellationRunnable());\n  }\n\n  private class BitmapFetchRunnable implements Runnable {\n    @Override\n    public void run() {\n      updateScreenBitmap();\n      mBackgroundHandler.post(mEventDispatchRunnable.withEndAction(this));\n    }\n\n    private void updateScreenBitmap() {\n      if (!mIsRunning) {\n        return;\n      }\n      Activity activity = mActivityTracker.tryGetTopActivity();\n      if (activity == null) {\n        return;\n      }\n      // This stuff needs to happen in the UI thread\n      View rootView = activity.getWindow().getDecorView();\n      try {\n        if (mBitmap == null) {\n          int viewWidth = rootView.getWidth();\n          int viewHeight = rootView.getHeight();\n          float scale = Math.min((float) mRequest.maxWidth / (float) viewWidth,\n              (float) mRequest.maxHeight / (float) viewHeight);\n          int destWidth = (int) (viewWidth * scale);\n          int destHeight = (int) (viewHeight * scale);\n          mBitmap = Bitmap.createBitmap(destWidth, destHeight, Bitmap.Config.RGB_565);\n          mCanvas = new Canvas(mBitmap);\n          Matrix matrix = new Matrix();\n          mTempSrc.set(0, 0, viewWidth, viewHeight);\n          mTempDst.set(0, 0, destWidth, destHeight);\n          matrix.setRectToRect(mTempSrc, mTempDst, Matrix.ScaleToFit.CENTER);\n          mCanvas.setMatrix(matrix);\n        }\n        rootView.draw(mCanvas);\n      } catch (OutOfMemoryError e) {\n        LogUtil.w(\"Out of memory trying to allocate screencast Bitmap.\");\n      }\n    }\n  }\n\n  private class EventDispatchRunnable implements Runnable {\n    private Runnable mEndAction;\n\n    private EventDispatchRunnable withEndAction(Runnable endAction) {\n      mEndAction = endAction;\n      return this;\n    }\n\n    @Override\n    public void run() {\n      if (!mIsRunning || mBitmap == null) {\n        return;\n      }\n      int width = mBitmap.getWidth();\n      int height = mBitmap.getHeight();\n      mStream.reset();\n      Base64OutputStream base64Stream = new Base64OutputStream(mStream, Base64.DEFAULT);\n      // request format is either \"jpeg\" or \"png\"\n      Bitmap.CompressFormat format = Bitmap.CompressFormat.valueOf(mRequest.format.toUpperCase());\n      mBitmap.compress(format, mRequest.quality, base64Stream);\n      mEvent.data = mStream.toString();\n      mMetadata.pageScaleFactor = 1;\n      mMetadata.deviceWidth = width;\n      mMetadata.deviceHeight = height;\n      mEvent.metadata = mMetadata;\n      mPeer.invokeMethod(\"Page.screencastFrame\", mEvent, null);\n      mMainHandler.postDelayed(mEndAction, FRAME_DELAY);\n    }\n  }\n\n  private class CancellationRunnable implements Runnable {\n    @Override\n    public void run() {\n      mHandlerThread.interrupt();\n      mMainHandler.removeCallbacks(mBitmapFetchRunnable);\n      mBackgroundHandler.removeCallbacks(mEventDispatchRunnable);\n      mIsRunning = false;\n      mHandlerThread = null;\n      mBitmap = null;\n      mCanvas = null;\n      mStream = null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/json/ObjectMapper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.json;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.GuardedBy;\n\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Constructor;\nimport java.lang.reflect.Field;\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Modifier;\nimport java.lang.reflect.ParameterizedType;\nimport java.lang.reflect.Type;\nimport java.util.ArrayList;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport com.facebook.stetho.common.ExceptionUtil;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\n\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\n\n/**\n * This class is a lightweight version of Jackson's ObjectMapper. It is designed to have a minimal\n * subset of the functionality required for stetho.\n * <p>\n * It would be awesome if there were a lightweight library that supported converting between\n * arbitrary {@link Object} and {@link JSONObject} representations.\n * <p>\n * Admittedly the other approach would be to use an Annotation Processor to create static conversion\n * functions that discover something like a {@link JsonProperty} and create a function at compile\n * time however since this is just being used for a simple debug utility and Kit-Kat caches the\n * results of reflection this class is sufficient for stethos needs.\n */\npublic class ObjectMapper {\n\n  @GuardedBy(\"mJsonValueMethodCache\")\n  private final Map<Class<?>, Method> mJsonValueMethodCache = new IdentityHashMap<>();\n\n  /**\n   * Support mapping between arbitrary classes and {@link JSONObject}.\n   * <note>\n   *   It is possible for a {@link Throwable} to be propagated out of this class if there is an\n   *   {@link InvocationTargetException}.\n   * </note>\n   * @param fromValue\n   * @param toValueType\n   * @param <T>\n   * @return\n   * @throws IllegalArgumentException when there is an error converting. One of either\n   * {@code fromValue.getClass()} or {@code toValueType} must be {@link JSONObject}.\n   */\n  public <T> T convertValue(Object fromValue, Class<T> toValueType)\n      throws IllegalArgumentException {\n    if (fromValue == null) {\n      return null;\n    }\n\n    if (toValueType != Object.class\n        && toValueType.isAssignableFrom(fromValue.getClass())) {\n      return (T) fromValue;\n    }\n\n    try {\n      if (fromValue instanceof JSONObject) {\n        return _convertFromJSONObject((JSONObject) fromValue, toValueType);\n      } else if (toValueType == JSONObject.class) {\n        return (T) _convertToJSONObject(fromValue);\n      } else {\n        throw new IllegalArgumentException(\n            \"Expecting either fromValue or toValueType to be a JSONObject\");\n      }\n    } catch (NoSuchMethodException e) {\n      throw new IllegalArgumentException(e);\n    } catch (IllegalAccessException e) {\n      throw new IllegalArgumentException(e);\n    } catch (InstantiationException e) {\n      throw new IllegalArgumentException(e);\n    } catch (JSONException e) {\n      throw new IllegalArgumentException(e);\n    } catch (InvocationTargetException e) {\n      throw ExceptionUtil.propagate(e.getCause());\n    }\n  }\n\n  private <T> T _convertFromJSONObject(JSONObject jsonObject, Class<T> type)\n      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException,\n      InstantiationException, JSONException {\n    Constructor<T> constructor = type.getDeclaredConstructor((Class[]) null);\n    constructor.setAccessible(true);\n    T instance = constructor.newInstance();\n    Field[] fields = type.getFields();\n    for (int i = 0; i < fields.length; ++i) {\n      Field field = fields[i];\n      if (Modifier.isStatic(field.getModifiers())) {\n        continue;\n      }\n      Object value = jsonObject.opt(field.getName());\n      Object setValue = getValueForField(field, value);\n      try {\n        field.set(instance, setValue);\n      } catch (IllegalArgumentException e) {\n        throw new IllegalArgumentException(\n            \"Class: \" + type.getSimpleName() + \" \" +\n            \"Field: \" + field.getName() + \" type \" + (setValue != null ?\n                setValue.getClass().getName()\n                : \"null\"),\n            e);\n      }\n    }\n    return instance;\n  }\n\n  private Object getValueForField(Field field, Object value)\n      throws JSONException {\n    try {\n      if (value != null) {\n        if (value == JSONObject.NULL) {\n          return null;\n        }\n        if (value.getClass() == field.getType()) {\n          return value;\n        }\n        if (value instanceof JSONObject) {\n          return convertValue(value, field.getType());\n        } else {\n          if (field.getType().isEnum()) {\n            return getEnumValue((String) value, field.getType().asSubclass(Enum.class));\n          } else if (value instanceof JSONArray) {\n            return convertArrayToList(field, (JSONArray) value);\n          } else if (value instanceof Number) {\n            // Need to convert value to Number This happens because json treats 1 as an Integer even\n            // if the field is supposed to be a Long\n            Number numberValue = (Number) value;\n            Class<?> clazz = field.getType();\n            if (clazz == Integer.class || clazz == int.class) {\n              return numberValue.intValue();\n            } else if (clazz == Long.class || clazz == long.class) {\n              return numberValue.longValue();\n            } else if (clazz == Double.class || clazz == double.class) {\n              return numberValue.doubleValue();\n            } else if (clazz == Float.class || clazz == float.class) {\n              return numberValue.floatValue();\n            } else if (clazz == Byte.class || clazz == byte.class) {\n              return numberValue.byteValue();\n            } else if (clazz == Short.class || clazz == short.class) {\n              return numberValue.shortValue();\n            } else {\n              throw new IllegalArgumentException(\"Not setup to handle class \" + clazz.getName());\n            }\n          }\n        }\n      }\n    } catch (IllegalAccessException e) {\n      throw new IllegalArgumentException(\"Unable to set value for field \" + field.getName(), e);\n    }\n    return value;\n  }\n\n  private Enum getEnumValue(String value, Class<? extends Enum> clazz) {\n    Method method = getJsonValueMethod(clazz);\n    if (method != null) {\n      return getEnumByMethod(value, clazz, method);\n    } else {\n      return Enum.valueOf(clazz, value);\n    }\n  }\n\n  /**\n   * In this case we know that there is an {@link Enum} decorated with {@link JsonValue}. This means\n   * that we need to iterate through all of the values of the {@link Enum} returned by the given\n   * {@link Method} to check the given value.\n   * @param value\n   * @param clazz\n   * @param method\n   * @return\n   */\n  private Enum getEnumByMethod(String value, Class<? extends Enum> clazz, Method method) {\n    Enum[] enumValues = clazz.getEnumConstants();\n    // Start at the front to ensure first always wins\n    for (int i = 0; i < enumValues.length; ++i) {\n      Enum enumValue = enumValues[i];\n      try {\n        Object o = method.invoke(enumValue);\n        if (o != null) {\n          if (o.toString().equals(value)) {\n            return enumValue;\n          }\n        }\n      } catch (Exception ex) {\n        throw new IllegalArgumentException(ex);\n      }\n    }\n    throw new IllegalArgumentException(\"No enum constant \" + clazz.getName() + \".\" + value);\n  }\n\n  private List<Object> convertArrayToList(Field field, JSONArray array)\n      throws IllegalAccessException, JSONException {\n    if (List.class.isAssignableFrom(field.getType())) {\n      ParameterizedType parameterizedType = (ParameterizedType) field.getGenericType();\n      Type[] types = parameterizedType.getActualTypeArguments();\n      if (types.length != 1) {\n        throw new IllegalArgumentException(\"Only able to handle a single type in a list \" +\n            field.getName());\n      }\n      Class arrayClass = (Class)types[0];\n      List<Object> objectList = new ArrayList<Object>();\n      for (int i = 0; i < array.length(); ++i) {\n        if (arrayClass.isEnum()) {\n          objectList.add(getEnumValue(array.getString(i), arrayClass));\n        } else if (canDirectlySerializeClass(arrayClass)) {\n          objectList.add(array.get(i));\n        } else {\n          JSONObject jsonObject = array.getJSONObject(i);\n          if (jsonObject == null) {\n            objectList.add(null);\n          } else {\n            objectList.add(convertValue(jsonObject, arrayClass));\n          }\n        }\n      }\n      return objectList;\n    } else {\n      throw new IllegalArgumentException(\"only know how to deserialize List<?> on field \"\n          + field.getName());\n    }\n  }\n\n  private JSONObject _convertToJSONObject(Object fromValue)\n      throws JSONException, InvocationTargetException, IllegalAccessException {\n    JSONObject jsonObject = new JSONObject();\n    Field[] fields = fromValue.getClass().getFields();\n    for (int i = 0; i < fields.length; ++i) {\n      Field field = fields[i];\n      if (Modifier.isStatic(field.getModifiers())) {\n        continue;\n      }\n      JsonProperty property = field.getAnnotation(JsonProperty.class);\n      if (property != null) {\n        // AutoBox here ...\n        Object value = field.get(fromValue);\n        Class clazz = field.getType();\n        if (value != null) {\n          clazz = value.getClass();\n        }\n        String name = field.getName();\n        if (property.required() && value == null) {\n          value = JSONObject.NULL;\n        } else if (value == JSONObject.NULL) {\n          // Leave it as null in this case.\n        } else {\n          value = getJsonValue(value, clazz, field);\n        }\n        jsonObject.put(name, value);\n      }\n    }\n    return jsonObject;\n  }\n\n  private Object getJsonValue(Object value, Class<?> clazz, Field field)\n      throws InvocationTargetException, IllegalAccessException {\n    if (value == null) {\n      // Now technically we /could/ return JsonNode.NULL here but Chrome's webkit inspector croaks\n      // if you pass a null \"id\"\n      return null;\n    }\n    if (List.class.isAssignableFrom(clazz)) {\n      return convertListToJsonArray(value);\n    }\n    // Finally check to see if there is a JsonValue present\n    Method m = getJsonValueMethod(clazz);\n    if (m != null) {\n      return m.invoke(value);\n    }\n    if (!canDirectlySerializeClass(clazz)) {\n      return convertValue(value, JSONObject.class);\n    }\n    // JSON has no support for NaN, Infinity or -Infinity, so we serialize\n    // then as strings. Google Chrome's inspector will accept them just fine.\n    if (clazz.equals(Double.class) || clazz.equals(Float.class)) {\n      double doubleValue = ((Number) value).doubleValue();\n      if (Double.isNaN(doubleValue)) {\n        return \"NaN\";\n      } else if (doubleValue == Double.POSITIVE_INFINITY) {\n        return \"Infinity\";\n      } else if (doubleValue == Double.NEGATIVE_INFINITY) {\n        return \"-Infinity\";\n      }\n    }\n    // hmm we should be able to directly serialize here...\n    return value;\n  }\n\n  private JSONArray convertListToJsonArray(Object value)\n      throws InvocationTargetException, IllegalAccessException {\n    JSONArray array = new JSONArray();\n    List<Object> list = (List<Object>) value;\n    for(Object obj : list) {\n      // Send null, if this is an array of arrays we are screwed\n      array.put(obj != null ? getJsonValue(obj, obj.getClass(), null /* field */) : null);\n    }\n    return array;\n  }\n\n  /**\n   *\n   * @param clazz\n   * @return the first method annotated with {@link JsonValue} or null if one does not exist.\n   */\n  @Nullable\n  private Method getJsonValueMethod(Class<?> clazz) {\n    synchronized (mJsonValueMethodCache) {\n      Method method = mJsonValueMethodCache.get(clazz);\n      if (method == null && !mJsonValueMethodCache.containsKey(clazz)) {\n        method = getJsonValueMethodImpl(clazz);\n        mJsonValueMethodCache.put(clazz, method);\n      }\n      return method;\n    }\n  }\n\n  @Nullable\n  private static Method getJsonValueMethodImpl(Class<?> clazz) {\n    Method[] methods = clazz.getMethods();\n    for(int i = 0; i < methods.length; ++i) {\n      Annotation jsonValue = methods[i].getAnnotation(JsonValue.class);\n      if (jsonValue != null) {\n        return methods[i];\n      }\n    }\n    return null;\n  }\n\n  private static boolean canDirectlySerializeClass(Class clazz)  {\n    return isWrapperOrPrimitiveType(clazz) ||\n        clazz.equals(String.class);\n  }\n\n  private static boolean isWrapperOrPrimitiveType(Class<?> clazz) {\n    return clazz.isPrimitive() ||\n        clazz.equals(Boolean.class) ||\n        clazz.equals(Integer.class) ||\n        clazz.equals(Character.class) ||\n        clazz.equals(Byte.class) ||\n        clazz.equals(Short.class) ||\n        clazz.equals(Double.class) ||\n        clazz.equals(Long.class) ||\n        clazz.equals(Float.class);\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/json/annotation/JsonProperty.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.json.annotation;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface JsonProperty {\n\n  boolean required() default false;\n\n}\n\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/json/annotation/JsonValue.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.json.annotation;\n\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\n\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface JsonValue {\n  \n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/AddressNameHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport com.facebook.stetho.common.ProcessUtil;\n\npublic class AddressNameHelper {\n  private static final String PREFIX = \"stetho_\";\n\n  public static String createCustomAddress(String suffix) {\n    final int userId = ProcessUtil.getUserId();\n    return\n        PREFIX +\n        ProcessUtil.getProcessName() +\n        (userId == 0 ? \"\" : (\"_\" + userId)) +\n        suffix;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/CompositeInputStream.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport com.facebook.stetho.common.LogUtil;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport javax.annotation.concurrent.NotThreadSafe;\n\n@NotThreadSafe\npublic class CompositeInputStream extends InputStream {\n  private final InputStream[] mStreams;\n  private int mCurrentIndex;\n\n  public CompositeInputStream(InputStream[] streams) {\n    if (streams == null || streams.length < 2) {\n      throw new IllegalArgumentException(\"Streams must be non-null and have more than 1 entry\");\n    }\n    mStreams = streams;\n    mCurrentIndex = 0;\n  }\n\n  @Override\n  public int available() throws IOException {\n    return mStreams[mCurrentIndex].available();\n  }\n\n  @Override\n  public void close() throws IOException {\n    closeAll(mCurrentIndex);\n  }\n\n  private void closeAll(int mostImportantIndex) throws IOException {\n    IOException exceptionToThrow = null;\n    for (int i = 0; i < mStreams.length; i++) {\n      try {\n        mStreams[i].close();\n      } catch (IOException e) {\n        IOException previousException = exceptionToThrow;\n        if (i == mostImportantIndex || exceptionToThrow == null) {\n          exceptionToThrow = e;\n        }\n        if (previousException != null && previousException != exceptionToThrow) {\n          LogUtil.w(previousException, \"Suppressing exception\");\n        }\n      }\n    }\n  }\n\n  @Override\n  public void mark(int readlimit) {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public boolean markSupported() {\n    return false;\n  }\n\n  @Override\n  public void reset() throws IOException {\n    throw new UnsupportedOperationException();\n  }\n\n  @Override\n  public int read(byte[] buffer) throws IOException {\n    return read(buffer, 0, buffer.length);\n  }\n\n  @Override\n  public int read(byte[] buffer, int byteOffset, int byteCount) throws IOException {\n    int n;\n    while ((n = mStreams[mCurrentIndex].read(buffer, byteOffset, byteCount)) == -1) {\n      if (!tryMoveToNextStream()) {\n        break;\n      }\n    }\n    return n;\n  }\n\n  @Override\n  public int read() throws IOException {\n    int b;\n    while ((b = mStreams[mCurrentIndex].read()) == -1) {\n      if (!tryMoveToNextStream()) {\n        break;\n      }\n    }\n    return b;\n  }\n\n  private boolean tryMoveToNextStream() {\n    if (mCurrentIndex + 1 < mStreams.length) {\n      mCurrentIndex++;\n      return true;\n    }\n    return false;\n  }\n\n  @Override\n  public long skip(long byteCount) throws IOException {\n    byte[] buf = new byte[(int)byteCount];\n    int n = read(buf);\n    return n >= 0 ? n : -1;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/LazySocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.net.LocalSocket;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\n\nimport java.io.IOException;\n\n/**\n * Optimization designed to allow us to lazily construct/configure the true Stetho server\n * only after the first caller connects.  This gives us much more wiggle room to have performance\n * impact in the set up path that only applies when Stetho is _used_, not simply enabled.\n */\npublic class LazySocketHandler implements SocketHandler {\n  private final SocketHandlerFactory mSocketHandlerFactory;\n\n  @Nullable\n  private SocketHandler mSocketHandler;\n\n  public LazySocketHandler(SocketHandlerFactory socketHandlerFactory) {\n    mSocketHandlerFactory = socketHandlerFactory;\n  }\n\n  @Override\n  public void onAccepted(LocalSocket socket) throws IOException {\n    getSocketHandler().onAccepted(socket);\n  }\n\n  @Nonnull\n  private synchronized SocketHandler getSocketHandler() {\n    if (mSocketHandler == null) {\n      mSocketHandler = mSocketHandlerFactory.create();\n    }\n    return mSocketHandler;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/LeakyBufferedInputStream.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport javax.annotation.concurrent.ThreadSafe;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n@ThreadSafe\npublic class LeakyBufferedInputStream extends BufferedInputStream {\n  private boolean mLeaked;\n  private boolean mMarked;\n\n  public LeakyBufferedInputStream(InputStream in, int bufSize) {\n    super(in, bufSize);\n  }\n\n  @Override\n  public synchronized void mark(int readlimit) {\n    throwIfLeaked();\n    mMarked = true;\n    super.mark(readlimit);\n  }\n\n  @Override\n  public synchronized void reset() throws IOException {\n    throwIfLeaked();\n    mMarked = false;\n    super.reset();\n  }\n\n  @Override\n  public boolean markSupported() {\n    return true;\n  }\n\n  public synchronized InputStream leakBufferAndStream() {\n    throwIfLeaked();\n    throwIfMarked();\n    mLeaked = true;\n    return new CompositeInputStream(\n        new InputStream[] {\n            new ByteArrayInputStream(clearBufferLocked()),\n            in\n        });\n  }\n\n  private byte[] clearBufferLocked() {\n    byte[] leaked = new byte[count - pos];\n    System.arraycopy(buf, pos, leaked, 0, leaked.length);\n    pos = 0;\n    count = 0;\n    return leaked;\n  }\n\n  private void throwIfLeaked() {\n    if (mLeaked) {\n      throw new IllegalStateException();\n    }\n  }\n\n  private void throwIfMarked() {\n    if (mMarked) {\n      throw new IllegalStateException();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/LocalSocketServer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.net.LocalServerSocket;\nimport android.net.LocalSocket;\nimport android.util.Log;\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.common.Util;\n\nimport javax.annotation.Nonnull;\n\nimport java.io.IOException;\nimport java.io.InterruptedIOException;\nimport java.net.BindException;\nimport java.net.SocketException;\nimport java.util.concurrent.atomic.AtomicInteger;\n\npublic class LocalSocketServer {\n  private static final String WORKER_THREAD_NAME_PREFIX = \"StethoWorker\";\n  private static final int MAX_BIND_RETRIES = 2;\n  private static final int TIME_BETWEEN_BIND_RETRIES_MS = 1000;\n\n  private final String mFriendlyName;\n  private final String mAddress;\n  private final SocketHandler mSocketHandler;\n  private final AtomicInteger mThreadId = new AtomicInteger();\n\n  private Thread mListenerThread;\n  private boolean mStopped;\n  private LocalServerSocket mServerSocket;\n\n  /**\n   * @param friendlyName identifier to help debug this server, used for naming threads and such.\n   * @param address the local socket address to listen on.\n   * @param socketHandler functional handler once a socket is accepted.\n   */\n  public LocalSocketServer(\n      String friendlyName,\n      String address,\n      SocketHandler socketHandler) {\n    mFriendlyName = Util.throwIfNull(friendlyName);\n    mAddress = Util.throwIfNull(address);\n    mSocketHandler = socketHandler;\n  }\n\n  public String getName() {\n    return mFriendlyName;\n  }\n\n  /**\n   * Binds to the address and listens for connections.\n   * <p/>\n   * If successful, this thread blocks forever or until {@link #stop} is called, whichever\n   * happens first.\n   *\n   * @throws IOException Thrown on failure to bind the socket.\n   */\n  public void run() throws IOException {\n    synchronized (this) {\n      if (mStopped) {\n        return;\n      }\n      mListenerThread = Thread.currentThread();\n    }\n\n    listenOnAddress(mAddress);\n  }\n\n  private void listenOnAddress(String address) throws IOException {\n    mServerSocket = bindToSocket(address);\n    LogUtil.i(\"Listening on @\" + address);\n\n    while (!Thread.interrupted()) {\n      try {\n        // Use previously accepted socket the first time around, otherwise wait to\n        // accept another.\n        LocalSocket socket = mServerSocket.accept();\n\n        // Start worker thread\n        Thread t = new WorkerThread(socket, mSocketHandler);\n        t.setName(\n            WORKER_THREAD_NAME_PREFIX +\n            \"-\" + mFriendlyName +\n            \"-\" + mThreadId.incrementAndGet());\n        t.setDaemon(true);\n        t.start();\n      } catch (SocketException se) {\n        // ignore exception if interrupting the thread\n        if (Thread.interrupted()) {\n          break;\n        }\n        LogUtil.w(se, \"I/O error\");\n      } catch (InterruptedIOException ex) {\n        break;\n      } catch (IOException e) {\n        LogUtil.w(e, \"I/O error initialising connection thread\");\n        break;\n      }\n    }\n\n    LogUtil.i(\"Server shutdown on @\" + address);\n  }\n\n  /**\n   * Stops the listener thread and unbinds the address.\n   */\n  public void stop() {\n    synchronized (this) {\n      mStopped = true;\n      if (mListenerThread == null) {\n        return;\n      }\n    }\n\n    mListenerThread.interrupt();\n    try {\n      if (mServerSocket != null) {\n        mServerSocket.close();\n      }\n    } catch (IOException e) {\n      // Don't care...\n    }\n  }\n\n  @Nonnull\n  private static LocalServerSocket bindToSocket(String address) throws IOException {\n    int retries = MAX_BIND_RETRIES;\n    IOException firstException = null;\n    do {\n      try {\n        if (LogUtil.isLoggable(Log.DEBUG)) {\n          LogUtil.d(\"Trying to bind to @\" + address);\n        }\n        return new LocalServerSocket(address);\n      } catch (BindException be) {\n        LogUtil.w(be, \"Binding error, sleep \" + TIME_BETWEEN_BIND_RETRIES_MS + \" ms...\");\n        if (firstException == null) {\n          firstException = be;\n        }\n        Util.sleepUninterruptibly(TIME_BETWEEN_BIND_RETRIES_MS);\n      }\n    } while (retries-- > 0);\n\n    throw firstException;\n  }\n\n  private static class WorkerThread extends Thread {\n    private final LocalSocket mSocket;\n    private final SocketHandler mSocketHandler;\n\n    public WorkerThread(LocalSocket socket, SocketHandler socketHandler) {\n      mSocket = socket;\n      mSocketHandler = socketHandler;\n    }\n\n    @Override\n    public void run() {\n      try {\n        mSocketHandler.onAccepted(mSocket);\n      } catch (IOException ex) {\n        LogUtil.w(\"I/O error: %s\", ex);\n      } finally {\n        try {\n          mSocket.close();\n        } catch (IOException ignore) {\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/PeerAuthorizationException.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\npublic class PeerAuthorizationException extends Exception {\n  public PeerAuthorizationException(String message) {\n    super(message);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/ProtocolDetectingSocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.content.Context;\nimport android.net.LocalSocket;\n\nimport javax.annotation.concurrent.NotThreadSafe;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\n/**\n * Socket handler which is designed to detect a difference in protocol signatures very early on\n * in the connection to figure out which real handler to route to.  This is used for performance\n * and backwards compatibility reasons to maintain Stetho having just one actual socket\n * connection despite dumpapp and DevTools now diverging in protocol.\n * <p />\n * Note this trick is only possible if the protocol requires that the client initiate the\n * conversation.  Otherwise, the server would be expected to say something before we know what\n * protocol the client is speaking.\n */\npublic class ProtocolDetectingSocketHandler extends SecureSocketHandler {\n  private static final int SENSING_BUFFER_SIZE = 256;\n\n  private final ArrayList<HandlerInfo> mHandlers = new ArrayList<>(2);\n\n  public ProtocolDetectingSocketHandler(Context context) {\n    super(context);\n  }\n\n  public void addHandler(MagicMatcher magicMatcher, SocketLikeHandler handler) {\n    mHandlers.add(new HandlerInfo(magicMatcher, handler));\n  }\n\n  @Override\n  protected void onSecured(LocalSocket socket) throws IOException {\n    LeakyBufferedInputStream leakyIn = new LeakyBufferedInputStream(\n        socket.getInputStream(),\n        SENSING_BUFFER_SIZE);\n\n    if (mHandlers.isEmpty()) {\n      throw new IllegalStateException(\"No handlers added\");\n    }\n\n    for (int i = 0, N = mHandlers.size(); i < N; i++) {\n      HandlerInfo handlerInfo = mHandlers.get(i);\n      leakyIn.mark(SENSING_BUFFER_SIZE);\n      boolean matches = handlerInfo.magicMatcher.matches(leakyIn);\n      leakyIn.reset();\n      if (matches) {\n        SocketLike socketLike = new SocketLike(socket, leakyIn);\n        handlerInfo.handler.onAccepted(socketLike);\n        return;\n      }\n    }\n\n    throw new IOException(\"No matching handler, firstByte=\" + leakyIn.read());\n  }\n\n  public interface MagicMatcher {\n    boolean matches(InputStream in) throws IOException;\n  }\n\n  public static class ExactMagicMatcher implements MagicMatcher {\n    private final byte[] mMagic;\n\n    public ExactMagicMatcher(byte[] magic) {\n      mMagic = magic;\n    }\n\n    @Override\n    public boolean matches(InputStream in) throws IOException {\n      byte[] buf = new byte[mMagic.length];\n      int n = in.read(buf);\n      return n == buf.length && Arrays.equals(buf, mMagic);\n    }\n  }\n\n  public static class AlwaysMatchMatcher implements MagicMatcher {\n    @Override\n    public boolean matches(InputStream in) throws IOException {\n      return true;\n    }\n  }\n\n  private static class HandlerInfo {\n    public final MagicMatcher magicMatcher;\n    public final SocketLikeHandler handler;\n\n    private HandlerInfo(MagicMatcher magicMatcher, SocketLikeHandler handler) {\n      this.magicMatcher = magicMatcher;\n      this.handler = handler;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/SecureSocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.Manifest;\nimport android.content.Context;\nimport android.content.pm.PackageManager;\nimport android.net.Credentials;\nimport android.net.LocalSocket;\nimport android.util.Log;\nimport com.facebook.stetho.common.LogUtil;\n\nimport java.io.IOException;\n\npublic abstract class SecureSocketHandler implements SocketHandler {\n  private final Context mContext;\n\n  public SecureSocketHandler(Context context) {\n    mContext = context;\n  }\n\n  @Override\n  public final void onAccepted(LocalSocket socket) throws IOException {\n    try {\n      enforcePermission(mContext, socket);\n      onSecured(socket);\n    } catch (PeerAuthorizationException e) {\n      LogUtil.e(\"Unauthorized request: \" + e.getMessage());\n    }\n  }\n\n  protected abstract void onSecured(LocalSocket socket) throws IOException;\n\n  private static void enforcePermission(Context context, LocalSocket peer)\n      throws IOException, PeerAuthorizationException {\n    Credentials credentials = peer.getPeerCredentials();\n\n    int uid = credentials.getUid();\n    int pid = credentials.getPid();\n\n    if (LogUtil.isLoggable(Log.VERBOSE)) {\n      LogUtil.v(\"Got request from uid=%d, pid=%d\", uid, pid);\n    }\n\n    String requiredPermission = Manifest.permission.DUMP;\n    int checkResult = context.checkPermission(requiredPermission, pid, uid);\n    if (checkResult != PackageManager.PERMISSION_GRANTED) {\n      throw new PeerAuthorizationException(\n          \"Peer pid=\" + pid + \", uid=\" + uid + \" does not have \" + requiredPermission);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/ServerManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport com.facebook.stetho.common.LogUtil;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\n\npublic class ServerManager {\n  private static final String THREAD_PREFIX = \"StethoListener\";\n  private final LocalSocketServer mServer;\n\n  private volatile boolean mStarted;\n\n  public ServerManager(LocalSocketServer server) {\n    mServer = server;\n  }\n\n  public void start() {\n    if (mStarted) {\n      throw new IllegalStateException(\"Already started\");\n    }\n    mStarted = true;\n    startServer(mServer);\n  }\n\n  private void startServer(final LocalSocketServer server) {\n    Thread listener = new Thread(THREAD_PREFIX + \"-\" + server.getName()) {\n      @Override\n      public void run() {\n        try {\n          server.run();\n        } catch (IOException e) {\n          LogUtil.e(e, \"Could not start Stetho server: %s\", server.getName());\n        }\n      }\n    };\n    listener.start();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/SocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.net.LocalSocket;\n\nimport java.io.IOException;\n\n/**\n * @see SecureSocketHandler\n */\npublic interface SocketHandler {\n  /**\n   * Server socket has been accepted and a dedicated thread has been allocated to process this\n   * callback.  Returning from this method or throwing an exception will attempt an orderly\n   * shutdown of the socket, however it will not be treated as an error if returning normally.\n   */\n  void onAccepted(LocalSocket socket) throws IOException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/SocketHandlerFactory.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\n/** @see LazySocketHandler */\npublic interface SocketHandlerFactory {\n  SocketHandler create();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/SocketLike.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.net.LocalSocket;\nimport com.facebook.stetho.server.CompositeInputStream;\nimport com.facebook.stetho.server.LeakyBufferedInputStream;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Utility to allow reading buffered data from a socket and then \"unreading\" the data\n * and combining it with the original unbuffered stream.  This is useful when\n * handing off from one logical protocol layer to the next, such as when upgrading an HTTP\n * connection to the websocket protocol.\n */\npublic class SocketLike {\n  private final LocalSocket mSocket;\n  private final LeakyBufferedInputStream mLeakyInput;\n\n  public SocketLike(SocketLike socketLike, LeakyBufferedInputStream leakyInput) {\n    this(socketLike.mSocket, leakyInput);\n  }\n\n  public SocketLike(LocalSocket socket, LeakyBufferedInputStream leakyInput) {\n    mSocket = socket;\n    mLeakyInput = leakyInput;\n  }\n\n  public InputStream getInput() throws IOException {\n    return mLeakyInput.leakBufferAndStream();\n  }\n\n  public OutputStream getOutput() throws IOException {\n    return mSocket.getOutputStream();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/SocketLikeHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server;\n\nimport android.net.LocalSocket;\n\nimport java.io.IOException;\n\n/**\n * Similar to {@link SocketHandler} but designed to operate on {@link SocketLike} instances\n * which allow for buffered \"peeks\" of data to decide which protocol handler to use.\n *\n * @see SocketHandler\n * @see SocketLike\n */\npublic interface SocketLikeHandler {\n  /** @see SocketHandler#onAccepted(LocalSocket) */\n  void onAccepted(SocketLike socket) throws IOException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/ExactPathMatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\npublic class ExactPathMatcher implements PathMatcher {\n  private final String mPath;\n\n  public ExactPathMatcher(String path) {\n    mPath = path;\n  }\n\n  @Override\n  public boolean match(String path) {\n    return mPath.equals(path);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/HandlerRegistry.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport java.util.ArrayList;\n\nimport androidx.annotation.Nullable;\n\npublic class HandlerRegistry {\n  private final ArrayList<PathMatcher> mPathMatchers = new ArrayList<>();\n  private final ArrayList<HttpHandler> mHttpHandlers = new ArrayList<>();\n\n  public synchronized void register(PathMatcher path, HttpHandler handler) {\n    mPathMatchers.add(path);\n    mHttpHandlers.add(handler);\n  }\n\n  public synchronized boolean unregister(PathMatcher path, HttpHandler handler) {\n    int index = mPathMatchers.indexOf(path);\n    if (index >= 0) {\n      if (handler == mHttpHandlers.get(index)) {\n        mPathMatchers.remove(index);\n        mHttpHandlers.remove(index);\n        return true;\n      }\n    }\n    return false;\n  }\n\n  @Nullable\n  public synchronized HttpHandler lookup(String path) {\n    for (int i = 0, N = mPathMatchers.size(); i < N; i++) {\n      if (mPathMatchers.get(i).match(path)) {\n        return mHttpHandlers.get(i);\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/HttpHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport com.facebook.stetho.server.SocketLike;\n\nimport java.io.IOException;\n\npublic interface HttpHandler {\n  boolean handleRequest(\n      SocketLike socket,\n      LightHttpRequest request,\n      LightHttpResponse response)\n      throws IOException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/HttpHeaders.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\npublic interface HttpHeaders {\n  String CONTENT_TYPE = \"Content-Type\";\n  String CONTENT_LENGTH = \"Content-Length\";\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/HttpStatus.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\npublic interface HttpStatus {\n  int HTTP_SWITCHING_PROTOCOLS = 101;\n  int HTTP_OK = 200;\n  int HTTP_NOT_FOUND = 404;\n  int HTTP_INTERNAL_SERVER_ERROR = 500;\n  int HTTP_NOT_IMPLEMENTED = 501;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/LightHttpBody.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.io.UnsupportedEncodingException;\n\npublic abstract class LightHttpBody {\n  public static LightHttpBody create(String body, String contentType) {\n    try {\n      return create(body.getBytes(\"UTF-8\"), contentType);\n    } catch (UnsupportedEncodingException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  public static LightHttpBody create(final byte[] body, final String contentType) {\n    return new LightHttpBody() {\n      @Override\n      public String contentType() {\n        return contentType;\n      }\n\n      @Override\n      public int contentLength() {\n        return body.length;\n      }\n\n      @Override\n      public void writeTo(OutputStream output) throws IOException {\n        output.write(body);\n      }\n    };\n  }\n\n  public abstract String contentType();\n  public abstract int contentLength();\n  public abstract void writeTo(OutputStream output) throws IOException;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/LightHttpMessage.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport java.util.ArrayList;\n\nimport androidx.annotation.Nullable;\n\npublic class LightHttpMessage {\n  public final ArrayList<String> headerNames = new ArrayList<>();\n  public final ArrayList<String> headerValues = new ArrayList<>();\n\n  public void addHeader(String name, String value) {\n    headerNames.add(name);\n    headerValues.add(value);\n  }\n\n  @Nullable\n  public String getFirstHeaderValue(String name) {\n    for (int i = 0, N = headerNames.size(); i < N; i++) {\n      if (name.equals(headerNames.get(i))) {\n        return headerValues.get(i);\n      }\n    }\n    return null;\n  }\n\n  public void reset() {\n    headerNames.clear();\n    headerValues.clear();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/LightHttpRequest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport android.net.Uri;\n\npublic class LightHttpRequest extends LightHttpMessage {\n  public String method;\n  public Uri uri;\n  public String protocol;\n\n  @Override\n  public void reset() {\n    super.reset();\n    this.method = null;\n    this.uri = null;\n    this.protocol = null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/LightHttpResponse.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\npublic class LightHttpResponse extends LightHttpMessage {\n  public int code;\n  public String reasonPhrase;\n  public LightHttpBody body;\n\n  public void prepare() {\n    if (body != null) {\n      addHeader(HttpHeaders.CONTENT_TYPE, body.contentType());\n      addHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(body.contentLength()));\n    }\n  }\n\n  @Override\n  public void reset() {\n    super.reset();\n    this.code = -1;\n    this.reasonPhrase = null;\n    this.body = null;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/LightHttpServer.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport android.net.Uri;\n\nimport com.facebook.stetho.server.LeakyBufferedInputStream;\nimport com.facebook.stetho.server.SocketLike;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\n\nimport androidx.annotation.Nullable;\n\n/**\n * Somewhat crude but very fast HTTP server designed exclusively to handle the\n * Chrome DevTools protocol, though sufficiently general to do other very basic things.\n * Performance is imperative here as Chrome aggressively polls Stetho asking for\n * meta data when the discovery window is open in Chrome.\n */\npublic class LightHttpServer {\n  private static final String TAG = \"LightHttpServer\";\n\n  private final HandlerRegistry mHandlerRegistry;\n\n  public LightHttpServer(HandlerRegistry handlerRegistry) {\n    mHandlerRegistry = handlerRegistry;\n  }\n\n  public void serve(SocketLike socket) throws IOException {\n    LeakyBufferedInputStream input = new LeakyBufferedInputStream(socket.getInput(), 1024);\n    OutputStream output = socket.getOutput();\n    HttpMessageReader reader = new HttpMessageReader(input);\n    HttpMessageWriter writer = new HttpMessageWriter(new BufferedOutputStream(output));\n\n    SocketLike anotherSocketLike = new SocketLike(socket, input);\n    LightHttpRequest scratchRequest = new LightHttpRequest();\n    LightHttpResponse scratchResponse = new LightHttpResponse();\n    LightHttpRequest request;\n\n    // This loops assumes we are always using keep-alive connections.  If we're wrong, we\n    // expect the client to just close the connection.\n    while ((request = readRequestMessage(scratchRequest, reader)) != null) {\n      final LightHttpResponse response = scratchResponse;\n      response.reset();\n\n      // Note, if we're upgrading to websockets, this will block for the lifetime of the\n      // websocket session...\n      boolean keepGoing = dispatchToHandler(anotherSocketLike, request, response);\n      if (!keepGoing) {\n        // Orderly shutdown, ignore response and break the loop.\n        break;\n      }\n\n      writeFullResponse(response, writer, output);\n    }\n  }\n\n  private boolean dispatchToHandler(\n      SocketLike socketLike,\n      LightHttpRequest request,\n      LightHttpResponse response)\n      throws IOException {\n    HttpHandler handler = mHandlerRegistry.lookup(request.uri.getPath());\n    if (handler == null) {\n      response.code = HttpStatus.HTTP_NOT_FOUND;\n      response.reasonPhrase = \"Not found\";\n      response.body = LightHttpBody.create(\"No handler found\\n\", \"text/plain\");\n      return true;\n    } else {\n      try {\n        return handler.handleRequest(socketLike, request, response);\n      } catch (RuntimeException e) {\n        response.code = HttpStatus.HTTP_INTERNAL_SERVER_ERROR;\n        response.reasonPhrase = \"Internal Server Error\";\n        StringWriter stack = new StringWriter();\n        PrintWriter stackWriter = new PrintWriter(stack);\n        try {\n          e.printStackTrace(stackWriter);\n        } finally {\n          stackWriter.close();\n        }\n        response.body = LightHttpBody.create(stack.toString(), \"text/plain\");\n        return true;\n      }\n    }\n  }\n\n  @Nullable\n  private static LightHttpRequest readRequestMessage(\n      LightHttpRequest request,\n      HttpMessageReader reader)\n      throws IOException {\n    request.reset();\n\n    String requestLine = reader.readLine();\n    if (requestLine == null) {\n      return null;\n    }\n\n    // Zero tolerance on URI encoding, that URI better not have a space in it...\n    String[] requestParts = requestLine.split(\" \", 3);\n    if (requestParts.length != 3) {\n      throw new IOException(\"Invalid request line: \" + requestLine);\n    }\n\n    request.method = requestParts[0];\n    request.uri = Uri.parse(requestParts[1]);\n    request.protocol = requestParts[2];\n\n    readHeaders(request, reader);\n\n    return request;\n  }\n\n  private static void readHeaders(\n      LightHttpMessage message,\n      HttpMessageReader reader) throws IOException {\n    String headerLine;\n    while (true) {\n      headerLine = reader.readLine();\n      if (headerLine == null) {\n        throw new EOFException();\n      } else if (\"\".equals(headerLine)) {\n        break;\n      } else {\n        String[] headerParts = headerLine.split(\": \", 2);\n        if (headerParts.length != 2) {\n          throw new IOException(\"Malformed header: \" + headerLine);\n        }\n        String name = headerParts[0];\n        String value = headerParts[1];\n\n        message.headerNames.add(name);\n        message.headerValues.add(value);\n      }\n    }\n  }\n\n  private static void writeFullResponse(\n      LightHttpResponse response,\n      HttpMessageWriter writer,\n      OutputStream output)\n      throws IOException {\n    response.prepare();\n    writeResponseMessage(response, writer);\n    if (response.body != null) {\n      response.body.writeTo(output);\n    }\n  }\n\n  public static void writeResponseMessage(LightHttpResponse response, HttpMessageWriter writer)\n      throws IOException {\n    writer.writeLine(\"HTTP/1.1 \" + response.code + \" \" + response.reasonPhrase);\n    for (int i = 0, N = response.headerNames.size(); i < N; i++) {\n      String name = response.headerNames.get(i);\n      String value = response.headerValues.get(i);\n      writer.writeLine(name + \": \" + value);\n    }\n    writer.writeLine();\n    writer.flush();\n  }\n\n  /**\n   * Efficient, unbuffered variation of {@link InputStreamReader} which assumes the input is\n   * always ASCII.  This is especially useful when you are certain that the client and server\n   * are both mechanized and will not contain non-ASCII characters in the control messages upon\n   * which this reader is applied.\n   */\n  private static class HttpMessageReader {\n    private final BufferedInputStream mIn;\n    private final StringBuilder mBuffer = new StringBuilder();\n    private final NewLineDetector mNewLineDetector = new NewLineDetector();\n\n    public HttpMessageReader(BufferedInputStream in) {\n      mIn = in;\n    }\n\n    @Nullable\n    public String readLine() throws IOException {\n      while (true) {\n        int b = mIn.read();\n        if (b < 0) {\n          return null;\n        }\n\n        char c = (char)b;\n        mNewLineDetector.accept(c);\n\n        switch (mNewLineDetector.state()) {\n          case NewLineDetector.STATE_ON_CRLF:\n            String result = mBuffer.toString();\n            mBuffer.setLength(0);\n            return result;\n          case NewLineDetector.STATE_ON_CR:\n            break;\n          case NewLineDetector.STATE_ON_OTHER:\n            mBuffer.append(c);\n            break;\n        }\n      }\n    }\n\n    private static class NewLineDetector {\n      private static final int STATE_ON_OTHER = 1;\n      private static final int STATE_ON_CR = 2;\n      private static final int STATE_ON_CRLF = 3;\n\n      private int state = STATE_ON_OTHER;\n\n      public void accept(char c) {\n        switch (state) {\n          case STATE_ON_OTHER:\n            if (c == '\\r') {\n              state = STATE_ON_CR;\n            }\n            break;\n          case STATE_ON_CR:\n            if (c == '\\n') {\n              state = STATE_ON_CRLF;\n            } else {\n              state = STATE_ON_OTHER;\n            }\n            break;\n          case STATE_ON_CRLF:\n            if (c == '\\r') {\n              state = STATE_ON_CR;\n            } else {\n              state = STATE_ON_OTHER;\n            }\n            break;\n          default:\n            throw new IllegalArgumentException(\"Unknown state: \" + state);\n        }\n      }\n\n      public int state() {\n        return state;\n      }\n    }\n  }\n\n  /**\n   * Similar in spirit to {@link HttpMessageReader} which assumes ASCII for all messages as\n   * a performance optimization.  Caller is responsible for flushing the writer.\n   * <p />\n   * Exposed publicly as a hack to support WebSocket upgrade.\n   */\n  public static class HttpMessageWriter {\n    private final BufferedOutputStream mOut;\n    private static final byte[] CRLF = \"\\r\\n\".getBytes();\n\n    public HttpMessageWriter(BufferedOutputStream out) {\n      mOut = out;\n    }\n\n    public void writeLine(String line) throws IOException {\n      for (int i = 0, N = line.length(); i < N; i++) {\n        char c = line.charAt(i);\n        mOut.write((int)c);\n      }\n      mOut.write(CRLF);\n    }\n\n    public void writeLine() throws IOException {\n      mOut.write(CRLF);\n    }\n\n    public void flush() throws IOException {\n      mOut.flush();\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/PathMatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\npublic interface PathMatcher {\n  boolean match(String path);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/server/http/RegexpPathMatcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.server.http;\n\nimport java.util.regex.Pattern;\n\npublic class RegexpPathMatcher implements PathMatcher {\n  private final Pattern mPattern;\n\n  public RegexpPathMatcher(Pattern pattern) {\n    mPattern = pattern;\n  }\n\n  @Override\n  public boolean match(String path) {\n    return mPattern.matcher(path).matches();\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/CloseCodes.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\n/**\n * Close codes as defined by RFC6455.\n */\npublic interface CloseCodes {\n  int NORMAL_CLOSURE = 1000;\n  int PROTOCOL_ERROR = 1002;\n  int CLOSED_ABNORMALLY = 1006;\n  int UNEXPECTED_CONDITION = 1011;\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/Frame.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * WebSocket frame as per RFC6455.\n */\nclass Frame {\n  public static final byte OPCODE_TEXT_FRAME = 0x1;\n  public static final byte OPCODE_BINARY_FRAME = 0x2;\n  public static final byte OPCODE_CONNECTION_CLOSE = 0x8;\n  public static final byte OPCODE_CONNECTION_PING = 0x9;\n  public static final byte OPCODE_CONNECTION_PONG = 0xA;\n\n  public boolean fin;\n  public boolean rsv1;\n  public boolean rsv2;\n  public boolean rsv3;\n  public byte opcode;\n  public boolean hasMask;\n  public long payloadLen;\n  public byte[] maskingKey;\n  public byte[] payloadData;\n\n  public void readFrom(BufferedInputStream input) throws IOException {\n    decodeFirstByte(readByteOrThrow(input));\n    byte maskAndFirstLengthBits = readByteOrThrow(input);\n    hasMask = (maskAndFirstLengthBits & 0x80) != 0;\n    payloadLen = decodeLength((byte)(maskAndFirstLengthBits & ~0x80), input);\n    maskingKey = hasMask ? decodeMaskingKey(input) : null;\n    payloadData = new byte[(int)payloadLen];\n    readBytesOrThrow(input, payloadData, 0, (int)payloadLen);\n    MaskingHelper.unmask(maskingKey, payloadData, 0, (int)payloadLen);\n  }\n\n  public void writeTo(BufferedOutputStream output) throws IOException {\n    output.write(encodeFirstByte());\n    byte[] lengthAndMaskBit = encodeLength(payloadLen);\n    if (hasMask) {\n      lengthAndMaskBit[0] |= 0x80;\n    }\n    output.write(lengthAndMaskBit, 0, lengthAndMaskBit.length);\n\n    if (hasMask) {\n      throw new UnsupportedOperationException(\"Writing masked data not implemented\");\n    }\n    output.write(payloadData, 0, (int) payloadLen);\n  }\n\n  private void decodeFirstByte(byte b) {\n    fin = (b & 0x80) != 0;\n    rsv1 = (b & 0x40) != 0;\n    rsv2 = (b & 0x20) != 0;\n    rsv3 = (b & 0x10) != 0;\n    opcode = (byte)(b & 0xf);\n  }\n\n  private byte encodeFirstByte() {\n    byte b = 0;\n    if (fin) {\n      b |= 0x80;\n    }\n    if (rsv1) {\n      b |= 0x40;\n    }\n    if (rsv2) {\n      b |= 0x20;\n    }\n    if (rsv3) {\n      b |= 0x10;\n    }\n    b |= (opcode & 0xf);\n    return b;\n  }\n\n  private long decodeLength(byte firstLenByte, InputStream in) throws IOException {\n    if (firstLenByte <= 125) {\n      return firstLenByte;\n    } else if (firstLenByte == 126) {\n      return (readByteOrThrow(in) & 0xff) << 8 | (readByteOrThrow(in) & 0xff);\n    } else if (firstLenByte == 127) {\n      long len = 0;\n      for (int i = 0; i < 8; i++) {\n        len <<= 8;\n        len |= (readByteOrThrow(in) & 0xff);\n      }\n      return len;\n    } else {\n      throw new IOException(\"Unexpected length byte: \" + firstLenByte);\n    }\n  }\n\n  private static byte[] encodeLength(long len) {\n    if (len <= 125) {\n      return new byte[] { (byte)len };\n    } else if (len <= 0xffff) {\n      return new byte[] {\n          126,\n          (byte)((len >> 8) & 0xff),\n          (byte)((len) & 0xff)\n      };\n    } else {\n      return new byte[] {\n          127,\n          (byte)((len >> 56) & 0xff),\n          (byte)((len >> 48) & 0xff),\n          (byte)((len >> 40) & 0xff),\n          (byte)((len >> 32) & 0xff),\n          (byte)((len >> 24) & 0xff),\n          (byte)((len >> 16) & 0xff),\n          (byte)((len >> 8) & 0xff),\n          (byte)((len) & 0xff)\n      };\n    }\n  }\n\n  private static byte[] decodeMaskingKey(InputStream in) throws IOException {\n    byte[] key = new byte[4];\n    readBytesOrThrow(in, key, 0, key.length);\n    return key;\n  }\n\n  private static void readBytesOrThrow(InputStream in, byte[] buf, int offset, int count)\n      throws IOException {\n    while (count > 0) {\n      int n = in.read(buf, offset, count);\n      if (n == -1) {\n        throw new EOFException();\n      }\n      count -= n;\n      offset += n;\n    }\n  }\n\n  private static byte readByteOrThrow(InputStream in) throws IOException {\n    int b = in.read();\n    if (b == -1) {\n      throw new EOFException();\n    }\n    return (byte)b;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/FrameHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport com.facebook.stetho.common.Utf8Charset;\n\nclass FrameHelper {\n  public static Frame createTextFrame(String payload) {\n    return createSimpleFrame(Frame.OPCODE_TEXT_FRAME, Utf8Charset.encodeUTF8(payload));\n  }\n\n  public static Frame createBinaryFrame(byte[] payload) {\n    return createSimpleFrame(Frame.OPCODE_BINARY_FRAME, payload);\n  }\n\n  public static Frame createCloseFrame(int closeCode, String reasonPhrase) {\n    byte[] reasonPhraseEncoded = null;\n    int payloadLen = 2;\n    if (reasonPhrase != null) {\n      reasonPhraseEncoded = Utf8Charset.encodeUTF8(reasonPhrase);\n      payloadLen += reasonPhraseEncoded.length;\n    }\n    byte[] payload = new byte[payloadLen];\n    payload[0] = (byte)((closeCode >> 8) & 0xff);\n    payload[1] = (byte)((closeCode) & 0xff);\n    if (reasonPhraseEncoded != null) {\n      System.arraycopy(reasonPhraseEncoded, 0, payload, 2, reasonPhraseEncoded.length);\n    }\n    return createSimpleFrame(Frame.OPCODE_CONNECTION_CLOSE, payload);\n  }\n\n  public static Frame createPingFrame(byte[] payload, int payloadLen) {\n    return createSimpleFrame(Frame.OPCODE_CONNECTION_PING, payload, payloadLen);\n  }\n\n  public static Frame createPongFrame(byte[] payload, int payloadLen) {\n    return createSimpleFrame(Frame.OPCODE_CONNECTION_PONG, payload, payloadLen);\n  }\n\n  private static Frame createSimpleFrame(byte opcode, byte[] payload) {\n    return createSimpleFrame(opcode, payload, payload.length);\n  }\n\n  private static Frame createSimpleFrame(byte opCode, byte[] payload, int payloadLen) {\n    Frame frame = new Frame();\n    frame.fin = true;\n    frame.hasMask = false;\n    frame.opcode = opCode;\n    frame.payloadLen = payloadLen;\n    frame.payloadData = payload;\n    return frame;\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/MaskingHelper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nclass MaskingHelper {\n  public static void unmask(byte[] key, byte[] data, int offset, int count) {\n    int index = 0;\n    while (count-- > 0) {\n      data[offset++] ^= key[index++ % key.length];\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/ReadCallback.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\ninterface ReadCallback {\n  void onCompleteFrame(byte opcode, byte[] payload, int payloadLen);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/ReadHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport java.io.BufferedInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nclass ReadHandler {\n  private final BufferedInputStream mBufferedInput;\n  private final SimpleEndpoint mEndpoint;\n\n  /**\n   * Used to build a larger payload over multiple frames.\n   */\n  private final ByteArrayOutputStream mCurrentPayload = new ByteArrayOutputStream();\n\n  public ReadHandler(InputStream bufferedInput, SimpleEndpoint endpoint) {\n    mBufferedInput = new BufferedInputStream(bufferedInput, 1024);\n    mEndpoint = endpoint;\n  }\n\n  /**\n   * Enter a loop processing incoming frames until orderly shutdown or a socket exception is\n   * thrown.  This method returns normally on orderly shutdown, throws otherwise.\n   *\n   * @throws IOException Socket exception during the read loop.\n   */\n  public void readLoop(ReadCallback readCallback) throws IOException {\n    Frame frame = new Frame();\n    do {\n      frame.readFrom(mBufferedInput);\n      mCurrentPayload.write(frame.payloadData, 0, (int)frame.payloadLen);\n      if (frame.fin) {\n        byte[] completePayload = mCurrentPayload.toByteArray();\n        readCallback.onCompleteFrame(frame.opcode, completePayload, completePayload.length);\n        mCurrentPayload.reset();\n      }\n    } while (frame.opcode != Frame.OPCODE_CONNECTION_CLOSE);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/SimpleEndpoint.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\n/**\n * Alternative to JSR-356's Endpoint class but with a less insane J2EE-style API.\n */\npublic interface SimpleEndpoint {\n\n  /**\n   * Invoked when a new WebSocket session is established.\n   *\n   * @param session Unique handle for this session.\n   */\n  void onOpen(SimpleSession session);\n\n  /**\n   * Invoked when a text-based message is received from the peer.  May have spanned multiple\n   * WebSocket packets.\n   *\n   * @param session Unique handle for this session.\n   * @param message Complete payload data.\n   */\n  void onMessage(SimpleSession session, String message);\n\n  /**\n   * Invoked when a binary message is received from the peer.  May have spanned multiple\n   * WebSocket packets.\n   *\n   * @param session Unique handle for this session.\n   * @param message Complete payload data.\n   * @param messageLen Maximum number of bytes of {@code message} to read.\n   */\n  void onMessage(SimpleSession session, byte[] message, int messageLen);\n\n  /**\n   * Invoked when a remote peer closed the WebSocket session or if {@link SimpleSession#close}\n   * is invoked on our side.\n   *\n   * @param session Unique handle for this session.\n   * @param closeReasonCode Close reason code (see RFC6455)\n   * @param closeReasonPhrase Possibly arbitrary text phrase associated with the reason code.\n   */\n  void onClose(SimpleSession session, int closeReasonCode, String closeReasonPhrase);\n\n  /**\n   * Invoked when errors occur out of the normal band of the WebSocket protocol.  This is\n   * intended for debug purposes and is generally not actionable.  The {@link #onClose} method\n   * will still be invoked in all cases, making it reasonable to simply log in this method.\n   *\n   * @param session Unique handle for this session.\n   * @param t Exception that occurred.\n   */\n  void onError(SimpleSession session, Throwable t);\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/SimpleSession.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\n/**\n * Alternative to JSR-356's Session class but with a less insane J2EE-style API.\n */\npublic interface SimpleSession {\n  void sendText(String payload);\n  void sendBinary(byte[] payload);\n\n  /**\n   * Request that the session be closed.\n   *\n   * @param closeReason Close reason, as per RFC6455\n   * @param reasonPhrase Possibly arbitrary close reason phrase.\n   */\n  void close(int closeReason, String reasonPhrase);\n\n  boolean isOpen();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/WebSocketHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport android.util.Base64;\nimport com.facebook.stetho.common.Utf8Charset;\nimport com.facebook.stetho.server.http.HttpHandler;\nimport com.facebook.stetho.server.http.HttpStatus;\nimport com.facebook.stetho.server.SocketLike;\nimport com.facebook.stetho.server.http.LightHttpBody;\nimport com.facebook.stetho.server.http.LightHttpMessage;\nimport com.facebook.stetho.server.http.LightHttpRequest;\nimport com.facebook.stetho.server.http.LightHttpResponse;\nimport com.facebook.stetho.server.http.LightHttpServer;\n\nimport javax.annotation.Nullable;\n\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.security.MessageDigest;\nimport java.security.NoSuchAlgorithmException;\n\n/**\n * Crazy kludge to support upgrading to the WebSocket protocol while still using the\n * {@link HttpHandler} harness.\n * <p>\n * The way this works is that we pump the request directly into our WebSocket implementation and\n * force write the response out to the connection without returning.  Then, we extract the\n * remaining buffered input stream bytes from the socket and stitch them together with the\n * raw sockets input stream and pass everything onto the WebSocket engine which blocks\n * until WebSocket orderly shutdown.\n */\npublic class WebSocketHandler implements HttpHandler {\n  private static final String HEADER_UPGRADE = \"Upgrade\";\n  private static final String HEADER_CONNECTION = \"Connection\";\n  private static final String HEADER_SEC_WEBSOCKET_KEY = \"Sec-WebSocket-Key\";\n  private static final String HEADER_SEC_WEBSOCKET_ACCEPT = \"Sec-WebSocket-Accept\";\n  private static final String HEADER_SEC_WEBSOCKET_PROTOCOL = \"Sec-WebSocket-Protocol\";\n  private static final String HEADER_SEC_WEBSOCKET_VERSION = \"Sec-WebSocket-Version\";\n\n  private static final String HEADER_UPGRADE_WEBSOCKET = \"websocket\";\n  private static final String HEADER_CONNECTION_UPGRADE = \"Upgrade\";\n  private static final String HEADER_SEC_WEBSOCKET_VERSION_13 = \"13\";\n\n  // Are you kidding me?  The WebSocket spec requires that we append this weird hardcoded String\n  // to the key we receive from the client, SHA-1 that, and base64 encode it back to the client.\n  // I'm guessing this is to prevent replay attacks of some kind but given that there's no actual\n  // security context here, I can only imagine that this is just security through obscurity in\n  // some fashion.\n  private static final String SERVER_KEY_GUID = \"258EAFA5-E914-47DA-95CA-C5AB0DC85B11\";\n\n  private final SimpleEndpoint mEndpoint;\n\n  public WebSocketHandler(SimpleEndpoint endpoint) {\n    mEndpoint = endpoint;\n  }\n\n  @Override\n  public boolean handleRequest(\n      SocketLike socket,\n      LightHttpRequest request,\n      LightHttpResponse response) throws IOException {\n    if (!isSupportableUpgradeRequest(request)) {\n      response.code = HttpStatus.HTTP_NOT_IMPLEMENTED;\n      response.reasonPhrase = \"Not Implemented\";\n      response.body = LightHttpBody.create(\n          \"Not a supported WebSocket upgrade request\\n\",\n          \"text/plain\");\n      return true;\n    }\n\n    // This will not return on successful WebSocket upgrade, but rather block until the session is\n    // shut down or a socket error occurs.\n    doUpgrade(socket, request, response);\n    return false;\n  }\n\n  private static boolean isSupportableUpgradeRequest(LightHttpRequest request) {\n    return HEADER_UPGRADE_WEBSOCKET.equalsIgnoreCase(getFirstHeaderValue(request, HEADER_UPGRADE)) &&\n        HEADER_CONNECTION_UPGRADE.equals(getFirstHeaderValue(request, HEADER_CONNECTION)) &&\n        HEADER_SEC_WEBSOCKET_VERSION_13.equals(\n            getFirstHeaderValue(request, HEADER_SEC_WEBSOCKET_VERSION));\n  }\n\n  private void doUpgrade(\n      SocketLike socketLike,\n      LightHttpRequest request,\n      LightHttpResponse response)\n      throws IOException {\n    response.code = HttpStatus.HTTP_SWITCHING_PROTOCOLS;\n    response.reasonPhrase = \"Switching Protocols\";\n    response.addHeader(HEADER_UPGRADE, HEADER_UPGRADE_WEBSOCKET);\n    response.addHeader(HEADER_CONNECTION, HEADER_CONNECTION_UPGRADE);\n    response.body = null;\n\n    String clientKey = getFirstHeaderValue(request, HEADER_SEC_WEBSOCKET_KEY);\n    if (clientKey != null) {\n      response.addHeader(HEADER_SEC_WEBSOCKET_ACCEPT, generateServerKey(clientKey));\n    }\n\n    InputStream in = socketLike.getInput();\n    OutputStream out = socketLike.getOutput();\n    LightHttpServer.writeResponseMessage(\n        response,\n        new LightHttpServer.HttpMessageWriter(new BufferedOutputStream(out)));\n\n    WebSocketSession session = new WebSocketSession(in, out, mEndpoint);\n    session.handle();\n  }\n\n  private static String generateServerKey(String clientKey) {\n    try {\n      String serverKey = clientKey + SERVER_KEY_GUID;\n      MessageDigest sha1 = MessageDigest.getInstance(\"SHA-1\");\n      sha1.update(Utf8Charset.encodeUTF8(serverKey));\n      return Base64.encodeToString(sha1.digest(), Base64.NO_WRAP);\n    } catch (NoSuchAlgorithmException e) {\n      throw new RuntimeException(e);\n    }\n  }\n\n  @Nullable\n  private static String getFirstHeaderValue(LightHttpMessage message, String headerName) {\n    return message.getFirstHeaderValue(headerName);\n  }\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/WebSocketSession.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.util.concurrent.atomic.AtomicBoolean;\n\n/**\n * Binding driver between raw socket I/O and a high-level WebSocket interface.  This implementation\n * is generally very weak and doesn't offer sensible optimizations such as re-used buffers,\n * efficient UTF-8 encoding/decoding, or the full spectrum of features defined in the RFC.\n */\nclass WebSocketSession implements SimpleSession {\n  private final ReadHandler mReadHandler;\n  private final WriteHandler mWriteHandler;\n  private final SimpleEndpoint mEndpoint;\n\n  private AtomicBoolean mIsOpen = new AtomicBoolean(false);\n  private volatile boolean mSentClose;\n\n  public WebSocketSession(\n      InputStream rawSocketInput,\n      OutputStream rawSocketOutput,\n      SimpleEndpoint endpoint) {\n    mReadHandler = new ReadHandler(rawSocketInput, endpoint);\n    mWriteHandler = new WriteHandler(rawSocketOutput);\n    mEndpoint = endpoint;\n  }\n\n  public void handle() throws IOException {\n    markAndSignalOpen();\n\n    // Loop until orderly shutdown or socket exception.\n    try {\n      mReadHandler.readLoop(mReadCallback);\n    } catch (EOFException e) {\n      // No need to rethrow, this can be considered a graceful shutdown of the socket (though\n      // not the WebSocket).\n      markAndSignalClosed(CloseCodes.UNEXPECTED_CONDITION, \"EOF while reading\");\n    } catch (IOException e) {\n      markAndSignalClosed(CloseCodes.CLOSED_ABNORMALLY, null /* reasonPhrase */);\n      throw e;\n    }\n  }\n\n  @Override\n  public void sendText(String payload) {\n    doWrite(FrameHelper.createTextFrame(payload));\n  }\n\n  @Override\n  public void sendBinary(byte[] payload) {\n    doWrite(FrameHelper.createBinaryFrame(payload));\n  }\n\n  @Override\n  public void close(int closeReason, String reasonPhrase) {\n    sendClose(closeReason, reasonPhrase);\n    markAndSignalClosed(closeReason, reasonPhrase);\n  }\n\n  private void sendClose(int closeReason, String reasonPhrase) {\n    doWrite(FrameHelper.createCloseFrame(closeReason, reasonPhrase));\n    markSentClose();\n  }\n\n  void markSentClose() {\n    mSentClose = true;\n  }\n\n  void markAndSignalOpen() {\n    if (!mIsOpen.getAndSet(true)) {\n      mEndpoint.onOpen(this /* session */);\n    }\n  }\n\n  void markAndSignalClosed(int closeReason, String reasonPhrase) {\n    if (mIsOpen.getAndSet(false)) {\n      mEndpoint.onClose(this /* session */, closeReason, reasonPhrase);\n    }\n  }\n\n  @Override\n  public boolean isOpen() {\n    return mIsOpen.get();\n  }\n\n  private void doWrite(Frame frame) {\n    if (signalErrorIfNotOpen()) {\n      return;\n    }\n    mWriteHandler.write(frame, mErrorForwardingWriteCallback);\n  }\n\n  /**\n   * Signals an error to the {@link SimpleEndpoint} if the session is closed.\n   *\n   * @return True if an error was signaled (the session is closed); false otherwise.\n   */\n  private boolean signalErrorIfNotOpen() {\n    if (!isOpen()) {\n      signalError(new IOException(\"Session is closed\"));\n      return true;\n    }\n    return false;\n  }\n\n  private void signalError(IOException e) {\n    mEndpoint.onError(this /* session */, e);\n  }\n\n  private final ReadCallback mReadCallback = new ReadCallback() {\n    @Override\n    public void onCompleteFrame(byte opcode, byte[] payload, int payloadLen) {\n      switch (opcode) {\n        case Frame.OPCODE_CONNECTION_CLOSE:\n          handleClose(payload, payloadLen);\n          break;\n        case Frame.OPCODE_CONNECTION_PING:\n          handlePing(payload, payloadLen);\n          break;\n        case Frame.OPCODE_CONNECTION_PONG:\n          handlePong(payload, payloadLen);\n          break;\n        case Frame.OPCODE_TEXT_FRAME:\n          handleTextFrame(payload, payloadLen);\n          break;\n        case Frame.OPCODE_BINARY_FRAME:\n          handleBinaryFrame(payload, payloadLen);\n          break;\n        default:\n          signalError(new IOException(\"Unsupported frame opcode=\" + opcode));\n          break;\n      }\n    }\n\n    private void handleClose(byte[] payload, int payloadLen) {\n      int closeCode;\n      String closeReasonPhrase;\n\n      if (payloadLen >= 2) {\n        closeCode = ((payload[0] & 0xff) << 8) | (payload[1] & 0xff);\n        closeReasonPhrase = (payloadLen > 2) ? new String(payload, 2, payloadLen - 2) : null;\n      } else {\n        closeCode = CloseCodes.CLOSED_ABNORMALLY;\n        closeReasonPhrase = \"Unparseable close frame\";\n      }\n\n      // We must acknowledge the peer's close frame.\n      if (!mSentClose) {\n        sendClose(CloseCodes.NORMAL_CLOSURE, \"Received close frame\");\n      }\n\n      markAndSignalClosed(closeCode, closeReasonPhrase);\n    }\n\n    private void handlePing(byte[] payload, int payloadLen) {\n      doWrite(FrameHelper.createPongFrame(payload, payloadLen));\n    }\n\n    private void handlePong(byte[] payload, int payloadLen) {\n      // Great, whatever...\n    }\n\n    private void handleTextFrame(byte[] payload, int payloadLen) {\n      mEndpoint.onMessage(WebSocketSession.this, new String(payload, 0, payloadLen));\n    }\n\n    private void handleBinaryFrame(byte[] payload, int payloadLen) {\n      mEndpoint.onMessage(WebSocketSession.this, payload, payloadLen);\n    }\n  };\n\n  private final WriteCallback mErrorForwardingWriteCallback = new WriteCallback() {\n    @Override\n    public void onFailure(IOException e) {\n      signalError(e);\n    }\n\n    @Override\n    public void onSuccess() {\n      // Boring...\n    }\n  };\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/WriteCallback.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport java.io.IOException;\n\ninterface WriteCallback {\n  void onFailure(IOException e);\n  void onSuccess();\n}\n"
  },
  {
    "path": "stetho/src/main/java/com/facebook/stetho/websocket/WriteHandler.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.websocket;\n\nimport javax.annotation.concurrent.ThreadSafe;\n\nimport java.io.BufferedOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n@ThreadSafe\nclass WriteHandler {\n  private final BufferedOutputStream mBufferedOutput;\n\n  public WriteHandler(OutputStream rawSocketOutput) {\n    mBufferedOutput = new BufferedOutputStream(rawSocketOutput, 1024);\n  }\n\n  public synchronized void write(Frame frame, WriteCallback callback) {\n    try {\n      frame.writeTo(mBufferedOutput);\n      mBufferedOutput.flush();\n      callback.onSuccess();\n    } catch (IOException e) {\n      callback.onFailure(e);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/PluginBuilderTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\nimport com.facebook.stetho.dumpapp.plugins.HprofDumperPlugin;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\nimport com.facebook.stetho.inspector.protocol.module.Debugger;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.Robolectric;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport java.io.IOException;\n\nimport static org.junit.Assert.assertFalse;\n\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\npublic class PluginBuilderTest {\n  private final Activity mActivity = Robolectric.setupActivity(Activity.class);\n\n  @Test\n  public void test_Remove_DefaultInspectorModulesBuilder() throws IOException {\n    final Class<Debugger> debuggerClass = Debugger.class;\n\n    Iterable<ChromeDevtoolsDomain> domains = new Stetho.DefaultInspectorModulesBuilder(mActivity)\n            .remove(debuggerClass.getName())\n            .finish();\n\n    boolean containsDebugggerDomain = false;\n    for (ChromeDevtoolsDomain domain : domains) {\n      if (domain.getClass().equals(debuggerClass)) {\n        containsDebugggerDomain = true;\n        break;\n      }\n    }\n\n    assertFalse(containsDebugggerDomain);\n  }\n\n  @Test\n  public void test_Remove_DefaultDumperPluginsBuilder() throws IOException {\n    //HprofDumperPlugin.NAME is private\n    final String hprofDumperPluginNAME = \"hprof\";\n    final Iterable<DumperPlugin> dumperPlugins = new Stetho.DefaultDumperPluginsBuilder(mActivity)\n            .remove(hprofDumperPluginNAME)\n            .finish();\n\n    boolean containsDebugggerDomain = false;\n    for (DumperPlugin plugin : dumperPlugins) {\n      if (plugin.getClass().equals(HprofDumperPlugin.class)) {\n        containsDebugggerDomain = true;\n        break;\n      }\n    }\n\n    assertFalse(containsDebugggerDomain);\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/database/DatabasePeerManagerTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.database;\n\nimport java.io.File;\nimport java.util.Arrays;\nimport java.util.List;\n\nimport org.junit.Test;\n\nimport static org.junit.Assert.assertArrayEquals;\n\npublic class DatabasePeerManagerTest {\n  @Test\n  public void testTidyDatabaseList() {\n    File[] databases = {\n        new File(\"foo.db\"), new File(\"foo.db-journal\"),\n        new File(\"bar.db\"), new File(\"bar.db-journal\"), new File( \"bar.db-uid\"),\n        new File(\"baz.db\"), new File(\"baz.db-somethingelse\"),\n        new File(\"dangling.db-journal\"),\n    };\n    File[] expected = {\n        new File( \"foo.db\"),\n        new File(\"bar.db\"),\n        new File(\"baz.db\"), new File(\"baz.db-somethingelse\"),\n        new File(\"dangling.db-journal\")\n    };\n    List<File> tidied = SqliteDatabaseDriver.tidyDatabaseList(Arrays.asList(databases));\n    assertArrayEquals(expected, tidied.toArray());\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/elements/android/MethodInvokerTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.widget.CheckBox;\nimport android.widget.TextView;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.Robolectric;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport static org.junit.Assert.assertEquals;\n\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\npublic class MethodInvokerTest {\n\n  private final Activity mActivity = Robolectric.setupActivity(Activity.class);\n  private final TextView mTextView = new TextView(mActivity);\n  private final CheckBox mCheckBox = new CheckBox(mActivity);\n  private final MethodInvoker mInvoker = new MethodInvoker();\n\n  @Before\n  public void setup() {\n  }\n\n  @Test\n  public void testSetCharSequence() {\n    mInvoker.invoke(mTextView, \"setText\", \"Hello World\");\n    assertEquals(\"Hello World\", mTextView.getText().toString());\n  }\n\n  @Test\n  public void testSetInteger() {\n    mInvoker.invoke(mTextView, \"setId\", \"2\");\n    assertEquals(2, mTextView.getId());\n  }\n\n  @Test\n  public void testSetFloat() {\n    mInvoker.invoke(mTextView, \"setTextSize\", \"34\");\n    assertEquals(34f, mTextView.getTextSize(), 0);\n  }\n\n  @Test\n  public void testSetBoolean() {\n    mInvoker.invoke(mCheckBox, \"setChecked\", \"true\");\n    assertEquals(true, mCheckBox.isChecked());\n  }\n\n  @Test\n  public void testSetAttributeAsTextIgnoreUnknownAttribute() {\n    // Should not throw\n    mInvoker.invoke(mTextView, \"setSomething\", \"foo\");\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/elements/android/ViewDescriptorTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.elements.android;\n\nimport android.app.Activity;\nimport android.os.Build;\nimport android.widget.CheckBox;\nimport android.widget.TextView;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.Robolectric;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport static org.mockito.Matchers.anyObject;\nimport static org.mockito.Matchers.anyString;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Mockito.verify;\n\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\npublic class ViewDescriptorTest {\n\n  private final MethodInvoker mMethodInvoker = mock(MethodInvoker.class);\n  private final ViewDescriptor mDescriptor = new ViewDescriptor(mMethodInvoker);\n  private final Activity mActivity = Robolectric.setupActivity(Activity.class);\n  private final TextView mTextView = new TextView(mActivity);\n  private final CheckBox mCheckBox = new CheckBox(mActivity);\n\n  @Test\n  public void testSetAttributeAsTextWithSetText() {\n    mDescriptor.setAttributesAsText(mTextView, \"text=\\\"Hello World\\\"\");\n    verify(mMethodInvoker).invoke(mTextView, \"setText\", \"Hello World\");\n  }\n\n  @Test\n  public void testSetAttributeAsTextWithSetId() {\n    mDescriptor.setAttributesAsText(mTextView, \"id=\\\"2\\\"\");\n    verify(mMethodInvoker).invoke(mTextView, \"setId\", \"2\");\n  }\n\n  @Test\n  public void testSetAttributeAsTextWithSetChecked() {\n    mDescriptor.setAttributesAsText(mCheckBox, \"checked=\\\"true\\\"\");\n    verify(mMethodInvoker).invoke(mCheckBox, \"setChecked\", \"true\");\n  }\n\n  @Test\n  public void testSetMultipleAttributesAsText() {\n    mDescriptor.setAttributesAsText(mTextView, \"id=\\\"2\\\" text=\\\"Hello World\\\"\");\n    verify(mMethodInvoker).invoke(mTextView, \"setId\", \"2\");\n    verify(mMethodInvoker).invoke(mTextView, \"setText\", \"Hello World\");\n  }\n\n  @Test\n  public void testSetAttributeAsTextIgnoreInvalidFormat() {\n    mDescriptor.setAttributesAsText(mTextView, \"garbage\");\n    verify(mMethodInvoker, never()).invoke(anyObject(), anyString(), anyString());\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/network/AsyncPrettyPrintResponseBodyTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport com.facebook.stetho.common.Util;\n\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.times;\nimport static org.mockito.Mockito.never;\nimport static org.mockito.Matchers.any;\n\nimport com.facebook.stetho.inspector.network.AsyncPrettyPrinter;\nimport com.facebook.stetho.inspector.network.AsyncPrettyPrinterExecutorHolder;\nimport dalvik.annotation.TestTargetClass;\nimport org.junit.Before;\nimport org.junit.Test;\n\nimport javax.annotation.Nullable;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.lang.Override;\nimport java.lang.String;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertNotNull;\nimport static org.junit.Assert.fail;\n\npublic class AsyncPrettyPrintResponseBodyTest {\n  private static final String TEST_REQUEST_ID = \"1234\";\n  private static final String TEST_HEADER_NAME = \"header name\";\n  private static final String TEST_HEADER_VALUE = \"header value\";\n  private static final String PRETTY_PRINT_PREFIX = \"pretty printed result: \";\n  private static final String[] UNREGISTERED_HEADER_NAMES = {\"unregistered header name 1\",\n      \"unregistered header name 2\",\n      \"unregistered header name 3\"};\n  private static final String[] UNREGISTERED_HEADER_VALUES = {\"unregistered header value 1\",\n      \"unregistered header value 2\",\n      \"unregistered header value 3\"};\n  private static final byte[] TEST_RESPONSE_BODY;\n  private static final ByteArrayInputStream mInputStream;\n\n  static {\n    int responseBodyLength = 4096 * 2 + 2048; // span multiple buffers when tee-ing\n    TEST_RESPONSE_BODY = new byte[responseBodyLength];\n    for (int i = 0; i < responseBodyLength; i++) {\n      TEST_RESPONSE_BODY[i] = positionToByte(i);\n    }\n    mInputStream = new ByteArrayInputStream(TEST_RESPONSE_BODY);\n  }\n\n  private AsyncPrettyPrinterRegistry mAsyncPrettyPrinterRegistry;\n  private PrettyPrinterTestFactory mPrettyPrinterTestFactory;\n  private ResponseBodyFileManager mResponseBodyFileManager;\n\n  @Before\n  public void setup() {\n    mPrettyPrinterTestFactory = new PrettyPrinterTestFactory();\n    mResponseBodyFileManager = mock(ResponseBodyFileManager.class);\n    mAsyncPrettyPrinterRegistry = new AsyncPrettyPrinterRegistry();\n    mAsyncPrettyPrinterRegistry.register(TEST_HEADER_NAME, mPrettyPrinterTestFactory);\n    AsyncPrettyPrinterExecutorHolder.ensureInitialized();\n  }\n\n  @Test\n  public void testAsyncPrettyPrinterResult() throws IOException {\n    StringWriter out = new StringWriter();\n    PrintWriter writer = new PrintWriter(out);\n    AsyncPrettyPrinter mAsyncPrettyPrinter = mPrettyPrinterTestFactory.getInstance(\n        TEST_HEADER_NAME,\n        TEST_HEADER_VALUE);\n    mAsyncPrettyPrinter.printTo(writer, mInputStream);\n    assertEquals(PRETTY_PRINT_PREFIX + Arrays.toString(TEST_RESPONSE_BODY), out.toString());\n  }\n\n  @Test\n  public void testInitAsyncPrettyPrinterForResponseWithRegisteredHeader() {\n    ArrayList<String> headerNames = new ArrayList<String>();\n    ArrayList<String> headerValues = new ArrayList<String>();\n\n    headerNames.add(UNREGISTERED_HEADER_NAMES[0]);\n    headerNames.add(UNREGISTERED_HEADER_NAMES[1]);\n    headerNames.add(TEST_HEADER_NAME);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[0]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[1]);\n    headerValues.add(TEST_HEADER_VALUE);\n\n    TestInspectorResponse testResponse = new TestInspectorResponse(\n        headerNames,\n        headerValues,\n        TEST_REQUEST_ID\n    );\n    AsyncPrettyPrinter prettyPrinter = NetworkEventReporterImpl.createPrettyPrinterForResponse(\n        testResponse,\n        mAsyncPrettyPrinterRegistry);\n    assertNotNull(prettyPrinter);\n  }\n\n  @Test\n  public void testInitAsyncPrettyPrinterForResponseWithUnregisteredHeader() {\n    ArrayList<String> headerNames = new ArrayList<String>();\n    ArrayList<String> headerValues = new ArrayList<String>();\n\n    headerNames.add(UNREGISTERED_HEADER_NAMES[0]);\n    headerNames.add(UNREGISTERED_HEADER_NAMES[1]);\n    headerNames.add(UNREGISTERED_HEADER_NAMES[2]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[0]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[1]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[2]);\n\n    TestInspectorResponse testResponse = new TestInspectorResponse(\n        headerNames,\n        headerValues,\n        TEST_REQUEST_ID\n    );\n    AsyncPrettyPrinter prettyPrinter = NetworkEventReporterImpl.createPrettyPrinterForResponse(\n        testResponse,\n        mAsyncPrettyPrinterRegistry);\n    assertEquals(null, prettyPrinter);\n  }\n\n  @Test\n  public void testGetInstanceWithUnmatchedHeader() {\n    ArrayList<String> headerNames = new ArrayList<String>();\n    ArrayList<String> headerValues = new ArrayList<String>();\n\n    headerNames.add(UNREGISTERED_HEADER_NAMES[0]);\n    headerNames.add(UNREGISTERED_HEADER_NAMES[1]);\n    headerNames.add(TEST_HEADER_NAME);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[0]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[1]);\n    headerValues.add(UNREGISTERED_HEADER_VALUES[2]);\n\n    TestInspectorResponse testResponse = new TestInspectorResponse(\n        headerNames,\n        headerValues,\n        TEST_REQUEST_ID\n    );\n    AsyncPrettyPrinter prettyPrinter = NetworkEventReporterImpl.createPrettyPrinterForResponse(\n        testResponse,\n        mAsyncPrettyPrinterRegistry);\n    assertEquals(null, prettyPrinter);\n  }\n\n  private class PrettyPrinterTestFactory extends DownloadingAsyncPrettyPrinterFactory {\n    @Override\n    protected void doPrint(PrintWriter output, InputStream payload, String schema)\n        throws IOException {\n      ByteArrayOutputStream out = new ByteArrayOutputStream();\n      Util.copy(payload, out, new byte[1024]);\n      String prettifiedContent = PRETTY_PRINT_PREFIX + Arrays.toString(out.toByteArray());\n      output.write(prettifiedContent);\n      output.close();\n    }\n\n    @Override\n    @Nullable\n    protected MatchResult matchAndParseHeader(String headerName, String headerValue) {\n      if (headerName.equals(TEST_HEADER_NAME) && headerValue.equals(TEST_HEADER_VALUE)) {\n        return new MatchResult(\"https://www.facebook.com\", PrettyPrinterDisplayType.TEXT);\n      } else {\n        return null;\n      }\n    }\n  }\n\n  private class TestInspectorResponse implements NetworkEventReporter.InspectorResponse {\n    private ArrayList<String> mHeaderNames;\n    private ArrayList<String> mHeaderValues;\n    private String mRequestId;\n\n    public TestInspectorResponse(\n        ArrayList<String> headerNames,\n        ArrayList<String> headerValues,\n        String requestId) {\n      mHeaderNames = headerNames;\n      mHeaderValues = headerValues;\n      mRequestId = requestId;\n    }\n\n    public int headerCount() {\n      return mHeaderNames.size();\n    }\n\n    public String headerName(int index) {\n      return mHeaderNames.get(index);\n    }\n\n    public String headerValue(int index) {\n      return mHeaderValues.get(index);\n    }\n\n    @Nullable\n    public String firstHeaderValue(String name) {\n      return mHeaderValues.get(0);\n    }\n\n    public String requestId() {\n      return mRequestId;\n    }\n\n    public String url() {\n      return \"test url\";\n    }\n\n    public int statusCode() {\n      return 200;\n    }\n\n    public String reasonPhrase() {\n      return \"test reason phrase\";\n    }\n\n    public boolean connectionReused() {\n      return false;\n    }\n\n    public int connectionId() {\n      return 111;\n    }\n\n    public boolean fromDiskCache() {\n      return false;\n    }\n  }\n\n  /**\n   * Returns the truncated byte value of position.\n   */\n  private static byte positionToByte(int position) {\n    return (byte) (position % 0xff);\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/network/GunzippingOutputStreamTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.zip.GZIPOutputStream;\n\nimport static org.junit.Assert.*;\n\n@RunWith(JUnit4.class)\npublic class GunzippingOutputStreamTest {\n  @Test(timeout = 1000)\n  public void testGunzip() throws IOException {\n    byte[] data = \"test123test123\".getBytes();\n\n    ByteArrayOutputStream out = new ByteArrayOutputStream();\n    OutputStream unzippingStream = GunzippingOutputStream.create(out);\n    OutputStream zippingStream = new GZIPOutputStream(unzippingStream);\n    zippingStream.write(data);\n    zippingStream.close();\n    assertArrayEquals(data, out.toByteArray());\n  }\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/inspector/network/ResponseHandlingInputStreamTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.inspector.network;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.util.Arrays;\n\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.helper.ChromePeerManager;\nimport com.facebook.stetho.inspector.protocol.module.Console;\n\nimport static org.mockito.Mockito.mock;\n\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.Mockito;\nimport org.powermock.api.mockito.PowerMockito;\nimport org.powermock.core.classloader.annotations.PrepareForTest;\nimport org.powermock.modules.junit4.PowerMockRunner;\n\nimport static org.junit.Assert.assertArrayEquals;\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(PowerMockRunner.class)\n@PrepareForTest(CLog.class)\npublic class ResponseHandlingInputStreamTest {\n\n  private static final String TEST_REQUEST_ID = \"1234\";\n\n  private static final byte[] TEST_RESPONSE_BODY;\n  static {\n    int responseBodyLength = 4096 * 2 + 2048; // span multiple buffers when tee-ing\n    TEST_RESPONSE_BODY = new byte[responseBodyLength];\n    for (int i = 0; i < responseBodyLength; i++) {\n      TEST_RESPONSE_BODY[i] = positionToByte(i);\n    }\n  }\n\n  private ByteArrayOutputStream mTestOutputStream;\n  private ResponseHandlingInputStream mResponseHandlingInputStream;\n  private NetworkPeerManager mNetworkPeerManager;\n  private NetworkEventReporterImpl mNetworkEventReporter;\n\n  @Before\n  public void setup() {\n    mTestOutputStream = new ByteArrayOutputStream();\n    // The only place this is used is when trying to write to the console. Since we are going to\n    // mock ResponseHandlingInputStream#writeToConsole passing null is fine.\n    mNetworkPeerManager = null;\n    mNetworkEventReporter = mock(NetworkEventReporterImpl.class);\n    mResponseHandlingInputStream = new ResponseHandlingInputStream(\n        new ByteArrayInputStream(TEST_RESPONSE_BODY),\n        TEST_REQUEST_ID,\n        mTestOutputStream,\n        null /* decompressedCounter */,\n        mNetworkPeerManager,\n        new DefaultResponseHandler(mNetworkEventReporter, TEST_REQUEST_ID));\n  }\n\n  @Test\n  public void testReadOneByte() throws IOException {\n    int result = mResponseHandlingInputStream.read();\n    assertEquals(TEST_RESPONSE_BODY[0], positionToByte(result));\n    assertBufferMatchesResponseBody(mTestOutputStream.toByteArray(), 1);\n\n    PowerMockito.mockStatic(CLog.class);\n    PowerMockito.doNothing().when(CLog.class);\n    CLog.writeToConsole(\n        Mockito.any(ChromePeerManager.class),\n        Mockito.any(Console.MessageLevel.class),\n        Mockito.any(Console.MessageSource.class),\n        Mockito.anyString());\n    mResponseHandlingInputStream.close();\n    PowerMockito.verifyStatic();\n  }\n\n  @Test\n  public void testReadPartial() throws IOException {\n    int numBytesToRead = TEST_RESPONSE_BODY.length / 2;\n    byte[] tempReadingBuffer = new byte[numBytesToRead];\n    int result = mResponseHandlingInputStream.read(tempReadingBuffer, 0, numBytesToRead);\n    assertEquals(numBytesToRead, result);\n    assertBufferMatchesResponseBody(tempReadingBuffer, numBytesToRead);\n    assertBufferMatchesResponseBody(mTestOutputStream.toByteArray(), numBytesToRead);\n\n    PowerMockito.mockStatic(CLog.class);\n    PowerMockito.doNothing().when(CLog.class);\n    CLog.writeToConsole(\n        Mockito.any(ChromePeerManager.class),\n        Mockito.any(Console.MessageLevel.class),\n        Mockito.any(Console.MessageSource.class),\n        Mockito.anyString());\n    mResponseHandlingInputStream.close();\n    PowerMockito.verifyStatic();\n  }\n\n  @Test\n  public void testReadFully() throws IOException {\n    byte[] tempReadingBuffer = new byte[TEST_RESPONSE_BODY.length];\n    int result = mResponseHandlingInputStream.read(tempReadingBuffer);\n    assertEquals(TEST_RESPONSE_BODY.length, result);\n    assertBufferMatchesResponseBody(tempReadingBuffer, TEST_RESPONSE_BODY.length);\n    assertBufferMatchesResponseBody(mTestOutputStream.toByteArray(), TEST_RESPONSE_BODY.length);\n\n    PowerMockito.mockStatic(CLog.class);\n    PowerMockito.verifyZeroInteractions(CLog.class);\n    mResponseHandlingInputStream.close();\n    PowerMockito.verifyStatic();\n  }\n\n  @Test\n  public void testSkipFew() throws IOException {\n    long numBytesToSkip = TEST_RESPONSE_BODY.length / 2;\n    long result = mResponseHandlingInputStream.skip(numBytesToSkip);\n    assertEquals(numBytesToSkip, result);\n    assertBufferMatchesResponseBody(mTestOutputStream.toByteArray(), (int) numBytesToSkip);\n\n    PowerMockito.mockStatic(CLog.class);\n    PowerMockito.doNothing().when(CLog.class);\n    CLog.writeToConsole(\n        Mockito.any(ChromePeerManager.class),\n        Mockito.any(Console.MessageLevel.class),\n        Mockito.any(Console.MessageSource.class),\n        Mockito.anyString());\n    mResponseHandlingInputStream.close();\n    PowerMockito.verifyStatic();\n  }\n\n  @Test\n  public void testSkipMany() throws IOException {\n    long numBytesToSkip = TEST_RESPONSE_BODY.length * 2;\n    long result = mResponseHandlingInputStream.skip(numBytesToSkip);\n    assertEquals((long) TEST_RESPONSE_BODY.length, result);\n    assertBufferMatchesResponseBody(mTestOutputStream.toByteArray(), TEST_RESPONSE_BODY.length);\n    PowerMockito.verifyZeroInteractions(CLog.class);\n    mResponseHandlingInputStream.close();\n  }\n\n  private static final class TestIOException extends IOException {}\n\n  @Test\n  public void testSwallowException() throws IOException {\n    OutputStream exceptionOutputStream = new OutputStream() {\n      @Override\n      public void write(int oneByte) throws IOException {\n        throw new TestIOException();\n      }\n    };\n    ResponseHandlingInputStream responseHandlingInputStream = new ResponseHandlingInputStream(\n        new ByteArrayInputStream(TEST_RESPONSE_BODY),\n        TEST_REQUEST_ID,\n        exceptionOutputStream,\n        null /* decompressedCounter */,\n        mNetworkPeerManager,\n        new DefaultResponseHandler(mNetworkEventReporter, TEST_REQUEST_ID));\n\n    PowerMockito.mockStatic(CLog.class);\n    responseHandlingInputStream.read();\n    PowerMockito.verifyStatic();\n  }\n\n  /**\n   * Returns the truncated byte value of position.\n   */\n  private static byte positionToByte(int position) {\n    return (byte) (position % 0xff);\n  }\n\n  /**\n   * Asserts that buffer's length equal to count and matches the first count bytes of the\n   * test response body.\n   */\n  private static void assertBufferMatchesResponseBody(byte[] buffer, int count) {\n    assertArrayEquals(Arrays.copyOf(TEST_RESPONSE_BODY, count), buffer);\n  }\n\n}\n"
  },
  {
    "path": "stetho/src/test/java/com/facebook/stetho/json/ObjectMapperTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.json;\n\nimport android.os.Build;\nimport com.facebook.stetho.json.annotation.JsonProperty;\nimport com.facebook.stetho.json.annotation.JsonValue;\nimport org.json.JSONArray;\nimport org.json.JSONException;\nimport org.json.JSONObject;\nimport org.junit.Before;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.ListIterator;\nimport java.util.Objects;\n\nimport static org.junit.Assert.assertEquals;\nimport static org.junit.Assert.assertTrue;\n\n/**\n * Tests for {@link ObjectMapper}\n */\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\npublic class ObjectMapperTest {\n\n  private ObjectMapper mObjectMapper;\n\n  @Before\n  public void setup() {\n    mObjectMapper = new ObjectMapper();\n  }\n\n  @Test\n  public void testJsonProperty() throws IOException, JSONException {\n    JsonPropertyString c = new JsonPropertyString();\n    c.testString = \"test\";\n    String expected = \"{\\\"testString\\\":\\\"test\\\"}\";\n\n    JSONObject jsonObject = mObjectMapper.convertValue(c, JSONObject.class);\n    String str = jsonObject.toString();\n    assertEquals(expected, str);\n\n    JsonPropertyString jsonPropertyString = mObjectMapper.convertValue(\n        new JSONObject(expected),\n        JsonPropertyString.class);\n    assertEquals(c, jsonPropertyString);\n  }\n\n  @Test\n  public void testNestedProperty() throws JSONException {\n    NestedJsonProperty njp = new NestedJsonProperty();\n    njp.child1 = new JsonPropertyString();\n    njp.child2 = new JsonPropertyInt();\n\n    njp.child1.testString = \"testString\";\n    njp.child2.i = 4;\n\n    // The ordering of serialization changes depending on Java 7 vs Java 8.\n    String expected7 = \"{\\\"child1\\\":{\\\"testString\\\":\\\"testString\\\"},\\\"child2\\\":{\\\"i\\\":4}}\";\n    String expected8 = \"{\\\"child2\\\":{\\\"i\\\":4},\\\"child1\\\":{\\\"testString\\\":\\\"testString\\\"}}\";\n\n    NestedJsonProperty parsed7 = mObjectMapper.convertValue(\n        new JSONObject(expected7),\n        NestedJsonProperty.class);\n    assertEquals(njp, parsed7);\n\n    NestedJsonProperty parsed8 = mObjectMapper.convertValue(\n        new JSONObject(expected8),\n        NestedJsonProperty.class);\n    assertEquals(njp, parsed8);\n\n    JSONObject jsonObject = mObjectMapper.convertValue(njp, JSONObject.class);\n\n    assertTrue(expected7.equals(jsonObject.toString()) || expected8.equals(jsonObject.toString()));\n  }\n\n  @Test\n  public void testEnumProperty() throws JSONException {\n    JsonPropertyEnum jpe = new JsonPropertyEnum();\n    jpe.enumValue = TestEnum.VALUE_TWO;\n\n    String expected = \"{\\\"enumValue\\\":\\\"two\\\"}\";\n\n    JsonPropertyEnum parsed = mObjectMapper.convertValue(\n        new JSONObject(expected),\n        JsonPropertyEnum.class);\n    assertEquals(jpe, parsed);\n\n    JSONObject jsonObject = mObjectMapper.convertValue(jpe, JSONObject.class);\n    assertEquals(expected, jsonObject.toString());\n  }\n\n  @Test\n  public void testListString() throws JSONException {\n    JsonPropertyStringList jpsl = new JsonPropertyStringList();\n    List<String> values = new ArrayList<String>();\n    jpsl.stringList = values;\n    values.add(\"one\");\n    values.add(\"two\");\n    values.add(\"three\");\n\n    String expected = \"{\\\"stringList\\\":[\\\"one\\\",\\\"two\\\",\\\"three\\\"]}\";\n    JsonPropertyStringList jsonPropertyStringList = mObjectMapper.convertValue(\n        new JSONObject(expected),\n        JsonPropertyStringList.class);\n    assertEquals(jpsl, jsonPropertyStringList);\n\n    JSONObject jsonObject = mObjectMapper.convertValue(jpsl, JSONObject.class);\n    String str = jsonObject.toString();\n\n    assertEquals(expected, str);\n  }\n\n  @Test\n  public void testSerializeMultitypedList() throws JSONException {\n    List<Object> list = new ArrayList<Object>();\n    list.add(\"foo\");\n    list.add(Collections.singletonList(\"bar\"));\n    JsonPropertyMultitypedList javaObj = new JsonPropertyMultitypedList();\n    javaObj.multitypedList = list;\n\n    String expected = \"{\\\"multitypedList\\\":[\\\"foo\\\",[\\\"bar\\\"]]}\";\n    JSONObject jsonObj = mObjectMapper.convertValue(javaObj, JSONObject.class);\n    String str = jsonObj.toString();\n\n    assertEquals(expected, str);\n  }\n\n  @Test\n  public void testSerializeListOfLists() throws JSONException {\n    List<List<String>> listOfLists = new ArrayList<List<String>>();\n    listOfLists.add(Collections.singletonList(\"foo\"));\n    ArrayList<String> sublist2 = new ArrayList<String>();\n    sublist2.add(\"1\");\n    sublist2.add(\"2\");\n    listOfLists.add(sublist2);\n    JsonPropertyListOfLists javaObj = new JsonPropertyListOfLists();\n    javaObj.listOfLists = listOfLists;\n\n    String expected = \"{\\\"listOfLists\\\":[[\\\"foo\\\"],[\\\"1\\\",\\\"2\\\"]]}\";\n    JSONObject jsonObj = mObjectMapper.convertValue(javaObj, JSONObject.class);\n    String str = jsonObj.toString();\n\n    assertEquals(expected, str);\n  }\n\n  @Test\n  public void testObjectToPrimitive() throws JSONException {\n    ArrayOfPrimitivesContainer container = new ArrayOfPrimitivesContainer();\n    ArrayList<Object> primitives = container.primitives;\n    primitives.add(Long.MIN_VALUE);\n    primitives.add(Long.MAX_VALUE);\n    primitives.add(Integer.MIN_VALUE);\n    primitives.add(Integer.MAX_VALUE);\n    primitives.add(Float.MIN_VALUE);\n    primitives.add(Float.MAX_VALUE);\n    primitives.add(Double.MIN_VALUE);\n    primitives.add(Double.MAX_VALUE);\n\n    String json = mObjectMapper.convertValue(container, JSONObject.class).toString();\n    JSONObject obj = new JSONObject(json);\n    JSONArray array = obj.getJSONArray(\"primitives\");\n    ArrayList<Object> actual = new ArrayList<>();\n    for (int i = 0, N = array.length(); i < N; i++) {\n      actual.add(array.get(i));\n    }\n    assertEquals(primitives.toString(), actual.toString());\n  }\n\n  public static class ArrayOfPrimitivesContainer {\n    @JsonProperty\n    public final ArrayList<Object> primitives = new ArrayList<>();\n  }\n\n  public static class NestedJsonProperty {\n    @JsonProperty(required = true)\n    public JsonPropertyString child1;\n\n    @JsonProperty\n    public JsonPropertyInt child2;\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null || !(o instanceof NestedJsonProperty)) {\n        return false;\n      }\n      return Objects.equals(child1, ((NestedJsonProperty) o).child1) &&\n          Objects.equals(child2, ((NestedJsonProperty) o).child2);\n    }\n  }\n\n  public static class JsonPropertyString {\n    @JsonProperty\n    public String testString;\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null || !(o instanceof JsonPropertyString)) {\n        return false;\n      }\n      return Objects.equals(testString, ((JsonPropertyString) o).testString);\n    }\n  }\n\n  public static class JsonPropertyInt {\n    @JsonProperty\n    public int i;\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null || !(o instanceof JsonPropertyInt)) {\n        return false;\n      }\n      return Objects.equals(i, ((JsonPropertyInt) o).i);\n    }\n  }\n\n  public static class JsonPropertyEnum {\n    @JsonProperty\n    public TestEnum enumValue;\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null || !(o instanceof JsonPropertyEnum)) {\n        return false;\n      }\n      return Objects.equals(enumValue, ((JsonPropertyEnum) o).enumValue);\n    }\n  }\n\n  public static class JsonPropertyStringList {\n    @JsonProperty\n    public List<String> stringList;\n\n    @Override\n    public boolean equals(Object o) {\n      if (o == null || !(o instanceof JsonPropertyStringList)) {\n        return false;\n      }\n      JsonPropertyStringList rhs = (JsonPropertyStringList) o;\n      if (stringList == null || rhs.stringList == null) {\n        return stringList == rhs.stringList;\n      }\n      if (stringList.size() != rhs.stringList.size()) {\n        return false;\n      }\n      ListIterator<String> myIter = stringList.listIterator();\n      ListIterator<String> rhsIter = rhs.stringList.listIterator();\n      while (myIter.hasNext()) {\n        if (!Objects.equals(myIter.next(), rhsIter.next())) {\n          return false;\n        }\n      }\n\n      return true;\n    }\n  }\n\n  public enum TestEnum {\n    VALUE_ONE(\"one\"),\n    VALUE_TWO(\"two\"),\n    VALUE_THREE(\"three\");\n\n    private final String mValue;\n\n    private TestEnum(String str) {\n      mValue = str;\n    }\n\n    @JsonValue\n    public String getValue() {\n      return mValue;\n    }\n  }\n\n  private static class JsonPropertyMultitypedList {\n    @JsonProperty\n    public List<Object> multitypedList;\n  }\n\n  private static class JsonPropertyListOfLists {\n    @JsonProperty\n    public List<List<String>> listOfLists;\n  }\n}\n"
  },
  {
    "path": "stetho-js-rhino/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-js-rhino/README.md",
    "content": "# Stetho's JavaScript Module\n\nThis [Stetho](https://facebook.github.io/stetho) plugin adds a JavaScript console by embedding Mozilla's [Rhino](https://github.com/mozilla/rhino).\n\n## Set-up\n\n### Download\nDownload [the latest JARs](https://github.com/facebook/stetho/releases/latest) or grab via Gradle:\n```groovy\nimplementation 'com.facebook.stetho:stetho-js-rhino:1.4.2'\n```\nor Maven:\n```xml\n<dependency>\n  <groupId>com.facebook.stetho</groupId>\n  <artifactId>stetho-js-rhino</artifactId>\n  <version>1.4.2</version>\n</dependency>\n```\n\nMake sure that you depend on the main `stetho` dependency too.\n\n### Putting it together\n\nThe Rhino JavaScript integration is automatically detected by Stetho and is\nenabled simply by adding the `stetho-js-rhino` dependency to your project.\n\nIf you want to configure the JavaScript environment you can pass your own\nvariables, classes, packages and functions and provide this custom runtime REPL using:\n\n```java\n    Stetho.initialize(Stetho.newInitializerBuilder(context)\n        .enableWebKitInspector(new InspectorModulesProvider() {\n          @Override\n          public Iterable<ChromeDevtoolsDomain> get() {\n            return new DefaultInspectorModulesBuilder(context).runtimeRepl(\n                new JsRuntimeReplFactoryBuilder(context)\n                    // Pass to JavaScript: var foo = \"bar\";\n                    .addVariable(\"foo\", \"bar\")\n                    .build()\n            ).finish();\n          }\n        })\n        .build();\n```\n\nFor more details see the next sections.\n\n### How it works\n\nAt the core this plugin initializes a JavaScript runtime provided by Mozilla's [Rhino](https://github.com/mozilla/rhino).\nThe runtime is configured so that it will work within an Android application.\nThis means that code has to run in interpreted mode as the more aggressive optimizations performed\nby Rhino are done through on-the-fly JVM bytecode generation and this won't work in Android which expects Dalvik bytecode.\n\nFor generic purposes the interpreted mode should have no performance impact since this is debug tool.\n\n## Customization\n\nBy default a JavaScript interpreter starts with an empty scope (environment) and has no default variables or functions set besides the ones described by the language specification.\n\nYou might be used to the browser setting up various objects for your like `document`, `console`, etc. This is something that's not part of the javascript specification and is particular only\nto the browser's runtime.\n\n## Example\n\nOnce you have enabled the JavaScript console in your app you will be able to run live JavaScript commands on your app from the console.\n\nHere's an example to show a toast from the console:\n\n```javascript\nimportPackage(android.widget);\nimportPackage(android.os);\nvar handler = new Handler(Looper.getMainLooper());\nhandler.post(function() { Toast.makeText(context, \"Hello from JavaScript\", Toast.LENGTH_LONG).show() });\n```\n\n### Default scope\n\nRhino offers the possibility to use an enhanced runtime where some utilities have been added\nin order to help the integration with the java runtime.\nThis is exactly the runtime that the plugin uses.\nThe default scope used by the plugin is described in more details in the article [Scripting Java](https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/Scripting_Java).\n\nThe scope can be enhanced by your application, if desired.\nYou can preload classes and packages, bind variables, objects and even functions.\nThis means that your java classes and objects can be accessed from JavaScript.\n\n### Builtins\n\nThe JavaScript runtime use by this plugin has been enhanced.\nBy default your application's package has been imported.\nSo things like `R.string.app` should work.\n\nThe functions `importClass` and `importPackage` have been added.\n\nA `console` object is available too. It supports only a `log()` method for now.\n\n### Import a class\n\nFirst define a JsRuntimeReplFactoryBuilder object:\n\n```\n// context is your application context\nJsRuntimeReplFactoryBuilder jsRuntimeBuilder = new JsRuntimeReplFactoryBuilder(context);\n```\n\nTo import a java class into the JavaScript runtime do:\n\n```java\njsRuntimeBuilder.importClass(R.class);\n```\n\n### Import a package\n\nTo import all classes in a java package into the JavaScript runtime do:\n\n```java\njsRuntimeBuilder.importPackage(\"android.content\");\n```\n\n### Variable binding\n\nHere's how to pass a variable to the JavaScript runtime:\n\n```java\njsRuntimeBuilder.addVariable(\"flag\", new AtomicBoolean(true));\n```\n**Note**: Java primitive types will be autoboxed, only objects can be passed to the javascript runtime.\n\n### Function binding\n\nYou can also add custom javascript functions that will be available to the runtime.\nThis requires a bit more of work on your part.\nRemember that you invoke methods on objects that you have already binded.\n\nIf you really want to define a top level function this is how it can be done:\n\n```java\n// Your application context\nfinal Context context = this;\n\nfinal Handler handler = new Handler(Looper.getMainLooper());\n\n// Add the function: void toast(String)\njsRuntimeBuilder.addFunction(\"toast\", new BaseFunction() {\n  @Override\n  public Object call(org.mozilla.javascript.Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {\n    // javascript passes the arguments as varags\n    final String message = args[0].toString();\n    handler.post(new Runnable() {\n      @Override\n      public void run() {\n        Toast.makeText(context, message, Toast.LENGTH_LONG).show();\n      }\n    });\n\n    // return undef in javascript\n    return org.mozilla.javascript.Context.getUndefinedValue();\n  }\n});\n```\n\n**Note**: This is a complex example since a toast be invoked from the main UI thread.\n\n## Limitations\n\nAs mentioned in the section [how it works](#how-it-works) the JavaScript runtime has to run in interpreted mode due to the nature of the android runtime.\n\n### Dex method count\n\nRhino is not a small library and it can increase your dex method count by more than 7,000.\nThe standard Rhino distribution includes a \"tools\" package that's not required by this plugin\nbut it is still bundled.\nThat package alone adds more than 1,200 methods to the dex count.\n\nHopefully the Rhino devs will split the distribution in smaller artifacts (see https://github.com/mozilla/rhino/issues/156).\nIn the meanwhile you should consider using proguard to shrink the dex method count.\n\nHere's the dex count of the stetho-sample compiled under various scenarios:\n\n|      | Original | JavaScript |\n| :--- | -------: | ---------: |\n| Dex  | 15,461   | 22,749     |\n| Size | 1.0M     | 1.6M       |\n\nWith proguard:\n\n|      | Original | JavaScript | JavaScript (w/o *tools*)     |\n| :--- | -------: | ---------: | ---------------------------: |\n| Dex  | 8,013    | 15,316     | 14,043                       |\n| Size | 847K     | 1.5M       | 1.4M                         |\n\n### Proguard\n\nTo proguard your project add the following rules to your proguard file:\n\n```\n# stetho\n+keep class com.facebook.stetho.** { *; }\n\n# rhino (javascript)\n-dontwarn org.mozilla.javascript.**\n-dontwarn org.mozilla.classfile.**\n-keep class org.mozilla.javascript.** { *; }\n```\n\nIf you want to remove the *tools* package for a more aggressive proguard use:\n\n```\n# stetho\n+keep class com.facebook.stetho.** { *; }\n\n# rhino (javascript)\n-dontwarn org.mozilla.javascript.**\n-dontwarn org.mozilla.classfile.**\n-keep class org.mozilla.classfile.** { *; }\n-keep class org.mozilla.javascript.* { *; }\n-keep class org.mozilla.javascript.annotations.** { *; }\n-keep class org.mozilla.javascript.ast.** { *; }\n-keep class org.mozilla.javascript.commonjs.module.** { *; }\n-keep class org.mozilla.javascript.commonjs.module.provider.** { *; }\n-keep class org.mozilla.javascript.debug.** { *; }\n-keep class org.mozilla.javascript.jdk13.** { *; }\n-keep class org.mozilla.javascript.jdk15.** { *; }\n-keep class org.mozilla.javascript.json.** { *; }\n-keep class org.mozilla.javascript.optimizer.** { *; }\n-keep class org.mozilla.javascript.regexp.** { *; }\n-keep class org.mozilla.javascript.serialize.** { *; }\n-keep class org.mozilla.javascript.typedarrays.** { *; }\n-keep class org.mozilla.javascript.v8dtoa.** { *; }\n-keep class org.mozilla.javascript.xml.** { *; }\n-keep class org.mozilla.javascript.xmlimpl.** { *; }\n```\n\n"
  },
  {
    "path": "stetho-js-rhino/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n        consumerProguardFiles 'proguard-consumer.pro'\n    }\n\n    lintOptions {\n        // Rhino has references to awt and swing\n        disable 'InvalidPackage'\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n    implementation 'org.mozilla:rhino:1.7.6'\n    implementation 'androidx.annotation:annotation:1.1.0'\n\n    testImplementation 'junit:junit:4.12'\n}\n\napply from: rootProject.file('release.gradle')\n"
  },
  {
    "path": "stetho-js-rhino/gradle.properties",
    "content": "POM_NAME=Stetho JavaScript (Rhino) module\nPOM_ARTIFACT_ID=stetho-js-rhino\nPOM_OPTIONAL_DEPS=com.facebook.stetho:stetho\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho-js-rhino/proguard-consumer.pro",
    "content": "-keep class com.facebook.stetho.rhino.** { *; }\n\n# rhino (javascript)\n-dontwarn org.mozilla.javascript.**\n-dontwarn org.mozilla.classfile.**\n-keep class org.mozilla.classfile.** { *; }\n-keep class org.mozilla.javascript.* { *; }\n-keep class org.mozilla.javascript.annotations.** { *; }\n-keep class org.mozilla.javascript.ast.** { *; }\n-keep class org.mozilla.javascript.commonjs.module.** { *; }\n-keep class org.mozilla.javascript.commonjs.module.provider.** { *; }\n-keep class org.mozilla.javascript.debug.** { *; }\n-keep class org.mozilla.javascript.jdk13.** { *; }\n-keep class org.mozilla.javascript.jdk15.** { *; }\n-keep class org.mozilla.javascript.json.** { *; }\n-keep class org.mozilla.javascript.optimizer.** { *; }\n-keep class org.mozilla.javascript.regexp.** { *; }\n-keep class org.mozilla.javascript.serialize.** { *; }\n-keep class org.mozilla.javascript.typedarrays.** { *; }\n-keep class org.mozilla.javascript.v8dtoa.** { *; }\n-keep class org.mozilla.javascript.xml.** { *; }\n-keep class org.mozilla.javascript.xmlimpl.** { *; }\n"
  },
  {
    "path": "stetho-js-rhino/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho.rhino\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "stetho-js-rhino/src/main/java/com/facebook/stetho/rhino/JsConsole.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.rhino;\n\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.protocol.module.Console.MessageLevel;\nimport com.facebook.stetho.inspector.protocol.module.Console.MessageSource;\nimport org.mozilla.javascript.Context;\nimport org.mozilla.javascript.Function;\nimport org.mozilla.javascript.ScriptRuntime;\nimport org.mozilla.javascript.Scriptable;\nimport org.mozilla.javascript.ScriptableObject;\nimport org.mozilla.javascript.annotations.JSFunction;\n\npublic class JsConsole extends ScriptableObject {\n\n  /**\n   * Serial version UID.\n   */\n  private static final long serialVersionUID = 1L;\n\n  /**\n   * <p>The zero-parameter constructor.</p>\n   *\n   * <p>When Context.defineClass is called with this class, it will construct\n   * JsConsole.prototype using this constructor.</p>\n   */\n  public JsConsole() {\n    // Empty\n  }\n\n  public JsConsole(ScriptableObject scope) {\n    setParentScope(scope);\n    Object ctor = ScriptRuntime.getTopLevelProp(scope, \"Console\");\n    if (ctor != null && ctor instanceof Scriptable) {\n      Scriptable scriptable = (Scriptable) ctor;\n      setPrototype((Scriptable) scriptable.get(\"prototype\", scriptable));\n    }\n  }\n\n  @Override\n  public String getClassName() {\n    return \"Console\";\n  }\n\n  @JSFunction\n  public static void log(Context cx, Scriptable thisObj, Object[] args, Function funObj) {\n    log(args);\n  }\n\n  // See https://developer.chrome.com/devtools/docs/console-api#consolelogobject-object\n  private static void log(Object [] rawArgs) {\n    String message = JsFormat.parse(rawArgs);\n    CLog.writeToConsole(MessageLevel.LOG, MessageSource.JAVASCRIPT, message);\n  }\n}\n"
  },
  {
    "path": "stetho-js-rhino/src/main/java/com/facebook/stetho/rhino/JsFormat.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.rhino;\n\nimport java.util.Locale;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport androidx.annotation.NonNull;\n\n/**\n * <p>Formatter that tries to mimic <pre>console.log()</pre>'s format as close as possible.</p>\n *\n * <p>We have to use a custom formatter because <pre>String.format()</pre> will fail with JavaScript\n * numbers. Using the conversion format %d in JavaScript causes a problem with Java's  <pre>String.format()</pre>.\n * This happens because %d expects an int/Integer but in JavaScript numbers are floats!\n * </p>\n *\n * <p>See <a href=\"https://developer.chrome.com/devtools/docs/console-api#consolelogobject-object\">Console API</a>.</p>\n */\nclass JsFormat {\n  /**\n   * Format specifier pattern.\n   * <p/>\n   * <code>%[argument_index$][flags][width][.precision]conversion</code>\n   */\n  private static final Pattern FORMAT_SPECIFIER_PATTERN = Pattern.compile(\n      \"^%\"\n          + \"([0-9]+ [$])?\"  // Index\n          + \"([0-9]+)?\"      // Width\n          + \"([.] [0-9]+)?\"  // Precision\n          + \"([difs])\",      // Conversion\n      Pattern.COMMENTS\n  );\n\n  /**\n   * Simple wrapper around a char[]. New versions of java make a full copy of\n   * the string when doing substring(). With this class we avoid the copies\n   * and substring is still O(1) vs O(n).\n   */\n  private static class ArrayCharSequence implements CharSequence {\n    private final @NonNull char[] array;\n    private final int start;\n    private final int end;\n\n    private ArrayCharSequence(@NonNull char[] array, int start, int end) {\n      this.array = array;\n      this.start = start;\n      this.end = end;\n    }\n\n    @Override\n    public int length() {\n      return end - start;\n    }\n\n    @Override\n    public char charAt(int index) {\n      return array[start + index];\n    }\n\n    @NonNull\n    @Override\n    public CharSequence subSequence(int start, int end) {\n      return new ArrayCharSequence(array, this.start + start, this.start + end);\n    }\n\n    private @NonNull CharSequence substring(int start) {\n      return new ArrayCharSequence(array, this.start + start, this.start + end);\n    }\n\n    @Override\n    public @NonNull String toString() {\n      return new String(array, start, end - start);\n    }\n  }\n\n  /**\n   * Takes the arguments that console.log() would use, parses them and returns the\n   * final string to output.\n   *\n   * @param args format and arguments\n   * @return a string with the message to output\n   */\n  static @NonNull String parse(@NonNull Object...args) {\n    // The params available, we need to know if they where taken or not\n    boolean[] argsUsed = new boolean[args.length];\n    // The first argument is the format and is always used\n    String format = (String) args[0];\n    argsUsed[0] = true;\n    int nextArgIndex = 1;\n\n    // Scan the format and find all %s patterns\n    final char[] chars = format.toCharArray();\n    StringBuilder buffer = new StringBuilder();\n    ArrayCharSequence sequence = new ArrayCharSequence(chars, 0, chars.length);\n    for (int i = 0; i < chars.length; ++i) {\n      char c = chars[i];\n      if (c != '%') {\n        // Keep eating chars until we get a '%'\n        buffer.append(c);\n        continue;\n      }\n\n      // Found a %, is it a stand alone one?\n      Matcher matcher = FORMAT_SPECIFIER_PATTERN.matcher(sequence.substring(i));\n      if (!matcher.find()) {\n        // Didn't find a valid format specifier, maybe it is a \"%%\" ?\n        if (i + 1 < chars.length) {\n          char peek = chars[i + 1];\n          if (peek == '%') {\n            // Found \"%%\" which at the end maps as a single '%'\n            ++i;\n          }\n        }\n\n        // A stand alone '%', we will just print it as it is\n        buffer.append('%');\n        continue;\n      }\n\n      // Analyze the format. We don't have named captures in android yet so we will inspect\n      // the groups. They are each optional but we can find out which one is which easily.\n      // Remember that we want to parse: %[argument_index$][flags][width][.precision]conversion\n      //\n      //  - `index` ends with '$'\n      //  - `precision` starts with '.'\n      //  - we don't support flags\n      //  - `width` is just numbers\n      //  - `conversion` is a single letter\n      int groupCount = matcher.groupCount();\n      int index = -1;\n      int width = -1;\n      int precision = -1;\n      char conversion = 0;\n      for (int groupIdx = 1; groupIdx <= groupCount; ++groupIdx) {\n        String value = matcher.group(groupIdx);\n        if (value == null || value.equals(\"\")) {\n          // Empty group, we ignore it\n          continue;\n        }\n\n        if (value.endsWith(\"$\")) {\n          // The `index` (it ends with a '$')\n          value = value.substring(0, value.length() - 1);\n          index = Integer.parseInt(value);\n          continue;\n        }\n\n        char first = value.charAt(0);\n        if (first == '.') {\n          // The `precision` (it starts with a dot '.')\n          value = value.substring(1);\n          precision = Integer.parseInt(value);\n        } else if (first >= '0' && first <= '9') {\n          // The `width`\n          width = Integer.parseInt(value);\n        } else {\n          // It has to be the `conversion`\n          conversion = first;\n        }\n      }\n\n      // Now we try to see which argument we have to take\n      String currentFormat = matcher.group();\n      final Object value;\n      final boolean found;\n      if (index > argsUsed.length || (width > -1 && index == -1)) {\n        // Index out of bounds (%1234$d), print the format as it is and ignore\n        value = null;\n        found = false;\n      } else if (index <= argsUsed.length && index > 0) {\n        // Index if valid (%3$d)\n        value = args[index];\n        argsUsed[index] = true;\n        nextArgIndex = index + 1;\n        found = true;\n      } else {\n        // No index provided (%d)\n        if (nextArgIndex < argsUsed.length) {\n          value = args[nextArgIndex];\n          argsUsed[nextArgIndex] = true;\n          ++nextArgIndex;\n          found = true;\n        } else {\n          // We have way too many %d, more than we have arguments!\n          value = null;\n          found = false;\n        }\n      }\n\n      if (!found) {\n        // Just dump the placeholder text as it is and ignore\n        buffer.append(currentFormat);\n        i += currentFormat.length() - 1;\n        continue;\n      }\n\n      // Apply the conversion\n      switch (conversion) {\n        case 'd':\n        case 'i':\n          Object l;\n          if (value instanceof String) {\n            try {\n              l = Long.parseLong((String) value);\n            } catch (NumberFormatException e) {\n              l = \"NaN\";\n            }\n          } else if (value instanceof Number) {\n            l = ((Number) value).intValue();\n          } else {\n            l = 0;\n          }\n          buffer.append(l);\n          break;\n\n        case 'f':\n          Object d;\n          if (value instanceof String) {\n            try {\n              d = Double.parseDouble((String) value);\n            } catch (NumberFormatException e) {\n              d = \"NaN\";\n            }\n          } else if (value instanceof Number) {\n            d = ((Number) value).doubleValue();\n          } else {\n            d = 0;\n          }\n\n          if (precision > -1 && d instanceof Number) {\n            d = String.format(Locale.US, \"%.\" + precision + \"f\", d);\n          }\n          buffer.append(d);\n          break;\n\n        case 's':\n        default:\n          buffer.append(value);\n          break;\n      }\n\n      i += currentFormat.length() - 1;\n    }\n\n    // Concatenate all params that have not been used\n    for (int j = 0; j < argsUsed.length; j++) {\n      boolean argUsed = argsUsed[j];\n      if (!argUsed) {\n        buffer.append(\" \");\n        buffer.append(args[j]);\n      }\n    }\n\n    return buffer.toString();\n  }\n}\n"
  },
  {
    "path": "stetho-js-rhino/src/main/java/com/facebook/stetho/rhino/JsRuntimeRepl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.rhino;\n\n\nimport com.facebook.stetho.inspector.console.RuntimeRepl;\n\nimport org.mozilla.javascript.Context;\nimport org.mozilla.javascript.ScriptableObject;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nclass JsRuntimeRepl implements RuntimeRepl {\n\n  private final @NonNull ScriptableObject mJsScope;\n\n  JsRuntimeRepl(@NonNull ScriptableObject scope) {\n    mJsScope = scope;\n  }\n\n  @Override\n  public @Nullable Object evaluate(@NonNull String expression) throws Throwable {\n      Object result;\n      final Context jsContext = enterJsContext();\n      try {\n        result = jsContext.evaluateString(mJsScope, expression, \"chrome\", 1, null);\n\n        // Google chrome automatically saves the last expression to `$_`, we do the same\n        Object jsValue = Context.javaToJS(result, mJsScope);\n        ScriptableObject.putProperty(mJsScope, \"$_\", jsValue);\n      } finally {\n        Context.exit();\n      }\n\n      return Context.jsToJava(result, Object.class);\n  }\n\n  /**\n   * Setups a proper javascript context so that it can run javascript code properly under android.\n   * For android we need to disable bytecode generation since the android vms don't understand JVM bytecode.\n   * @return a proper javascript context\n   */\n  static @NonNull Context enterJsContext() {\n    final Context jsContext = Context.enter();\n\n    // If we cause the context to throw a runtime exception from this point\n    // we need to make sure that exit the context.\n    try {\n      jsContext.setLanguageVersion(Context.VERSION_1_8);\n\n      // We can't let Rhino to optimize the JS and to use a JIT because it would generate JVM bytecode\n      // and android runs on DEX bytecode. Instead we need to go in interpreted mode.\n      jsContext.setOptimizationLevel(-1);\n    } catch (RuntimeException e) {\n      // Something bad happened to the javascript context but it might still be usable.\n      // The first thing to do is to exit the context and then propagate the error.\n      Context.exit();\n      throw e;\n    }\n\n    return jsContext;\n  }\n}\n"
  },
  {
    "path": "stetho-js-rhino/src/main/java/com/facebook/stetho/rhino/JsRuntimeReplFactoryBuilder.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.rhino;\n\nimport android.util.Log;\n\nimport com.facebook.stetho.common.LogUtil;\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.console.RuntimeRepl;\nimport com.facebook.stetho.inspector.console.RuntimeReplFactory;\nimport com.facebook.stetho.inspector.protocol.module.Console;\n\nimport org.mozilla.javascript.Context;\nimport org.mozilla.javascript.Function;\nimport org.mozilla.javascript.ImporterTopLevel;\nimport org.mozilla.javascript.Scriptable;\nimport org.mozilla.javascript.ScriptableObject;\nimport org.mozilla.javascript.Undefined;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\nimport androidx.annotation.NonNull;\n\n/**\n * <p>Builder used to setup the javascript runtime to be used by stetho.</p>\n *\n * <p>You can use this builder to configure the javacript environment by preloading:\n * <ul>\n *   <li>Java classes</li>\n *   <li>Java packages (with all their java classes)</li>\n *   <li>Variables</li>\n *   <li>Functions</li>\n * </ul>\n * </p>\n *\n * <p>Your application context package is automatically visible with this builder.</p>\n */\npublic class JsRuntimeReplFactoryBuilder {\n\n  /**\n   * Name of the \"source\" file used for reporting JavaScript compilation errors (or runtime errors).\n   * Since this is evaluated from a chrome inspector window we pass \"chrome\".\n   */\n  private static final String SOURCE_NAME = \"chrome\";\n\n  /**\n   * Android application context.\n   */\n  private final android.content.Context mContext;\n\n  /**\n   * Java classes to import into the javascript environment.\n   */\n  private final Set<Class<?>> mClasses = new HashSet<>();\n\n  /**\n   * Java packages to import into the javascript environment.\n   * All classes inside the package will be imported.\n   */\n  private final Set<String> mPackages = new HashSet<>();\n\n  /**\n   * Variables to bind to the javascript environment.\n   */\n  private final Map<String, Object> mVariables = new HashMap<>();\n\n  /**\n   * Global mFunctions to add to the javascript environment.\n   */\n  private final Map<String, Function> mFunctions = new HashMap<>();\n\n  public static RuntimeReplFactory defaultFactory(@NonNull android.content.Context context) {\n    return new JsRuntimeReplFactoryBuilder(context).build();\n  }\n\n  public JsRuntimeReplFactoryBuilder(@NonNull android.content.Context context) {\n    mContext = context;\n\n    // We import the app's package name by default\n    mPackages.add(context.getPackageName());\n\n    // Predefine $_ which holds the value of the last expression evaluated\n    mVariables.put(\"$_\", Context.getUndefinedValue());\n  }\n\n  /**\n   * Request that the given java class be imported in the javascript runtime.\n   * @param aClass the java class to import\n   * @return the builder\n   */\n  public @NonNull\n  JsRuntimeReplFactoryBuilder importClass(@NonNull Class<?> aClass) {\n    mClasses.add(aClass);\n    return this;\n  }\n\n  /**\n   * Request that the given package name will be imported in the javascript runtime.\n   * This means that all classes (enums and interfaces) will be imported.\n   * @param packageName the java package name to import\n   * @return the builder\n   */\n  public @NonNull\n  JsRuntimeReplFactoryBuilder importPackage(@NonNull String packageName) {\n    mPackages.add(packageName);\n    return this;\n  }\n\n  /**\n   * Add a variable (binding) to the javascript environment.\n   * @param name the javascript variable name\n   * @param value the value to add\n   * @return the builder\n   */\n  public JsRuntimeReplFactoryBuilder addVariable(@NonNull String name, Object value) {\n    mVariables.put(name, value);\n    return this;\n  }\n\n  /**\n   * Adds a function to the javascript environment.\n   * @param name the javascript function name\n   * @param function the function\n   * @return the builder\n   */\n  public @NonNull\n  JsRuntimeReplFactoryBuilder addFunction(@NonNull String name, @NonNull Function function) {\n    mFunctions.put(name, function);\n    return this;\n  }\n\n  /**\n   * Build the runtime REPL instance to be supplied to the Stetho {@code Runtime} module.\n   */\n  public RuntimeReplFactory build() {\n    return new RuntimeReplFactory() {\n      @Override\n      public RuntimeRepl newInstance() {\n        return new JsRuntimeRepl(initJsScope());\n      }\n    };\n  }\n\n  /**\n   * Initializes a proper javascript scope (runtime environment holding variables).\n   * @return a javascript scope\n   */\n  private @NonNull ScriptableObject initJsScope() {\n    final Context jsContext = JsRuntimeRepl.enterJsContext();\n    try {\n      ScriptableObject scope = initJsScope(jsContext);\n      return scope;\n    } finally {\n      Context.exit();\n    }\n  }\n\n  private @NonNull ScriptableObject initJsScope(@NonNull Context jsContext) {\n    // Set the main Rhino goodies\n    ImporterTopLevel importerTopLevel = new ImporterTopLevel(jsContext);\n    ScriptableObject scope = jsContext.initStandardObjects(importerTopLevel, false);\n\n    ScriptableObject.putProperty(scope, \"context\", Context.javaToJS(mContext, scope));\n\n    try {\n      importClasses(jsContext, scope);\n      importPackages(jsContext, scope);\n      importConsole(scope);\n      importVariables(scope);\n      importFunctions(scope);\n    } catch (StethoJsException e) {\n      String message = String.format(\"%s\\n%s\", e.getMessage(), Log.getStackTraceString(e));\n      LogUtil.e(e, message);\n      CLog.writeToConsole(Console.MessageLevel.ERROR, Console.MessageSource.JAVASCRIPT, message);\n    }\n\n    return scope;\n  }\n\n  private void importClasses(@NonNull Context jsContext, @NonNull ScriptableObject scope) throws StethoJsException {\n    // Import the classes that the caller requested\n    for (Class<?> aClass : mClasses) {\n      String className = aClass.getName();\n      try {\n        // import from default classes\n        String expression = String.format(\"importClass(%s)\", className);\n        jsContext.evaluateString(scope, expression, SOURCE_NAME, 1, null);\n      } catch (Exception e) {\n        try {\n          // import from application classes\n          String expression = String.format(\"importClass(Packages.%s)\", className);\n          jsContext.evaluateString(scope, expression, SOURCE_NAME, 1, null);\n        } catch (Exception e1) {\n          throw new StethoJsException(e1, \"Failed to import class: %s\", className);\n        }\n      }\n    }\n  }\n\n  private void importPackages(@NonNull Context jsContext, @NonNull ScriptableObject scope) throws StethoJsException {\n    // Import the packages that the caller requested\n    for (String packageName : mPackages) {\n      try {\n        // import from default packages\n        String expression = String.format(\"importPackage(%s)\", packageName);\n        jsContext.evaluateString(scope, expression, SOURCE_NAME, 1, null);\n      } catch (Exception e) {\n        try {\n          // import from application packages\n          String expression = String.format(\"importPackage(Packages.%s)\", packageName);\n          jsContext.evaluateString(scope, expression, SOURCE_NAME, 1, null);\n        } catch (Exception e1) {\n          throw new StethoJsException(e, \"Failed to import package: %s\", packageName);\n        }\n      }\n    }\n  }\n\n  private void importConsole(@NonNull ScriptableObject scope) throws StethoJsException {\n    // Set the `console` object\n    try {\n      ScriptableObject.defineClass(scope, JsConsole.class);\n      JsConsole console = new JsConsole(scope);\n      scope.defineProperty(\"console\", console, ScriptableObject.DONTENUM);\n    } catch (Exception e) {\n      throw new StethoJsException(e, \"Failed to setup javascript console\");\n    }\n  }\n\n  private void importVariables(@NonNull ScriptableObject scope) throws StethoJsException {\n    // Define the variables\n    for (Map.Entry<String, Object> entrySet : mVariables.entrySet()) {\n      String varName = entrySet.getKey();\n      Object varValue = entrySet.getValue();\n      try {\n        Object jsValue;\n        if (varValue instanceof Scriptable || varValue instanceof Undefined) {\n          jsValue = varValue;\n        } else {\n          jsValue = Context.javaToJS(varValue, scope);\n        }\n        ScriptableObject.putProperty(scope, varName, jsValue);\n      } catch (Exception e) {\n        throw new StethoJsException(e, \"Failed to setup variable: %s\", varName);\n      }\n    }\n  }\n\n  private void importFunctions(@NonNull ScriptableObject scope) throws StethoJsException {\n    // Define the functions\n    for (Map.Entry<String, Function> entrySet : mFunctions.entrySet()) {\n      String functionName = entrySet.getKey();\n      Function function = entrySet.getValue();\n      try {\n        ScriptableObject.putProperty(scope, functionName, function);\n      } catch (Exception e) {\n        throw new StethoJsException(e, \"Failed to setup function: %s\", functionName);\n      }\n    }\n  }\n\n  private static class StethoJsException extends Exception {\n    StethoJsException(Throwable rootCause, String format, Object...args) {\n      super(args.length == 0 ? format : String.format(format, args), rootCause);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-js-rhino/src/test/java/com/facebook/stetho/rhino/JsFormatTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.rhino;\n\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.junit.runners.JUnit4;\n\nimport static org.junit.Assert.assertEquals;\n\n@RunWith(JUnit4.class)\npublic class JsFormatTest {\n\n  @Test\n  public void testParse() {\n    assertEquals(\"%d\", JsFormat.parse(\"%d\"));\n    assertEquals(\"23\", JsFormat.parse(\"%d\", 23));\n    assertEquals(\"2\", JsFormat.parse(\"%d\", 2.7));\n    assertEquals(\"23.37\", JsFormat.parse(\"%.2f\", 23.3678));\n    assertEquals(\"hellow world\", JsFormat.parse(\"hellow %s\", \"world\"));\n    assertEquals(\"%$2d dp 5 1 2 3 4\", JsFormat.parse(\"%$2d dp 5\", 1, 2, 3 ,4));\n    assertEquals(\"2 dp 4 1 3 4\", JsFormat.parse(\"%2$d dp 4\", 1, 2, 3 ,4));\n    assertEquals(\"NaN dp 4 one three four\", JsFormat.parse(\"%2$d dp 4\", \"one\", \"two\", \"three\", \"four\"));\n    assertEquals(\"two dp 4 one three four\", JsFormat.parse(\"%2$s dp 4\", \"one\", \"two\", \"three\", \"four\"));\n    assertEquals(\"two vs two one three four\", JsFormat.parse(\"%2$s vs %2$s\", \"one\", \"two\", \"three\", \"four\"));\n\n    assertEquals(\"two vs two one three four\", JsFormat.parse(\"%2$s vs %2$s\", \"one\", \"two\", \"three\", \"four\"));\n    assertEquals(\"two vs four one three\", JsFormat.parse(\"%2$s vs %4$s\", \"one\", \"two\", \"three\", \"four\"));\n    assertEquals(\"two vs four %.3$4f one three 1234.5678\", JsFormat.parse(\"%2$s vs %4$s %.3$4f\", \"one\", \"two\", \"three\", \"four\", 1234.5678));\n    assertEquals(\"two vs four %.2$4f one three 1234.5678\", JsFormat.parse(\"%2$s vs %4$s %.2$4f\", \"one\", \"two\", \"three\", \"four\", 1234.5678));\n    assertEquals(\"two vs four 1234.57 one three\", JsFormat.parse(\"%2$s vs %4$s %.2f\", \"one\", \"two\", \"three\", \"four\", 1234.5678));\n\n    assertEquals(\"1234.57\", JsFormat.parse(\"%.2f\", 1234.5678));\n    assertEquals(\"%,2f 1234.5678\", JsFormat.parse(\"%,2f\", 1234.5678));\n    assertEquals(\"1234.57\", JsFormat.parse(\"%.2f\", 1234.5678));\n    assertEquals(\"NaN\", JsFormat.parse(\"%.2f\", \"hello\"));\n    assertEquals(\"%.2a hello\", JsFormat.parse(\"%.2a\", \"hello\"));\n    assertEquals(\"% cool .2a hello\", JsFormat.parse(\"%% cool .2a\", \"hello\"));\n    assertEquals(\"two vs four 1234.5678 one three\", JsFormat.parse(\"%2$s vs %4$s %s\", \"one\", \"two\", \"three\", \"four\", 1234.5678));\n    assertEquals(\"two vs four %s\", JsFormat.parse(\"two vs four %s\"));\n    assertEquals(\"two vs four % one three\", JsFormat.parse(\"two vs four %\", \"one\", \"three\"));\n    assertEquals(\"two one two four oops three\", JsFormat.parse(\"%2$s %1$s %s %4$s %s\", \"one\", \"two\", \"three\", \"four\", \"oops\"));\n  }\n}\n"
  },
  {
    "path": "stetho-okhttp/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-okhttp/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    lintOptions {\n        // This seems to be firing due to okio referencing java.nio.File\n        // which is harmless for us. Not sure how to disable this in\n        // more targeted fashion...\n        warning 'InvalidPackage'\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n    implementation 'com.squareup.okhttp:okhttp:2.7.2'\n\n    testImplementation 'junit:junit:4.12'\n    testImplementation('org.robolectric:robolectric:2.4') {\n        exclude module: 'commons-logging'\n        exclude module: 'httpclient'\n    }\n    testImplementation 'org.powermock:powermock-api-mockito:1.6.6'\n    testImplementation 'org.powermock:powermock-module-junit4:1.6.6'\n\n    // Needed for Robolectric and PowerMock to be combined in a single test.\n    testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.6'\n    testImplementation 'org.powermock:powermock-classloading-xstream:1.6.6'\n\n    testImplementation 'com.squareup.okhttp:mockwebserver:2.7.2'\n}\n\napply from: rootProject.file('release.gradle')\n"
  },
  {
    "path": "stetho-okhttp/gradle.properties",
    "content": "POM_NAME=Stetho OkHttp module\nPOM_ARTIFACT_ID=stetho-okhttp\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho-okhttp/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho.okhttp\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "stetho-okhttp/src/main/java/com/facebook/stetho/okhttp/StethoInterceptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.okhttp;\n\nimport com.facebook.stetho.inspector.network.DefaultResponseHandler;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.RequestBodyHelper;\nimport com.squareup.okhttp.Connection;\nimport com.squareup.okhttp.Interceptor;\nimport com.squareup.okhttp.MediaType;\nimport com.squareup.okhttp.Request;\nimport com.squareup.okhttp.RequestBody;\nimport com.squareup.okhttp.Response;\nimport com.squareup.okhttp.ResponseBody;\nimport okio.BufferedSink;\nimport okio.BufferedSource;\nimport okio.Okio;\n\nimport javax.annotation.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Provides easy integration with <a href=\"http://square.github.io/okhttp/\">OkHttp</a> 2.2.0+\n * by way of the new <a href=\"https://github.com/square/okhttp/wiki/Interceptors\">Interceptor</a>\n * system. To use:\n * <pre>\n *   OkHttpClient client = new OkHttpClient();\n *   client.networkInterceptors().add(new StethoInterceptor());\n * </pre>\n *\n * @deprecated replaced with {@code com.facebook.stetho.okhttp3.StethoInterceptor}.\n */\n@Deprecated\npublic class StethoInterceptor implements Interceptor {\n  private final NetworkEventReporter mEventReporter = NetworkEventReporterImpl.get();\n\n  @Override\n  public Response intercept(Chain chain) throws IOException {\n    String requestId = mEventReporter.nextRequestId();\n\n    Request request = chain.request();\n\n    RequestBodyHelper requestBodyHelper = null;\n    if (mEventReporter.isEnabled()) {\n      requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId);\n      OkHttpInspectorRequest inspectorRequest =\n          new OkHttpInspectorRequest(requestId, request, requestBodyHelper);\n      mEventReporter.requestWillBeSent(inspectorRequest);\n    }\n\n    Response response;\n    try {\n      response = chain.proceed(request);\n    } catch (IOException e) {\n      if (mEventReporter.isEnabled()) {\n        mEventReporter.httpExchangeFailed(requestId, e.toString());\n      }\n      throw e;\n    }\n\n    if (mEventReporter.isEnabled()) {\n      if (requestBodyHelper != null && requestBodyHelper.hasBody()) {\n        requestBodyHelper.reportDataSent();\n      }\n\n      Connection connection = chain.connection();\n      if (connection == null) {\n        throw new IllegalStateException(\n            \"No connection associated with this request; \" +\n                \"did you use addInterceptor instead of addNetworkInterceptor?\");\n      }\n      mEventReporter.responseHeadersReceived(\n          new OkHttpInspectorResponse(\n              requestId,\n              request,\n              response,\n              connection));\n\n      ResponseBody body = response.body();\n      MediaType contentType = null;\n      InputStream responseStream = null;\n      if (body != null) {\n        contentType = body.contentType();\n        responseStream = body.byteStream();\n      }\n\n      responseStream = mEventReporter.interpretResponseStream(\n          requestId,\n          contentType != null ? contentType.toString() : null,\n          response.header(\"Content-Encoding\"),\n          responseStream,\n          new DefaultResponseHandler(mEventReporter, requestId));\n      if (responseStream != null) {\n        response = response.newBuilder()\n            .body(new ForwardingResponseBody(body, responseStream))\n            .build();\n      }\n    }\n\n    return response;\n  }\n\n  private static class OkHttpInspectorRequest implements NetworkEventReporter.InspectorRequest {\n    private final String mRequestId;\n    private final Request mRequest;\n    private RequestBodyHelper mRequestBodyHelper;\n\n    public OkHttpInspectorRequest(\n        String requestId,\n        Request request,\n        RequestBodyHelper requestBodyHelper) {\n      mRequestId = requestId;\n      mRequest = request;\n      mRequestBodyHelper = requestBodyHelper;\n    }\n\n    @Override\n    public String id() {\n      return mRequestId;\n    }\n\n    @Override\n    public String friendlyName() {\n      // Hmm, can we do better?  tag() perhaps?\n      return null;\n    }\n\n    @Nullable\n    @Override\n    public Integer friendlyNameExtra() {\n      return null;\n    }\n\n    @Override\n    public String url() {\n      return mRequest.urlString();\n    }\n\n    @Override\n    public String method() {\n      return mRequest.method();\n    }\n\n    @Nullable\n    @Override\n    public byte[] body() throws IOException {\n      RequestBody body = mRequest.body();\n      if (body == null) {\n        return null;\n      }\n      OutputStream out = mRequestBodyHelper.createBodySink(firstHeaderValue(\"Content-Encoding\"));\n      BufferedSink bufferedSink = Okio.buffer(Okio.sink(out));\n      try {\n        body.writeTo(bufferedSink);\n      } finally {\n        bufferedSink.close();\n      }\n      return mRequestBodyHelper.getDisplayBody();\n    }\n\n    @Override\n    public int headerCount() {\n      return mRequest.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n      return mRequest.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n      return mRequest.headers().value(index);\n    }\n\n    @Nullable\n    @Override\n    public String firstHeaderValue(String name) {\n      return mRequest.header(name);\n    }\n  }\n\n  private static class OkHttpInspectorResponse implements NetworkEventReporter.InspectorResponse {\n    private final String mRequestId;\n    private final Request mRequest;\n    private final Response mResponse;\n    private final Connection mConnection;\n\n    public OkHttpInspectorResponse(\n        String requestId,\n        Request request,\n        Response response,\n        Connection connection) {\n      mRequestId = requestId;\n      mRequest = request;\n      mResponse = response;\n      mConnection = connection;\n    }\n\n    @Override\n    public String requestId() {\n      return mRequestId;\n    }\n\n    @Override\n    public String url() {\n      return mRequest.urlString();\n    }\n\n    @Override\n    public int statusCode() {\n      return mResponse.code();\n    }\n\n    @Override\n    public String reasonPhrase() {\n      return mResponse.message();\n    }\n\n    @Override\n    public boolean connectionReused() {\n      // Not sure...\n      return false;\n    }\n\n    @Override\n    public int connectionId() {\n      return mConnection.hashCode();\n    }\n\n    @Override\n    public boolean fromDiskCache() {\n      return mResponse.cacheResponse() != null;\n    }\n\n    @Override\n    public int headerCount() {\n      return mResponse.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n      return mResponse.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n      return mResponse.headers().value(index);\n    }\n\n    @Nullable\n    @Override\n    public String firstHeaderValue(String name) {\n      return mResponse.header(name);\n    }\n  }\n\n  private static class ForwardingResponseBody extends ResponseBody {\n    private final ResponseBody mBody;\n    private final BufferedSource mInterceptedSource;\n\n    public ForwardingResponseBody(ResponseBody body, InputStream interceptedStream) {\n      mBody = body;\n      mInterceptedSource = Okio.buffer(Okio.source(interceptedStream));\n    }\n\n    @Override\n    public MediaType contentType() {\n      return mBody.contentType();\n    }\n\n    @Override\n    public long contentLength() throws IOException {\n      return mBody.contentLength();\n    }\n\n    @Override\n    public BufferedSource source() {\n      // close on the delegating body will actually close this intercepted source, but it\n      // was derived from mBody.byteStream() therefore the close will be forwarded all the\n      // way to the original.\n      return mInterceptedSource;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-okhttp/src/test/java/com/facebook/stetho/okhttp/StethoInterceptorTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.okhttp;\n\nimport android.net.Uri;\nimport android.os.Build;\nimport com.facebook.stetho.inspector.network.DecompressionHelper;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.ResponseHandler;\nimport com.squareup.okhttp.Connection;\nimport com.squareup.okhttp.Interceptor;\nimport com.squareup.okhttp.MediaType;\nimport com.squareup.okhttp.OkHttpClient;\nimport com.squareup.okhttp.Protocol;\nimport com.squareup.okhttp.Request;\nimport com.squareup.okhttp.RequestBody;\nimport com.squareup.okhttp.Response;\nimport com.squareup.okhttp.ResponseBody;\nimport com.squareup.okhttp.mockwebserver.MockResponse;\nimport com.squareup.okhttp.mockwebserver.MockWebServer;\nimport okio.Buffer;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.powermock.api.mockito.PowerMockito;\nimport org.powermock.core.classloader.annotations.PowerMockIgnore;\nimport org.powermock.core.classloader.annotations.PrepareForTest;\nimport org.powermock.modules.junit4.rule.PowerMockRule;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport javax.annotation.Nullable;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.zip.GZIPOutputStream;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Matchers.*;\nimport static org.mockito.Mockito.mock;\n\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\n@PowerMockIgnore({ \"org.mockito.*\", \"org.robolectric.*\", \"android.*\", \"javax.net.ssl.*\" })\n@PrepareForTest(NetworkEventReporterImpl.class)\npublic class StethoInterceptorTest {\n  @Rule\n  public PowerMockRule rule = new PowerMockRule();\n\n  private NetworkEventReporter mMockEventReporter;\n  private StethoInterceptor mInterceptor;\n  private OkHttpClient mClientWithInterceptor;\n\n  @Before\n  public void setUp() {\n    PowerMockito.mockStatic(NetworkEventReporterImpl.class);\n\n    mMockEventReporter = mock(NetworkEventReporter.class);\n    Mockito.when(mMockEventReporter.isEnabled()).thenReturn(true);\n    PowerMockito.when(NetworkEventReporterImpl.get()).thenReturn(mMockEventReporter);\n\n    mInterceptor = new StethoInterceptor();\n    mClientWithInterceptor = new OkHttpClient();\n    mClientWithInterceptor.networkInterceptors().add(mInterceptor);\n  }\n\n  @Test\n  public void testHappyPath() throws IOException {\n    InOrder inOrder = Mockito.inOrder(mMockEventReporter);\n    hookAlmostRealRequestWillBeSent(mMockEventReporter);\n    ByteArrayOutputStream capturedOutput =\n        hookAlmostRealInterpretResponseStream(mMockEventReporter);\n\n    Uri requestUri = Uri.parse(\"http://www.facebook.com/nowhere\");\n    String requestText = \"Test input\";\n    Request request = new Request.Builder()\n        .url(requestUri.toString())\n        .method(\n            \"POST\",\n            RequestBody.create(MediaType.parse(\"text/plain\"), requestText))\n        .build();\n    String originalBodyData = \"Success!\";\n    Response reply = new Response.Builder()\n        .request(request)\n        .protocol(Protocol.HTTP_1_1)\n        .code(200)\n        .body(ResponseBody.create(MediaType.parse(\"text/plain\"), originalBodyData))\n        .build();\n    Response filteredResponse =\n        mInterceptor.intercept(\n            new SimpleTestChain(request, reply, mock(Connection.class)));\n\n    inOrder.verify(mMockEventReporter).isEnabled();\n    inOrder.verify(mMockEventReporter)\n        .requestWillBeSent(any(NetworkEventReporter.InspectorRequest.class));\n    inOrder.verify(mMockEventReporter)\n        .dataSent(\n            anyString(),\n            eq(requestText.length()),\n            eq(requestText.length()));\n    inOrder.verify(mMockEventReporter)\n        .responseHeadersReceived(any(NetworkEventReporter.InspectorResponse.class));\n\n    String filteredResponseString = filteredResponse.body().string();\n    String interceptedOutput = capturedOutput.toString();\n\n    inOrder.verify(mMockEventReporter).dataReceived(anyString(), anyInt(), anyInt());\n    inOrder.verify(mMockEventReporter).responseReadFinished(anyString());\n\n    assertEquals(originalBodyData, filteredResponseString);\n    assertEquals(originalBodyData, interceptedOutput);\n\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  @Test\n  public void testWithRequestCompression() throws IOException {\n    AtomicReference<NetworkEventReporter.InspectorRequest> capturedRequest =\n        hookAlmostRealRequestWillBeSent(mMockEventReporter);\n\n    MockWebServer server = new MockWebServer();\n    server.start();\n    server.enqueue(new MockResponse()\n        .setBody(\"Success!\"));\n\n    final byte[] decompressed = \"Request text\".getBytes();\n    final byte[] compressed = compress(decompressed);\n    assertNotEquals(\n        \"Bogus test: decompressed and compressed lengths match\",\n        compressed.length, decompressed.length);\n\n    RequestBody compressedBody = RequestBody.create(\n        MediaType.parse(\"text/plain\"),\n        compress(decompressed));\n    Request request = new Request.Builder()\n        .url(server.getUrl(\"/\"))\n        .addHeader(\"Content-Encoding\", \"gzip\")\n        .post(compressedBody)\n        .build();\n    Response response = mClientWithInterceptor.newCall(request).execute();\n\n    // Force a read to complete the flow.\n    response.body().string();\n\n    assertArrayEquals(decompressed, capturedRequest.get().body());\n    Mockito.verify(mMockEventReporter)\n        .dataSent(\n            anyString(),\n            eq(decompressed.length),\n            eq(compressed.length));\n\n    server.shutdown();\n  }\n\n  @Test\n  public void testWithResponseCompression() throws IOException {\n    ByteArrayOutputStream capturedOutput = hookAlmostRealInterpretResponseStream(mMockEventReporter);\n\n    byte[] uncompressedData = repeat(\".\", 1024).getBytes();\n    byte[] compressedData = compress(uncompressedData);\n\n    MockWebServer server = new MockWebServer();\n    server.start();\n    server.enqueue(new MockResponse()\n        .setBody(new Buffer().write(compressedData))\n        .addHeader(\"Content-Encoding: gzip\"));\n\n    Request request = new Request.Builder()\n        .url(server.url(\"/\"))\n        .build();\n    Response response = mClientWithInterceptor.newCall(request).execute();\n\n    // Verify that the final output and the caller both saw the uncompressed stream.\n    assertArrayEquals(uncompressedData, response.body().bytes());\n    assertArrayEquals(uncompressedData, capturedOutput.toByteArray());\n\n    // And verify that the StethoInterceptor was able to see both.\n    Mockito.verify(mMockEventReporter)\n        .dataReceived(\n            anyString(),\n            eq(compressedData.length),\n            eq(uncompressedData.length));\n\n    server.shutdown();\n  }\n\n  private static String repeat(String s, int reps) {\n    StringBuilder b = new StringBuilder(s.length() * reps);\n    while (reps-- > 0) {\n      b.append(s);\n    }\n    return b.toString();\n  }\n\n  private static byte[] compress(byte[] data) throws IOException {\n    ByteArrayOutputStream buf = new ByteArrayOutputStream();\n    GZIPOutputStream out = new GZIPOutputStream(buf);\n    out.write(data);\n    out.close();\n    return buf.toByteArray();\n  }\n\n  private static AtomicReference<NetworkEventReporter.InspectorRequest>\n      hookAlmostRealRequestWillBeSent(\n          final NetworkEventReporter mockEventReporter) {\n    final AtomicReference<NetworkEventReporter.InspectorRequest> capturedRequest =\n        new AtomicReference<>(null);\n    Mockito.doAnswer(\n        new Answer<Void>() {\n          @Override\n          public Void answer(InvocationOnMock invocation) throws Throwable {\n            Object[] args = invocation.getArguments();\n            NetworkEventReporter.InspectorRequest request =\n                (NetworkEventReporter.InspectorRequest)args[0];\n            capturedRequest.set(request);\n\n            // Access the body, causing the body helper to perform decompression...\n            request.body();\n            return null;\n          }\n        })\n        .when(mockEventReporter)\n            .requestWillBeSent(\n                any(NetworkEventReporter.InspectorRequest.class));\n    return capturedRequest;\n  }\n\n  /**\n   * Provide a suitably \"real\" implementation of\n   * {@link NetworkEventReporter#interpretResponseStream} for our mock to test that\n   * events are properly delegated.\n   */\n  private static ByteArrayOutputStream hookAlmostRealInterpretResponseStream(\n      final NetworkEventReporter mockEventReporter) {\n    final ByteArrayOutputStream capturedOutput = new ByteArrayOutputStream();\n    Mockito.when(\n        mockEventReporter.interpretResponseStream(\n            anyString(),\n            anyString(),\n            anyString(),\n            any(InputStream.class),\n            any(ResponseHandler.class)))\n        .thenAnswer(\n            new Answer<InputStream>() {\n              @Override\n              public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {\n                Object[] args = invocationOnMock.getArguments();\n                String requestId = (String)args[0];\n                String contentEncoding = (String)args[2];\n                InputStream responseStream = (InputStream)args[3];\n                ResponseHandler responseHandler = (ResponseHandler)args[4];\n                return DecompressionHelper.teeInputWithDecompression(\n                    null /* networkPeerManager */,\n                    requestId,\n                    responseStream,\n                    capturedOutput,\n                    contentEncoding,\n                    responseHandler);\n              }\n            });\n    return capturedOutput;\n  }\n\n  private static class SimpleTestChain implements Interceptor.Chain {\n    private final Request mRequest;\n    private final Response mResponse;\n    @Nullable private final Connection mConnection;\n\n    public SimpleTestChain(Request request, Response response, @Nullable Connection connection) {\n      mRequest = request;\n      mResponse = response;\n      mConnection = connection;\n    }\n\n    @Override\n    public Request request() {\n      return mRequest;\n    }\n\n    @Override\n    public Response proceed(Request request) throws IOException {\n      if (mRequest != request) {\n        throw new IllegalArgumentException(\n            \"Expected \" + System.identityHashCode(mRequest) +\n                \"; got \" + System.identityHashCode(request));\n      }\n      return mResponse;\n    }\n\n    @Override\n    public Connection connection() {\n      return mConnection;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-okhttp3/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-okhttp3/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n        testInstrumentationRunner \"android.support.test.runner.AndroidJUnitRunner\"\n    }\n\n    lintOptions {\n        // This seems to be firing due to okio referencing java.nio.File\n        // which is harmless for us. Not sure how to disable this in\n        // more targeted fashion...\n        warning 'InvalidPackage'\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n    implementation 'com.squareup.okhttp3:okhttp:3.4.2'\n\n    testImplementation 'junit:junit:4.12'\n    testImplementation('org.robolectric:robolectric:2.4') {\n        exclude module: 'commons-logging'\n        exclude module: 'httpclient'\n    }\n    testImplementation 'org.powermock:powermock-api-mockito:1.6.6'\n    testImplementation 'org.powermock:powermock-module-junit4:1.6.6'\n\n    // Needed for Robolectric and PowerMock to be combined in a single test.\n    testImplementation 'org.powermock:powermock-module-junit4-rule:1.6.6'\n    testImplementation 'org.powermock:powermock-classloading-xstream:1.6.6'\n\n    testImplementation 'com.squareup.okhttp3:mockwebserver:3.4.2'\n}\n\napply from: rootProject.file('release.gradle')\n"
  },
  {
    "path": "stetho-okhttp3/gradle.properties",
    "content": "POM_NAME=Stetho OkHttp 3 module\nPOM_ARTIFACT_ID=stetho-okhttp3\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho-okhttp3/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho.okhttp3\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "stetho-okhttp3/src/main/java/com/facebook/stetho/okhttp3/StethoInterceptor.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.okhttp3;\n\nimport com.facebook.stetho.inspector.network.DefaultResponseHandler;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.RequestBodyHelper;\nimport okhttp3.*;\nimport okio.BufferedSink;\nimport okio.BufferedSource;\nimport okio.Okio;\n\nimport javax.annotation.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\n\n/**\n * Provides easy integration with <a href=\"http://square.github.io/okhttp/\">OkHttp</a> 3.x by way of\n * the new <a href=\"https://github.com/square/okhttp/wiki/Interceptors\">Interceptor</a> system. To\n * use:\n * <pre>\n *   OkHttpClient client = new OkHttpClient.Builder()\n *       .addNetworkInterceptor(new StethoInterceptor())\n *       .build();\n * </pre>\n */\npublic class StethoInterceptor implements Interceptor {\n  private final NetworkEventReporter mEventReporter = NetworkEventReporterImpl.get();\n\n  @Override\n  public Response intercept(Chain chain) throws IOException {\n    String requestId = mEventReporter.nextRequestId();\n\n    Request request = chain.request();\n\n    RequestBodyHelper requestBodyHelper = null;\n    if (mEventReporter.isEnabled()) {\n      requestBodyHelper = new RequestBodyHelper(mEventReporter, requestId);\n      OkHttpInspectorRequest inspectorRequest =\n          new OkHttpInspectorRequest(requestId, request, requestBodyHelper);\n      mEventReporter.requestWillBeSent(inspectorRequest);\n    }\n\n    Response response;\n    try {\n      response = chain.proceed(request);\n    } catch (IOException e) {\n      if (mEventReporter.isEnabled()) {\n        mEventReporter.httpExchangeFailed(requestId, e.toString());\n      }\n      throw e;\n    }\n\n    if (mEventReporter.isEnabled()) {\n      if (requestBodyHelper != null && requestBodyHelper.hasBody()) {\n        requestBodyHelper.reportDataSent();\n      }\n\n      Connection connection = chain.connection();\n      if (connection == null) {\n        throw new IllegalStateException(\n            \"No connection associated with this request; \" +\n                \"did you use addInterceptor instead of addNetworkInterceptor?\");\n      }\n      mEventReporter.responseHeadersReceived(\n          new OkHttpInspectorResponse(\n              requestId,\n              request,\n              response,\n              connection));\n\n      ResponseBody body = response.body();\n      MediaType contentType = null;\n      InputStream responseStream = null;\n      if (body != null) {\n        contentType = body.contentType();\n        responseStream = body.byteStream();\n      }\n\n      responseStream = mEventReporter.interpretResponseStream(\n          requestId,\n          contentType != null ? contentType.toString() : null,\n          response.header(\"Content-Encoding\"),\n          responseStream,\n          new DefaultResponseHandler(mEventReporter, requestId));\n      if (responseStream != null) {\n        response = response.newBuilder()\n            .body(new ForwardingResponseBody(body, responseStream))\n            .build();\n      }\n    }\n\n    return response;\n  }\n\n  private static class OkHttpInspectorRequest implements NetworkEventReporter.InspectorRequest {\n    private final String mRequestId;\n    private final Request mRequest;\n    private RequestBodyHelper mRequestBodyHelper;\n\n    public OkHttpInspectorRequest(\n        String requestId,\n        Request request,\n        RequestBodyHelper requestBodyHelper) {\n      mRequestId = requestId;\n      mRequest = request;\n      mRequestBodyHelper = requestBodyHelper;\n    }\n\n    @Override\n    public String id() {\n      return mRequestId;\n    }\n\n    @Override\n    public String friendlyName() {\n      // Hmm, can we do better?  tag() perhaps?\n      return null;\n    }\n\n    @Nullable\n    @Override\n    public Integer friendlyNameExtra() {\n      return null;\n    }\n\n    @Override\n    public String url() {\n      return mRequest.url().toString();\n    }\n\n    @Override\n    public String method() {\n      return mRequest.method();\n    }\n\n    @Nullable\n    @Override\n    public byte[] body() throws IOException {\n      RequestBody body = mRequest.body();\n      if (body == null) {\n        return null;\n      }\n      OutputStream out = mRequestBodyHelper.createBodySink(firstHeaderValue(\"Content-Encoding\"));\n      BufferedSink bufferedSink = Okio.buffer(Okio.sink(out));\n      try {\n        body.writeTo(bufferedSink);\n      } finally {\n        bufferedSink.close();\n      }\n      return mRequestBodyHelper.getDisplayBody();\n    }\n\n    @Override\n    public int headerCount() {\n      return mRequest.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n      return mRequest.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n      return mRequest.headers().value(index);\n    }\n\n    @Nullable\n    @Override\n    public String firstHeaderValue(String name) {\n      return mRequest.header(name);\n    }\n  }\n\n  private static class OkHttpInspectorResponse implements NetworkEventReporter.InspectorResponse {\n    private final String mRequestId;\n    private final Request mRequest;\n    private final Response mResponse;\n    private @Nullable final Connection mConnection;\n\n    public OkHttpInspectorResponse(\n        String requestId,\n        Request request,\n        Response response,\n        @Nullable Connection connection) {\n      mRequestId = requestId;\n      mRequest = request;\n      mResponse = response;\n      mConnection = connection;\n    }\n\n    @Override\n    public String requestId() {\n      return mRequestId;\n    }\n\n    @Override\n    public String url() {\n      return mRequest.url().toString();\n    }\n\n    @Override\n    public int statusCode() {\n      return mResponse.code();\n    }\n\n    @Override\n    public String reasonPhrase() {\n      return mResponse.message();\n    }\n\n    @Override\n    public boolean connectionReused() {\n      // Not sure...\n      return false;\n    }\n\n    @Override\n    public int connectionId() {\n      return mConnection == null ? 0 : mConnection.hashCode();\n    }\n\n    @Override\n    public boolean fromDiskCache() {\n      return mResponse.cacheResponse() != null;\n    }\n\n    @Override\n    public int headerCount() {\n      return mResponse.headers().size();\n    }\n\n    @Override\n    public String headerName(int index) {\n      return mResponse.headers().name(index);\n    }\n\n    @Override\n    public String headerValue(int index) {\n      return mResponse.headers().value(index);\n    }\n\n    @Nullable\n    @Override\n    public String firstHeaderValue(String name) {\n      return mResponse.header(name);\n    }\n  }\n\n  private static class ForwardingResponseBody extends ResponseBody {\n    private final ResponseBody mBody;\n    private final BufferedSource mInterceptedSource;\n\n    public ForwardingResponseBody(ResponseBody body, InputStream interceptedStream) {\n      mBody = body;\n      mInterceptedSource = Okio.buffer(Okio.source(interceptedStream));\n    }\n\n    @Override\n    public MediaType contentType() {\n      return mBody.contentType();\n    }\n\n    @Override\n    public long contentLength() {\n      return mBody.contentLength();\n    }\n\n    @Override\n    public BufferedSource source() {\n      // close on the delegating body will actually close this intercepted source, but it\n      // was derived from mBody.byteStream() therefore the close will be forwarded all the\n      // way to the original.\n      return mInterceptedSource;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-okhttp3/src/test/java/com/facebook/stetho/okhttp3/StethoInterceptorTest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.okhttp3;\n\nimport android.net.Uri;\nimport android.os.Build;\nimport com.facebook.stetho.inspector.network.DecompressionHelper;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.ResponseHandler;\nimport okhttp3.Connection;\nimport okhttp3.Interceptor;\nimport okhttp3.MediaType;\nimport okhttp3.OkHttpClient;\nimport okhttp3.Protocol;\nimport okhttp3.Request;\nimport okhttp3.RequestBody;\nimport okhttp3.Response;\nimport okhttp3.ResponseBody;\nimport okhttp3.mockwebserver.MockResponse;\nimport okhttp3.mockwebserver.MockWebServer;\nimport okio.Buffer;\nimport org.junit.Before;\nimport org.junit.Rule;\nimport org.junit.Test;\nimport org.junit.runner.RunWith;\nimport org.mockito.InOrder;\nimport org.mockito.Mockito;\nimport org.mockito.invocation.InvocationOnMock;\nimport org.mockito.stubbing.Answer;\nimport org.powermock.api.mockito.PowerMockito;\nimport org.powermock.core.classloader.annotations.PowerMockIgnore;\nimport org.powermock.core.classloader.annotations.PrepareForTest;\nimport org.powermock.modules.junit4.rule.PowerMockRule;\nimport org.robolectric.RobolectricTestRunner;\nimport org.robolectric.annotation.Config;\n\nimport javax.annotation.Nullable;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.concurrent.atomic.AtomicReference;\nimport java.util.zip.GZIPOutputStream;\n\nimport static org.junit.Assert.*;\nimport static org.mockito.Matchers.*;\nimport static org.mockito.Mockito.mock;\n\n@Config(emulateSdk = Build.VERSION_CODES.JELLY_BEAN)\n@RunWith(RobolectricTestRunner.class)\n@PowerMockIgnore({ \"org.mockito.*\", \"org.robolectric.*\", \"android.*\", \"javax.net.ssl.*\" })\n@PrepareForTest(NetworkEventReporterImpl.class)\npublic class StethoInterceptorTest {\n  @Rule\n  public PowerMockRule rule = new PowerMockRule();\n\n  private NetworkEventReporter mMockEventReporter;\n  private StethoInterceptor mInterceptor;\n  private OkHttpClient mClientWithInterceptor;\n\n  @Before\n  public void setUp() {\n    PowerMockito.mockStatic(NetworkEventReporterImpl.class);\n\n    mMockEventReporter = mock(NetworkEventReporter.class);\n    Mockito.when(mMockEventReporter.isEnabled()).thenReturn(true);\n    PowerMockito.when(NetworkEventReporterImpl.get()).thenReturn(mMockEventReporter);\n\n    mInterceptor = new StethoInterceptor();\n    mClientWithInterceptor = new OkHttpClient.Builder()\n            .addNetworkInterceptor(mInterceptor)\n            .build();\n  }\n\n  @Test\n  public void testHappyPath() throws IOException {\n    InOrder inOrder = Mockito.inOrder(mMockEventReporter);\n    hookAlmostRealRequestWillBeSent(mMockEventReporter);\n    ByteArrayOutputStream capturedOutput =\n        hookAlmostRealInterpretResponseStream(mMockEventReporter);\n\n    Uri requestUri = Uri.parse(\"http://www.facebook.com/nowhere\");\n    String requestText = \"Test input\";\n    Request request = new Request.Builder()\n        .url(requestUri.toString())\n        .method(\n            \"POST\",\n            RequestBody.create(MediaType.parse(\"text/plain\"), requestText))\n        .build();\n    String originalBodyData = \"Success!\";\n    Response reply = new Response.Builder()\n        .request(request)\n        .protocol(Protocol.HTTP_1_1)\n        .code(200)\n        .body(ResponseBody.create(MediaType.parse(\"text/plain\"), originalBodyData))\n        .build();\n    Response filteredResponse =\n        mInterceptor.intercept(\n            new SimpleTestChain(request, reply, mock(Connection.class)));\n\n    inOrder.verify(mMockEventReporter).isEnabled();\n    inOrder.verify(mMockEventReporter)\n        .requestWillBeSent(any(NetworkEventReporter.InspectorRequest.class));\n    inOrder.verify(mMockEventReporter)\n        .dataSent(\n            anyString(),\n            eq(requestText.length()),\n            eq(requestText.length()));\n    inOrder.verify(mMockEventReporter)\n        .responseHeadersReceived(any(NetworkEventReporter.InspectorResponse.class));\n\n    String filteredResponseString = filteredResponse.body().string();\n    String interceptedOutput = capturedOutput.toString();\n\n    inOrder.verify(mMockEventReporter).dataReceived(anyString(), anyInt(), anyInt());\n    inOrder.verify(mMockEventReporter).responseReadFinished(anyString());\n\n    assertEquals(originalBodyData, filteredResponseString);\n    assertEquals(originalBodyData, interceptedOutput);\n\n    inOrder.verifyNoMoreInteractions();\n  }\n\n  @Test\n  public void testWithRequestCompression() throws IOException {\n    AtomicReference<NetworkEventReporter.InspectorRequest> capturedRequest =\n        hookAlmostRealRequestWillBeSent(mMockEventReporter);\n\n    MockWebServer server = new MockWebServer();\n    server.start();\n    server.enqueue(new MockResponse()\n        .setBody(\"Success!\"));\n\n    final byte[] decompressed = \"Request text\".getBytes();\n    final byte[] compressed = compress(decompressed);\n    assertNotEquals(\n        \"Bogus test: decompressed and compressed lengths match\",\n        compressed.length, decompressed.length);\n\n    RequestBody compressedBody = RequestBody.create(\n        MediaType.parse(\"text/plain\"),\n        compress(decompressed));\n    Request request = new Request.Builder()\n        .url(server.url(\"/\"))\n        .addHeader(\"Content-Encoding\", \"gzip\")\n        .post(compressedBody)\n        .build();\n    Response response = mClientWithInterceptor.newCall(request).execute();\n\n    // Force a read to complete the flow.\n    response.body().string();\n\n    assertArrayEquals(decompressed, capturedRequest.get().body());\n    Mockito.verify(mMockEventReporter)\n        .dataSent(\n            anyString(),\n            eq(decompressed.length),\n            eq(compressed.length));\n\n    server.shutdown();\n  }\n\n  @Test\n  public void testWithResponseCompression() throws IOException {\n    ByteArrayOutputStream capturedOutput = hookAlmostRealInterpretResponseStream(mMockEventReporter);\n\n    byte[] uncompressedData = repeat(\".\", 1024).getBytes();\n    byte[] compressedData = compress(uncompressedData);\n\n    MockWebServer server = new MockWebServer();\n    server.start();\n    server.enqueue(new MockResponse()\n        .setBody(new Buffer().write(compressedData))\n        .addHeader(\"Content-Encoding: gzip\"));\n\n    Request request = new Request.Builder()\n        .url(server.url(\"/\"))\n        .build();\n    Response response = mClientWithInterceptor.newCall(request).execute();\n\n    // Verify that the final output and the caller both saw the uncompressed stream.\n    assertArrayEquals(uncompressedData, response.body().bytes());\n    assertArrayEquals(uncompressedData, capturedOutput.toByteArray());\n\n    // And verify that the StethoInterceptor was able to see both.\n    Mockito.verify(mMockEventReporter)\n        .dataReceived(\n            anyString(),\n            eq(compressedData.length),\n            eq(uncompressedData.length));\n\n    server.shutdown();\n  }\n\n  private static String repeat(String s, int reps) {\n    StringBuilder b = new StringBuilder(s.length() * reps);\n    while (reps-- > 0) {\n      b.append(s);\n    }\n    return b.toString();\n  }\n\n  private static byte[] compress(byte[] data) throws IOException {\n    ByteArrayOutputStream buf = new ByteArrayOutputStream();\n    GZIPOutputStream out = new GZIPOutputStream(buf);\n    out.write(data);\n    out.close();\n    return buf.toByteArray();\n  }\n\n  private static AtomicReference<NetworkEventReporter.InspectorRequest>\n      hookAlmostRealRequestWillBeSent(\n          final NetworkEventReporter mockEventReporter) {\n    final AtomicReference<NetworkEventReporter.InspectorRequest> capturedRequest =\n        new AtomicReference<>(null);\n    Mockito.doAnswer(\n        new Answer<Void>() {\n          @Override\n          public Void answer(InvocationOnMock invocation) throws Throwable {\n            Object[] args = invocation.getArguments();\n            NetworkEventReporter.InspectorRequest request =\n                (NetworkEventReporter.InspectorRequest)args[0];\n            capturedRequest.set(request);\n\n            // Access the body, causing the body helper to perform decompression...\n            request.body();\n            return null;\n          }\n        })\n        .when(mockEventReporter)\n            .requestWillBeSent(\n                any(NetworkEventReporter.InspectorRequest.class));\n    return capturedRequest;\n  }\n\n  /**\n   * Provide a suitably \"real\" implementation of\n   * {@link NetworkEventReporter#interpretResponseStream} for our mock to test that\n   * events are properly delegated.\n   */\n  private static ByteArrayOutputStream hookAlmostRealInterpretResponseStream(\n      final NetworkEventReporter mockEventReporter) {\n    final ByteArrayOutputStream capturedOutput = new ByteArrayOutputStream();\n    Mockito.when(\n        mockEventReporter.interpretResponseStream(\n            anyString(),\n            anyString(),\n            anyString(),\n            any(InputStream.class),\n            any(ResponseHandler.class)))\n        .thenAnswer(\n            new Answer<InputStream>() {\n              @Override\n              public InputStream answer(InvocationOnMock invocationOnMock) throws Throwable {\n                Object[] args = invocationOnMock.getArguments();\n                String requestId = (String)args[0];\n                String contentEncoding = (String)args[2];\n                InputStream responseStream = (InputStream)args[3];\n                ResponseHandler responseHandler = (ResponseHandler)args[4];\n                return DecompressionHelper.teeInputWithDecompression(\n                    null /* networkPeerManager */,\n                    requestId,\n                    responseStream,\n                    capturedOutput,\n                    contentEncoding,\n                    responseHandler);\n              }\n            });\n    return capturedOutput;\n  }\n\n  private static class SimpleTestChain implements Interceptor.Chain {\n    private final Request mRequest;\n    private final Response mResponse;\n    @Nullable private final Connection mConnection;\n\n    public SimpleTestChain(Request request, Response response, @Nullable Connection connection) {\n      mRequest = request;\n      mResponse = response;\n      mConnection = connection;\n    }\n\n    @Override\n    public Request request() {\n      return mRequest;\n    }\n\n    @Override\n    public Response proceed(Request request) throws IOException {\n      if (mRequest != request) {\n        throw new IllegalArgumentException(\n            \"Expected \" + System.identityHashCode(mRequest) +\n                \"; got \" + System.identityHashCode(request));\n      }\n      return mResponse;\n    }\n\n    @Override\n    public Connection connection() {\n      return mConnection;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-sample/build.gradle",
    "content": "repositories {\n    // See dependencies below.\n    mavenLocal()\n}\n\napply plugin: 'com.android.application'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        applicationId \"com.facebook.stetho.sample\"\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n    }\n\n    buildTypes {\n        // release target excludes Stetho for illustration purposes.\n        // See dependencies for instructions on how Stetho developers\n        // can test this.\n        release {\n            debuggable false\n            signingConfig signingConfigs.debug\n        }\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation project(':stetho-urlconnection')\n\n    // Uncomment if you wish to play with the Console evaluation\n    // features of the Rhino JS implementation.  Disabled by default\n    // because it is a large JAR by comparison to the rest of\n    // Stetho.\n    //debugCompile project(':stetho-js-rhino')\n\n    // We must use Maven dependency resolution to demonstrate our usage\n    // of optional dependencies to include stetho-urlconnection but not\n    // the full blown stetho.  For most projects, of course the simpler\n    // way to express this is:\n    //\n    //   dependencies {\n    //     debugImplementation 'com.facebook.stetho:stetho:<VERSION>'\n    //     implementation 'com.facebook.stetho:stetho-urlconnection:<VERSION>'\n    //   }\n    //\n    // For Stetho developers, to verify locally that things are working\n    // properly, you must install the latest version to ~/.m2/repository as\n    // such:\n    //\n    //   ./gradlew installArchives\n    //\n    // Then uncomment the Maven style dependency and comment the project one:\n    //releaseImplementation \"com.facebook.stetho:stetho-urlconnection:${VERSION_NAME}\"\n    releaseImplementation project(':stetho-urlconnection')\n\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n}\n"
  },
  {
    "path": "stetho-sample/src/debug/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    package=\"com.facebook.stetho.sample\">\n\n  <uses-permission android:name=\"android.permission.READ_CALENDAR\" />\n\n  <application\n      tools:replace=\"android:name\"\n      android:name=\".SampleDebugApplication\" />\n\n</manifest>\n"
  },
  {
    "path": "stetho-sample/src/debug/java/com/facebook/stetho/sample/APODDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.content.ContentResolver;\nimport android.database.Cursor;\nimport com.facebook.stetho.dumpapp.ArgsHelper;\nimport com.facebook.stetho.dumpapp.DumpException;\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\nimport java.io.PrintStream;\nimport java.util.Iterator;\n\npublic class APODDumperPlugin implements DumperPlugin {\n\n  private static final String NAME = \"apod\";\n\n  private static final String CMD_LIST = \"list\";\n  private static final String CMD_CLEAR = \"clear\";\n  private static final String CMD_DELETE = \"delete\";\n  private static final String CMD_REFRESH = \"refresh\";\n\n  private final ContentResolver mContentResolver;\n  private final APODRssFetcher mAPODRssFetcher;\n\n  public APODDumperPlugin(ContentResolver contentResolver) {\n    mContentResolver = contentResolver;\n    mAPODRssFetcher = new APODRssFetcher(mContentResolver);\n  }\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpException {\n    PrintStream writer = dumpContext.getStdout();\n    Iterator<String> argsIter = dumpContext.getArgsAsList().iterator();\n\n    String command = ArgsHelper.nextOptionalArg(argsIter, null);\n\n    if (CMD_LIST.equalsIgnoreCase(command)) {\n      doList(writer);\n    } else if (CMD_DELETE.equalsIgnoreCase(command)) {\n      doRemove(writer, argsIter);\n    } else if (CMD_CLEAR.equalsIgnoreCase(command)) {\n      doClear(writer);\n    } else if (CMD_REFRESH.equalsIgnoreCase(command)) {\n      doRefresh(writer);\n    } else {\n      usage(writer);\n      if (command != null) {\n        throw new DumpUsageException(\"Unknown command: \" + command);\n      }\n    }\n  }\n\n  private void doList(PrintStream writer) {\n    Cursor cursor = mContentResolver.query(\n        APODContract.CONTENT_URI,\n        null /* projection */,\n        null /* selection */,\n        null /* selectionArgs */,\n        APODContract.Columns._ID);\n\n    int count = 0;\n\n    while (cursor.moveToNext()) {\n      writer.println(String.format(\"Row #%d\", count++));\n      for (int i = 0; i < cursor.getColumnCount(); ++i) {\n        writer.println(String.format(\"  %s: %s\", cursor.getColumnName(i), cursor.getString(i)));\n      }\n    }\n\n    writer.println();\n  }\n\n  private void doRemove(PrintStream writer, Iterator<String> argsIter) throws DumpUsageException {\n    String rowId = ArgsHelper.nextArg(argsIter, \"Expected rowId\");\n\n    delete(writer, APODContract.Columns._ID + \"=?\", new String[] {rowId});\n  }\n\n  private void doClear(PrintStream writer) {\n    delete(writer, null, null);\n  }\n\n  private void doRefresh(PrintStream writer) {\n    mAPODRssFetcher.fetchAndStore();\n    writer.println(\"Submitted request to fetch new data\");\n  }\n\n  private void delete(PrintStream writer, String where, String[] args) {\n    int result = mContentResolver.delete(APODContract.CONTENT_URI, where, args);\n\n    writer.println(\"Removed \" + result + \" rows.\");\n  }\n\n  private static void usage(PrintStream writer) {\n    final String cmdName = \"dumpapp \" + NAME;\n    final String usagePrefix = \"Usage: \" + cmdName + \" \";\n\n    writer.println(usagePrefix + \"<command> [command-options]\");\n    writer.print(usagePrefix + CMD_LIST);\n    writer.println();\n    writer.print(usagePrefix + CMD_CLEAR);\n    writer.println();\n    writer.print(usagePrefix + CMD_DELETE + \" <rowId>\");\n    writer.println();\n    writer.print(usagePrefix + CMD_REFRESH);\n    writer.println();\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/debug/java/com/facebook/stetho/sample/HelloWorldDumperPlugin.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport java.io.BufferedReader;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.PrintStream;\nimport java.util.Iterator;\n\nimport android.text.TextUtils;\n\nimport com.facebook.stetho.dumpapp.ArgsHelper;\nimport com.facebook.stetho.dumpapp.DumpException;\nimport com.facebook.stetho.dumpapp.DumpUsageException;\nimport com.facebook.stetho.dumpapp.DumperContext;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\n\npublic class HelloWorldDumperPlugin implements DumperPlugin {\n  private static final String NAME = \"hello\";\n\n  @Override\n  public String getName() {\n    return NAME;\n  }\n\n  @Override\n  public void dump(DumperContext dumpContext) throws DumpException {\n    PrintStream writer = dumpContext.getStdout();\n    Iterator<String> args = dumpContext.getArgsAsList().iterator();\n\n    String helloToWhom = ArgsHelper.nextOptionalArg(args, null);\n    if (helloToWhom != null) {\n      doHello(dumpContext.getStdin(), writer, helloToWhom);\n    } else {\n      doUsage(writer);\n    }\n  }\n\n  private void doHello(InputStream in, PrintStream writer, String name) throws DumpException {\n    if (TextUtils.isEmpty(name)) {\n      // This will print an error to the dumpapp user and cause a non-zero exit of the\n      // script.\n      throw new DumpUsageException(\"Name is empty\");\n    } else if (\"-\".equals(name)) {\n      try {\n        name = new BufferedReader(new InputStreamReader(in)).readLine();\n      } catch (IOException e) {\n        throw new DumpException(e.toString());\n      }\n    }\n\n    writer.println(\"Hello \" + name + \"!\");\n  }\n\n  private void doUsage(PrintStream writer) {\n    writer.println(\"Usage: dumpapp \" + NAME + \" <name>\");\n    writer.println();\n    writer.println(\"If <name> is '-', the name will be read from stdin.\");\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/debug/java/com/facebook/stetho/sample/SampleDebugApplication.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.annotation.TargetApi;\nimport android.content.Context;\nimport android.os.Build;\nimport android.os.SystemClock;\nimport android.provider.CalendarContract;\nimport android.util.Log;\n\nimport com.facebook.stetho.DumperPluginsProvider;\nimport com.facebook.stetho.InspectorModulesProvider;\nimport com.facebook.stetho.Stetho;\nimport com.facebook.stetho.dumpapp.DumperPlugin;\nimport com.facebook.stetho.inspector.database.ContentProviderDatabaseDriver;\nimport com.facebook.stetho.inspector.database.ContentProviderSchema;\nimport com.facebook.stetho.inspector.database.ContentProviderSchema.Table;\nimport com.facebook.stetho.inspector.protocol.ChromeDevtoolsDomain;\n\npublic class SampleDebugApplication extends SampleApplication {\n  private static final String TAG = \"SampleDebugApplication\";\n\n  @Override\n  public void onCreate() {\n    super.onCreate();\n\n    long startTime = SystemClock.elapsedRealtime();\n    initializeStetho(this);\n    long elapsed = SystemClock.elapsedRealtime() - startTime;\n    Log.i(TAG, \"Stetho initialized in \" + elapsed + \" ms\");\n  }\n\n  private void initializeStetho(final Context context) {\n    // See also: Stetho.initializeWithDefaults(Context)\n    Stetho.initialize(Stetho.newInitializerBuilder(context)\n        .enableDumpapp(new DumperPluginsProvider() {\n          @Override\n          public Iterable<DumperPlugin> get() {\n            return new Stetho.DefaultDumperPluginsBuilder(context)\n                .provide(new HelloWorldDumperPlugin())\n                .provide(new APODDumperPlugin(context.getContentResolver()))\n                .finish();\n          }\n        })\n        .enableWebKitInspector(new ExtInspectorModulesProvider(context))\n        .build());\n  }\n\n  private static class ExtInspectorModulesProvider implements InspectorModulesProvider {\n\n    private Context mContext;\n\n    ExtInspectorModulesProvider(Context context) {\n      mContext = context;\n    }\n\n    @Override\n    public Iterable<ChromeDevtoolsDomain> get() {\n      return new Stetho.DefaultInspectorModulesBuilder(mContext)\n          .provideDatabaseDriver(createContentProviderDatabaseDriver(mContext))\n          .finish();\n    }\n\n    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)\n    private ContentProviderDatabaseDriver createContentProviderDatabaseDriver(Context context) {\n      ContentProviderSchema calendarsSchema = new ContentProviderSchema.Builder()\n          .table(new Table.Builder()\n              .uri(CalendarContract.Calendars.CONTENT_URI)\n              .projection(new String[] {\n                  CalendarContract.Calendars._ID,\n                  CalendarContract.Calendars.NAME,\n                  CalendarContract.Calendars.ACCOUNT_NAME,\n                  CalendarContract.Calendars.IS_PRIMARY,\n              })\n              .build())\n          .build();\n\n      // sample events content provider we want to support\n      ContentProviderSchema eventsSchema = new ContentProviderSchema.Builder()\n          .table(new Table.Builder()\n              .uri(CalendarContract.Events.CONTENT_URI)\n              .projection(new String[]{\n                  CalendarContract.Events._ID,\n                  CalendarContract.Events.TITLE,\n                  CalendarContract.Events.DESCRIPTION,\n                  CalendarContract.Events.ACCOUNT_NAME,\n                  CalendarContract.Events.DTSTART,\n                  CalendarContract.Events.DTEND,\n                  CalendarContract.Events.CALENDAR_ID,\n              })\n              .build())\n          .build();\n      return new ContentProviderDatabaseDriver(context, calendarsSchema, eventsSchema);\n    }\n  }\n\n}\n"
  },
  {
    "path": "stetho-sample/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho.sample\">\n\n  <uses-permission android:name=\"android.permission.INTERNET\" />\n\n  <application\n      android:label=\"@string/app_name\"\n      android:name=\".SampleApplication\">\n\n    <activity\n        android:label=\"@string/app_name\"\n        android:name=\".MainActivity\">\n      <intent-filter>\n        <action android:name=\"android.intent.action.MAIN\"/>\n        <category android:name=\"android.intent.category.LAUNCHER\"/>\n        <category android:name=\"android.intent.category.DEFAULT\"/>\n      </intent-filter>\n    </activity>\n\n    <activity\n        android:label=\"@string/settings_title\"\n        android:name=\".SettingsActivity\" />\n\n    <activity\n        android:label=\"@string/apod_title\"\n        android:name=\".APODActivity\" />\n\n    <activity\n        android:label=\"@string/irc_connect_title\"\n        android:name=\".IRCConnectActivity\" />\n\n    <activity\n        android:label=\"@string/irc_chat_title\"\n        android:name=\".IRCChatActivity\" />\n\n    <provider\n        android:name=\".APODContentProvider\"\n        android:authorities=\"com.facebook.stetho.sample.apod\"\n        android:exported=\"false\" />\n\n  </application>\n\n</manifest>\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/APODActivity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport java.io.IOException;\n\nimport android.app.ListActivity;\nimport android.app.LoaderManager;\nimport android.content.Context;\nimport android.content.CursorLoader;\nimport android.content.Intent;\nimport android.content.Loader;\nimport android.database.CharArrayBuffer;\nimport android.database.Cursor;\nimport android.graphics.Bitmap;\nimport android.graphics.BitmapFactory;\nimport android.graphics.drawable.BitmapDrawable;\nimport android.os.Bundle;\nimport android.util.Log;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.view.ViewGroup;\nimport android.widget.CursorAdapter;\nimport android.widget.ImageView;\nimport android.widget.TextView;\n\n/**\n * Simple demonstration of fetching and caching a specific RSS feed showing the\n * \"Astronomy Picture of the Day\" feed from NASA.  This demonstrates both the database access\n * and network inspection features of Stetho.\n */\npublic class APODActivity extends ListActivity {\n  private static final int LOADER_APOD_POSTS = 1;\n\n  private static final String TAG = \"APODActivity\";\n\n  private APODPostsAdapter mAdapter;\n\n  public static void show(Context context) {\n    context.startActivity(new Intent(context, APODActivity.class));\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    getLoaderManager().initLoader(LOADER_APOD_POSTS, new Bundle(), mLoaderCallback);\n\n    new APODRssFetcher(getContentResolver()).fetchAndStore();\n\n    mAdapter = new APODPostsAdapter(this);\n    setListAdapter(mAdapter);\n  }\n\n  @Override\n  protected void onDestroy() {\n    super.onDestroy();\n    getLoaderManager().destroyLoader(LOADER_APOD_POSTS);\n  }\n\n  private final LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback =\n      new LoaderManager.LoaderCallbacks<Cursor>() {\n    @Override\n    public Loader<Cursor> onCreateLoader(int id, Bundle args) {\n      switch (id) {\n        case LOADER_APOD_POSTS:\n          return APODPostsQuery.createCursorLoader(APODActivity.this);\n        default:\n          throw new IllegalArgumentException(\"id=\" + id);\n      }\n    }\n\n    @Override\n    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {\n      mAdapter.changeCursor(data);\n    }\n\n    @Override\n    public void onLoaderReset(Loader<Cursor> loader) {\n      mAdapter.changeCursor(null);\n    }\n  };\n\n  private class APODPostsAdapter extends CursorAdapter {\n    private final LayoutInflater mInflater;\n\n    public APODPostsAdapter(Context context) {\n      super(context, null /* cursor */, false /* autoRequery */);\n      mInflater = LayoutInflater.from(context);\n    }\n\n    @Override\n    public View newView(Context context, Cursor cursor, ViewGroup parent) {\n      View view = mInflater.inflate(R.layout.apod_list_item, parent, false);\n      view.setTag(new ViewHolder(view));\n      return view;\n    }\n\n    @Override\n    public void bindView(View view, Context context, Cursor cursor) {\n      ViewHolder holder = (ViewHolder)view.getTag();\n      int bindPosition = cursor.getPosition();\n      holder.position = bindPosition;\n\n      final String imageUrl = cursor.getString(APODPostsQuery.LARGE_IMAGE_URL_INDEX);\n      holder.image.setImageDrawable(null);\n      fetchImage(imageUrl, bindPosition, holder);\n\n      cursor.copyStringToBuffer(APODPostsQuery.TITLE_INDEX, holder.titleBuffer);\n\n      setTextWithBuffer(holder.title, holder.titleBuffer);\n\n      cursor.copyStringToBuffer(APODPostsQuery.DESCRIPTION_TEXT_INDEX, holder.descriptionBuffer);\n      setTextWithBuffer(holder.description, holder.descriptionBuffer);\n    }\n\n    // Really crude image handling.  Please don't do this in a real app :)\n    private void fetchImage(\n        final String imageUrl,\n        final int bindPosition,\n        final ViewHolder holder) {\n      Networker.HttpRequest imageRequest = Networker.HttpRequest.newBuilder()\n          .method(Networker.HttpMethod.GET)\n          .url(imageUrl)\n          .build();\n      Networker.get().submit(imageRequest, new Networker.Callback() {\n        @Override\n        public void onResponse(Networker.HttpResponse result) {\n          if (bindPosition == holder.position) {\n            Log.d(TAG, \"Got \" + imageUrl + \": \" + result.statusCode + \", \" + result.body.length);\n            if (result.statusCode == 200) {\n              final Bitmap bitmap =\n                  BitmapFactory.decodeByteArray(result.body, 0, result.body.length);\n              APODActivity.this.runOnUiThread(new Runnable() {\n                @Override\n                public void run() {\n                  holder.image.setImageDrawable(new BitmapDrawable(bitmap));\n                }\n              });\n            }\n          }\n        }\n\n        @Override\n        public void onFailure(IOException e) {\n          // Let Stetho demonstrate the errors :)\n        }\n      });\n    }\n  }\n\n  private static class ViewHolder {\n    public final ImageView image;\n    public final TextView title;\n    public final CharArrayBuffer titleBuffer = new CharArrayBuffer(32);\n    public final TextView description;\n    public final CharArrayBuffer descriptionBuffer = new CharArrayBuffer(64);\n\n    int position;\n\n    public ViewHolder(View v) {\n      image = (ImageView)v.findViewById(R.id.image);\n      title = (TextView)v.findViewById(R.id.title);\n      description = (TextView)v.findViewById(R.id.description);\n    }\n  }\n\n  private static void setTextWithBuffer(TextView textView, CharArrayBuffer buffer) {\n    textView.setText(buffer.data, 0, buffer.sizeCopied);\n  }\n\n  private static class APODPostsQuery {\n    public static String[] PROJECTION = {\n        APODContract.Columns._ID,\n        APODContract.Columns.TITLE,\n        APODContract.Columns.DESCRIPTION_TEXT,\n        APODContract.Columns.LARGE_IMAGE_URL,\n    };\n\n    public static final int ID_INDEX = 0;\n    public static final int TITLE_INDEX = 1;\n    public static final int DESCRIPTION_TEXT_INDEX = 2;\n    public static final int LARGE_IMAGE_URL_INDEX = 3;\n\n    public static CursorLoader createCursorLoader(Context context) {\n      return new CursorLoader(\n          context,\n          APODContract.CONTENT_URI,\n          PROJECTION,\n          null /* selection */,\n          null /* selectionArgs */,\n          null /* sortOrder */);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/APODContentProvider.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.content.ContentProvider;\nimport android.content.ContentProviderOperation;\nimport android.content.ContentProviderResult;\nimport android.content.ContentValues;\nimport android.content.Context;\nimport android.content.OperationApplicationException;\nimport android.database.Cursor;\nimport android.database.sqlite.SQLiteDatabase;\nimport android.database.sqlite.SQLiteOpenHelper;\nimport android.net.Uri;\n\nimport java.util.ArrayList;\n\npublic class APODContentProvider extends ContentProvider {\n  private APODSQLiteOpenHelper mOpenHelper;\n\n  @Override\n  public boolean onCreate() {\n    mOpenHelper = new APODSQLiteOpenHelper(getContext());\n    return true;\n  }\n\n  @Override\n  public Cursor query(\n      Uri uri,\n      String[] projection,\n      String selection,\n      String[] selectionArgs,\n      String sortOrder) {\n    SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n    Cursor cursor = db.query(\n        APODContract.TABLE_NAME,\n        projection,\n        selection,\n        selectionArgs,\n        null /* groupBy */,\n        null /* having */,\n        sortOrder,\n        null /* limit */);\n    cursor.setNotificationUri(getContext().getContentResolver(), APODContract.CONTENT_URI);\n    return cursor;\n  }\n\n  @Override\n  public String getType(Uri uri) {\n    return null;\n  }\n\n  @Override\n  public Uri insert(Uri uri, ContentValues values) {\n    SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n    long id = db.insert(APODContract.TABLE_NAME, null /* nullColumnHack */, values);\n    notifyChange();\n    return uri.buildUpon().appendEncodedPath(String.valueOf(id)).build();\n  }\n\n  @Override\n  public int delete(Uri uri, String selection, String[] selectionArgs) {\n    SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n    int count = db.delete(APODContract.TABLE_NAME, selection, selectionArgs);\n    notifyChange();\n    return count;\n  }\n\n  @Override\n  public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {\n    SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n    int count = db.update(APODContract.TABLE_NAME, values, selection, selectionArgs);\n    notifyChange();\n    return count;\n  }\n\n  @Override\n  public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations)\n      throws OperationApplicationException {\n    SQLiteDatabase db = mOpenHelper.getWritableDatabase();\n    db.beginTransaction();\n    try {\n      ContentProviderResult[] results = super.applyBatch(operations);\n      db.setTransactionSuccessful();\n      return results;\n    } finally {\n      db.endTransaction();\n      notifyChange();\n    }\n  }\n\n  private void notifyChange() {\n    getContext().getContentResolver().notifyChange(APODContract.CONTENT_URI, null /* observer */);\n  }\n\n  private static class APODSQLiteOpenHelper extends SQLiteOpenHelper {\n    private static final String DB_NAME = \"apod.db\";\n    private static final int DB_VERSION = 2;\n\n    public APODSQLiteOpenHelper(Context context) {\n      super(context, DB_NAME, null /* factory */, DB_VERSION);\n    }\n\n    @Override\n    public void onCreate(SQLiteDatabase db) {\n      db.execSQL(\n          \"CREATE TABLE \" + APODContract.TABLE_NAME + \" (\" +\n              APODContract.Columns._ID + \" INTEGER PRIMARY KEY AUTOINCREMENT, \" +\n              APODContract.Columns.TITLE + \" TEXT, \" +\n              APODContract.Columns.DESCRIPTION_IMAGE_URL + \" TEXT, \" +\n              APODContract.Columns.DESCRIPTION_TEXT + \" TEXT, \" +\n              APODContract.Columns.LARGE_IMAGE_URL + \" TEXT \" +\n              \")\");\n    }\n\n    @Override\n    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {\n      drop(db);\n      onCreate(db);\n    }\n\n    private void drop(SQLiteDatabase db) {\n      db.execSQL(\"DROP TABLE \" + APODContract.TABLE_NAME);\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/APODContract.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.net.Uri;\nimport android.provider.BaseColumns;\n\npublic interface APODContract {\n  String AUTHORITY = \"com.facebook.stetho.sample.apod\";\n  Uri CONTENT_URI = Uri.parse(\"content://\" + AUTHORITY);\n\n  String TABLE_NAME = \"rss_items\";\n\n  interface Columns extends BaseColumns {\n    String TITLE = \"title\";\n    String DESCRIPTION_TEXT = \"description_text\";\n    String DESCRIPTION_IMAGE_URL = \"description_image_url\";\n    String LARGE_IMAGE_URL = \"large_image_url\";\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/APODRssFetcher.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.content.ContentProviderOperation;\nimport android.content.ContentResolver;\nimport android.content.ContentValues;\nimport android.content.OperationApplicationException;\nimport android.net.Uri;\nimport android.os.RemoteException;\nimport android.util.Log;\nimport android.util.Xml;\nimport com.facebook.stetho.common.Utf8Charset;\nimport com.facebook.stetho.common.Util;\nimport org.xmlpull.v1.XmlPullParser;\nimport org.xmlpull.v1.XmlPullParserException;\n\nimport javax.annotation.Nullable;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.concurrent.CountDownLatch;\n\npublic class APODRssFetcher {\n  private static final String TAG = \"APODRssFetcher\";\n  \n  private static final String APOD_RSS_URL = \"https://apod.nasa.gov/apod.rss\";\n\n  private final ContentResolver mContentResolver;\n\n  public APODRssFetcher(ContentResolver contentResolver) {\n    mContentResolver = contentResolver;\n  }\n\n  public void fetchAndStore() {\n    Networker.HttpRequest request = Networker.HttpRequest.newBuilder()\n        .friendlyName(\"APOD RSS\")\n        .method(Networker.HttpMethod.GET)\n        .url(APOD_RSS_URL)\n        .build();\n    Networker.get().submit(request, mStoreRssResponse);\n  }\n\n  private final Networker.Callback mStoreRssResponse = new Networker.Callback() {\n    @Override\n    public void onResponse(Networker.HttpResponse result) {\n      if (result.statusCode == 200) {\n        try {\n          List<RssItem> rssItems = parseRss(result.body);\n          List<ApodItem> apodItems = decorateRssItemsWithLinkImages(rssItems);\n          store(apodItems);\n        } catch (XmlPullParserException e) {\n          Log.e(TAG, \"Parse error\", e);\n        } catch (OperationApplicationException e) {\n          Log.e(TAG, \"Database write error\", e);\n        } catch (RemoteException e) {\n          // Not recoverable, our process or the system_server must be dying...\n          throw new RuntimeException(e);\n        } catch (IOException e) {\n          // Reading from a byte[] shouldn't cause this...\n          throw new RuntimeException(e);\n        }\n      }\n    }\n\n    @Override\n    public void onFailure(IOException e) {\n      // Show in Stetho :)\n    }\n\n    private List<RssItem> parseRss(byte[] body) throws IOException, XmlPullParserException {\n      XmlPullParser parser = Xml.newPullParser();\n      parser.setInput(new ByteArrayInputStream(body), \"UTF-8\");\n      List<RssItem> items = new RssParser(parser).parse();\n      Log.d(TAG, \"Fetched \" + items.size() + \" items\");\n\n      return items;\n    }\n\n    public List<ApodItem> decorateRssItemsWithLinkImages(List<RssItem> rssItems) {\n      ArrayList<ApodItem> apodItems = new ArrayList<>(rssItems.size());\n      final CountDownLatch fetchLinkLatch = new CountDownLatch(rssItems.size());\n      for (RssItem rssItem : rssItems) {\n        final ApodItem apodItem = new ApodItem();\n        apodItem.rssItem = rssItem;\n        fetchLinkPage(rssItem.link, new PageScrapedCallback() {\n          @Override\n          public void onPageScraped(@Nullable List<String> imageUrls) {\n            apodItem.largeImageUrl = imageUrls != null && !imageUrls.isEmpty()\n                ? imageUrls.get(0)\n                : null;\n            fetchLinkLatch.countDown();\n          }\n        });\n        apodItems.add(apodItem);\n      }\n\n      // Wait for all link fetches to complete, despite running them in parallel...\n      Util.awaitUninterruptibly(fetchLinkLatch);\n\n      return apodItems;\n    }\n\n    private void store(List<ApodItem> items) throws RemoteException, OperationApplicationException {\n      ArrayList<ContentProviderOperation> operations = new ArrayList<ContentProviderOperation>();\n      operations.add(\n          ContentProviderOperation.newDelete(APODContract.CONTENT_URI)\n              .build());\n      for (ApodItem item : items) {\n        Log.d(TAG, \"Add item: \" + item.rssItem.title);\n        operations.add(\n            ContentProviderOperation.newInsert(APODContract.CONTENT_URI)\n                .withValues(convertItemToValues(item))\n                .build());\n      }\n\n      mContentResolver.applyBatch(APODContract.AUTHORITY, operations);\n    }\n\n    private ContentValues convertItemToValues(ApodItem item) {\n      ContentValues values = new ContentValues();\n      values.put(APODContract.Columns.TITLE, item.rssItem.title);\n\n      ArrayList<String> imageUrls = new ArrayList<>();\n      String strippedText = HtmlScraper.parseWithImageTags(\n          item.rssItem.description,\n          null /* origin */,\n          imageUrls);\n\n      // Hack to remove some strange non-printing character at the start...\n      strippedText = strippedText.substring(1).trim();\n      String imageUrl = !imageUrls.isEmpty() ? imageUrls.get(0) : null;\n\n      values.put(APODContract.Columns.DESCRIPTION_IMAGE_URL, imageUrl);\n      values.put(APODContract.Columns.DESCRIPTION_TEXT, strippedText);\n      values.put(APODContract.Columns.LARGE_IMAGE_URL, item.largeImageUrl);\n\n      return values;\n    }\n  };\n\n  private void fetchLinkPage(String linkUrl, PageScrapedCallback callback) {\n    String originUrl = getOriginUri(Uri.parse(linkUrl)).toString();\n    Networker.HttpRequest request = Networker.HttpRequest.newBuilder()\n        .friendlyName(\"fetchLinkPage\")\n        .method(Networker.HttpMethod.GET)\n        .url(linkUrl)\n        .build();\n    Networker.get().submit(request, new PageScrapeNetworkCallback(originUrl, callback));\n  }\n\n  private static Uri getOriginUri(Uri uri) {\n    Uri.Builder b = uri.buildUpon();\n    b.encodedPath(null);\n\n    List<String> segments = uri.getPathSegments();\n    for (int i = 0; i < segments.size() - 1; i++) {\n      b.appendEncodedPath(segments.get(i));\n    }\n\n    return b.build();\n  }\n\n  private static class PageScrapeNetworkCallback implements Networker.Callback {\n    @Nullable private final String mOrigin;\n    private final PageScrapedCallback mDelegate;\n\n    public PageScrapeNetworkCallback(@Nullable String origin, PageScrapedCallback delegate) {\n      mOrigin = origin;\n      mDelegate = delegate;\n    }\n\n    @Override\n    public void onResponse(Networker.HttpResponse result) {\n      ArrayList<String> imageUrls = new ArrayList<>();\n      String htmlText = Utf8Charset.decodeUTF8(result.body);\n      HtmlScraper.parseWithImageTags(htmlText, mOrigin, imageUrls);\n      mDelegate.onPageScraped(imageUrls);\n    }\n\n    @Override\n    public void onFailure(IOException e) {\n      mDelegate.onPageScraped(null /* imageUrls */);\n    }\n  }\n\n  private static class RssParser {\n    private final XmlPullParser mParser;\n\n    public RssParser(XmlPullParser parser) {\n      mParser = parser;\n    }\n\n    public List<RssItem> parse() throws IOException, XmlPullParserException {\n      ArrayList<RssItem> items = new ArrayList<RssItem>();\n\n      mParser.nextTag();\n      mParser.require(XmlPullParser.START_TAG, null, \"rss\");\n      mParser.nextTag();\n      mParser.require(XmlPullParser.START_TAG, null, \"channel\");\n\n      while (mParser.next() != XmlPullParser.END_TAG) {\n        if (mParser.getEventType() != XmlPullParser.START_TAG) {\n          continue;\n        }\n\n        String name = mParser.getName();\n        if (name.equals(\"item\")) {\n          items.add(readItem());\n        } else {\n          skip();\n        }\n      }\n\n      return items;\n    }\n\n    private RssItem readItem() throws XmlPullParserException, IOException {\n      mParser.require(XmlPullParser.START_TAG, null, \"item\");\n      RssItem item = new RssItem();\n      while (mParser.next() != XmlPullParser.END_TAG) {\n        if (mParser.getEventType() != XmlPullParser.START_TAG) {\n          continue;\n        }\n        String name = mParser.getName();\n        if (name.equals(\"title\")) {\n          item.title = readTextFromTag(\"title\");\n        } else if (name.equals(\"description\")) {\n          item.description = readTextFromTag(\"description\");\n        } else if (name.equals(\"link\")) {\n          item.link = readTextFromTag(\"link\");\n        } else {\n          skip();\n        }\n      }\n      return item;\n    }\n\n    private String readTextFromTag(String tagName) throws IOException, XmlPullParserException {\n      mParser.require(XmlPullParser.START_TAG, null, tagName);\n      String text = readText();\n      mParser.require(XmlPullParser.END_TAG, null, tagName);\n      return text;\n    }\n\n    private String readText() throws IOException, XmlPullParserException {\n      String result = \"\";\n      if (mParser.next() == XmlPullParser.TEXT) {\n        result = mParser.getText();\n        mParser.nextTag();\n      }\n      return result;\n    }\n\n    private void skip() throws IOException, XmlPullParserException {\n      if (mParser.getEventType() != XmlPullParser.START_TAG) {\n        throw new IllegalStateException();\n      }\n      int depth = 1;\n      while (depth != 0) {\n        switch (mParser.next()) {\n          case XmlPullParser.END_TAG:\n            depth--;\n            break;\n          case XmlPullParser.START_TAG:\n            depth++;\n            break;\n        }\n      }\n    }\n  }\n\n  private static class RssItem {\n    public String title;\n    public String description;\n    public String link;\n  }\n\n  private static class ApodItem {\n    public RssItem rssItem;\n    @Nullable public String largeImageUrl;\n  }\n\n  private interface PageScrapedCallback {\n    /**\n     * @param imageUrls Image URLs that were scraped or null if the page could not be fetched or\n     *     parsed.\n     */\n    public void onPageScraped(@Nullable List<String> imageUrls);\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/Constants.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\npublic class Constants {\n  public static final String TAG = \"StethoSample\";\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/HtmlScraper.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.graphics.Color;\nimport android.graphics.drawable.ColorDrawable;\nimport android.graphics.drawable.Drawable;\nimport android.net.Uri;\nimport android.text.Html;\nimport android.text.TextUtils;\n\nimport javax.annotation.Nullable;\nimport java.util.List;\n\npublic class HtmlScraper {\n  /**\n   * Scrapes an HTML page for &lt;img&gt; tags.\n   *\n   * @return Scraped plain text\n   */\n  public static String parseWithImageTags(\n      String htmlText,\n      @Nullable String originUrl,\n      List<String> outImageUrls) {\n    ExtractImageGetter imageGetter = new ExtractImageGetter(originUrl, outImageUrls);\n    String strippedText = Html.fromHtml(\n        htmlText,\n        imageGetter,\n        null /* tagHandler */)\n        .toString();\n\n    return strippedText.trim();\n  }\n\n  private static class ExtractImageGetter implements Html.ImageGetter {\n    @Nullable private final String mOriginUrl;\n    private final List<String> mSources;\n\n    public ExtractImageGetter(@Nullable String originUrl, List<String> outSources) {\n      mOriginUrl = originUrl;\n      mSources = outSources;\n    }\n\n    @Override\n    public Drawable getDrawable(String source) {\n      if (mOriginUrl != null && TextUtils.isEmpty(Uri.parse(source).getScheme())) {\n        StringBuilder newSource = new StringBuilder();\n        newSource.append(mOriginUrl);\n        if (!mOriginUrl.endsWith(\"/\") && !source.startsWith(\"/\")) {\n          newSource.append(\"/\");\n        }\n        newSource.append(source);\n        source = newSource.toString();\n      }\n      mSources.add(source);\n\n      // Dummy drawable.\n      return new ColorDrawable(Color.TRANSPARENT);\n    }\n\n    public List<String> getSources() {\n      return mSources;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/IRCChatActivity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.KeyEvent;\nimport android.view.View;\nimport android.view.inputmethod.EditorInfo;\nimport android.widget.ArrayAdapter;\nimport android.widget.ListView;\nimport android.widget.TextView;\nimport android.widget.Toast;\n\nimport java.io.IOException;\nimport java.util.Locale;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.Executors;\n\nimport javax.annotation.Nullable;\n\npublic class IRCChatActivity extends Activity {\n  private static final int DEFAULT_PORT = 6667;\n\n  private static final String EXTRA_HOST_AND_MAYBE_PORT = \"host\";\n  private static final String EXTRA_NICKNAME = \"nickname\";\n\n  private SimpleIRCConnectionManager mSimpleIRCConnectionManager;\n\n  private ExecutorService mConnectionExecutor;\n  private boolean mIsTearingDown;\n\n  private ListView mConsoleDisplay;\n  private IRCConsoleRowAdapter mConsoleRowAdapter;\n  private TextView mConsoleInput;\n\n  public static void showForResult(\n      Activity context,\n      int requestCode,\n      String hostAndMaybePort,\n      String nickname) {\n    Intent intent = new Intent(context, IRCChatActivity.class);\n    intent.putExtra(EXTRA_HOST_AND_MAYBE_PORT, hostAndMaybePort);\n    intent.putExtra(EXTRA_NICKNAME, nickname);\n    context.startActivityForResult(intent, requestCode);\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.irc_chat_activity);\n\n    mConsoleDisplay = (ListView) findViewById(R.id.console_display);\n    mConsoleRowAdapter = new IRCConsoleRowAdapter(this);\n    mConsoleDisplay.setAdapter(mConsoleRowAdapter);\n\n    mConsoleInput = (TextView) findViewById(R.id.console_input);\n    mConsoleInput.setOnEditorActionListener(mOnConsoleInputEditorAction);\n    findViewById(R.id.console_send).setOnClickListener(mConsoleSendClicked);\n\n    // Will re-enable once we connect...\n    mConsoleInput.setEnabled(false);\n\n    mSimpleIRCConnectionManager = new SimpleIRCConnectionManager(\n        getIntent().getStringExtra(EXTRA_HOST_AND_MAYBE_PORT),\n        getIntent().getStringExtra(EXTRA_NICKNAME));\n    mConnectionExecutor = Executors.newCachedThreadPool();\n    mConnectionExecutor.execute(new Runnable() {\n      @Override\n      public void run() {\n        mSimpleIRCConnectionManager.runConnectLoop();\n      }\n    });\n  }\n\n  @Override\n  protected void onDestroy() {\n    mSimpleIRCConnectionManager.shutdown();\n    mConnectionExecutor.shutdown();\n    mIsTearingDown = true;\n    super.onDestroy();\n  }\n\n  private void onConnected() {\n    mConsoleInput.setEnabled(true);\n  }\n\n  private void onIncomingMessage(String message) {\n    if (mIsTearingDown) {\n      return;\n    }\n    mConsoleRowAdapter.add(message);\n  }\n\n  private void onDisconnectOrConnectFailed(@Nullable IOException exception) {\n    if (mIsTearingDown) {\n      return;\n    }\n\n    mIsTearingDown = true;\n\n    final String error;\n    if (exception != null) {\n      Toast.makeText(\n          this,\n          \"Error: \" + exception.getMessage(),\n          Toast.LENGTH_LONG)\n          .show();\n      error = exception.getMessage();\n    } else {\n      error = null;\n    }\n    new IRCChatActivityResult(error).setResult(this);\n    finish();\n  }\n\n  private final TextView.OnEditorActionListener mOnConsoleInputEditorAction =\n      new TextView.OnEditorActionListener() {\n    @Override\n    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {\n      switch (actionId) {\n        case EditorInfo.IME_ACTION_SEND:\n          doSendMessage();\n          return true;\n        default:\n          return false;\n      }\n    }\n  };\n\n  private final View.OnClickListener mConsoleSendClicked = new View.OnClickListener() {\n    @Override\n    public void onClick(View v) {\n      doSendMessage();\n    }\n  };\n\n  private void doSendMessage() {\n    final String message = mConsoleInput.getText().toString();\n    mConsoleInput.setText(\"\");\n    if (!TextUtils.isEmpty(message)) {\n      mConnectionExecutor.execute(new Runnable() {\n        @Override\n        public void run() {\n          mSimpleIRCConnectionManager.send(message);\n        }\n      });\n    }\n  }\n\n  private class SimpleIRCConnectionManager {\n    @Nullable private volatile IRCClientConnection mConnection;\n    private volatile boolean mShutdownRequested;\n\n    private final String mHost;\n    private final int mPort;\n    private final String mNickname;\n\n    public SimpleIRCConnectionManager(String hostAndPort, String nickname) {\n      String[] hostAndPortParts = hostAndPort.split(\":\", 2);\n      if (hostAndPortParts.length == 2) {\n        mHost = hostAndPortParts[0];\n        mPort = Integer.parseInt(hostAndPortParts[1]);\n      } else {\n        mHost = hostAndPort;\n        mPort = DEFAULT_PORT;\n      }\n      mNickname = nickname;\n    }\n\n    public void runConnectLoop() {\n      boolean graceful = false;\n      try {\n        final IRCClientConnection conn = IRCClientConnection.connect(mHost, mPort);\n        try {\n          mConnection = conn;\n          doConnectLoop(conn);\n        } finally {\n          mConnection = null;\n          conn.close();\n        }\n        graceful = true;\n      } catch (IOException e) {\n        invokeOnDisconnectOrConnectFailed(e);\n      } finally {\n        if (graceful) {\n          invokeOnDisconnectOrConnectFailed(null /* exception */);\n        }\n      }\n    }\n\n    private void doConnectLoop(IRCClientConnection conn) throws IOException {\n      if (mShutdownRequested) {\n        return;\n      }\n\n      runOnUiThread(new Runnable() {\n        @Override\n        public void run() {\n          onConnected();\n        }\n      });\n\n      conn.send(String.format(Locale.US, \"NICK %s\", mNickname));\n      conn.send(String.format(Locale.US, \"USER %s %s blablabla :%s\", mNickname, mHost, mNickname));\n      while (!mShutdownRequested) {\n        final String message = conn.read();\n        if (message == null) {\n          break;\n        }\n        runOnUiThread(new Runnable() {\n          @Override\n          public void run() {\n            onIncomingMessage(message);\n          }\n        });\n      }\n    }\n\n    public void send(String message) {\n      IRCClientConnection conn = mConnection;\n      if (conn != null) {\n        try {\n          conn.send(message);\n        } catch (IOException e) {\n          invokeOnDisconnectOrConnectFailed(e);\n        }\n      }\n    }\n\n    public void shutdown() {\n      mShutdownRequested = true;\n\n      // Force a socket closure to cause an immediate effect in Stetho.\n      IRCClientConnection conn = mConnection;\n      if (conn != null) {\n        try {\n          conn.close();\n        } catch (IOException e) {\n          invokeOnDisconnectOrConnectFailed(e);\n        }\n      }\n    }\n\n    private void invokeOnDisconnectOrConnectFailed(@Nullable final IOException e) {\n      runOnUiThread(new Runnable() {\n        @Override\n        public void run() {\n          onDisconnectOrConnectFailed(e);\n        }\n      });\n    }\n  }\n\n  private static class IRCConsoleRowAdapter extends ArrayAdapter<String> {\n    public IRCConsoleRowAdapter(Context context) {\n      super(context, R.layout.irc_console_row);\n    }\n  }\n\n  public static class IRCChatActivityResult {\n    private static final String EXTRA_RESULT_CONNECT_ERROR = \"error\";\n\n    @Nullable public final String connectError;\n\n    public static IRCChatActivityResult fromResult(int resultCode, Intent data) {\n      if (resultCode == RESULT_CANCELED) {\n        return new IRCChatActivityResult(null /* connectError */);\n      } else {\n        return new IRCChatActivityResult(data.getStringExtra(EXTRA_RESULT_CONNECT_ERROR));\n      }\n    }\n\n    public IRCChatActivityResult(@Nullable String connectError) {\n      this.connectError = connectError;\n    }\n\n    public boolean wasUserDisconnect() {\n      return connectError == null;\n    }\n\n    private void setResult(Activity activity) {\n      if (wasUserDisconnect()) {\n        activity.setResult(RESULT_CANCELED);\n      } else {\n        activity.setResult(\n            RESULT_OK,\n            new Intent(activity, IRCChatActivity.class)\n                .putExtra(EXTRA_RESULT_CONNECT_ERROR, connectError));\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/IRCClientConnection.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.SimpleTextInspectorWebSocketFrame;\n\nimport java.io.BufferedReader;\nimport java.io.BufferedWriter;\nimport java.io.Closeable;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\nimport java.io.OutputStreamWriter;\nimport java.net.InetSocketAddress;\nimport java.net.Socket;\n\nimport javax.annotation.Nullable;\n\n/**\n * Simple IRC client connection system to demonstrate Stetho's \"websocket\" (any arbitrary\n * socket will work too) support.\n */\npublic class IRCClientConnection implements Closeable {\n  private final StethoReporter mReporter;\n\n  private final Socket mSocket;\n  private final BufferedReader mInput;\n  private final BufferedWriter mOutput;\n\n  public static IRCClientConnection connect(String host, int port) throws IOException {\n    StethoReporter reporter = new StethoReporter();\n    Socket socket = new Socket();\n    reporter.onPreConnect(host, port);\n    try {\n      socket.connect(new InetSocketAddress(host, port));\n      reporter.onPostConnect();\n    } catch (IOException e) {\n      reporter.onError(e);\n      try {\n        socket.close();\n        throw e;\n      } finally {\n        reporter.onClosed();\n      }\n    }\n    return new IRCClientConnection(reporter, socket, \"UTF-8\");\n  }\n\n  private IRCClientConnection(\n      StethoReporter reporter,\n      Socket socket,\n      String charset)\n      throws IOException {\n    mReporter = reporter;\n    mSocket = socket;\n    mInput = new BufferedReader(new InputStreamReader(socket.getInputStream(), charset));\n    mOutput = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(), charset));\n  }\n\n  @Nullable\n  public String read() throws IOException {\n    try {\n      String message = mInput.readLine();\n      if (message != null) {\n        mReporter.onReceive(message);\n        maybeHandleIncomingMessage(message);\n      }\n      return message;\n    } catch (IOException e) {\n      mReporter.onError(e);\n      throw e;\n    }\n  }\n\n  public void send(String message) throws IOException {\n    mReporter.onSend(message);\n    try {\n      mOutput.write(message + \"\\r\\n\");\n      mOutput.flush();\n    } catch (IOException e) {\n      mReporter.onError(e);\n      throw e;\n    }\n  }\n\n  private boolean maybeHandleIncomingMessage(String message) throws IOException {\n    if (message.startsWith(\"PING \")) {\n      send(\"PONG \" + message.substring(\"PING \".length()));\n      return true;\n    }\n    return false;\n  }\n\n  public void close() throws IOException {\n    try {\n      try {\n        mOutput.close();\n      } catch (IOException e) {\n        mReporter.onError(e);\n        throw e;\n      }\n    } finally {\n      try {\n        mSocket.close();\n      } catch (IOException e) {\n        mReporter.onError(e);\n\n        // Technically this should use addSuppressed but it's KITKAT only and this is a demo...\n        throw e;\n      } finally {\n        mReporter.onClosed();\n      }\n    }\n  }\n\n  private static class StethoReporter {\n    private final NetworkEventReporter mReporter;\n    private final String mRequestId;\n\n    public StethoReporter() {\n      mReporter = NetworkEventReporterImpl.get();\n      mRequestId = mReporter.nextRequestId();\n    }\n\n    public void onPreConnect(String host, int port) {\n      mReporter.webSocketCreated(mRequestId, \"irc://\" + host + \":\" + port);\n    }\n\n    public void onPostConnect() {\n      // Sadly, nothing to report...\n    }\n\n    public void onError(IOException e) {\n      mReporter.webSocketFrameError(mRequestId, e.getMessage());\n    }\n\n    public void onClosed() {\n      mReporter.webSocketClosed(mRequestId);\n    }\n\n    public void onSend(String message) {\n      mReporter.webSocketFrameSent(new SimpleTextInspectorWebSocketFrame(mRequestId, message));\n    }\n\n    public void onReceive(String message) {\n      mReporter.webSocketFrameReceived(new SimpleTextInspectorWebSocketFrame(mRequestId, message));\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/IRCConnectActivity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.app.Activity;\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.text.TextUtils;\nimport android.view.View;\nimport android.widget.EditText;\nimport android.widget.TextView;\n\nimport com.facebook.stetho.sample.IRCChatActivity.IRCChatActivityResult;\n\nimport java.util.Random;\n\npublic class IRCConnectActivity extends Activity {\n  private static final String DEFAULT_HOST = \"irc.freenode.net\";\n\n  private static final int REQUEST_CODE_CHAT = 1;\n\n  private TextView mIRCPriorError;\n  private EditText mIRCServer;\n  private EditText mIRCNickname;\n\n  public static void show(Context context) {\n    Intent intent = new Intent(context, IRCConnectActivity.class);\n    context.startActivity(intent);\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.irc_connect_activity);\n\n    mIRCPriorError = (TextView) findViewById(R.id.irc_prior_error);\n    mIRCServer = (EditText) findViewById(R.id.irc_server);\n    if (TextUtils.isEmpty(mIRCServer.getText())) {\n      mIRCServer.setText(DEFAULT_HOST);\n    }\n    mIRCNickname = (EditText) findViewById(R.id.irc_nickname);\n    if (TextUtils.isEmpty(mIRCNickname.getText())) {\n      mIRCNickname.setText(\"stetho\" + (new Random().nextInt(9999) + 1));\n    }\n\n    findViewById(R.id.irc_connect).setOnClickListener(mConnectClicked);\n  }\n\n  @Override\n  protected void onActivityResult(int requestCode, int resultCode, Intent data) {\n    switch (requestCode) {\n      case REQUEST_CODE_CHAT:\n        IRCChatActivityResult parsedResult =\n            IRCChatActivityResult.fromResult(resultCode, data);\n        if (parsedResult.wasUserDisconnect()) {\n          mIRCPriorError.setText(\"\");\n          mIRCPriorError.setVisibility(View.GONE);\n        } else {\n          mIRCPriorError.setText(\"ERROR: \" + parsedResult.connectError);\n          mIRCPriorError.setVisibility(View.VISIBLE);\n        }\n        break;\n      default:\n        throw new IllegalArgumentException(\"Unknown requestCode=\" + requestCode);\n    }\n  }\n\n  private final View.OnClickListener mConnectClicked = new View.OnClickListener() {\n    @Override\n    public void onClick(View v) {\n      IRCChatActivity.showForResult(\n          IRCConnectActivity.this,\n          REQUEST_CODE_CHAT,\n          mIRCServer.getText().toString(),\n          mIRCNickname.getText().toString());\n    }\n  };\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/MainActivity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.app.Activity;\nimport android.app.Dialog;\nimport android.content.SharedPreferences;\nimport android.os.Bundle;\nimport android.preference.PreferenceManager;\nimport android.view.LayoutInflater;\nimport android.view.View;\nimport android.widget.Toast;\n\npublic class MainActivity extends Activity {\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n    setContentView(R.layout.main_activity);\n\n    // Demonstrate that it is removed from the release build...\n    if (!isStethoPresent()) {\n      Toast.makeText(\n          this,\n          getString(R.string.stetho_missing, BuildConfig.BUILD_TYPE),\n          Toast.LENGTH_LONG)\n          .show();\n    }\n\n    findViewById(R.id.settings_btn).setOnClickListener(mMainButtonClicked);\n    findViewById(R.id.apod_btn).setOnClickListener(mMainButtonClicked);\n    findViewById(R.id.irc_btn).setOnClickListener(mMainButtonClicked);\n    findViewById(R.id.about).setOnClickListener(mMainButtonClicked);\n  }\n\n  private static boolean isStethoPresent() {\n    try {\n      Class.forName(\"com.facebook.stetho.Stetho\");\n      return true;\n    } catch (ClassNotFoundException e) {\n      return false;\n    }\n  }\n\n  @Override\n  protected void onResume() {\n    super.onResume();\n    getPrefs().registerOnSharedPreferenceChangeListener(mToastingPrefListener);\n  }\n\n  @Override\n  protected void onPause() {\n    super.onPause();\n    getPrefs().unregisterOnSharedPreferenceChangeListener(mToastingPrefListener);\n  }\n\n  private SharedPreferences getPrefs() {\n    return PreferenceManager.getDefaultSharedPreferences(this /* context */);\n  }\n\n  private final View.OnClickListener mMainButtonClicked = new View.OnClickListener() {\n    @Override\n    public void onClick(View v) {\n      int id = v.getId();\n      if (id == R.id.settings_btn) {\n        SettingsActivity.show(MainActivity.this);\n      } else if (id == R.id.apod_btn) {\n        APODActivity.show(MainActivity.this);\n      } else if (id == R.id.irc_btn) {\n        IRCConnectActivity.show(MainActivity.this);\n      } else if (id == R.id.about) {\n        View view = LayoutInflater.from(MainActivity.this).inflate(R.layout.dialog_layout, null);\n        Dialog dialog = new Dialog(MainActivity.this);\n        dialog.setContentView(view);\n        dialog.setTitle(getString(R.string.app_name));\n        dialog.show();\n      }\n    }\n  };\n\n  private final SharedPreferences.OnSharedPreferenceChangeListener mToastingPrefListener =\n      new SharedPreferences.OnSharedPreferenceChangeListener() {\n    @Override\n    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {\n      Object value = sharedPreferences.getAll().get(key);\n      Toast.makeText(\n          MainActivity.this,\n          getString(R.string.pref_change_message, key, value),\n          Toast.LENGTH_SHORT).show();\n    }\n  };\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/Networker.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport com.facebook.stetho.urlconnection.ByteArrayRequestEntity;\nimport com.facebook.stetho.urlconnection.SimpleRequestEntity;\nimport com.facebook.stetho.urlconnection.StethoURLConnectionManager;\n\nimport javax.annotation.Nullable;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.util.concurrent.Executor;\nimport java.util.concurrent.Executors;\nimport java.util.zip.GZIPInputStream;\n\n/**\n * Very simple centralized network middleware for illustration purposes.\n */\npublic class Networker {\n  private static Networker sInstance;\n\n  private final Executor sExecutor = Executors.newFixedThreadPool(4);\n\n  private static final int READ_TIMEOUT_MS = 10000;\n  private static final int CONNECT_TIMEOUT_MS = 15000;\n\n  private static final String HEADER_ACCEPT_ENCODING = \"Accept-Encoding\";\n  private static final String GZIP_ENCODING = \"gzip\";\n\n  public static synchronized Networker get() {\n    if (sInstance == null) {\n      sInstance = new Networker();\n    }\n    return sInstance;\n  }\n\n  private Networker() {\n  }\n\n  public void submit(HttpRequest request, Callback callback) {\n    sExecutor.execute(new HttpRequestTask(request, callback));\n  }\n\n  private class HttpRequestTask implements Runnable {\n    private final HttpRequest request;\n    private final Callback callback;\n    private final StethoURLConnectionManager stethoManager;\n\n    public HttpRequestTask(HttpRequest request, Callback callback) {\n      this.request = request;\n      this.callback = callback;\n      stethoManager = new StethoURLConnectionManager(request.friendlyName);\n    }\n\n    @Override\n    public void run() {\n      try {\n        HttpResponse response = doFetch();\n        callback.onResponse(response);\n      } catch (IOException e) {\n        callback.onFailure(e);\n      }\n    }\n\n    private HttpResponse doFetch() throws IOException {\n      HttpURLConnection conn = configureAndConnectRequest();\n      try {\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        InputStream rawStream = conn.getInputStream();\n        try {\n          // Let Stetho see the raw, possibly compressed stream.\n          rawStream = stethoManager.interpretResponseStream(rawStream);\n          InputStream decompressedStream = applyDecompressionIfApplicable(conn, rawStream);\n          if (decompressedStream != null) {\n            copy(decompressedStream, out, new byte[1024]);\n          }\n        } finally {\n          if (rawStream != null) {\n            rawStream.close();\n          }\n        }\n        return new HttpResponse(conn.getResponseCode(), out.toByteArray());\n      } finally {\n        conn.disconnect();\n      }\n    }\n\n    private HttpURLConnection configureAndConnectRequest() throws IOException {\n      URL url = new URL(request.url);\n\n      // Note that this does not actually create a new connection so it is appropriate to\n      // defer preConnect until after the HttpURLConnection instance is configured.  Do not\n      // invoke connect, conn.getInputStream, conn.getOutputStream, etc before calling\n      // preConnect!\n      HttpURLConnection conn = (HttpURLConnection)url.openConnection();\n      try {\n        conn.setReadTimeout(READ_TIMEOUT_MS);\n        conn.setConnectTimeout(CONNECT_TIMEOUT_MS);\n        conn.setRequestMethod(request.method.toString());\n\n        // Adding this disables transparent gzip compression so that we can intercept\n        // the raw stream and display the correct response body size.\n        requestDecompression(conn);\n\n        SimpleRequestEntity requestEntity = null;\n        if (request.body != null) {\n          requestEntity = new ByteArrayRequestEntity(request.body);\n        }\n\n        stethoManager.preConnect(conn, requestEntity);\n        try {\n          if (request.method == HttpMethod.POST) {\n            if (requestEntity == null) {\n              throw new IllegalStateException(\"POST requires an entity\");\n            }\n            conn.setDoOutput(true);\n\n            requestEntity.writeTo(conn.getOutputStream());\n          }\n\n          // Ensure that we are connected after this point.  Note that getOutputStream above will\n          // also connect and exchange HTTP messages.\n          conn.connect();\n\n          stethoManager.postConnect();\n\n          return conn;\n        } catch (IOException inner) {\n          // This must only be called after preConnect.  Failures before that cannot be\n          // represented since the request has not yet begun according to Stetho.\n          stethoManager.httpExchangeFailed(inner);\n          throw inner;\n        }\n      } catch (IOException outer) {\n        conn.disconnect();\n        throw outer;\n      }\n    }\n  }\n\n  private static void requestDecompression(HttpURLConnection conn) {\n    conn.setRequestProperty(HEADER_ACCEPT_ENCODING, GZIP_ENCODING);\n  }\n\n  @Nullable\n  private static InputStream applyDecompressionIfApplicable(\n      HttpURLConnection conn, @Nullable InputStream in) throws IOException {\n    if (in != null && GZIP_ENCODING.equals(conn.getContentEncoding())) {\n      return new GZIPInputStream(in);\n    }\n    return in;\n  }\n\n  private static void copy(InputStream in, OutputStream out, byte[] buf) throws IOException {\n    if (in == null) {\n      return;\n    }\n    int n;\n    while ((n = in.read(buf)) != -1) {\n      out.write(buf, 0, n);\n    }\n  }\n\n  public static class HttpRequest {\n    public final String friendlyName;\n    public final HttpMethod method;\n    public final String url;\n    public final byte[] body;\n\n    public static Builder newBuilder() {\n      return new Builder();\n    }\n\n    HttpRequest(Builder b) {\n      if (b.method == HttpMethod.POST) {\n        if (b.body == null) {\n          throw new IllegalArgumentException(\"POST must have a body\");\n        }\n      } else if (b.method == HttpMethod.GET) {\n        if (b.body != null) {\n          throw new IllegalArgumentException(\"GET cannot have a body\");\n        }\n      }\n      this.friendlyName = b.friendlyName;\n      this.method = b.method;\n      this.url = b.url;\n      this.body = b.body;\n    }\n\n    public static class Builder {\n      private String friendlyName;\n      private Networker.HttpMethod method;\n      private String url;\n      private byte[] body = null;\n\n      Builder() {\n      }\n\n      public Builder friendlyName(String friendlyName) {\n        this.friendlyName = friendlyName;\n        return this;\n      }\n\n      public Builder method(Networker.HttpMethod method) {\n        this.method = method;\n        return this;\n      }\n\n      public Builder url(String url) {\n        this.url = url;\n        return this;\n      }\n\n      public Builder body(byte[] body) {\n        this.body = body;\n        return this;\n      }\n\n      public HttpRequest build() {\n        return new HttpRequest(this);\n      }\n    }\n  }\n\n  public static enum HttpMethod {\n    GET, POST\n  }\n\n  public static class HttpResponse {\n    public final int statusCode;\n    public final byte[] body;\n\n    HttpResponse(int statusCode, byte[] body) {\n      this.statusCode = statusCode;\n      this.body = body;\n    }\n  }\n\n  public interface Callback {\n    public void onResponse(HttpResponse result);\n    public void onFailure(IOException e);\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/SampleApplication.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.app.Application;\n\npublic class SampleApplication extends Application {\n  @Override\n  public void onCreate() {\n    super.onCreate();\n\n    // Your normal application code here.  See SampleDebugApplication for Stetho initialization.\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/java/com/facebook/stetho/sample/SettingsActivity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.sample;\n\nimport android.content.Context;\nimport android.content.Intent;\nimport android.os.Bundle;\nimport android.preference.PreferenceActivity;\n\npublic class SettingsActivity extends PreferenceActivity {\n  public static void show(Context context) {\n    context.startActivity(new Intent(context, SettingsActivity.class));\n  }\n\n  @Override\n  protected void onCreate(Bundle savedInstanceState) {\n    super.onCreate(savedInstanceState);\n\n    // Trying to avoid a dependency on the support library and go all the way back to Gingerbread,\n    // so we can't rely on the fragment-based preferences and must use the old deprecated methods.\n    addPreferencesFromResource(R.xml.settings);\n  }\n}\n"
  },
  {
    "path": "stetho-sample/src/main/res/layout/apod_list_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"15dp\">\n\n  <ImageView\n      android:id=\"@+id/image\"\n      android:layout_width=\"@dimen/apod_image_width\"\n      android:layout_height=\"@dimen/apod_image_height\"\n      android:scaleType=\"centerCrop\"\n      />\n\n  <LinearLayout\n      android:orientation=\"vertical\"\n      android:layout_width=\"0px\"\n      android:layout_height=\"wrap_content\"\n      android:layout_weight=\"1\"\n      android:layout_gravity=\"center_vertical\"\n      android:layout_marginLeft=\"10dp\">\n\n      <TextView\n          android:id=\"@+id/title\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          style=\"?android:attr/textAppearanceMedium\"\n          android:maxLines=\"1\"\n          android:layout_marginBottom=\"10dp\"\n          />\n\n      <TextView\n          android:id=\"@+id/description\"\n          android:layout_width=\"match_parent\"\n          android:layout_height=\"wrap_content\"\n          style=\"?android:attr/textAppearanceSmall\"\n          android:maxLines=\"1\"\n          />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "stetho-sample/src/main/res/layout/dialog_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\"\n    android:padding=\"@dimen/dialog_padding\">\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/about_info_title\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"@dimen/dialog_padding\"\n        android:text=\"@string/about_info_content\" />\n\n\n</LinearLayout>"
  },
  {
    "path": "stetho-sample/src/main/res/layout/irc_chat_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"15dp\">\n\n  <ListView\n      android:id=\"@+id/console_display\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"0px\"\n      android:layout_weight=\"1\"\n      android:layout_marginBottom=\"10dp\"\n      android:transcriptMode=\"normal\"\n      />\n\n  <LinearLayout\n      android:orientation=\"horizontal\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\">\n\n    <EditText\n        android:id=\"@+id/console_input\"\n        android:layout_width=\"0px\"\n        android:layout_weight=\"1\"\n        android:layout_height=\"wrap_content\"\n        android:typeface=\"monospace\"\n        android:inputType=\"textAutoCorrect\"\n        android:imeOptions=\"actionSend\"\n        android:maxLines=\"1\">\n\n      <requestFocus />\n\n    </EditText>\n\n    <Button\n        android:id=\"@+id/console_send\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/send\"\n        />\n\n  </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "stetho-sample/src/main/res/layout/irc_connect_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"15dp\"\n    android:gravity=\"center_vertical\">\n\n  <TextView\n      android:id=\"@+id/irc_prior_error\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:visibility=\"gone\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/irc_server\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <EditText\n      android:id=\"@+id/irc_server\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:inputType=\"text\"\n      android:maxLines=\"1\"\n      android:imeOptions=\"actionNext\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <TextView\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:text=\"@string/irc_nickname\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <EditText\n      android:id=\"@+id/irc_nickname\"\n      android:layout_width=\"match_parent\"\n      android:layout_height=\"wrap_content\"\n      android:inputType=\"text\"\n      android:maxLines=\"1\"\n      android:imeOptions=\"actionGo\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <Button\n      android:id=\"@+id/irc_connect\"\n      android:layout_width=\"wrap_content\"\n      android:layout_height=\"wrap_content\"\n      android:layout_gravity=\"center_horizontal\"\n      android:text=\"@string/irc_connect\"\n      />\n\n</LinearLayout>"
  },
  {
    "path": "stetho-sample/src/main/res/layout/irc_console_row.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:typeface=\"monospace\"\n    />"
  },
  {
    "path": "stetho-sample/src/main/res/layout/main_activity.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:padding=\"15dp\">\n\n  <Button\n      android:id=\"@+id/settings_btn\"\n      android:text=\"@string/settings_btn\"\n      android:layout_width=\"@dimen/main_button_width\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <Button\n      android:id=\"@+id/apod_btn\"\n      android:text=\"@string/apod_btn\"\n      android:layout_width=\"@dimen/main_button_width\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <Button\n      android:id=\"@+id/irc_btn\"\n      android:text=\"@string/irc_btn\"\n      android:layout_width=\"@dimen/main_button_width\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n  <Button\n      android:id=\"@+id/about\"\n      android:text=\"@string/about\"\n      android:layout_width=\"@dimen/main_button_width\"\n      android:layout_height=\"wrap_content\"\n      android:layout_marginBottom=\"10dp\"\n      />\n\n</LinearLayout>\n"
  },
  {
    "path": "stetho-sample/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n  <array name=\"pref_list_values\">\n    <item>foo</item>\n    <item>bar</item>\n    <item>baz</item>\n  </array>\n  <array name=\"pref_list_keys\">\n    <item>Foo</item>\n    <item>Bar</item>\n    <item>Baz</item>\n  </array>\n</resources>\n\n"
  },
  {
    "path": "stetho-sample/src/main/res/values/dimens.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n  <dimen name=\"main_button_width\">150dp</dimen>\n  <dimen name=\"apod_image_width\">90dp</dimen>\n  <dimen name=\"apod_image_height\">90dp</dimen>\n  <dimen name=\"dialog_padding\">20dp</dimen>\n</resources>\n"
  },
  {
    "path": "stetho-sample/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<resources>\n  <string name=\"app_name\">Stetho Sample</string>\n  <string name=\"settings_btn\">Settings</string>\n  <string name=\"apod_btn\">APOD RSS Feed</string>\n  <string name=\"irc_btn\">IRC Client</string>\n  <string name=\"settings_title\">Settings</string>\n  <string name=\"pref_category1_title\">General</string>\n  <string name=\"pref_checkbox_title\">Boolean setting</string>\n  <string name=\"pref_checkbox_summary\">Set the value of this boolean property</string>\n  <string name=\"pref_category2_title\">Other Options</string>\n  <string name=\"pref_list_title\">List of choices</string>\n  <string name=\"pref_list_summary\">Choose from a set of values</string>\n  <string name=\"pref_list_dialog_title\">Choices</string>\n  <string name=\"pref_change_message\">%1$s is now \\'%2$s\\'</string>\n  <string name=\"apod_title\">APOD Listing</string>\n  <string name=\"irc_connect_title\">IRC Connect</string>\n  <string name=\"irc_chat_title\">IRC Chat</string>\n  <string name=\"stetho_missing\">Stetho missing in %s build!</string>\n  <string name=\"send\">Send</string>\n  <string name=\"irc_connect\">Connect</string>\n  <string name=\"irc_nickname\">Nickname:</string>\n  <string name=\"irc_server\">IRC Server:</string>\n  <string name=\"about\">About</string>\n  <string name=\"about_info_title\">A debug bridge for Android applications</string>\n  <string name=\"about_info_content\">you can view window View Hierarchy now !</string>\n</resources>\n"
  },
  {
    "path": "stetho-sample/src/main/res/xml/settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n  <PreferenceCategory android:title=\"@string/pref_category1_title\">\n\n    <CheckBoxPreference\n        android:key=\"checkbox_preference\"\n        android:title=\"@string/pref_checkbox_title\"\n        android:summary=\"@string/pref_checkbox_summary\"\n        />\n\n  </PreferenceCategory>\n\n  <PreferenceCategory android:title=\"@string/pref_category2_title\">\n\n    <ListPreference\n        android:key=\"list_preference\"\n        android:title=\"@string/pref_list_title\"\n        android:summary=\"@string/pref_list_summary\"\n        android:entries=\"@array/pref_list_keys\"\n        android:entryValues=\"@array/pref_list_values\"\n        android:dialogTitle=\"@string/pref_list_dialog_title\"\n        />\n\n  </PreferenceCategory>\n\n</PreferenceScreen>\n"
  },
  {
    "path": "stetho-timber/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-timber/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation 'com.jakewharton.timber:timber:4.1.2'\n}\n\napply from: rootProject.file('release.gradle')\n"
  },
  {
    "path": "stetho-timber/gradle.properties",
    "content": "#\n# Copyright (c) Facebook, Inc. and its affiliates.\n#\n# This source code is licensed under the MIT license found in the\n# LICENSE file in the root directory of this source tree.\n#\n\nPOM_NAME=Stetho Timber module\nPOM_ARTIFACT_ID=stetho-timber\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho-timber/src/main/AndroidManifest.xml",
    "content": "<!--\n  ~ Copyright (c) Facebook, Inc. and its affiliates.\n  ~\n  ~ This source code is licensed under the MIT license found in the\n  ~ LICENSE file in the root directory of this source tree.\n  -->\n\n<manifest\n        xmlns:tools=\"http://schemas.android.com/tools\"\n        package=\"com.facebook.stetho.timber\">\n\n    //fixes Timber Library min sdk issue\n    <uses-sdk tools:overrideLibrary=\"timber.log\"/>\n\n    <application/>\n\n</manifest>\n"
  },
  {
    "path": "stetho-timber/src/main/java/com/facebook/stetho/timber/StethoTree.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.timber;\n\nimport android.util.Log;\nimport com.facebook.stetho.inspector.console.CLog;\nimport com.facebook.stetho.inspector.console.ConsolePeerManager;\nimport com.facebook.stetho.inspector.protocol.module.Console;\nimport timber.log.Timber;\n\n/**\n * Timber tree implementation which forwards logs to the Chrome Dev console.\n * Plant it using {@link Timber#plant(Timber.Tree)}\n * <pre>\n *   {@code\n *   Timber.plant(new StethoTree())\n *   }\n * </pre>\n */\npublic class StethoTree extends Timber.Tree {\n  @Override\n  protected void log(int priority, String tag, String message, Throwable t) {\n\n    ConsolePeerManager peerManager = ConsolePeerManager.getInstanceOrNull();\n    if (peerManager == null) {\n      return;\n    }\n\n    Console.MessageLevel logLevel;\n\n    switch (priority) {\n      case Log.VERBOSE:\n      case Log.DEBUG:\n        logLevel = Console.MessageLevel.DEBUG;\n        break;\n      case Log.INFO:\n        logLevel = Console.MessageLevel.LOG;\n        break;\n      case Log.WARN:\n        logLevel = Console.MessageLevel.WARNING;\n        break;\n      case Log.ERROR:\n      case Log.ASSERT:\n        logLevel = Console.MessageLevel.ERROR;\n        break;\n      default:\n        logLevel = Console.MessageLevel.LOG;\n    }\n\n    CLog.writeToConsole(\n        logLevel,\n        Console.MessageSource.OTHER,\n        message\n    );\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/.gitignore",
    "content": "/build\n"
  },
  {
    "path": "stetho-urlconnection/build.gradle",
    "content": "apply plugin: 'com.android.library'\n\nandroid {\n    compileSdkVersion rootProject.ext.compileSdkVersion\n\n    defaultConfig {\n        minSdkVersion 14\n        targetSdkVersion rootProject.ext.targetSdkVersion\n        versionCode 1\n        versionName \"1.0\"\n    }\n}\n\ndependencies {\n    implementation project(':stetho')\n    implementation 'com.google.code.findbugs:jsr305:3.0.2'\n}\n\napply from: rootProject.file('release.gradle')\n"
  },
  {
    "path": "stetho-urlconnection/gradle.properties",
    "content": "POM_NAME=Stetho HttpURLConnection module\nPOM_ARTIFACT_ID=stetho-urlconnection\nPOM_OPTIONAL_DEPS=com.facebook.stetho:stetho\nPOM_PACKAGING=aar\n"
  },
  {
    "path": "stetho-urlconnection/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    package=\"com.facebook.stetho.urlconnection\">\n\n    <application />\n\n</manifest>\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/ByteArrayRequestEntity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\npublic class ByteArrayRequestEntity implements SimpleRequestEntity {\n  private final byte[] mData;\n\n  public ByteArrayRequestEntity(byte[] data) {\n    mData = data;\n  }\n\n  @Override\n  public void writeTo(OutputStream out) throws IOException {\n    out.write(mData);\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/SimpleRequestEntity.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * Narrow alternative to Apache's HttpEntity which makes it easier to repeat the body\n * so that Stetho can intercept it.  This simplification makes it possible to avoid\n * also using an intercepted stream for POST bodies as we do with responses.\n */\npublic interface SimpleRequestEntity {\n  void writeTo(OutputStream out) throws IOException;\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/StethoURLConnectionManager.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport javax.annotation.Nullable;\nimport javax.annotation.concurrent.NotThreadSafe;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\n\n/**\n * Individual connection flow manager that aids in communicating network events to Stetho\n * via the {@link com.facebook.stetho.inspector.network.NetworkEventReporter} API.  This class is\n * stateful and should be instantiated for each individual HTTP request.\n * <p>\n * Be aware that there are caveats with inspection using {@link HttpURLConnection} on Android:\n * <ul>\n * <li>Compressed payload sizes are typically not available, even when compression was in use over\n * the wire.\n * <li>Redirects are by default handled internally, making it impossible to visualize them.\n * To visualize them, redirects must be handled manually by invoking\n * {@link HttpURLConnection#setFollowRedirects(boolean)}.\n * </ul>\n */\n@NotThreadSafe\npublic class StethoURLConnectionManager {\n  private static final boolean sIsStethoPresent;\n\n  static {\n    boolean isStethoPresent = false;\n    try {\n      Class.forName(\"com.facebook.stetho.Stetho\");\n      isStethoPresent = true;\n    } catch (ClassNotFoundException e) {\n    }\n    sIsStethoPresent = isStethoPresent;\n  }\n\n  @Nullable\n  private final Holder mHolder;\n\n  // Holder hides StethoURLConnectionManagerImpl from the class verifier as per:\n  // http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom\n  private static class Holder {\n    private final StethoURLConnectionManagerImpl impl;\n\n    public Holder(@Nullable String friendlyName) {\n      impl = new StethoURLConnectionManagerImpl(friendlyName);\n    }\n  }\n\n  public StethoURLConnectionManager(@Nullable String friendlyName) {\n    if (sIsStethoPresent) {\n      mHolder = new Holder(friendlyName);\n    } else {\n      mHolder = null;\n    }\n  }\n\n  public boolean isStethoEnabled() {\n    return mHolder != null && mHolder.impl.isStethoActive();\n  }\n\n  /**\n   * Indicates that the {@link HttpURLConnection} instance has been configured and is about\n   * to be used to initiate an actual HTTP connection.  Call this method before any of the\n   * active methods such as {@link HttpURLConnection#connect()},\n   * {@link HttpURLConnection#getInputStream()}, or {@link HttpURLConnection#getOutputStream()}\n   *\n   * @param connection Connection instance configured with a method and headers.\n   * @param requestEntity Represents the request body if the request method supports it.\n   */\n  public void preConnect(\n      HttpURLConnection connection,\n      @Nullable SimpleRequestEntity requestEntity) {\n    if (mHolder != null) {\n      mHolder.impl.preConnect(connection, requestEntity);\n    }\n  }\n\n  /**\n   * Indicates that the {@link HttpURLConnection} has just successfully exchanged HTTP messages\n   * (request headers + body and response headers) with the server but has not yet consumed\n   * the response body.\n   *\n   * @throws IOException May throw an exception internally due to {@link HttpURLConnection}\n   *     method signatures.  The request should be considered aborted/failed if this method\n   *     throws.\n   */\n  public void postConnect() throws IOException {\n    if (mHolder != null) {\n      mHolder.impl.postConnect();\n    }\n  }\n\n  /**\n   * Indicates that there was a non-recoverable failure during HTTP message exchange at some\n   * point between {@link #preConnect} and {@link #interpretResponseStream}.\n   *\n   * @param ex Relay the exception that was thrown from {@link java.net.HttpURLConnection}\n   */\n  public void httpExchangeFailed(IOException ex) {\n    if (mHolder != null) {\n      mHolder.impl.httpExchangeFailed(ex);\n    }\n  }\n\n  /**\n   * Deliver the response stream from {@link HttpURLConnection#getInputStream()} to\n   * Stetho so that it can be intercepted.  Note that compression is transparently\n   * supported on modern Android systems and no special awareness is necessary for\n   * gzip compression on the wire.  Unfortunately this means that it is sometimes impossible\n   * to determine whether compression actually occurred and so Stetho may report inflated\n   * byte counts.\n   * <p>\n   * If the {@code Content-Length} header is provided by the server, this will be assumed to be\n   * the raw byte count on the wire.\n   *\n   * @param responseStream Stream as furnished by {@link HttpURLConnection#getInputStream()}.\n   *\n   * @return The filtering stream which is to be read after this method is called.\n   */\n  public InputStream interpretResponseStream(@Nullable InputStream responseStream) {\n    if (mHolder != null) {\n      return mHolder.impl.interpretResponseStream(responseStream);\n    } else {\n      return responseStream;\n    }\n  }\n\n  /**\n   * Convenience method to access the lower level\n   * {@link com.facebook.stetho.inspector.network.NetworkEventReporter} API (must be explicitly\n   * cast).\n   *\n   * @deprecated This should no longer be used as it could potentially break the mechanism\n   *     we use to allow convenient stripping of Stetho from release builds when using this\n   *     module.  If you need access to this, consider writing your own custom version of this\n   *     module.\n   */\n  @Deprecated\n  @Nullable\n  public Object getStethoHook() {\n    if (mHolder != null) {\n      return mHolder.impl.getStethoHook();\n    } else {\n      return null;\n    }\n  }\n\n  /**\n   * Low level method to access this request's unique identifier according to\n   * {@link com.facebook.stetho.inspector.network.NetworkEventReporter}.  Most callers won't\n   * need this.\n   */\n  @Nullable\n  public String getStethoRequestId() {\n    if (mHolder != null) {\n      return mHolder.impl.getStethoRequestId();\n    } else {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/StethoURLConnectionManagerImpl.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport com.facebook.stetho.inspector.network.DefaultResponseHandler;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.NetworkEventReporterImpl;\nimport com.facebook.stetho.inspector.network.RequestBodyHelper;\n\nimport javax.annotation.Nonnull;\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.net.HttpURLConnection;\n\n/**\n * Isolated implementation class to allow us to escape the verifier if Stetho is not\n * present.  This is done for convenience so that {@link StethoURLConnectionManager} hooks can be\n * left in a release build without any significant overhead either at runtime or in the compiled\n * APK.\n */\nclass StethoURLConnectionManagerImpl {\n  private final NetworkEventReporter mStethoHook = NetworkEventReporterImpl.get();\n  private final String mRequestId;\n  @Nullable\n  private final String mFriendlyName;\n\n  private HttpURLConnection mConnection;\n  @Nullable private URLConnectionInspectorRequest mInspectorRequest;\n  @Nullable private RequestBodyHelper mRequestBodyHelper;\n\n  public StethoURLConnectionManagerImpl(@Nullable String friendlyName) {\n    mRequestId = mStethoHook.nextRequestId();\n    mFriendlyName = friendlyName;\n  }\n\n  public boolean isStethoActive() {\n    return mStethoHook.isEnabled();\n  }\n\n  /**\n   * @see StethoURLConnectionManager#preConnect\n   */\n  public void preConnect(\n      HttpURLConnection connection,\n      @Nullable SimpleRequestEntity requestEntity) {\n    throwIfConnection();\n    mConnection = connection;\n    if (isStethoActive()) {\n      mRequestBodyHelper = new RequestBodyHelper(mStethoHook, getStethoRequestId());\n      mInspectorRequest = new URLConnectionInspectorRequest(\n          getStethoRequestId(),\n          mFriendlyName,\n          connection,\n          requestEntity,\n          mRequestBodyHelper);\n      mStethoHook.requestWillBeSent(mInspectorRequest);\n    }\n  }\n\n  /**\n   * @see StethoURLConnectionManager#postConnect\n   */\n  public void postConnect() throws IOException {\n    throwIfNoConnection();\n    if (isStethoActive()) {\n      if (mRequestBodyHelper != null && mRequestBodyHelper.hasBody()) {\n        mRequestBodyHelper.reportDataSent();\n      }\n      mStethoHook.responseHeadersReceived(\n          new URLConnectionInspectorResponse(\n              getStethoRequestId(),\n              mConnection));\n    }\n  }\n\n  /**\n   * @see StethoURLConnectionManager#httpExchangeFailed\n   */\n  public void httpExchangeFailed(IOException ex) {\n    throwIfNoConnection();\n    if (isStethoActive()) {\n      mStethoHook.httpExchangeFailed(getStethoRequestId(), ex.toString());\n    }\n  }\n\n  /**\n   * @see StethoURLConnectionManager#interpretResponseStream\n   */\n  public InputStream interpretResponseStream(@Nullable InputStream responseStream) {\n    throwIfNoConnection();\n    if (isStethoActive()) {\n      // Note that Content-Encoding is stripped out by HttpURLConnection on modern versions of\n      // Android (fun fact, it's powered by okhttp) when decompression is handled transparently.\n      // When this occurs, we will not be able to report the compressed size properly.  Callers,\n      // however, can disable this behaviour which will once again give us access to the raw\n      // Content-Encoding so that we can handle it properly.\n      responseStream = mStethoHook.interpretResponseStream(\n          getStethoRequestId(),\n          mConnection.getHeaderField(\"Content-Type\"),\n          mConnection.getHeaderField(\"Content-Encoding\"),\n          responseStream,\n          new DefaultResponseHandler(mStethoHook, getStethoRequestId()));\n    }\n    return responseStream;\n  }\n\n  private void throwIfNoConnection() {\n    if (mConnection == null) {\n      throw new IllegalStateException(\"Must call preConnect\");\n    }\n  }\n\n  private void throwIfConnection() {\n    if (mConnection != null) {\n      throw new IllegalStateException(\"Must not call preConnect twice\");\n    }\n  }\n\n  /**\n   * Convenience method to access the lower level {@link NetworkEventReporter} API.\n   */\n  public NetworkEventReporter getStethoHook() {\n    return mStethoHook;\n  }\n\n  /**\n   * @see StethoURLConnectionManager#getStethoRequestId()\n   */\n  @Nonnull\n  public String getStethoRequestId() {\n    return mRequestId;\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/URLConnectionInspectorHeaders.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport android.util.Pair;\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\n\nimport java.util.ArrayList;\n\nclass URLConnectionInspectorHeaders implements NetworkEventReporter.InspectorHeaders {\n  private final ArrayList<Pair<String, String>> mHeaders;\n\n  public URLConnectionInspectorHeaders(ArrayList<Pair<String, String>> headers) {\n    mHeaders = headers;\n  }\n\n  @Override\n  public int headerCount() {\n    return mHeaders.size();\n  }\n\n  @Override\n  public String headerName(int index) {\n    return mHeaders.get(index).first;\n  }\n\n  @Override\n  public String headerValue(int index) {\n    return mHeaders.get(index).second;\n  }\n\n  @Override\n  public String firstHeaderValue(String name) {\n    int N = headerCount();\n    for (int i = 0; i < N; i++) {\n      if (name.equalsIgnoreCase(headerName(i))) {\n        return headerValue(i);\n      }\n    }\n    return null;\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/URLConnectionInspectorRequest.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\nimport com.facebook.stetho.inspector.network.RequestBodyHelper;\n\nimport javax.annotation.Nullable;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\nimport java.net.HttpURLConnection;\n\nclass URLConnectionInspectorRequest\n    extends URLConnectionInspectorHeaders\n    implements NetworkEventReporter.InspectorRequest {\n  private final String mRequestId;\n  private final String mFriendlyName;\n  @Nullable private final SimpleRequestEntity mRequestEntity;\n  private final RequestBodyHelper mRequestBodyHelper;\n  private final String mUrl;\n  private final String mMethod;\n\n  public URLConnectionInspectorRequest(\n      String requestId,\n      String friendlyName,\n      HttpURLConnection configuredRequest,\n      @Nullable SimpleRequestEntity requestEntity,\n      RequestBodyHelper requestBodyHelper) {\n    super(Util.convertHeaders(configuredRequest.getRequestProperties()));\n    mRequestId = requestId;\n    mFriendlyName = friendlyName;\n    mRequestEntity = requestEntity;\n    mRequestBodyHelper = requestBodyHelper;\n    mUrl = configuredRequest.getURL().toString();\n    mMethod = configuredRequest.getRequestMethod();\n  }\n\n  @Override\n  public String id() {\n    return mRequestId;\n  }\n\n  @Override\n  public String friendlyName() {\n    return mFriendlyName;\n  }\n\n  @Override\n  public Integer friendlyNameExtra() {\n    return null;\n  }\n\n  @Override\n  public String url() {\n    return mUrl;\n  }\n\n  @Override\n  public String method() {\n    return mMethod;\n  }\n\n  @Nullable\n  @Override\n  public byte[] body() throws IOException {\n    if (mRequestEntity != null) {\n      OutputStream out = mRequestBodyHelper.createBodySink(firstHeaderValue(\"Content-Encoding\"));\n      try {\n        mRequestEntity.writeTo(out);\n      } finally {\n        out.close();\n      }\n      return mRequestBodyHelper.getDisplayBody();\n    } else {\n      return null;\n    }\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/URLConnectionInspectorResponse.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport com.facebook.stetho.inspector.network.NetworkEventReporter;\n\nimport java.io.IOException;\nimport java.net.HttpURLConnection;\n\nclass URLConnectionInspectorResponse\n    extends URLConnectionInspectorHeaders\n    implements NetworkEventReporter.InspectorResponse {\n  private final String mRequestId;\n  private final String mUrl;\n  private final int mStatusCode;\n  private final String mStatusMessage;\n\n  public URLConnectionInspectorResponse(String requestId, HttpURLConnection conn) throws IOException {\n    super(Util.convertHeaders(conn.getHeaderFields()));\n    mRequestId = requestId;\n    mUrl = conn.getURL().toString();\n    mStatusCode = conn.getResponseCode();\n    mStatusMessage = conn.getResponseMessage();\n  }\n\n  @Override\n  public String requestId() {\n    return mRequestId;\n  }\n\n  @Override\n  public String url() {\n    return mUrl;\n  }\n\n  @Override\n  public int statusCode() {\n    return mStatusCode;\n  }\n\n  @Override\n  public String reasonPhrase() {\n    return mStatusMessage;\n  }\n\n  @Override\n  public boolean connectionReused() {\n    // No idea...\n    return false;\n  }\n\n  @Override\n  public int connectionId() {\n    return mRequestId.hashCode();\n  }\n\n  @Override\n  public boolean fromDiskCache() {\n    return false;\n  }\n}\n"
  },
  {
    "path": "stetho-urlconnection/src/main/java/com/facebook/stetho/urlconnection/Util.java",
    "content": "/*\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found in the\n * LICENSE file in the root directory of this source tree.\n */\n\npackage com.facebook.stetho.urlconnection;\n\nimport android.util.Pair;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\nclass Util {\n  public static ArrayList<Pair<String, String>> convertHeaders(Map<String, List<String>> map) {\n    ArrayList<Pair<String, String>> array = new ArrayList<Pair<String, String>>();\n    for (Map.Entry<String, List<String>> mapEntry : map.entrySet()) {\n      for (String mapEntryValue : mapEntry.getValue()) {\n        // HttpURLConnection puts a weird null entry in the header map that corresponds to\n        // the HTTP response line (for instance, HTTP/1.1 200 OK).  Ignore that weirdness...\n        if (mapEntry.getKey() != null) {\n          array.add(Pair.create(mapEntry.getKey(), mapEntryValue));\n        }\n      }\n    }\n    return array;\n  }\n}\n"
  }
]