From 4644cb7ebb30bf88573b286c9a32cea4358cc230 Mon Sep 17 00:00:00 2001 From: Jon Davis Date: Fri, 11 Aug 2023 15:19:11 -0700 Subject: [PATCH] Ported Web Inspector docs from trac.webkit.org --- .../Deep Dive/Web Inspector/AnIntroduction.md | 144 +++++++++ docs/Deep Dive/Web Inspector/Contributing.md | 84 ++++++ .../RemoteInspectorWebKitGTKandWPE.md | 22 ++ .../Web Inspector/WebInspectorCodeStyle.md | 281 ++++++++++++++++++ .../Web Inspector/WebInspectorDebugging.md | 109 +++++++ .../Web Inspector/WebInspectorTests.md | 209 +++++++++++++ .../Web Inspector/WebReplayMechanics.md | 252 ++++++++++++++++ 7 files changed, 1101 insertions(+) create mode 100644 docs/Deep Dive/Web Inspector/AnIntroduction.md create mode 100644 docs/Deep Dive/Web Inspector/Contributing.md create mode 100644 docs/Deep Dive/Web Inspector/RemoteInspectorWebKitGTKandWPE.md create mode 100644 docs/Deep Dive/Web Inspector/WebInspectorCodeStyle.md create mode 100644 docs/Deep Dive/Web Inspector/WebInspectorDebugging.md create mode 100644 docs/Deep Dive/Web Inspector/WebInspectorTests.md create mode 100644 docs/Deep Dive/Web Inspector/WebReplayMechanics.md diff --git a/docs/Deep Dive/Web Inspector/AnIntroduction.md b/docs/Deep Dive/Web Inspector/AnIntroduction.md new file mode 100644 index 00000000..5f063d09 --- /dev/null +++ b/docs/Deep Dive/Web Inspector/AnIntroduction.md @@ -0,0 +1,144 @@ +# Introduction + +The Web Inspector allows you to view the page source, live DOM hierarchy, script debugging, profiling and more! + +## Enabling the Web Inspector + +### Safari + +* Enable the Develop menu option in the Advanced preferences. +* Use the optional toolbar button, Develop menu or Inspect Element context menu to access the Web Inspector. + +### Other WebKit clients + +* Find the application's bundle identifier. +* Enter the following command once in Terminal (inserting the bundle identifier) + +``` +defaults write "bundle-identifier-here" WebKitDeveloperExtras -bool true +``` + +* Relaunch the application in order to use the Web Inspector + +## Using the Web Inspector + +The Web Inspector can be opened by '''right clicking anywhere on a web page''' and choosing '''Inspect Element'''. Once open, it highlights the node on the page as it is selected in the hierarchy. You can also search for nodes by node name, id and CSS class name. + +The Node pane shows the current node type and name, as well as any element attributes. + +Under the Style pane we show all the CSS rules that apply to the focused node. These rules are listed in cascade order with overridden properties striked-out—letting you truly see how cascading stylesheets affect the page layout. All shorthand properties have a disclosure-triangle to show and hide the expanded properties created by the shorthand. + +The Metrics pane provides a quick visual look at how margins, borders and padding affect the current node. + +Various HTML and JavaScript properties, including length of text nodes, offsetWidth/Height, class names, and parent/sibling information are vieweable in the Properties pane. + +See [Safari User Guide for Web Developers](http://developer.apple.com/safari/library/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/UsingtheWebInspector/UsingtheWebInspector.html) for more details on other panels of the Web Inspector. + +## Hacking on the Web Inspector + +Most of the Web Inspector's code is HTML, JavaScript, and CSS—so it's very easy to implement new features and fix bugs! + +[List Web Inspector bugs and feature requests](http://tinyurl.com/2vqypl) + +## Related Blog Posts + +* [Introducing the Web Inspector](http://webkit.org/blog/41/introducing-the-web-inspector) +* [Yet another one more thing… a new Web Inspector!](http://webkit.org/blog/108/yet-another-one-more-thing-a-new-web-inspector) +* [Web Inspector Redesign](http://webkit.org/blog/197/web-inspector-redesign) +* [Web Inspector Updates](http://webkit.org/blog/829/web-inspector-updates/) +* [State of Web Inspector](https://www.webkit.org/blog/2518/state-of-web-inspector/) + +## Shortcut Keys + +### Safari + +| | Mac | Windows / Linux | +|----------------------------|-------|------------------| +| Toggle Web Inspector | ⌥⌘I | Ctrl-Alt-I | +| Show Error Console | ⌥⌘C | Ctrl-Alt-C | +| Start Profiling Javascript | ⌥⇧⌘P | Ctrl-Alt-P | + +### Web Inspector + +| | Mac | Windows / Linux | +|-----------------------------|-----|-----------------| +| Next Panel | ⌘] | Ctrl-] | +| Previous Panel | ⌘[ | Ctrl-[ | +| Toggle Console | ⎋ | Esc | +| Focus Search Box | ⌘F | Ctrl-F | +| Find Next | ⌘G | Ctrl-G | +| Find Previous | ⇧⌘G | Ctrl-Shift-G | + +### Console + +| | Mac | Windows / Linux | +|-----------------------------|----------|-----------------| +| Next Suggestion | ⇥ | Tab | +| Previous Suggestion | ⇧⇥ | Shift-Tab | +| Accept Suggestion | → | Right | +| Previous Command / Line | ↑ | Up | +| Next Command / Line | ↓ | Down | +| Previous Command | ⌃P | | +| Next Command | ⌃N | | +| Clear History | ⌘K or ⌃L | Ctrl-L | +| Execute | ⏎ | Enter | + +### Elements Panel + +| | Mac | Windows / Linux | +|-----------------------------|--------|-----------------| +| Navigate | ↑ ↓ | Up/Down | +| Expand/Collapse Node | ← → | Right/Left | +| Expand Node | Double-Click on tag | Double-Click on tag | +| Edit Attribute | ⏎ or Double-Click on attribute | Enter or Double-Click on attribute | + +### Styles Pane + +| | Mac | Windows / Linux | +|-----------------------------------|----------------------------|-----------------| +| Edit Rule | Double-Click | Double-Click | +| Edit Next/Previous Property | ⇥ / ⇧⇥ | Tab/Shift-Tab | +| Insert New Property | Double-Click on whitespace | Double-Click on whitespace | +| Increment/Decrement Value | ⌥↑ /⌥ ↓ | Alt- Up/Alt-Down | +| Increment/Decrement Value by 10 | ⌥⇧↑ / ⌥⇧↓ | Alt-Shift-Up/Alt-Shift-Down | +| Increment/Decrement Value by 10 | ⌥PageUp / ⌥PageDown | Alt-PageUp/Alt-PageDown | +| Increment/Decrement Value by 100 | ⌥⇧PageUp / ⌥⇧PageDown | Shift-PageUp/Shift-PageDown | +| Increment/Decrement Value by 0.1 | ⌃⌥↑ / ⌃⌥↓ | Control-Alt-Up/Control-Alt-Down | + +### Debugger + +| | Mac | Windows / Linux | +|-------------------------------|--------------|------------------| +| Select Next Call Frame | ⌃. | Ctrl-. | +| Select Previous Call Frame | ⌃, | Ctrl-, | +| Continue | F8 or ⌘/ | F8 or Ctrl-/ | +| Step Over | F10 or ⌘' | F10 or Ctrl-' | +| Step Into | F11 or ⌘; | F11 or Ctrl-; | +| Step Out | ⇧F11 or ⇧⌘; | Shift-F11 or Ctrl-Shift-; | +| Evaluate Selection | ⇧⌘E | Ctrl-Shift-E | +| Toggle Breakpoint Condition | Click on line number | Click on line number | +| Edit Breakpoint Condition | Right-Click on line number | Right-Click on line number | + + +## Using the Web Inspector remotely + +### Remote Web Inspector on GTK+ and EFL ports + + * For the GTK and EFL ports, this can be enabled with an environment variable. Check the documentation on EnvironmentVariables + +``` +Computer1 # export WEBKIT_INSPECTOR_SERVER=${ip.ad.dre.ss}:${port} +Computer1 # MiniBrowser http://google.com +# +Computer2 # MiniBrowser http://${ip.ad.dre.ss}:${port} +``` + + * The very same version of WebKit has to be used on the other side + +### Apple Web Inspector Remote Experiment + +[Safari User Guide for Web Developers](http://developer.apple.com/safari/library/documentation/AppleApplications/Conceptual/Safari_Developer_Guide/UsingtheWebInspector/UsingtheWebInspector.html) +In early 2010, an experiment was made to get Web Inspector to run in a plain old web page, debugging a remote web browsing session in another browser window. + +A write-up is available here: +[weinre - Web Inspector Remote](http://muellerware.org/papers/weinre/manual.html), and the relevant source and demo archives are attached to this page. diff --git a/docs/Deep Dive/Web Inspector/Contributing.md b/docs/Deep Dive/Web Inspector/Contributing.md new file mode 100644 index 00000000..e9c5272e --- /dev/null +++ b/docs/Deep Dive/Web Inspector/Contributing.md @@ -0,0 +1,84 @@ +# Contributing to Web Inspector + +## Modifying the Web Inspector + +The Web Inspector user interface is implemented using JavaScript, CSS, and HTML. So, it's relatively easy to dig into the Web Inspector's sources and fix bugs or add new features. + +This wiki page documents the minimal steps required to modify styles used by the Web Inspector and submit your changes as a patch for review. + +Let's say, we don't like red color for CSS property names, and we would prefer property names to be purple instead. Let's get started! + +## Inspect The Inspector + +Since the Web Inspector UI is just another web page, we can inspect the Web Inspector with a second-level Web Inspector instance to quickly see what's going on. This requires a few magic settings to enable the "Inspect..." context menu on the Web Inspector window. + +For the Mac port, set the following defaults to allow inspecting the inspector. + +``` +defaults write NSGlobalDomain WebKitDebugDeveloperExtrasEnabled -bool YES +``` + + +After updating these settings, run the [https://developer.apple.com/safari/technology-preview/ Safari Technology Preview]. Then, open the Web Inspector and right-click to inspect the inspector itself. + +By inspecting the CSS property names in the second-level inspector, we quickly find that the colors are defined by rules in `Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css`. +To create and submit a patch with our changes, we must to create an accompanying Bugzilla bug, and compute the diff of our changes against WebKit trunk. + +## Create / Update a Bug Report + + * [Existing Web Inspector Bugs](https://bugs.webkit.org/buglist.cgi?query_format=advanced&short_desc_type=allwordssubstr&short_desc=&component=Web+Inspector&long_desc_type=substring&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&keywords_type=allwords&keywords=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&emailassigned_to1=1&emailtype1=substring&email1=&emailassigned_to2=1&emailreporter2=1&emailcc2=1&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&chfieldfrom=&chfieldto=Now&chfieldvalue=&cmdtype=doit&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0=) + * [Create New Web Inspector Bug](http://webkit.org/new-inspector-bug) + +The WebKit project uses "bugs" in Bugzilla for fixes, new features, and any other code changes. Every commit must have an accompanying Bugzilla bug. + +So, the first step is to ensure that your proposed enhancement or fix has an associated bug. + +Once you find or create a bug report, make sure to add a comment stating your intent to work on the bug. +This step is very important; comments on bugs in the Web Inspector Bugzilla component will automatically notify Web Inspector reviewers. + +This will allow them to answer any questions you may have about a proposed fix, and give feedback, pointers, and guidance for solving the issue. + +## Now Do Your Hacking + + 1. [Get the Code](https://webkit.org/getting-the-code/) + +``` +git clone https://github.com/WebKit/WebKit.git WebKit +cd WebKit +git checkout -b purple_css_values +``` + + 2. [Build WebKit](http://webkit.org/building/build.html) + +``` +Tools/Scripts/build-webkit --release +``` + + A clean build takes 20-80 minutes depending on the vintage of your machine. + + 3. [Run it](http://webkit.org/building/run.html) + +``` +Tools/Scripts/run-minibrowser --release +``` + + 4. Edit `Source/WebInspectorUI/UserInterface/Views/SpreadsheetCSSStyleDeclarationEditor.css` within the repo. + + 5. Run `make -C Source/WebInspectorUI release` to copy files from `Source/WebInspectorUI/UserInterface` to the build directory. Do it after every time you modify Inspector's files. + + 6. Look at your changes + +``` +git status +git diff Source/WebInspectorUI/ +``` + + 7. [Submit a PR](https://webkit.org/contributing-code/#overview) and wait for a review! + +``` +git add -u +git commit +Tools/Scripts/git-webkit pull-request +``` + +If you have any questions there are always people willing to help! Just jump onto [webkit.slack.com](https://webkit.slack.com), #webkit-inspector channel. diff --git a/docs/Deep Dive/Web Inspector/RemoteInspectorWebKitGTKandWPE.md b/docs/Deep Dive/Web Inspector/RemoteInspectorWebKitGTKandWPE.md new file mode 100644 index 00000000..b1a6ceda --- /dev/null +++ b/docs/Deep Dive/Web Inspector/RemoteInspectorWebKitGTKandWPE.md @@ -0,0 +1,22 @@ +# Using the Remote Inspector with WebKitGTK+ and WPE + +The remote inspector enables debugging of web pages in environment where you might not be able to run the web inspector directly, such as WPE running in embedded targets. + +To run the remote inspector, you need to: + +- set the environment variable `WEBKIT_INSPECTOR_SERVER=ip:port` before running jsc or a browser/launcher powered by WPE or WebKitGTK+ +- enable the WebKitSettings `enable-developer-extras` + +For example: + +``` +export WEBKIT_INSPECTOR_SERVER=192.168.0.50:5000 +MiniBrowser --enable-developer-extras=true https://wpewebkit.org +``` + +Then, open another browser with the same version of WebKitGTK+ (matching the WPE version if it's the case) and open `inspector://ip:port`: + + +``` +MiniBrowser inspector://192.168.0.50:5000 +``` diff --git a/docs/Deep Dive/Web Inspector/WebInspectorCodeStyle.md b/docs/Deep Dive/Web Inspector/WebInspectorCodeStyle.md new file mode 100644 index 00000000..24b4e400 --- /dev/null +++ b/docs/Deep Dive/Web Inspector/WebInspectorCodeStyle.md @@ -0,0 +1,281 @@ +# Web Inspector Syle Guide + +These are JavaScript coding styles used in the [https://trac.webkit.org/browser/trunk/Source/WebInspectorUI/ Source/WebInspectorUI/UserInterface](https://trac.webkit.org/browser/trunk/Source/WebInspectorUI/ Source/WebInspectorUI/UserInterface) folder. + +(Note: Few if any of these guidelines are checked by `check-webkit-style`. There's a tracking bug for that: [https://bugs.webkit.org/show_bug.cgi?id=125045](https://bugs.webkit.org/show_bug.cgi?id=125045)) + +## Non-code Style + +For user-facing strings, we follow the macOS Human Interface Guidelines. + +* [Guidelines for help tags](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/Assistance.html) (aka tooltips) +* [Guidelines for UI labels](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/TerminologyWording.html#//apple_ref/doc/uid/20000957-CH15-SW4) +* [Guidelines for keyboard shortcuts](https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/OSXHIGuidelines/Keyboard.html#//apple_ref/doc/uid/20000957-CH84-SW1) + +## Localization + +* Include a unique key and comment when dealing with arbitrary strings. Use `@` in the key to denote where in the interface the string can be found. (ex: `WI.UIString("Frames")` vs `WI.UIString("Frames", "Frames @ Execution Context Picker", "Title for list of HTML subframe JavaScript execution contexts")`). + +## Tokens, spacing, indentation, syntax + +* No trailing whitespace. +* Indent with 4 spaces. +* Double quoted strings; use template strings if a bunch of interpolation is required. +* The `{` after a named, non-inlined function goes on the next line. Anywhere else, the `{` stays on the same line. +* Style for object literals is: `{key1: value1, key2: value2}`. When key and variable names coincide, use the syntax `{key}` rather than `{key: key}`. If the object is complex enough, each `key: value,` should be on its own line. +* Always include a trailing comma for object literals. +* Add new lines before and after different tasks performed in the same function. +* Else-blocks should share a line with leading `}`. +* Long promise chains should place `.then()` blocks on a new line. +* Calling a constructor with no arguments should have no parenthesis `()`. (ex: `var map = new Map;`) +* Put anonymous functions inline if they are not used as a subroutine. +* Prefer `let` to `var`, unless the variable is not used in a block scoping manner. Be careful when using `let` with `case` switches, as [all switch cases share the same block by default](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/let#Temporal_dead_zone_and_errors_with_let). Only use `const` for values that will not change between executions (i.e. actual constants). +* Use arrow functions when possible, unless it makes code less readable. See below for examples. +* For default parameters, add a space around the default assignment: `function foo(isGood = false)` +* Trivial public getters can be made a single line and moved to the top of the list of getters in a class, unless there is a corresponding setter. + +## Naming things + +* Avoid using the "on" prefix where possible. The `_onFoo` methods can just be `_foo` or `_handleFoo` (preferred for event listeners). +* New class names should use the name of the base class as a suffix. (ex: `TimelinesContentView` < `ContentView`). Exceptions: classes extending `WI.Object` (unless they are a represented object). +* Spell out `identifier` instead of `id` if not doing so would result in a name ending with capitalized `Id`. For example, just `this.id` is fine, but `this.breakpointId` should be `this.breakpointIdentifier`. +* An object's events live on the `Event` property of the constructor. Event names are properties on the `Event` object, and property values duplicate the event name, but are lowercased, hyphenated, and prefixed with the constructor name. See the skeleton example below. +* When serializing a function to be evaluated in a different execution context, such as from inspector to inspected page or layout test to inspector, make it obvious where the function is going to be evaluated. For example, if a function will be sent from Inspector context to inspected page context, the name `inspectedPage_node_getFlowInfo()` signifies that the function will be evaluated in the inspected page, with `this` bound to a node, and it performs the action `getFlowInfo`. + +## API preferences + +* Use `Map` and `Set` collections instead of plain objects if the key values are unknown or not monotonic (i.e., frequently added then removed). +* Use `hsla()` over hex or `rgba()` for colors in CSS. +* Use `for..of` syntax when performing actions on each element. Use `forEach` when chaining methods in a functional style. Use a classical for loop when doing index math. +* When using `forEach` or `map`, use an arrow function or supply the `this`-object as the optional second parameter rather than binding it. +* In promise chains, use arrow functions for lexical `this`, rather than assigning `const instance = this;' or `.bind`ing every function's `this`-argument. +* Use destructuring assignment when digging values out of a JSON object or "args" object. +* Use default parameters when it makes sense. +* Use `super` to make calls to base class (possibly overridden) methods. + +## Layering and abstractions + +* Firewall the protocol inside the Manager classes. JSON objects received from the protocol are called "payload" in the code. The payload is usually deconstructed at the Managers level and passes down as smart objects inheriting from `WI.Object`. +* Avoid accessing *View classes from *Manager or *Object classes. This is a layering violation that prevents writing tests for models. +* Avoid storing DOM elements in *Manager or *Object classes. (see above.) +* In the backend, avoid using Inspector TypeBuilders outside of InspectorAgent classes. We want to isolate protocol considerations from other functionality in JavaScriptCore and WebCore. + +## Understanding and Using Promises + +[What's so great about Promises?](http://blog.parse.com/2013/01/29/whats-so-great-about-javascript-promises/) [The point of promises is to give us back functional composition](http://domenic.me/2012/10/14/youre-missing-the-point-of-promises/) and error bubbling in the async world. They do this by saying that your functions should return a promise, which can do one of two things: + +1. Become __fulfilled__ by a **value** +2. Become __rejected__ with an **Error instance** or by throwing an exception + +A promise that is eiher fulfilled or rejected is said to be __settled__. A promise that has not settled is said to be __pending__. + +And, if you have a correctly implemented `then()` function, then fulfillment and rejection will compose just like their synchronous counterparts, with fulfillments flowing up a compositional chain, but being interrupted at any time by a rejection that is only handled by someone who declares they are ready to handle it. + +### Promise Gotchas + +(Summarized from [change.org Blog](http://making.change.org/post/69613524472/promises-and-error-handling) and [The Art of Code Blog](http://taoofcode.net/promise-anti-patterns/)) + +* Don't nest promises to perform multiple async operations; instead, chain them or use `Promise.all()`. +* Beware of storing or returning promise values that are not from the end of a chain. Each `.then()` returns a new promise value, so return the last promise. +* Use `Promise.all()` with `map()` to process an array of asynchronous work in parallel. Use `Promise.all()` with `reduce()` to sequence an array asynchronous work. +* If a result may be a promise or an actual value, wrap the value in a promise, e.g., `Promise.resolve(val)` +* Use `.catch()` at the end of a chain to perform error handling. '''Most promise chains should have a catch block to avoid dropping errors'''. +* To reject a promise, throw an `Error` instance or call the `reject` callback with an `Error` instance. +* A `.catch()` block is considered resolved if it does not re-throw an `Error` instance. Re-throw if you want to log an error message and allow other parts of a chain (i.e, an API client) to handle an error condition. +* Don't directly pass a promise's `resolve` function to `Object.addEventListener`, as it will leak the promise if the event never fires. Instead, use a single-fire `WI.EventListener` object defined outside of the promise chain and connect it inside a `.then()` body. Inside the `.catch` block, disconnect the `EventListener` if necessary. +* For APIs that return promises, document what the fulfilled value will be, if any. Example: `createSession() // --> (sessionId)` + +## Arrow Functions + +Arrow functions simplify a common use of anonymous functions by providing a shorter syntax, lexical binding of `this` and `arguments`, and implicit return. While this new syntax enables new levels of terse code, we must take care to keep our code readable. + +### Implicit return + +Arrow functions with one expression have an implicit return. All of these are equivalent (modulo `this` binding, arguments, constructor usage, etc.): + +``` +1 let foo = val => val; +2 let foo = (val) => val +3 let foo = (val) => val; +4 let foo = (val) => { return value++; }; +5 let foo = (val) => { + return value++; + }; +6 let foo = function doStuff(val) { return value++; }; +7 let foo = function doStuff(val) { + return value++; + }; +``` + +Never use option (1), because it is a special case that only applies when the function has one argument, reducing predictability. + +In cases where the return value is used and the single expression is a constant ("foo"), a variable (foo), a member (this.foo), or evaluates to a Promise, use option (2). Never use braces though, because implicit return only works if there are no braces around the single expression. + +In cases where the expression computes a value (a + 42) or performs a side effect (++a), prefer option 5. +In some sense, curly braces are a signpost to the effect of "careful, we do actual work here". + +If the implicit return is not used (4, 5, 6, 7), always put the function body on new lines from the `{` and `}` (as demonstrated in 5 and 7). + +GOOD: + +``` +setTimeout(() => { + testRunner.notifyDone(); +}, 0); +``` + +BAD: + +``` +// return value not implicitly returned + +setTimeout(() => { + testRunner.notifyDone() +}, 0); +``` + + +``` +// implicit return value not used + +setTimeout(() => testRunner.notifyDone(), 0); +``` + +### When not to arrow + +When assigning a function to a subclass prototype (in the old way of setting up classes), always use the normal function syntax, to avoid breaking subclasses who use a different 'this' binding. Note that arrow functions are NOT acceptable for assigning functions to singleton objects like `WI`, since the captured lexical `this` is typically the global object. + +GOOD: + +``` +Base.prototype.compute = function(a, b, c) { + // ... +}; + +Foo.prototype.compute = function(a, b, c) { + Base.prototype.compute.call(this, a, b, c); +}; + +WI.UIString = function(format, args) { + // ... +}; +``` + +BAD: + +``` +// `this` will be `window` + +Base.prototype.compute = (a, b, c) => { + // ... +}; + +Foo.prototype.compute = (a, b, c) => { + Base.prototype.compute.call(this, a, b, c); +}; + +WI.UIString = (format, args) => { + // ... +}; +``` + +Also use the normal function syntax when naming an anonymous function improves readability of the code. In this case, use Function.prototype.bind or assign the arrow function into a local variable first. + +GOOD: + +``` +Promise.resolve().then( + function resolved(value) { ... }, + function rejected(value) { ... } +); +``` + +BAD: + +``` +Promise.resolve().then( + (value) => { ... }, + (value) => { ... } +); +``` + + +## New class skeleton + +New Inspector object classes use ES6 class syntax and should have the following format: + +``` +WI.NewObjectType = class NewObjectType extends WI.Object +{ + constructor(type, param) + { + console.assert(param instanceof WI.ExpectedType); + + super(); + + this._type = type; + this._propertyName = param; + } + + // Static + + static computeBestWidth(things) + { + // ... + return 3.14159; + } + + // Public + + get type() { return this._type; } + + get propertyName() + { + return this._propertyName; + } + + set propertyName(value) + { + this._propertyName = value; + this.dispatchEventToListeners(WI.NewObjectType.Event.PropertyWasChanged); + } + + publicMethod() + { + /* public methods called outside the class */ + } + + // Protected + + protectedMethod(event) + { + /* delegate methods and overrides */ + } + + // Private + + _privateMethod() + { + /* private methods are underscore prefixed */ + } +}; + +WI.NewObjectType.Event = { + PropertyWasChanged: "new-object-type-property-was-changed", +}; + +``` + +## CSS + +### z-index + +Z-index variables are defined in [Variables.css](https://trac.webkit.org/browser/trunk/Source/WebInspectorUI/UserInterface/Views/Variables.css). Usage example: + +``` +.popover { + z-index: var(--z-index-popover); +} +``` + +Read more about the rationale in [Bug 151978](https://bugs.webkit.org/show_bug.cgi?id=151978). diff --git a/docs/Deep Dive/Web Inspector/WebInspectorDebugging.md b/docs/Deep Dive/Web Inspector/WebInspectorDebugging.md new file mode 100644 index 00000000..a351959e --- /dev/null +++ b/docs/Deep Dive/Web Inspector/WebInspectorDebugging.md @@ -0,0 +1,109 @@ +# Debugging the Web Inspector + +This page contains tips and suggested workflows for isolating, understanding, and fixing code in the Web Inspector, particularly in the user interface. + +## Inspecting the Inspector + +For the Mac port, set the following defaults to allow inspecting a '''local''' Web Inspector. + +``` +defaults write com.apple.Safari WebKitDeveloperExtrasEnabled -bool YES +defaults write com.apple.Safari WebKitDebugDeveloperExtrasEnabled -bool YES +``` + +NOTE: You may need to first give Terminal Full Disk Access. Remember to turn this off afterwards. + +``` +System Preferences > Security & Privacy > Privacy give Terminal "Full Disk Access" +``` + + +## Rebuilding When Files Change + +The Web Inspector interface is loaded from the build directory (./WebKitBuild/), not the source tree (./Source/WebInspectorUI/). +Its code is not compiled like other parts of WebKit, but it is processed by scripts that copy its resources to the build directory. +Thus, to see changes you've made to Web Inspector's JS, CSS, or images, you must re-run the inspector build scripts. This can be done without recompiling all of WebKit by running the following: + +``` +make -C Source/WebInspectorUI/ release +``` + + +To automate this step, you can connect the above command to `entr`. +The `entr(1)` tool (http://entrproject.org/) can perform an action when it detects that files have changed. +The following command will run indefinitely, invoking the inspector's build scripts whenever any interface files change. + +``` +find -E Source/WebInspectorUI/ -regex ".*\.(js|css|html|svg|png)" | entr make -C Source/WebInspectorUI/ release +``` + +Then, you can open and close the inspector (or reload with Cmd+R) to see the new changes. + +NOTE: depending on your system configuration, you may need to adjust the maximum open files limit for entr to work in this case. There are approximately 1000 inspector files, so this can be fixed with the following: + +``` +ulimit -n 2048 +``` + +## Using Logging inside WebInspectorUI + +To log console messages from the inspected page and inspector pages to the system console, set the following preferences. + +``` +defaults write com.apple.Safari "com.apple.Safari.ContentGroupPageIdentifier.WebKit2LogsPageMessagesToSystemConsoleEnabled" -bool YES +defaults write com.apple.Safari WebKitLogsPageMessagesToSystemConsoleEnabled -bool YES +defaults write com.apple.Safari WebKitDebugLogsPageMessagesToSystemConsoleEnabled -bool YES +``` + +Using `console.log` and friends in the inspector interface's code will log messages in the next-level inspector. +However, both will be interleaved if you enable output to the system console as above. + +# Tips for Debugging Tests + +## Force Synchronous TestHarness Output + +Setting `InspectorTest.debug()` will log all inspector protocol traffic and `console.log` output to stderr which can be observed when the test completes or times out. + +Setting `InspectorTest.forceDebugLogging = true` will force all test output to be emitted via window.alert, which in a LayoutTest will add a message to test output without modifying the test page. + +This is useful if you suspect problems in the test harness itself, or if the test crashes before writing buffered output into the test page (which is usually scraped to produce the test output). + +## Logging to System Console/stderr While Running Tests + +This is basically the same as above, except that the defaults domain is different. Since the test executable WebKitTestRunner resets its domain defaults on every run, you must set logging defaults globally. This is not recommended for other purposes since it may cause unrelated WebKit instances to log lots of messages. + +``` +defaults write -g "com.apple.Safari.ContentGroupPageIdentifier.WebKit2LogsPageMessagesToSystemConsoleEnabled" -bool YES +defaults write -g WebKitLogsPageMessagesToSystemConsoleEnabled -bool YES +defaults write -g WebKitDebugLogsPageMessagesToSystemConsoleEnabled -bool YES +``` + +## Disabling Minification and Concatenation + +By default, all Inspector resources are combined in a single file to minimize the time spent loading many small local files through WebKit's loading infrastructure. Unfortunately, this can make stack traces in test output hard to read. To disable combining of test resources: + +### On Mac + +Go to the file: + +``` +./OpenSource/Source/WebInspectorUI/Configurations/DebugRelease.xcconfig +``` + +and set `COMBINE_TEST_RESOURCES = NO`. Then rebuild the WebInspectorUI project: + +``` +make -C OpenSource/Source/WebInspectorUI/ release +``` + +and run your test again. + +### On Linux GTK + +Add `COMBINE_TEST_RESOURCES=NO` to `--cmakeargs`. In Debug build inspector resources are not combined by default, if you want to run Release binary but disable combining of inspector UI resources add `COMBINE_INSPECTOR_RESOURCES=NO`. Build WebKit: + +``` +build-webkit --gtk --cmakeargs="-DCOMBINE_INSPECTOR_RESOURCES=NO -DCOMBINE_TEST_RESOURCES=NO" +``` + +and run your test again. diff --git a/docs/Deep Dive/Web Inspector/WebInspectorTests.md b/docs/Deep Dive/Web Inspector/WebInspectorTests.md new file mode 100644 index 00000000..93b0f9b9 --- /dev/null +++ b/docs/Deep Dive/Web Inspector/WebInspectorTests.md @@ -0,0 +1,209 @@ +# Writing Web Inspector Tests + +This page describes how various parts of the Web Inspector are tested. + +----- + +## Types of Tests + +There are several types of inspector tests: + + * **Protocol Tests** exercise the **inspector backend** independently of any particular frontend. + * **Frontend Tests** exercise the **models and controllers** underlying the Web Inspector user interface. + * **Manual Tests** exercise the user interface in ways that are difficult to automate or require infrastructure that doesn't exist yet. + * **Library Tests** exercise subsystems such as pretty printing or the protocol generator in isolation from a running Web Inspector instance. + +To date, the Web Inspector has no automated tests that exercise the user interface. In practice, the Inspector UI changes frequently, so such tests tend to be brittle, and have traditionally not been worth the trouble of maintaining. + +## How Tests Execute + + Each test is an HTML file in a per-domain directory within `LayoutTests/inspector/` or `LayoutTests/http/tests/inspector/` (for tests that load files over HTTP). Some tests may additionally include external files, which are included in special `resources/` directories that are automatically excluded from the test search path. All tests must decide which test harness to use by including either `protocol-test.js` or `inspector-test.js`. + + When the test page finishes loading, it calls the `runTest()` method provided, which signals the test harness to set up a test inspector instance that inspects the test page. Each test page defines a special `test()` method, which is automatically marshalled and injected into the Inspector instance's context. Most scripts execute in the inspector's JavaScript context, and occasionally evaluate some code in the test page's context to log test results and to trigger specific inspectable behaviors. + +## Protocol Tests + +Protocol tests are appropriate for testing inspector features that require the use of a few commands and events between the backend and frontend, and do not require the inspected page to be reloaded. Protocol tests are fairly low-level, and exercise the Inspector backend independent of a particular frontend and its controllers and models. In other words, you cannot test Managers or other classes in the WebInspector namespace using a protocol test. + +The `protocol-test.js` stub creates a dummy inspector frontend by using `window.open()` from the test page, and establishes bidirectional communication with the __child__ inspector page using `window.postMessage` and a `message` event handler. The "inspector" page that is loaded into the iframe is located at `Source/WebInspectorUI/Base/TestStub.html`. The code that runs inside the Inspector frame (i.e., code within the test() method) has access to the protocol test harness, whose methods are prefixed with `ProtocolTest`. Protocol-specific methods for sending commands and awaiting on events are available in the InspectorProtocol namespace. + +## Frontend Tests + +Frontend tests exercise the functionality of models and controllers specific to WebInspectorUI (the user interface included in WebKit trunk). They use a real, headless Web Inspector frontend that persists across navigations of the inspected (test) page. + +The `inspector-test.js` stub creates a real (for WebKit2, separate process) inspector frontend. Instead of the normal Web Inspector base page (`Main.html`), it loads a smaller version (`Test.html`) which does not load Views and other code not used by tests. The code that runs inside the Inspector (i.e., code within the test() method) has access to the frontend test harness, whose methods are prefixed with `InspectorTest`. Like ordinary Web Inspector code, injected inspector code has full access to models and controllers in the `WebInspector` namespace. (However, as noted above, not all files are loaded in the test version of the Inspector. You may need to add additional files to `Test.html` when testing new code or adding inter-class dependencies.) + +## Manual Tests + +A manual test requires manual interaction with a test page to exercise specific behaviors. The test page should describe the necessary interaction steps, and the expected output/behavior. + +## Library Tests + + * Pretty printing tests: these cover behavior of our pretty-printing code, and should be converted into layout tests. + * Protocol generator tests: these test inputs to the generator are designed to detect changes in the protocol generator's code generation routines. They do not contain any assertions. To run the tests, execute `Tools/Scripts/run-inspector-generator-tests`. + * TODO: do we have other ad-hoc tests? + +----- + +## How to Write Tests + +The properties that we strive for when writing inspector tests are: + + * **consistent**: a test should be consistent between runs, and not sporadically fail or time out. + * **robust**: a test should be robust to underlying changes in data structures or other minor changes to the code being exercised. It should not require frequent adjustments. + * **high coverage**: to uncover bugs and unaddressed situations, a test should exercise as many normal and exceptional code paths as possible. + * **self-documenting**: a test should act as executable documentation for the expected and unexpected use cases or behaviors of the code being exercised. + +With these properties in mind, here are a few hints for writing good tests: + + * Use good names in the test filename (`inspector/domain/description-of-test.html`), test suite name (`Domain.descriptionOfTest`), and in each test case's name (`TestSomethingInteresting`) and description (`"This test ensures something interesting"`). + * Use `AsyncTestSuite` (documented below) to avoid common pitfalls involved in testing asynchronous code, such as when testing the result of a command sent to the inspector backend. + * Use assertions to test invariants, pre-conditions, and post-conditions. Assertions should not need to be changed unless the code under test changes in significant ways. + * Assertions should always document the expected condition in the message, usually using obligatory language such as "should be", "should contain", "should not", etc. For example, the following shows a good and bad assertion message: + +``` +InspectorTest.expectThat(fontFamilyNames.length >= 5, "Has at least 5 fonts"); // BAD! +InspectorTest.expectThat(fontFamilyNames.length >= 5, "Family names list should contain at least 5 fonts"); // GOOD! +``` + +* Use `expectThat` instead of `assert` whenever possible. The former will always add output to the test, prefixing the condition with `PASS:` or `FAIL:`; the latter only produces output when the condition evaluates to false. Why should the default be to always log? For someone trying to understand how a test works or debug a failing test, the extra output is very helpful in understanding control flow. +* Log runtime states sparingly, and only those that may help to diagnose a failing test. Logging runtime states can make tests more self-documenting at the expense of reducing robustness. For example, if a test logs source code locations, these could change (and cause the test to fail) if text is added to or removed from relevant file. It is better to assert actual output against known-good outputs. Don't dump runtime state that is machine-dependent. + +## Assertion Matchers + +``` +InspectorTest.expectThat +InspectorTest.expectFalse +InspectorTest.expectNull +InspectorTest.expectNotNull +InspectorTest.expectEqual +InspectorTest.expectNotEqual +InspectorTest.expectShallowEqual +InspectorTest.expectNotShallowEqual +InspectorTest.expectEqualWithAccuracy +InspectorTest.expectLessThan +InspectorTest.expectLessThanOrEqual +InspectorTest.expectGreaterThan +InspectorTest.expectGreaterThanOrEqual +``` + +## Important Test Fixtures + +Common to both protocol tests and frontend tests are the `TestHarness` and `TestSuite` classes. TestHarness and its subclasses (bound to the globals ProtocolTest or InspectorTest) provide basic mechanisms for logging, asserting, and starting or stopping the test. Protocol and frontend tests each have their own subclass of `TestHarness` which contains methods specific to one environment. + +`TestSuite` and its subclasses `AsyncTestSuite` and `SyncTestSuite` help us to write robust, well-documented, and fast tests. All new tests should use these classes. Each test file consists of one (or more) test suite(s). A suite consists of multiple test cases which execute sequentially in the order that they are added. If a test case fails, later test cases are skipped to avoid spurious failures caused by dependencies between test cases. Test cases are added to the suite imperatively, and then executed using the `runTestCases()` or `runTestCasesAndFinish()` methods. This allows for programmatic construction of test suites that exercise code using many different inputs. + +A `SyncTestSuite` executes its test cases synchronously, one after another in a loop. It is usually used for unit tests that do not require communication with the backend. Each test case provides a test method which takes no arguments and returns `true` or `false` to indicate test success or failure, respectively. + +An `AsyncTestSuite` executes its test cases asynchronously, one after another, by chaining together promises created for each test. Each test case provides a test method which takes two callback arguments: `resolve` and `reject`. At runtime, each test method is turned into a Promise; like a Promise, the test signals success by calling `resolve()`, and signals failure by calling `reject()` or throwing an `Error` instance. + +## How to Debug Tests + +In general, the strategies for [wiki:"WebInspectorDebugging" debugging the Web Inspector] and debugging WebCore/WebKit2 apply the same to debugging inspector tests. Sometimes, tests can be more difficult to debug because the test harness' marshalling code can be broken by incorrectly written tests or bugs in the test harness. The test stubs provide several flags that enable extra or more reliable logging for debug purposes. Flags can be set in the corresponding `Test/TestStub.html` file for all test runs, or at the top of a `test()` method to only affect one test. + +For protocol tests: + +``` +// Debug logging is synchronous on the test page. +ProtocolTest.forceDebugLogging = false; + +// Tee all TestHarness commands to stderr from within the Inspector. +ProtocolTest.dumpActivityToSystemConsole = false; + +// Best used in combination with dumpActivityToSystemConsole. +ProtocolTest.dumpInspectorProtocolMessages = false; + +// Enables all of the above. +ProtocolTest.debug(); +``` + +For frontend tests: + +``` +// Debug logging is synchronous on the test page. +InspectorTest.forceDebugLogging = false; + +// Tee all TestHarness commands to stderr from within the Inspector. +InspectorTest.dumpActivityToSystemConsole = false; + +// Best used in combination with dumpActivityToSystemConsole. +InspectorBackend.dumpInspectorProtocolMessages = false; + +// Enables all of the above. +InspectorTest.debug(); +``` + +### Attaching a Debugger to Tests with DumpRenderTree (WebKit1) + +``` +$ DYLD_FRAMEWORK_PATH=WebKitBuild/Debug lldb -- WebKitBuild/Debug/DumpRenderTree +(lldb) run LayoutTests/inspector/dom/focus.html +``` + +To run DumpRenderTree in "server mode", which the run-webkit-tests uses to run multiple tests without restarting the process, make a file called "tests-to-run.txt" with one test per line, and launch this way instead: + +``` +(lldb) process launch -i tests-to-run.txt "-" +``` + +### Attaching a Debugger to Tests with WebKitTestRunner (WebKit2) + +TODO + +# Example Test (uses inspector-test.js, AsyncTestSuite) + +``` + + + + + + + +

