diff options
Diffstat (limited to 'devtools/docs')
24 files changed, 1166 insertions, 0 deletions
diff --git a/devtools/docs/.gitignore b/devtools/docs/.gitignore new file mode 100644 index 000000000..9a8882dd2 --- /dev/null +++ b/devtools/docs/.gitignore @@ -0,0 +1 @@ +_book
\ No newline at end of file diff --git a/devtools/docs/README.md b/devtools/docs/README.md new file mode 100644 index 000000000..163445fb3 --- /dev/null +++ b/devtools/docs/README.md @@ -0,0 +1,41 @@ +# Firefox Developer Tools + +Hello! This documentation is for developers who want to work on the +developer tools. If you are looking for general docs about how to use +the tools, checkout [this MDN +page](https://developer.mozilla.org/en-US/docs/Tools). + +These docs explain how the developer tools work at high-level, as well +as providing links to reference documentation. This is a good starting +point if you are a new contributor, or want to learn how our protocol, +a specific tool, or something else works. + +If you are looking to **start hacking** on the developer tools, all of +this information is documented on the +[Hacking](https://wiki.mozilla.org/DevTools/Hacking) wiki page. + +A very quick version: + +``` +$ hg clone http://hg.mozilla.org/integration/fx-team +$ ./mach build +$ ./mach run -P development +``` + +You can also clone via git from +`https://github.com/mozilla/gecko-dev.git`. Note that the workflow for +submitting patches may be a little different if using git. + +Please see the [Hacking](https://wiki.mozilla.org/DevTools/Hacking) +page for a lot more information! + +All of our **coding standards** are documented on the [Coding +Standards](https://wiki.mozilla.org/DevTools/CodingStandards) wiki +page. + +We use ESLint to enforce coding standards, and if you can run it +straight from the command like this: + +``` +./mach eslint path/to/directory +``` diff --git a/devtools/docs/SUMMARY.md b/devtools/docs/SUMMARY.md new file mode 100644 index 000000000..a4c858a15 --- /dev/null +++ b/devtools/docs/SUMMARY.md @@ -0,0 +1,19 @@ + +# Summary + +* [Tool Architectures](tools.md) + * [Inspector](inspector-panel.md) + * [Memory](memory-panel.md) + * [Debugger](debugger-panel.md) + * [Responsive Design Mode](responsive-design-mode.md) +* [Frontend](frontend.md) + * [Panel SVGs](svgs.md) + * [React](react.md) + * [Guidelines](react-guidelines.md) + * [Tips](react-tips.md) + * [Redux](redux.md) + * [Guidelines](redux-guidelines.md) + * [Tips](redux-tips.md) +* [Backend](backend.md) + * [Protocol](protocol.md) + * [Debugger API](debugger-api.md) diff --git a/devtools/docs/backend.md b/devtools/docs/backend.md new file mode 100644 index 000000000..d599e05fd --- /dev/null +++ b/devtools/docs/backend.md @@ -0,0 +1,2 @@ + +These files provide information about general backend architecture.
\ No newline at end of file diff --git a/devtools/docs/debugger-api.md b/devtools/docs/debugger-api.md new file mode 100644 index 000000000..4c165f57a --- /dev/null +++ b/devtools/docs/debugger-api.md @@ -0,0 +1,9 @@ + +The Debugger API is a low-level API that provides methods for +introspecting and affecting a target environment like a page. You can +find JavaScript sources, set breakpoints on them, and more. + +This API is completely documented on the [Debgger +API](https://developer.mozilla.org/en-US/docs/Tools/Debugger-API) MDN +page + diff --git a/devtools/docs/debugger-panel.md b/devtools/docs/debugger-panel.md new file mode 100644 index 000000000..1cb97dadc --- /dev/null +++ b/devtools/docs/debugger-panel.md @@ -0,0 +1,2 @@ + +This is just a stub until we write debugger docs.
\ No newline at end of file diff --git a/devtools/docs/frontend.md b/devtools/docs/frontend.md new file mode 100644 index 000000000..5aa605069 --- /dev/null +++ b/devtools/docs/frontend.md @@ -0,0 +1,2 @@ + +These files provide information about general frontend architecture.
\ No newline at end of file diff --git a/devtools/docs/http-inspector.md b/devtools/docs/http-inspector.md new file mode 100644 index 000000000..53908fd76 --- /dev/null +++ b/devtools/docs/http-inspector.md @@ -0,0 +1,167 @@ +# HTTP Inspector (aka XHR Spy) +This document is intended as a description of HTTP Inspector feature allowing +inline inspection of HTTP logs displayed in the Console panel. The documents +focuses on internal architecture. + +For detailed feature description see the following doc +(many screenshots included): +https://docs.google.com/document/d/1zQniwU_dkt-VX1qY1Vp-SWxEVA4uFcDCrtH03tGoHHM/edit# + +_HTTP Inspector feature is available in the Console panel (for web developers) +as well as in the Browser Console (for devtools and extension developers)._ + +The current implementation is based on React (no XUL) and some of the existing +components should also be used when porting the Network panel to HTML. + +The entire feature lives in `devtools/client/webconsole/net` directory. + +## General Description +The entry point for HTTP log inspection is represented by an expand/toggle +button displayed in front a log in the Console panel: + +[+] GET XHR http://example.com/test-request.php + +Clicking on the [+] button expands the log and shows a body with HTTP details +right underneath. The info body is rendered by: +`devtools/client/webconsole/net/components/net-info-body` component. + +HTTP info is divided into several tabs: + +* Headers: send and received HTTP headers +* Params: URL parameters (query string) +* Post: HTTP post body +* Response: HTTP response body +* Cookies: Sent and received cookies + +### Headers Tab +`devtools/client/webconsole/net/components/headers-tab` + +This is the default active tab and it's responsible for rendering +HTTP headers. There are several header groups displayed: + +* Response Headers +* Requests Headers +* Cached Headers (not implemented yet) + +Individual sections are expandable/collapsible. + +Rendering of the groups is done by `NetInfoGroup` and `NetInfoGroupList` +components. + +### Params Tab +`devtools/client/webconsole/net/components/params-tab` + +This tab is responsible for rendering URL parameters (query string) +and it's available only if the URL has any parameters. Individual +parameters are parsed and displayed as a list of name/value pairs. + +Rendering of the parameter list is done by `NetInfoParams` component. + +### Post Tab +`devtools/client/webconsole/net/components/post-tab` + +This tab is responsible for rendering HTTP post body sent to the server. + +### Response Tab +`devtools/client/webconsole/net/components/response-tab` + +This tab is responsible for rendering HTTP response body received from +the server. There might be more than one section displaying the data +depending on the current response mime-type. + +* Raw Data: This section is always displayed. It renders data in a raw +form just like they are received from the server. +* JSON: This section is available in case of JSON responses [1]. +It parses the response and displays it as an expandable tree. +* Image: This section is available in case of image responses [2]. +The response is decoded and displayed as an image. +* XML: this section is available in case of HTML/XML responses [3] +The response is parsed using DOM parser and displayed as an XML markup. + +[1] List of JSON mime-types: `devtools/client/webconsole/net/utils/json` +[2] List of Image mime-types: `devtools/client/webconsole/net/utils/json` +[3] List of XML/HTML mime-types: `devtools/client/webconsole/net/utils/net` + +Response data are fetched using `LongStringClient`, so if data are bigger +than defined limit (see `devtools/server/main.js - LONG_STRING_LENGTH) +the user needs to manually require the rest (there is a link at the end +of incomplete response body that allows this). + +The raw section is collapsed by default if there is another presentation +of the data. + +### Cookies Tab +`devtools/client/webconsole/net/components/cookies-tab` + +This tab is responsible for displaying HTTP cookies. +There are two groups: + +* Request Cookies +* Response Cookies + +Rendering of the groups is done by `NetInfoGroup` and `NetInfoGroupList` +components. The tab is not presented if there are no cookies. + +## Architecture +This sections describes internal architecture of HTTPi feature. + +### Main +`devtools/client/webconsole/net/main` + +This is the main module of HTTPi. It represents the root module +of the feature. + +The main responsibility of the module is handling network logs forwarded +from webconsole.js. This modules creates one instance of `NetRequest` +object for every `NetworkEvent` (one object for every HTTP request). + +### NetRequest +`devtools/client/webconsole/net/net-request` + +This module represents `NetRequest` object. It's the internal representation +of HTTP request and it keeps its state. All HTTP details fetched dynamically +from the backend are stored in this object. + +This object is responsible for: +* Adding a toggle button in Console UI (displayed in front of HTTP requests) +* Listening for a click event on the toggle button. +* Sending messages to web console client object to request HTTP details. +* Refreshing the UI as HTTP details are coming from the overlay. + +Note that `NetRequest` is using a small helper object `DataProvider` for +requesting HTTP details. `DataProvider` is the connection between `NetRequest` +and the backend. + +### Data Provider +`devtools/client/webconsole/net/data-provider` + +This module is using webconsole client object to get data from the backend. + +### Utils +`devtools/client/webconsole/net/utils` + +There are also some utility modules implementing helper functions. +The important thing is that these modules doesn't require any chrome +privileges and are ready to run inside content scope. + +### Components +* `NetInfoBody` Renders the entire HTTP details body displayed when the + user expands a network log. +* `NetInfoGroup` Renders a group (a section within tab). For example, + Request Headers section in Headers tab corresponds to one group. +* `NetInfoGroupList` List of groups. There might be more groups of data + within one tab. For example, the Headers tab has Requested and Response + headers groups. +* `NetInfoParams` List of name-value pairs. It's used e.g. by the Headers + or Params tab. +* `HeadersTab` Displays HTTP headers. +* `PostTab` Displays HTTP posted body data. +* `ParamsTab` Displays URL query string. +* `ResponseTab` Displays HTTP response body data. +* `CookiesTab` Displays cookies. +* `Spinner` Represents a throbber displayed when the UI is waiting for + incoming data. +* `SizeLimit` Represents a link that can be used to fetch the + rest of data from the backend (debugger server). Used for HTTP post + and response bodies. +* `XmlView` Renders XML markup. diff --git a/devtools/docs/inspector-panel.md b/devtools/docs/inspector-panel.md new file mode 100644 index 000000000..435b03651 --- /dev/null +++ b/devtools/docs/inspector-panel.md @@ -0,0 +1,98 @@ +# High-Level Inspector Architecture
+
+## UI structure
+The Inspector panel is a tab in the toolbox. Like all tabs, it's in its own iframe.
+
+The high-level hierarchy looks something like this:
+
+ Toolbox
+ |
+ InspectorPanel
+ |
+ +-------------+------------------+---------------+
+ | | | |
+ MarkupView SelectorSearch HTMLBreadcrumbs ToolSidebar widget (iframes)
+ |
+ +- RuleView
+ |
+ +- ComputedView
+ |
+ +- LayoutView
+ |
+ +- FontInspector
+ |
+ +- AnimationInspector
+
+## Server dependencies
+- The inspector panel relies on a series of actors that live on the server.
+- Some of the most important actors are actually instantiated by the toolbox, because these actors are needed for other panels to preview and link to DOM nodes. For example, the webconsole needs to output DOM nodes, highlight them in the page on mouseover, and open them in the inspector on click. This is achieved using some of the same actors that the inspector panel uses.
+- See Toolbox.prototype.initInspector: This method instantiates the InspectorActor, WalkerActor and HighlighterActor lazily, whenever they're needed by a panel.
+
+## Panel loading overview
+- As with other panels, this starts with Toolbox.prototype.loadTool(id)
+- For the inspector though, this calls Toolbox.prototype.initInspector
+- When the panel's open method is called:
+ - It uses the WalkerActor for the first time to know the default selected node (which could be a node that was selected before on the same page).
+ - It starts listening to the WalkerActor's "new-root" events to know when to display a new DOM tree (when there's a page navigation).
+ - It creates the breadcrumbs widget, the sidebar widget, the search widget, the markup-view
+- Sidebar:
+ - When this widget initializes, it loads its sub-iframes (rule-view, ...)
+ - Each of these iframes contain panel that, in turn, listen to inspector events like "new-node-front" to know when to retrieve information about a node (i.e the rule-view will fetch the css rules for a node).
+- Markup-view:
+ - This panel initializes in its iframe, and gets a reference to the WalkerActor. It uses it to know the DOM tree to display. It knows when nodes change (markup-mutations), and knows what root node to start from.
+ - It only displays the nodes that are supposed to be visible at first (usually html, head, body and direct children of body).
+ - Then, as you expand nodes, it uses the WalkerActor to get more nodes lazily. It only ever knows data about nodes that have already been expanded once in the UI.
+
+## Server-side structure
+Simplified actor hierarchy
+
+ InspectorActor
+ |
+ +---------------+
+ | |
+ WalkerActor PageStyleActor (for rule-view/computed-view)
+ | |
+ NodeActor StyleRuleActor
+
+__InspectorActor__
+
+This tab-level actor is the one the inspector-panel connects to. It doesn't do much apart from creating and returning the WalkerActor and PageStyleActor.
+
+__WalkerActor__
+
+- Single most important part of the inspector panel.
+- Responsible for walking the DOM on the current page but:
+ - also walks iframes
+ - also finds pseudo-elements ::before and ::after
+ - also finds anonymous content (e.g. in the BrowserToolbox)
+- The actor uses an instance of inIDeepTreeWalker to walk the DOM
+- Provides a tree of NodeActor objects that reflects the DOM.
+- But only has a partial knowledge of the DOM (what is currently displayed/expanded in the MarkupView). It doesn't need to walk the whole tree when you first instantiate it.
+- Reflects some of the usual DOM APIs like querySelector.
+- Note that methods like querySelector return arbitrarily nested NodeActors, in which case the WalkerActor also sends the list of parents to link the returned nodes to the closest known nodes, so the UI can display the tree correctly.
+- Emits events when there are DOM mutations. These events are sent to the front-end and used to, for example refresh the markup-view. This uses an instance of MutationObserver (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) configured with, in particular, nativeAnonymousChildList set to true, so that mutation events are also sent when pseudo elements are added/removed via css.
+
+__NodeActor__
+
+- Representation of a single DOM node (tagname, namespace, attributes, parent, sibblings, ...), which panels use to display previews of nodes.
+- Also provide useful methods to:
+ - change attributes
+ - scroll into view
+ - get event listeners data
+ - get image data
+ - get unique css selector
+
+## Highlighters
+
+One of the important aspects of the inspector is the highlighters.
+Some documentation already at: https://wiki.mozilla.org/DevTools/Highlighter
+
+We don't just have 1 highlighter, we have a framework for highlighters:
+- a (chrome-only) platform API to inject markup in a native-anonymous node in content (that works on all targets)
+- a number of specific highlighter implementations (css transform, rect, selector, geometry, rulers, ...)
+- a CustomHighlighterActor to get instances of specific highlighters
+
+The entry point is toolbox-highlighter-utils.js:
+- get it with toolbox.highlighterUtils
+- use this to easily highlight any node in the page (with the usual box model highlighter),
+- also use this to instantiate an other specific highlighter
diff --git a/devtools/docs/memory-panel.md b/devtools/docs/memory-panel.md new file mode 100644 index 000000000..25b86f066 --- /dev/null +++ b/devtools/docs/memory-panel.md @@ -0,0 +1,226 @@ +# Memory Tool Architecture + +The memory tool is built of three main elements: + +1. The live heap graph exists in memory, and is managed by the C++ allocator and + garbage collector. In order to get access to the structure of this graph, a + specialized interface is created to represent its state. The `JS::ubi::Node` + is the basis for this representation. This interface can be created from the + live heap graph, or a serialized, offline snapshot from a previous moment in + time. Our various heap analyses (census, dominator trees, shortest paths, + etc) run on top of `JS::ubi::Node` graphs. The `ubi` in the name stands for + "ubiquitous" and provides a namespace for memory analyses in C++ code. + +2. The `HeapAnalysesWorker` runs in a worker thread, performing analyses on + snapshots and translating the results into something the frontend can render + simply and quickly. The `HeapAnalysesClient` is used to communicate between + the worker and the main thread. + +3. Finally, the last element is the frontend that renders data received from the + `HeapAnalysesClient` to the DOM and translates user input into requests for + new data with the `HeapAnalysesClient`. + +Unlike other tools (such as the JavaScript debugger), the memory tool makes very +little use of the Remote Debugger Server and the actors that reside in it. Use +of the [`MemoryActor`](devtools/server/actors/memory.js) is limited to toggling +allocation stack recording on and off, and transferring heap snapshots from the +debuggee (which is on the server) to the `HeapAnalysesWorker` (which is on the +client). A nice benefit that naturally emerges, is that supporting "legacy" +servers (eg, using Firefox Developer Edition as a client to remote debug a +release Firefox for Android server) is a no-op. As we add new analyses, we can +run them on snapshots taken on old servers no problem. The only requirement is +that changes to the snapshot format itself remain backwards compatible. + +## `JS::ubi::Node` + +`JS::ubi::Node` is a lightweight serializable interface that can represent the +current state of the heap graph. For a deeper dive into the particulars of how +it works, it is very well documented in the `js/public/UbiNode.h` + +A "heap snapshot" is a representation of the heap graph at some particular past +instance in time. + +A "heap analysis" is an algorithm that runs on a `JS::ubi::Node` heap graph. +Generally, analyses can run on either the live heap graph or a deserialized +snapshot. Example analyses include "census", which aggregates and counts nodes +into various user-specified buckets; "dominator trees", which compute the +[dominates](https://en.wikipedia.org/wiki/Dominator_%28graph_theory%29) relation +and retained size for all nodes in the heap graph; and "shortest paths" which +finds the shortest paths from the GC roots to some subset of nodes. + +### Saving Heap Snapshots + +Saving a heap snapshot has a few requirements: + +1. The binary format must remain backwards compatible and future extensible. + +2. The live heap graph must not mutate while we are in the process of + serializing it. + +3. The act of saving a heap snapshot should impose as little memory overhead as + possible. If we are taking a snapshot to debug frequent out-of-memory errors, + we don't want to trigger an OOM ourselves! + +To solve (1), we use the [protobuf](https://developers.google.com/protocol-buffers/) +message format. The message definitions themselves are in +`devtools/shared/heapsnapshot/CoreDump.proto`. We always use `optional` fields +so we can change our mind about what fields are required sometime in the future. +Deserialization checks the semantic integrity of deserialized protobuf messages. + +For (2), we rely on SpiderMonkey's GC rooting hazard static analysis and the +`AutoCheckCannotGC` dynamic analysis to ensure that neither JS nor GC runs and +modifies objects or moves them from one address in memory to another. There is +no equivalent suppression and static analysis technique for the +[cycle collector](https://developer.mozilla.org/en/docs/Interfacing_with_the_XPCOM_cycle_collector), +so care must be taken not to invoke methods that could start cycle collection or +mutate the heap graph from the cycle collector's perspective. At the time of +writing, we don't yet support saving the cycle collector's portion of the heap +graph in snapshots, but that work is deemed Very Important and Very High +Priority. + +Finally, (3) imposes upon us that we do not build the serialized heap snapshot +binary blob in memory, but instead stream it out to disk while generating it. + +Once all of that is accounted for, saving snapshots becomes pretty straight +forward. We traverse the live heap graph with `JS::ubi::Node` and +`JS::ubi::BreadthFirst`, create a protobuf message for each node and each node's +edges, and write these messages to disk before continuing the traversal to the +next node. + +This functionality is exposed to chrome JavaScript as the +`[ThreadSafe]ChromeUtils.saveHeapSnapshot` function. See +`dom/webidl/ThreadSafeChromeUtils.webidl` for API documentation. + +### Reading Heap Snapshots + +Reading heap snapshots has less restrictions than saving heap snapshots. The +protobuf messages that make up the core dump are deserialized one by one, stored +as a set of `DeserializedNode`s and a set of `DeserializedEdge`s, and the result +is a `HeapSnapshot` instance. + +The `DeserializedNode` and `DeserializedEdge` classes implement the +`JS::ubi::Node` interface. Analyses running on offline heap snapshots rather +than the live heap graph operate on these classes (unknowingly, of course). + +For more details, see the +[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.cpp) +and +[`mozilla::devtools::Deserialized{Node,Edge}`](devtools/shared/heapsnapshot/DeserializedNode.h) +classes. + +### Heap Analyses + +Heap analyses operate on `JS::ubi::Node` graphs without knowledge of whether +that graph is backed by the live heap graph or an offline heap snapshot. They +must make sure never to allocate GC things or modify the live heap graph. + +In general, analyses are implemented in their own `js/public/Ubi{AnalysisName}.h` +header (eg `js/public/UbiCensus.h`), and are exposed to chrome JavaScript code +via a method on the [`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl +interface. + +For each analysis we expose to chrome JavaScript on the `HeapSnapshot` webidl +interface, there is a small amount of glue code in Gecko. The +[`mozilla::devtools::HeapSnapshot`](devtools/shared/heapsnapshot/HeapSnapshot.h) +C++ class implements the webidl interface. The analyses methods (eg +`ComputeDominatorTree`) take the deserialized nodes and edges from the heap +snapshot, create `JS::ubi::Node`s from them, call the analyses from +`js/public/Ubi*.h`, and wrap the results in something that can be represented in +JavaScript. + +For API documentation on running specific analyses, see the +[`HeapSnapshot`](dom/webidl/HeapSnapshot.webidl) webidl interface. + +### Testing `JS::ubi::Node`, Snapshots, and Analyses + +The majority of the tests reside within `devtools/shared/heapsnapshot/tests/**`. +For reading and saving heap snapshots, most tests are gtests. The gtests can be +run with the `mach gtest DevTools.*` command. The rest are integration sanity +tests to make sure we can read and save snapshots in various environments, such +as xpcshell or workers. These can be run with the usual `mach test $PATH` +commands. + +There are also `JS::ubi::Node` related unit tests in +`js/src/jit-test/tests/heap-analysis/*`, `js/src/jit-test/tests/debug/Memory-*`, +and `js/src/jsapi-tests/testUbiNode.cpp`. See +https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/Running_Automated_JavaScript_Tests#Running_jit-tests +for running the JIT tests. + +## `HeapAnalysesWorker` + +The `HeapAnalysesWorker` orchestrates running specific analyses on snapshots and +transforming the results into something that can simply and quickly be rendered +by the frontend. The analyses can take some time to run (sometimes on the order +of seconds), so doing them in a worker thread allows the interface to stay +responsive. The `HeapAnalysisClient` provides the main thread's interface to the +worker. + +The `HeapAnalysesWorker` doesn't actually do much itself; mostly just shuffling +data and transforming it from one representation to another or calling C++ +utility functions exposed by webidl that do those things. Most of these are +implemented as traversals of the resulting census or dominator trees. + +See the following files for details on the various data transformations and +shuffling that the `HeapAnalysesWorker` delegates to. + +* `devtools/shared/heapsnapshot/CensusUtils.js` +* `devtools/shared/heapsnapshot/CensusTreeNode.js` +* `devtools/shared/heapsnapshot/DominatorTreeNode.js` + +### Testing the `HeapAnalysesWorker` and `HeapAnalysesClient` + +Tests for the `HeapAnalysesWorker` and `HeapAnalysesClient` reside in +`devtools/shared/heapsnapshot/tests/**` and can be run with the usual `mach test +$PATH` command. + +## Frontend + +The frontend of the memory tool is built with React and Redux. + +[React has thorough documentation.](https://facebook.github.io/react/) + +[Redux has thorough documentation.](http://rackt.org/redux/index.html) + +We have React components in `devtools/client/memory/components/*`. + +We have Redux reducers in `devtools/client/memory/reducers/*`. + +We have Redux actions and action-creating tasks in +`devtools/client/memory/actions/*`. + +React components should be pure functions from their props to the rendered +(virtual) DOM. Redux reducers should also be observably pure. + +Impurity within the frontend is confined to the tasks that are creating and +dispatching actions. All communication with the outside world (such as the +`HeapAnalysesWorker`, the Remote Debugger Server, or the file system) is +restricted to within these tasks. + +### Snapshots State + +On the JavaScript side, the snapshots represent a reference to the underlying +heap dump and the various analyses. The following diagram represents a finite +state machine describing the snapshot states. Any of these states may go to the +ERROR state, from which they can never leave. + +``` +SAVING → SAVED → READING → READ + ↗ + IMPORTING +``` + +Each of the report types (census, diffing, tree maps, dominators) have their own states as well, and are documented at `devtools/client/memory/constants.js`. +These report states are updated as the various filtering and selecting options +are updated in the UI. + +### Testing the Frontend + +Unit tests for React components are in `devtools/client/memory/test/chrome/*`. + +Unit tests for actions, reducers, and state changes are in +`devtools/client/memory/test/unit/*`. + +Holistic integration tests for the frontend and the whole memory tool are in +`devtools/client/memory/test/browser/*`. + +All tests can be run with the usual `mach test $PATH` command. diff --git a/devtools/docs/protocol.md b/devtools/docs/protocol.md new file mode 100644 index 000000000..e4cbb5dc0 --- /dev/null +++ b/devtools/docs/protocol.md @@ -0,0 +1,6 @@ + +The devtools is built around a client/server architecture because the +tools run outside of the process it debugs. This protocol is +documented extensively in the [Remote Debugging +Protocol](https://wiki.mozilla.org/Remote_Debugging_Protocol) wiki +page.
\ No newline at end of file diff --git a/devtools/docs/react-guidelines.md b/devtools/docs/react-guidelines.md new file mode 100644 index 000000000..b6fe32fb8 --- /dev/null +++ b/devtools/docs/react-guidelines.md @@ -0,0 +1,73 @@ + +# Guidelines for Writing React + +These are soft rules for writing react devtools code. Try to stick to +these for consistency, and if you disagree, file a bug to change these +docs and we can talk about it. + +**Please also read** the [coding + standards](https://wiki.mozilla.org/DevTools/CodingStandards#React_.26_Redux) +for react and redux code. The guidelines here are more general +patterns not specific to code style. + +### Why no JSX? + +You probably already noticed we don't use JSX. The answer isn't +complicated: we don't build our JS code, and we write directly for our +JS engine, SpiderMonkey. It already supports much of ES6, but it does +not support JSX (which is not a standard). + +This may change if we ever adopt a build step. Even so, the author is +not convinced that JSX helps enough to warrant all the syntax. It is +clearer sometimes, but it can be noisy switching between JSX and JS a +lot. + +It's not as bad as you may think! If you are used to JSX it may be an +adjustment, but you won't miss it too much. + +### One component per file + +Try to only put one component in a file. This helps avoid large files +full of components, but it's also technically required for how we wrap +components with factories. See the next rule. + +It also makes it easier to write tests because you might not export +some components, so tests can't access them. + +You can include small helper components in the same file if you really +want to, but note that they won't be directly tested and you will have +to use `React.createElement` or immediately wrap them in factories to +use them. + +### Export the component directly and create factory on import + +Modules are the way components interact. Ideally every component lives +in a separate file and they require whatever they need. This allows +tests to access all components and use module boundaries to wrap +components. + +For example, we don't use JSX, so we need to create factories for +components to use them as functions. A simple way to do this is on +import: + +```js +const Thing1 = React.createFactory(require('./thing1')); +const Thing2 = React.createFactory(require('./thing2')); +``` + +It adds a little noise, but then you can do `Thing1({ ... })` instead +of `React.createElement(Thing1, { ... })`. Definitely worth it. + +Additionally, make sure to export the component class directly: + +```js +const Thing1 = React.createClass({ ... }); +module.exports = Thing1; +``` + +Do not export `{ Thing1 }` or anything like that. This is required for +the factory wrapping as well as hot reloading. + +### More to Come + +This is just a start. We will add more to this document.
\ No newline at end of file diff --git a/devtools/docs/react-tips.md b/devtools/docs/react-tips.md new file mode 100644 index 000000000..05711859c --- /dev/null +++ b/devtools/docs/react-tips.md @@ -0,0 +1,19 @@ +# React Tips + +Learn various tips and tricks for working with our React code. + +## Hot Reloading + +If you followed [this +rule](react-guidelines.md#export-the-component-directly-and-create-factory-on-import) +about exporting components, and are using the BrowserLoader, you can +hot reload any React component. Just turn on the pref +`devtools.loader.hotreload`, re-open the devtools, and you should be +able to save any React component and instantly see changes. + +This does not reset the state of your app, so you can quickly se +changes in the same context. + +## Profiling + +We need information here about how to use React.addons.Perf to profile the app.
\ No newline at end of file diff --git a/devtools/docs/react.md b/devtools/docs/react.md new file mode 100644 index 000000000..55882ebab --- /dev/null +++ b/devtools/docs/react.md @@ -0,0 +1,157 @@ + +We use [React](http://facebook.github.io/react/) to write our user +interfaces. In here you can find an explanation of why we chose React +and a short primer on it. Additionally, we list best practices that +all devtools code should adhere to when writing React. + +# Quick Intro + +This is a very quick introduction on how to *use* React, but does not +explain in-depth the concepts behind it. If you want more in-depth +articles, I recommend the following links: + +* http://facebook.github.io/react/docs/tutorial.html - the official tutorial +* https://github.com/petehunt/react-howto - how to learn React +* http://jlongster.com/Removing-User-Interface-Complexity,-or-Why-React-is-Awesome - long read but explains the concepts in depth + +React embraces components as a way of thinking about UIs. Components +are the center of everything: they are composable like functions, +testable like JSON data, and provide lifecycle APIs for more complex +scenarios. + +A component can represent anything from a single item in a list to a +complete virtualized grid that is made up of sub-components. They can +be used to abstract out "behaviors" instead of UI elements (think of a +`Selectable` component). React's API makes it easy to break up your UI +into whatever abstractions you need. + +The core idea of a component is simple: it's something that takes +properties and returns a DOM-like structure. + +```js +function Item({ name, iconURL }) { + return div({ className: "item" }, + img({ className: "icon", href: iconURL }), + name); +} +``` + +The `div` and `span` functions refer to `React.DOM.div` and +`React.DOM.span`. React provides constructors for all DOM elements on +`React.DOM`. These conform to the standard API for creating elements: +the first argument takes properties, and the rest are children. + +You can see component composition kick in when using `Item`: + +```js +const Item = React.createFactory(require('./Item')); + +function List({ items }) { + return div({ className: "list" }, + items.map(item => Item({ name: item.name, icon: item.iconURL))); +} +``` + +You can use custom components exactly the same way you use native +ones! The only difference is we wrapped it in a factory when importing +instead of using the React.DOM functions. Factories are just a way of +turning a component into a convenient function. Without factories, you +need to do do `React.createElement(Item, { ... })`, which is exactly +the same as `Item({ ... })` if using a factory. + +## Rendering and Updating Components + +Now that we have some components, how do we render them? You use +`React.render` for that: + +```js +let items = [{ name: "Dubois", iconURL: "dubois.png" }, + { name: "Ivy", iconURL: "ivy.png" }]; + +React.render(List({ items: items }), + document.querySelector("#mount")); +``` + +This renders a `List` component, given `items`, to a DOM node with an +id of `mount`. Typically you have a top-level `App` component that is +the root of everything, and you would render it like so. + +What about updating? First, let's talk about data. The above +components take data from above and render out DOM structure. If any +user events were involved, the components would call callbacks passed +as props, so events walk back up the hierarchy. The conceptual model +is data goes down, and events come up. + +You usually want to change data in response to events, and rerender +the UI with the new data. What does that look like? There are two +places where React will rerender components: + +1\. Any additional `React.render` calls. Once a component is mounted, +you can call `React.render` again to the same place and React will see +that it's already mounted and perform an update instead of a full +render. For example, this code adds an item in response to an event +and updates the UI, and will perform optimal incremental updates: + +```js +function addItem(item) { + render([...items, item]); +} + +function render(items) { + React.render(List({ items: items, + onAddItem: addItem }), + document.querySelector("#mount")); +} + +render(items); +``` + +2\. Changing component local state. This is much more common. React +allows components to have local state, and whenever the state is +changed with the `setState` API it will rerender that specific +component. If you use component local state, you need to create a +component with `createClass`: + +```js +const App = React.createClass({ + getInitialState: function() { + return { items: [] }; + }, + + handleAddItem: function(item) { + const items = [...this.props.items, item]; + this.setState({ items: items }); + }, + + render: function() { + return List({ items: this.state.items, + onAddItem: this.handleAddItem }); + } +}); + ``` + +If you are using something like Redux to manage state this is handled +automatically for you with the library you use to bind Redux with +React. See more in [Redux](redux.html). + +## DOM Diffing + +What does it mean when React "updates" a component, and how does it +know which DOM to change? React achieves this with a technique called +DOM diffing. This alleviates the need for the programmer to worry +about how updates are actually applied to the DOM, and components can +render DOM structure declaratively in response to data. In the above +examples, when adding an item, React knows to only add a new DOM node +instead of recreating the whole list each time. + +DOM diffing is possible because our components return what's called +"virtual DOM": a lightweight JSON structure that React can use to diff +against previous versions, and generate minimal changes to the real DOM. + +This also makes it really east to test components with a real DOM: +just make sure the virtual DOM has what it should. + +## Next + +Read the [React Guidelines](react-guidelines.md) next to learn how to +write React code specifically for the devtools.
\ No newline at end of file diff --git a/devtools/docs/redux-guidelines.md b/devtools/docs/redux-guidelines.md new file mode 100644 index 000000000..1782a6de4 --- /dev/null +++ b/devtools/docs/redux-guidelines.md @@ -0,0 +1,52 @@ +### Getting data from the store + +To get data from the store, use `connect()`. + +When using connect, you'll break up your component into two parts: + +1. The part that displays the data (presentational component) + + // todos.js + const Todos = React.createClass({ + propTypes: { + todos: PropTypes.array.isRequired + } + + render: function() {...} + }) + + module.exports = Todos; + +2. The part that gets the data from the store (container component) + + // todos-container.js + const Todos = require("path/to/todos"); + + function mapStateToProps(state) { + return { + todos: state.todos + }; + } + + module.exports = connect(mapStateToProps)(Todos); + + +`connect()` generates the container component. It wraps around the presentational component that was passed in (e.g. Todos). + +The `mapStateToProps` is often called a selector. That's because it selects data from the state object. When the container component is rendering, the the selector will be called. It will pick out the data that the presentational component is going to need. Redux will take this object and pass it in to the presentational component as props. + +With this setup, a presentational component is easy to share across different apps. It doesn't have any dependencies on the app, or any hardcoded expectations about how to get data. It just gets props that are passed to it and renders them. + +For more advanced use cases, you can pass additional parameters into the selector and `connect()` functions. Read about those in the [`connect()`](https://github.com/reactjs/react-redux/blob/master/docs/api.md#connectmapstatetoprops-mapdispatchtoprops-mergeprops-options) docs. + +--- + +Need to answer the following questions: + +* How do I do I load asynchronous data? +* How do I do optimistic updates or respond to errors from async work? +* Do I use Immutable.js for my state? +* What file structure should I use? +* How do I test redux code? + +And more. diff --git a/devtools/docs/redux-tips.md b/devtools/docs/redux-tips.md new file mode 100644 index 000000000..c8bef72d7 --- /dev/null +++ b/devtools/docs/redux-tips.md @@ -0,0 +1,5 @@ + +Need to document: + +* How to attach various redux loggers +* How to hot reload redux code
\ No newline at end of file diff --git a/devtools/docs/redux.md b/devtools/docs/redux.md new file mode 100644 index 000000000..5aab53caf --- /dev/null +++ b/devtools/docs/redux.md @@ -0,0 +1,160 @@ + +We use [Redux](https://github.com/reactjs/redux) to manage application +state. The [docs](http://redux.js.org/) do a good job explaining the +concepts, so go read them. + +# Quick Intro + +Just like the [React introduction](react.html), this is a quick +introduction to redux, focusing on how it fits into React and why we +chose it. + +One of the core problems that React does not address is managing +state. In the React intro, we talked about data flowing down and +events flowing up. Conceptually this is nice, but you quickly run into +awkward situations in large apps. + +Let's look at an example. Say you have a page with a tabbed interface. +Here, `Tab1` is managing a list of items, so naturally it uses local +state. `Tab2` renders different stuff. + +```js +const Tab1 = React.createClass({ + getInitialState: function() { + return { items: [] }; + }, + + handleAddItem: function(item) { + this.setState({ items: [...this.state.items, item]}); + }, + + render: function() { + /* ... Renders the items and button to add new item ... */ + } +}); + +const Tab2 = React.createClass({ + render: function() { + /* ... Renders other data ... */ + } +}); + +// Assume `Tab1` and `Tab2` are wrapped with a factory when importing +const Tabs = React.createClass({ + render: function() { + return div( + { className: 'tabs' }, + // ... Render the tab buttons ... + Tab1(), + Tab2() + ); + } +}); +``` + +What happens when `Tab2` needs the list of items though? This scenario +comes up all time: components that aren't directly related need access +to the same state. A small change would be to move the `items` state +up to the `Tabs` component, and pass it down to both `Tab1` and `Tab2`. + +But now `Tabs` has to implement the `handleAddItem` method to add an +item because it's managing that state. This quickly gets ugly as the +end result is the root component ends up with a ton of state and +methods to manage it: a [god +component](https://en.wikipedia.org/wiki/God_object) is born. + +Additionally, how do we know what data each tab needs? We end up +passing *all* the state down because we don't know. This is not a +modular solution: one object managing the state and every component +receiving the entire state is like using tons of global variables. + +## Evolution of Flux + +Facebook addressed this with the +[flux](https://facebook.github.io/flux/) architecture, which takes the +state out of the components and into a "store". Redux is the latest +evolution of this idea and solves a lot of problems previous flux +libraries had (read it's documentation for more info). + +Because the state exists outside the component tree, any component can +read from it. Additionally, **state is updated with +[actions](http://redux.js.org/docs/basics/Actions.html)** that any +component can fire. We have [guidelines](redux-guidelines) for where +to read/write state, but it completely solves the problem described +above. Both `Tab1` and `Tab2` would be listening for changes in the +`item` state, and `Tab1` would fire actions to change it. + +With redux, **state is managed modularly with +[reducers](http://redux.js.org/docs/basics/Reducers.html)** but tied +together into a single object. This means a single JS object +represents most* of your state. It may sound crazy at first, but think +of it as an object with references to many pieces of state; that's all +it is. + +This makes it very easy to test, debug, and generally think about. You +can log your entire state to the console and inspect it. You can even +dump in old states and "replay" to see how the UI changed over time. + +I said "most*" because it's perfectly fine to use both component local +state and redux. Be aware that any debugging tools will not see local +state at all though. It should only be used for transient state; we'll +talk more about that in the guidelines. + +## Immutability + +Another important concept is immutability. In large apps, mutating +state makes it very hard to track what changed when. It's very easy to +run into situations where something changes out from under you, and +the UI is rendered with invalid data. + +Redux enforces the state to be updated immutably. That means you +always return new state. It doesn't mean you do a deep copy of the +state each time: when you need to change some part of the tree you +only need to create new objects to replace the ones your changing (and +walk up to the root to create a new root). Unchanged subtrees will +reference the same objects. + +This removes a whole class of errors, almost like Rust removing a +whole class of memory errors by enforcing ownership. + +## Order of Execution + +One of best things about React is that **rendering is synchronous**. That +means when you render a component, given some data, it will fully +render in the same tick. If you want the UI to change over time, you +have to change the *data* and rerender, instead of arbitrary UI +mutations. + +The reason this is desired is because if you build the UI around +promises or event emitters, updating the UI becomes very brittle +because anything can happen at any time. The state might be updated in +the middle of rendering it, maybe because you resolved a few promises +which made your rendering code run a few ticks later. + +Redux embraces the synchronous execution semantics as well. What this +means is that everything happens in a very controlled way. When +updating state through an action, all reducers are run and a new state +is synchronously generated. At that point, the new state is handed off +to React and synchronously rendered. + +Updating and rendering happen in two phases, so the UI will *always* +represent consistent state. The state can never be in the middle of +updating when rendering. + +What about asynchronous work? That's where +[middleware](http://redux.js.org/docs/advanced/Middleware.html) come +in. At this point you should probably go study our code, but +middleware allows you to dispatch special actions that indicate +asynchronous work. The middleware will catch these actions and do +something async, dispatching "raw" actions along the way (it's common +to emit a START, DONE, and ERROR action). + +**Ultimately there are 3 "phases" or level of abstraction**: the async +layer talks to the network and may dispatch actions, actions are +synchronously pumped through reducers to generate state, and state is +rendered with react. + +## Next + +Read the [Redux Guidelines](redux-guidelines.md) next to learn how to +write React code specifically for the devtools.
\ No newline at end of file diff --git a/devtools/docs/responsive-design-mode.md b/devtools/docs/responsive-design-mode.md new file mode 100644 index 000000000..fc348fb28 --- /dev/null +++ b/devtools/docs/responsive-design-mode.md @@ -0,0 +1,70 @@ +# Responsive Design Mode Architecture + +## Context + +You have a single browser tab that has visited several pages, and now has a +history that looks like, in oldest to newest order: + +1. https://newsblur.com +2. https://mozilla.org (← current page) +3. https://convolv.es + +## Opening RDM During Current Firefox Session + +When opening RDM, the browser tab's history must preserved. Additionally, we +strive to preserve the exact state of the currently displayed page (effectively +any in-page state, which is important for single page apps where data can be +lost if they are reloaded). + +This seems a bit convoluted, but one advantage of this technique is that it +preserves tab state since the same tab is reused. This helps to maintain any +extra state that may be set on tab by add-ons or others. + +1. Create a temporary, hidden tab to load the tool UI. +2. Mark the tool tab browser's docshell as active so the viewport frame is + created eagerly and will be ready to swap. +3. Create the initial viewport inside the tool UI. +4. Swap tab content from the regular browser tab to the browser within the + viewport in the tool UI, preserving all state via + `gBrowser._swapBrowserDocShells`. +5. Force the original browser tab to be non-remote since the tool UI must be + loaded in the parent process, and we're about to swap the tool UI into + this tab. +6. Swap the tool UI (with viewport showing the content) into the original + browser tab and close the temporary tab used to load the tool via + `swapBrowsersAndCloseOther`. +7. Start a tunnel from the tool tab's browser to the viewport browser + so that some browser UI functions, like navigation, are connected to + the content in the viewport, instead of the tool page. + +## Closing RDM During Current Firefox Session + +To close RDM, we follow a similar process to the one from opening RDM so we can +restore the content back to a normal tab. + +1. Stop the tunnel between outer and inner browsers. +2. Create a temporary, hidden tab to hold the content. +3. Mark the content tab browser's docshell as active so the frame is created + eagerly and will be ready to swap. +4. Swap tab content from the browser within the viewport in the tool UI to the + regular browser tab, preserving all state via + `gBrowser._swapBrowserDocShells`. +5. Force the original browser tab to be remote since web content is loaded in + the child process, and we're about to swap the content into this tab. +6. Swap the content into the original browser tab and close the temporary tab + used to hold the content via `swapBrowsersAndCloseOther`. + +## Session Restore + +When restarting Firefox and restoring a user's browsing session, we must +correctly restore the tab history. If the RDM tool was opened when the session +was captured, then it would be acceptable to either: + +* A: Restore the tab content without any RDM tool displayed **OR** +* B: Restore the RDM tool the tab content inside, just as before the restart + +We currently follow path A (no RDM after session restore), which seems more in +line with how the rest of DevTools currently functions after restore. To do so, +we watch for `beforeunload` events on the tab at shutdown and quickly exit RDM +so that session restore records only the original page content during its final +write at shutdown. diff --git a/devtools/docs/styles/website.css b/devtools/docs/styles/website.css new file mode 100644 index 000000000..414f3fbd8 --- /dev/null +++ b/devtools/docs/styles/website.css @@ -0,0 +1,13 @@ + +.book .book-summary ul.summary li { + cursor: pointer; +} + +.book .book-body .page-wrapper .page-inner section.normal p, +.book .book-body .page-wrapper .page-inner section.normal pre { + margin: .85em 0; +} + +.book .book-body .page-wrapper .page-inner section.normal pre { + line-height: 1.25em; +}
\ No newline at end of file diff --git a/devtools/docs/svgs.md b/devtools/docs/svgs.md new file mode 100644 index 000000000..90ff0ff5e --- /dev/null +++ b/devtools/docs/svgs.md @@ -0,0 +1,42 @@ +# Panel SVGs +These are the guidelines for creating devtools SVGs to make sure they're as small and neatly formatted as possible. The Mozilla Developer SVG guidelines can be found [here](https://developer.mozilla.org/en-US/docs/Mozilla/Developer_guide/SVG_Guidelines). + +## Explanation of Pixel Grid +Since so many of our SVGs appear so small, designing them on the pixel grid will help them not appear fuzzy when they're sized down to 16x16 pixels. There is program-specific documentation in both the [Illustrator](#illustrator) and [Sketch](#sketch) sections. + +## Panel Icon Requirements +The devtools panel icons do a couple of things in a specific way; following these guidelines will help stick your patch: + +1. **Inline fill colors.** Devtools panel icons all use ```fill="#0b0b0b"``` in the ```<svg>``` tag. +2. **Inline opacities.** Devtools panel icons also inline opacities on their relevant path. + +## Illustrator +For Illustrator you'll want the following document settings: + +- **Document settings**: ```Units: pixels```, ```Advanced``` > check ```Align New Objects to Pixel Grid``` +- **Transform Panel**: for existing artwork not on pixel grid, select shape and then within ```Transform``` > ```Advanced``` > check ```Align to Pixel Grid``` + +You can get a more detailed breakdown with images [here](http://medialoot.com/blog/3-valuable-pixel-perfect-illustrator-techniques/). + +You can download a sample Illustrator file [here](https://www.dropbox.com/home/Mozilla_MobileUX_Share/Internal%20Assets/Templates/Firefox?preview=pixel-grid-illustrator.ai). + +### Tips for Object Creation +When you're designing your icons in a graphics editor like Adobe Illustrator, there are a lot of things you can do that will bring down the size of the file and make your SVGs easier for the developers to work with. Here are some of them: + +- **Expand paths**: Instead of having multiple shapes overlapping each other, expand shapes using the pathfinder. +![Use pathfinder to expand shapes](./svgs/pathfinder.gif) +- Simplify paths (```Object``` > ```Path``` > ```Simplify```) +- Expand objects so that strokes become objects. This has the added benefit of keeping the stroke size intact as the SVG is resized. +![Expand strokes to make them objects](./svgs/expand-strokes.gif) + +## Sketch +Sketch vector work is a little different but the fundamentals (keeping your SVG small, expanding all paths) is the same. Here's what we've found helps to build clean icons: + +- **Build your icon at 16x16 with the Pixel Grid turned on.** You can turn the pixel grid on at ```View > Canvas > Show Pixels``` + +- **Make sure that all x/y coordinates are full pixels for lines/rectangles.** Sub-pixels = not on pixel grid. +![Position in the upper right hand corner of Sketch](./svgs/sketch-position.png) + +- **Expand all your paths so strokes expand properly as the SVG gets resized.** You can do this at ```Layer > Paths > Vectorize Stroke```. + +- **Align anything that isn't boxy to the pixel grid with item selected then ```Layer > Round to Nearest Pixel Edge```.**
\ No newline at end of file diff --git a/devtools/docs/svgs/expand-strokes.gif b/devtools/docs/svgs/expand-strokes.gif Binary files differnew file mode 100644 index 000000000..9e15767c5 --- /dev/null +++ b/devtools/docs/svgs/expand-strokes.gif diff --git a/devtools/docs/svgs/pathfinder.gif b/devtools/docs/svgs/pathfinder.gif Binary files differnew file mode 100644 index 000000000..73d376b10 --- /dev/null +++ b/devtools/docs/svgs/pathfinder.gif diff --git a/devtools/docs/svgs/sketch-position.png b/devtools/docs/svgs/sketch-position.png Binary files differnew file mode 100644 index 000000000..f8874d108 --- /dev/null +++ b/devtools/docs/svgs/sketch-position.png diff --git a/devtools/docs/tools.md b/devtools/docs/tools.md new file mode 100644 index 000000000..380f39fcc --- /dev/null +++ b/devtools/docs/tools.md @@ -0,0 +1,2 @@ + +These files provide a high-level overview of each tool and how they work.
\ No newline at end of file |