Test CSS.createStyleSheet.

+ + +``` diff --git a/docs/Deep Dive/Web Inspector/WebReplayMechanics.md b/docs/Deep Dive/Web Inspector/WebReplayMechanics.md new file mode 100644 index 00000000..1091fac0 --- /dev/null +++ b/docs/Deep Dive/Web Inspector/WebReplayMechanics.md @@ -0,0 +1,252 @@ +# Web Replay Mechanics + +*Brian Burg * + +---- + +Web replay is a new technology for low-overhead deterministic replay of web applications. This document explains how the feature is implemented in the WebKit engine, with a focus on the replay infrastructure and how it integrates with other parts of WebKit. (It does not describe the various UI integrations of replay functionality with the WebKit Web Inspector.) It is intended for consumption by other WebKit engineers and browser hackers. Where the text describes planned work or work-in-progress, the relevant Bugzilla bugs are linked. + +For more background on the origins and research context of this technology, the reader should see the paper published at UIST 2013: [Interactive Record/Replay for Web Application Debugging](http://homes.cs.washington.edu/~mernst/pubs/record-replay-uist2013.pdf) + +---- + +## Why Web Replay? + +Web replay is important because it is a fundamental building block for the next generation of end-user bug reporting, performance testing, and interactive developer tools: + +* By capturing a buggy execution of a web application interaction into a self-contained replayable file, an end-user can provide unambiguous, executable "reproduction steps" for an issue that they are experiencing on the client-side. + +* By capturing executions of normal browsing behavior and saving them to disk, we can re-execute on new versions of WebKit to measure performance regressions in rendering real-world web applications. + +* Developer tools are able to re-execute a specific execution and gather additional runtime data after the program has already run. A developer can interactively probe deeper into what exactly happened during an execution, rather than guessing about what happened and speculatively logging evidence to the console. + +---- + +## Background + +Deterministic replay is a well-established family of VM and runtime techniques for capturing and reproducing a specific execution of a program. Replay uses the observation that a program, absent any sources of non-determinism, will behave deterministically: that is, it always perform the same computations and arrive at the same result. + +Deterministic replay systems manipulate sources of non-determinism so that an execution will proceed deterministically when desired. Deterministic replay actually has two phases: record and replay. During recording, non-determinism is interposed and saved into a log by some mechanism; during replaying, related mechanisms re-use the saved non-deterministic data while disabling new sources of non-determinism. + +Sources of non-determinism vary by programming environment, but generally speaking, most deterministic replay systems must deal with the following sources: execution schedules (thread schedules and/or event queue schedules); the environment (filesystem, network, current time, system state, window dimensions, etc), and user (or device) input. For example, hypervisor-based deterministic replay is concerned with system calls, interrupt timings and orderings, thread schedules, and nondeterministic instructions. The web has its own set of non-deterministic sources, which will be covered below. + +---- + +## Implementation + +Web replay (feature flag: `WEB_REPLAY`) is the deterministic replay system implemented in WebKit. Its implementation strategy is adapted from approaches most commonly used by virtual machine deterministic replay systems. Its design exploits the resemblance between virtual machines and web browsers as platforms for executing untrusted, sandboxed code. By adding determinism mechanisms at the same points as in VMs, we have confidence that the technical approach will scale to all sources of non-determinism in WebKit. + +Thus far, the implementation has scaled to replaying sites as complicated as Twitter (in a staging branch, [github.com/burg/replay-staging/](https://github.com/burg/replay-staging/)). However, there are still many engine features that are not yet handled correctly. + +### On Web vs Browser Nondeterminism + +Web replay is primarily intended for use by web developers via the Web Inspector, so we focus specifically on nondeterminism that may factor into bugs in user applications. Concretely, this means that *all JavaScript code within a specific `Page` runs in the same order and computes the same result*. Working backwards from things that transitively cause JavaScript to execute, we can also say that *DOM events, Timers, and resource loader callbacks must also run in the same order*. + +Conversely, there are many sources of nondeterminism in the browser and rendering engine that do **not** cause JavaScript to run, and thus can be ignored. For example, several parts of layout and rendering are quite nondeterministic but this nondeterminism does not affect JavaScript computation. (In cases where JS needs to query layout information, script will synchronously wait for layout results.) Other irrelevant nondeterminism includes 'script-transparent' optimizations in JSC (i.e., GC and JIT), code that runs in the Web Inspector itself, and other background activities that don't cause script to run in the replayed page. + +Web replay recordings may be useful for browser hackers as well (to reproduce a flaky failure or crash) but this is not the main goal of Web Replay. Projects like the `rr` library focus on whole-browser deterministic replay. + +### Replay Infrastructure + +Web Replay is a page-level property; the entire frame tree for a page is captured or deterministically replayed together. `ReplayController` (owned by `Page`) is the coordinator of all replay activities. Each `Document` and `JSGlobalObject` has a reference to an `InputCursor`, which represents the page's current position in the recording during capturing or playback. InputCursor is also a gatekeeper to the saved recording, allowing clients to save and load nondeterministic inputs through it. A page cannot be capturing and replaying at the same time; to enforce this, separate InputCursor subclasses exist (`CaptureInputCursor` and `ReplayInputCursor`). As documents attach and detach from frames, they start and stop contributing to the determinism captured in the overall replay recording. + +Since web replay is designed to be be used from the Web Inspector, the `Replay` domain (and `ReplayAgent`) is the main API for controlling capture and replay. `ReplayController` also receives a few inspector callbacks through `ReplayAgent` (`didCommitLoad` and `will`/`didDispatchEVent`) but these could be moved elsewhere to support running web replay through an API independent of the Web Inspector. + +### Replay Mechanisms + +Nondeterminism of web content can be categorized into **event loop inputs** and **memoized inputs**. Event loop inputs are sources of nondeterminism that are conceptually executed sequentially in the main run loop. For example, user inputs, network callbacks, and asynchronous timers are event loop inputs. Memoized inputs are sources of nondeterminism that act as nondeterministic data sources, but are not executed themselves. For example, `Date.now()`, `document.cookie`, and most of `window.navigator` can provide nondeterministic data to executing scripts. + +Web Replay uses a variety of tricks to accurately capture and replay sources of nondeterminism. The main way that we capture nondeterminism is to insert branches at control flow points that handle nondeterministic inputs. The branch checks whether the associated `Document`'s `InputCursor` is currently capturing, and if so, saves a copy of the nondeterministic data (a generated subclass of `NondeterministicInput` or `EventLoopInput`) into the active recording. + +To memoize or reuse the return values of DOM APIs as they are passed to script, we have added the `Nondeterministic` IDL property that automatically generates this branch in the JS bindings. Specializations of the `MemoizedDOMResult` class are used to store this data using a ctypes-like API. + +To replay nondeterministic event loop inputs, we implemented a synthetic event loop called `EventLoopInputDispatcher`, which asynchronously dispatches these inputs in order. During "real time" playback mode, the dispatcher uses observed run loop timings to simulate the user's original interactions. This dispatcher can also "pause" playback by simply not dispatching any more event loop inputs and suspending active DOM objects in replayed script contexts. + +For event loop inputs, it is generally simpler and less buggy to capture and replay the nondeterminism as "early" as possible in the web content process. In the case of a mouse click, we could capture/replay at the level of DOM events, `EventHandler::handleMouse`, `WebPage::handleMouse`. However, replaying at a later point in control flow will often cause the rendering engine to be in an inconsistent state and miss important (typically non-DOM event) changes. For example, simulating DOM "click" events rather than calling `WebPage::handleMouseFoo` will cause the focus controller to miss some user inputs. + +All known nondeterministic inputs are defined in JSON files, grouped by the framework (JSC, WebCore, WK2) in which the nondeterminism occurs. The CodeGeneratorReplayInputs.py script generates simple classes for each input type and input encode/decode methods for serialization. + +### Testing Strategy + +Testing the web replay feature requires several complementary testing strategies. + +#### Automated Value Testing + +Tests that exercise handling of value-oriented (as opposed to scheduling-oriented) nondeterminism are straightforward to write. A prototypical test page computes a diffable result using nondeterministic values. To detect unhandled nondeterminism a replay test library captures and subsequently replays the test page, and the test fails if the computed result differs on capturing and replaying. + + * Network replay and cookies can be exercised by http tests that return random content and/or values. + * Random numbers, current time, and similar stateless DOM APIs are trivial. + +#### Automated Schedule Testing + +The nondeterminism of task ordering/scheduling (and attempts to make it deterministic for replay purposes) is inherently hard to test with confidence. The main issue is that event loop turns that cause script to execute must always run in the same order. However, we do not know all of the sources of nondeterminism, thus we don't know which event loop turns may or may not be permuted. + +For example, a DOM timer may be serviced before or after an internal `Timer`, depending on non-local effects such as system load. If the Timer could cause nondeterministic effects, then it too should fire deterministically. However, if such a test were to fail, we could not conclusively say that the Timer was to blame. Other web features (which we don't yet know about) may have perturbed the schedule by adding their own nondeterministic actions into the event loop. + + +#### User Input Testing + +For actions such as mouse, keyboard, and touch inputs, it is difficult to perform automated testing that can actually detect unhandled nondeterminism. For example, a layout test that programmatically submits mouse inputs will always be deterministic because script execution in the test page is deterministic. Testing of device input replay would have to be done using native events, such as through a TestWebKitAPI test. + +For now, replay of device inputs is covered by manual tests which dump user event data (`MouseEvent`, etc) and present an easy-to-verify hash which should be the same on capture and subsequent playbacks. + +### Supported Environment + +ENABLE(WEB_REPLAY) is enabled by default for the Mac port. It should compile for other ports, and there is no reason why it wouldn't work for EFL/GTK. There are very few examples of platform-specific nondeterminism that is relevant to replaying web content. + +The current approach to capturing inputs requires intercept hooks in WebKit/WebKit2 or between them and WebCore. Only WebKit2 support is planned, since several important Web Inspector replay integrations are incompatible with a single-process+nested run loop execution model (details: [https://webkit.org/b/135830](https://webkit.org/b/135830)). + +### Error Checking + +Web browsers contain many sources of nondeterminism, and it is inevitable that we may not handle (or even know about!) all of these sources. Nevertheless, we would like to minimize the impact of missed nondeterminism, and make it easier to diagnose instances of replay divergence. To these ends, several error-checking facilities have been implemented. + +We can make several assertions during capturing or deterministic playback in order to detect bugs and unhandled nondeterminism: + + * If a DOM event fires during capture/replay and we didn't capture or replay the originating event loop input, then an unhandled nondeterministic event loop input is running. + * The number of DOM events and memoized inputs encountered per event loop input should be the same during capturing and subsequent playbacks. + +In some cases, it makes sense to check for deterministic creation of specific objects (in particular, DOMTimers and ResourceLoaders). This is accomplished by saving an ordinal as a memoized input during capturing, and comparing it to the observed ordinal during playback. A discrepancy can reveal unhandled nondeterminism which caused the object initialization. + +[Web Replay: detect replay divergence in number of memoized inputs used per event loop input](https://webkit.org/b/131287) + +[Web Replay: detect replay divergence in number of DOM events dispatched per EventLoopInput](https://webkit.org/b/129695) + +Many times, the assertions above will detect "benevolent", or harmless, nondeterminism. For example, an inconsistent number of DOM events may be fired during a playback, but if no listeners respond to the events, then the program will still execute deterministically. Right now, divergence detection is implemented as ASSERTs; it would be better in the long run to log the problem to the console and continue on a best-effort basis. + +[Web Inspector: gracefully report web replay errors in the Inspector console](https://webkit.org/b/131279) + +### Executions and Sessions + +Because web replay is a page-level property, we must start capturing a recording at a main frame navigation. Capturing starts by performing a full refresh to cause a navigation of the main frame. Similarly, playback begins by requesting a navigation to the same page. Originally, replay recordings could span multiple main frame navigations, forming one large recording. This is simpler to implement, but has drawbacks: + + * To replay to points near the end of the recording, all previous main frame navigations must be executed. There's no way to start replaying from the middle. + * Passively capturing all nondeterminism (for example, whenever the inspector is open) is infeasible, as monolithic recordings can never be pruned. + +To cater to these use cases, a more flexible session-based API is being pursued. A //replay segment// is a self-contained replay recording that spans between two main frame navigations. A //replay session// consists of multiple replay segments that are replayed in sequence. When main frame navigations happen during capturing, the replay infrastructure ends the current segment and appends a new one. + +Because segments are self-contained, playback can begin from any segment, rather than only the start of the session. Segments can also be rearranged, added or removed independently. This supports a ring-buffer approach to passively capturing nondeterminism, where the oldest segments are pruned as new segments are added. + +A significant challenge in implementing the sessions+segments approach is disentangling the interleaved execution of two main frames into two independent segments. When a main frame navigation happens, for a time there are actually two main frames running script: the outgoing document and incoming (aka provisional) document. During capturing, the replay controller must disambiguate actions between the two segments. Any persistent state necessary to start playback from the replay segment is saved as initialization inputs. This includes state such as the back-forward list and script-accessible history. During playback, main frame navigations between two replay segments do not cause interleaved execution, because segment order can change. + +[Web Replay: support multi-segment replay sessions](https://webkit.org/b/131989) + +[Web Replay: save and restore page history state at main frame navigations](https://webkit.org/b/131043) + + +### Replaying Specific Web Features + +#### User interactions + +User can trigger rich interactions with web pages. In all cases, these inputs (keyboard, touch, mouse, wheel, resizing, etc) come from the OS, then to WebKit/WebKit2, and then to WebCore. These are captured/replayed in `UserInputBridge`, which tees inputs during capturing and pumps saved inputs during playback (while ignoring new user inputs). + +Navigations and other top-level commands can be captured in the same way as input device interactions. (History commands require additional support, see below.) + +[Web Replay: capture and replay Reload, Navigate, and Stop commands](https://webkit.org/b/129447) + +[Web Replay: replay history navigations originating from WK2/UIProcess](https://webkit.org/b/131084) + +In the short term, we want to move capturing of user interactions directly into `WebPage.cpp` rather than using a proxy-like class. This allows replay to use fewer, higher-level inputs such as `HandleMouseEvent` (corresponding to a `WebMouseEvent`) rather than several derivatives such as separate inputs for Mouse Press/Release/Move. This refactoring is tracked by a meta-bug [https://bugs.webkit.org/showdependencytree.cgi?id=136294&hide/resolved=1](https://bugs.webkit.org/showdependencytree.cgi?id=136294&hide/resolved=1) + + +#### ResourceLoader callbacks + +Callbacks during resource loading are nondeterministic both in their content (HTTP headers, response data) and in their scheduling. For purposes of deterministic JS execution, it is important that resource loading callbacks execute in a well-defined order on the WebProcess main thread with the same data. It is not important whether networking activity that happens before main thread processing (loading in network process, going through OS network stack) is deterministic, as long as the same main thread callbacks happen. + +The current implementation (3x PFR below) intercepts callbacks inside of `ResourceLoader` class during capturing. During playback, the resource is simply not scheduled, ensuring that all loading callbacks for the resource come from replayed inputs. This design is agnostic to whether the NetworkProcess is used or not, since interception happpens after resource data is sent to WebProcess, and requests must be scheduled to be sent through NetworkProcess/platform networking libraries. + +Network replay inputs consist of copied callback data and an ordinal representing the related `ResourceLoader` instance. In order to retrieve the `ResourceLoader` and simulate callbacks to it during playback, the main frame's `DocumentLoader**` maintains a mapping between resource loaders' ordinal and unique identifier. During playback, a `ResourceLoader` is obtained by doing a lookup of the ordinal and corresponding unique identifier Note that this depends on deterministic `ResourceLoader` initialization; WebCore resource loaders are initiated by WebCore itself, and depend on deterministic parsing and script execution. + +Crucially, deterministic resource loading can be affected by caching in WebCore. Suppose a resource loads from network during capture, but loads from the WebCore cache during playback. In the latter case, loading the resource will cause a different series of callbacks, causing different RunLoop interleavings that leads to replay divergence. + +The first patch below moves unique identifier assignment and ordinal tracking to the main frame's document loader. This was chosen as the main frame document loader closely corresponds to the extent of a `ReplaySessionSegment`. Alternatively, each document loader could store its own ordinal mapping and the replay input could also store a document index. + +[Web Replay: Move createUniqueIdentifier() from ProgressTracker to DocumentLoader](https://webkit.org/b/130865) + +[Web Replay: add page-level setting to bypass the MemoryCache](https://webkit.org/b/130728) + +[Web Replay: capture and replay ResourceLoader callbacks](https://webkit.org/b/129391) + + +#### Internal WebCore nondeterminism + +Timers are used throughout WebCore to do things asynchronously, which can cause nondeterministic execution of JavaScript. WebCore has many timers, but for the purposes of deterministic replay, we only care about timers that (transitively) can cause JavaScript code to execute. Timer-like behavior falls into two categories: + + * Web-facing - script can directly request asynchronous execution via `setTimeout`, `setInterval`, `requestAnimationFrame`, promises, and some other indirect mechanisms (animation/transition events). The ordering of these must be captured and simulated when replaying. + + * Internal - script can run due to nondeterminism of WebCore or the platform itself. Some examples are animation, asynchronous parsing, `Document::checkLoadComplete()`, `EventSender`, `DocumentEventQueue`. + +To address implicit asynchronous nondeterminism (i.e. from WebCore's internal Timers), we create a `Timer` workalike called `ReplayableTimer`, which logs timer creation and firing when capturing execution, and is manually scheduled during replay. When neither capturing or replaying execution, the timer defers to an inner `Timer` instance. + +[Web Replay: make calls into FrameLoader::checkLoadComplete() deterministic](https://webkit.org/b/129451) + +Timers have a few levels of functionality: one-shot, repeating, suspendable, and DOMTimer. Each of these adds more nondeterministic functionality, such as returning the last interval or suspending when the page suspends. + +The current plan is to implement `ReplayableTimer` in several phases, +culminating in supporting capture/replay of DOMTimer simply by virtue of its extending `SuspendableTimer`. Until then, we could use an alternate DOM timer replay technique that creates a `InstrumentedDOMTimer` or `DeterministicDOMTimer` during capture and replay, respectively. It would be great to land this workaround in the interim, since DOM timers are a key source of important nondeterminism in web content. Without addressing DOM timers, most replay features cannot be consistently exercised in a layout test. + +### Deterministic History + +History navigations can be initiated by scripts (window.history) or users (back/forward buttons). Scripts can also inspect history using the window.pushState API. Since every replay segment is independent, this history state must be serialized and restored at the start of every segment. + +There is the question of what happens when navigation actions seek to previous back-forward entries. The current plan is to disable the PageCache, so any navigation of the main frame does a full load and effectively begins a new replay segment. However, instead of appending a new history item, seeking to an earlier item will prune later history items. + + +#### Persistent State and Environment + +The browser has a lot of script-visible persistent state, which is nondeterministic across different runs, computers, time of day, etc. So, this nondeterminism must be controlled. + +To address these sources of nondeterminism, we have a choice between tricking scripts into recieving deterministic results from APIs (typically by capturing and reusing memoized values), and actually restoring the engine's persistent state to what it was during the captured execution. Memoizing values is usually simpler to implement, but creates larger recordings. Dumping and restoring state requires saving a large dump of data once, rather than at each use. However, many of WebKit's persistent state stores (local storage, cookies, history) are not architected in a way to permit straightforward snapshotting and restoration. Thus, memoization-based approaches are generally preferable; this can be revisited if it causes performance issues or such inputs come to dominate recording size. + +[Web Replay: make uses of localStorage/sessionStorage deterministic](https://webkit.org/b/131007) + +[Web Replay: save and restore initial main frame size during capture and replay](https://webkit.org/b/131337) + +[Web Replay: capture and reset initial frame active/focus states](https://webkit.org/b/129694) + +#### Async Scrolling + +Asynchronous scrolling is disabled whenever capturing or replaying execution. + +#### Animations and Transitions + +To fire animation/transition DOM events deterministically, WebCore must deoptimize to software animations when actively capturing or replaying execution. Otherwise, is not possible to precisely control animation progress on a per-tick (i.e., ) basis. The software path will be made deterministic using `ReplayableTimers` (see below). + +[Web Replay: make animations deterministic during capture and replay](https://webkit.org/b/130997) + +#### Workers and Message-passing + +The Web Inspector does not support inspection of worker contexts, so there is no way to tell what's executing in the worker. This allows replay to simply capture and memoize the messages sent between workers and the main thread context. During playback, Workers do not need to re-execute, as long as their uses of nondeterminism can be isolated and ignored. This includes navigator, XHR network requests, DOM timers, etc. (full list here: [https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Functions_and_classes_available_to_workers)) + +[Web Replay: capture and replay messages from worker contexts](https://webkit.org/b/131547) + +Unlike workers, for `postMessage()` between frames in the main execution context, we must replay code in each frame as well as the delivery of the postMessage from one frame to another, since it is asynchronous. This is captured/replayed similarly to a setTimeout() call. + +[Web Replay: capture and replay window.postMessage and 'message' events](https://webkit.org/b/131548) + +### Serialization + +Web replay recordings and their inputs are serializable to JSON and are only accessible from the WebProcess main thread. In code, recordings are implemented by the `ReplaySession` class, and are segmented into multiple `ReplaySessionSegments`. Each segment begins at a main frame navigation. + +Each framework (JSC, WebCore) defines its own replay inputs and encode/decode methods. The top-level recording encode/decode methods will soon move to WebKit2 so that inputs defined in WK2 can be encoded/decoded. + +[Web Replay: encode replay inputs through ReplayClient](https://webkit.org/b/140448) + +[Web Replay: use framework prefixes for framework-specific replay input code](https://webkit.org/b/140447) + +[Web Replay: support generating replay inputs for WebKit framework](https://webkit.org/b/140446) + +Recordings are serialized to JSON as nested objects. This is accomplished using a variant of WK2's `KeyedCoder` interface called `EncodedValue`. The main differences between the two are: + +#### EncodedValue assumes that encode/decode methods use public class methods + +Why? Specializations of the `EncodingTraits` struct implement standalone encode/decode methods for every `NondeterministicInput` subclass and other WebCore types such as `ResourceResponse`, `FormData`, etc. Originally, this was done to avoid painful rebases/merges with fundamental WebCore classes like `ResourceResponse`; it has stayed this way because the maintenance burden is low. + +#### EncodedValue supports 1-1 correspondence between encoded/decoded objects. + +Why? In the past, we found a stack-based encoder/decoder context makes it very hard to automatically generate serialization code. In particular, WebCore objects that must be stored in a vector or as a key's value in different decode methods are tricky to write because the "current context" is managed by multiple methods. + +The current API is based on mirrors (a design concept from reflection APIs) and is very straightfoward: `EncodingTraits::encode(T)` produces an `EncodedValue` for the argument, and `EncodingTraits::decode(EncodedValue, T&)` produces a `T` (shared or owned) in the outparam. Encode/decode methods for inputs can uniformly call encode/decode on any member data type with an EncodingTraits specialization and get back an EncodedValue. + +#### EncodedValue serializes objects to JSON, not to a byte buffer. + +Why? This allows the inspector frontend and on-disk recordings to share a common file format and structure. Using `String` keys and values also makes its possible to have some backwards compatibility for free. For example, the recording format does not depend on the order or representation of enum values, because it stores the symbolic enum value names as strings. `EncodedValue` encoders and decoders reuse the JSON parsing and generation provided by `InspectorValue` subclasses.