When using 'domain' module, it calls various EventEmitter methods
like 'listenerCount' that we do not expect. To avoid this problem
in the future, we validate the method name before sending it over
the protocol connection.
Currently, we always throw from FrameSession._stopScreencast
when not running with video, and immediately catch it in
CRPage.didClose (thanks to debugger to point that).
Overall, we have code prepared for start/stop API, which
we never did, so it makes sense to simplify code a bit,
and throw if something goes wrong.
This fixes the local -> remote frame swap when
Page.frameDetached arrives before Target.attachedToTarget.
Instead of error-prone logic we do currently, new CDP exposes
frame detach reason that we can use.
New webkit build, generated by 19f21b1bde, changed webkit build
layout: now there are subfolders that contain libraries and executables, and some of the dependencies are no longer bundled.
This patch:
- teaches launch doctor new directories structure: subfolders are now inspected for missing dependencies, and they are also used in the `LD_LIBRARY_PATH`.
- adds `libevent` and `libicudata` libs to the mapping for ubuntu 18.04
This enables filling the input based on the connected label:
```html
<label for=target>Name</label><input id=target>
```
```js
await page.fill('text=Name', 'Alice');
```
Consider the following sequence:
- page opens a popup;
- popup target is attached, we start initializing it;
- user calls browser.close();
- browser is closed, and popup initialization fails;
- we report "errored page" on the already closed context;
- RPC client cannot make sense of this:
"Cannot find parent object BrowserContext@guid to create Frame@guid"
This issue was revealed during Firefox pipe migration.
Certain environments, e.g. Azure Pipelines, override default user
inside container with a custom one, whereas fail to pass proper
seccomp profile for the docker image.
As a result, chromium sandboxing fails.
To ease life of devops deploying tests in various CI's, this patch
disables Chromium sandbox by default.
References #4084
There is a race between "close" event coming from the server and
"close" command issued from the client.
This is similar to calling close after disconnect, so added tests.
This saves some CPU cycles while waiting for the page to
change the state, e.g. for animations to complete.
Note that retrying logic is only applicable in rare
circumstances like unexpected scroll in the middle of an
action, or some overlay blocking the click. Usually,
action times out in this cases while retrying.
We currently spawn a process per page when recording
video in Chromium. This triggers "too many listeners" on the
process object once you have enough pages open.
A few details on locking registry to prohibit concurrent access:
- locking is done by creating a `__dirlock` directory in the top-level
of our registry.
- since `__dirlock` directory does not match any of browser
directories, old versions of the installer will ignore it
- in case of concurrent access, installation will wait for a lock to be
released for 10 minutes, periodically trying to grab the lock. If it
fails to do so in 10 minutes, the installation will fail.
Fixes#3912
- This leaves just `recordVideos` and `videoSize` options on the context.
- Videos are saved to `artifactsPath`. We also save their ids to trace.
- `context.close()` waits for the processed videos.
api(trace): introduce artifacts options
This introduces launch({ artifactsPath }) and newContext({ relativeArtifactsPath, recordTrace }) options.
- artifactsPath option controls the directory where all artifacts go. If not passed, artifacts are not collected.
- relativeArtifactsPath can be used to put context-specific artifacts into a subfolder. If not passed, shared artifactsPath is used.
- recordTrace controls trace recording.
We also expose trace types under playwright/types/trace.d.ts.
In the follow up:
- videos will be put into artifactsPath;
- downloads will be put into artifactsPath, or keep using existing downloadsPath when artifactsPath is not specified.
These methods are the only users of waitForNavigation and
waitForLoadState on the server side. This refactor lifts the
Progress wrapper to the top-most goBack/goForward/reload call
and leaves waitForNavigation/waitForLoadState as internal helpers.
This way we get a single Progress for the actual api call.
We now use 'launch' under the hood, which erroneously throws
when 'port' is present.
Instead, moved validation to the client side where it belongs,
added tests for validation errors.
Sometimes, we are unable to take a frame snapshot. The most common
example would be "frame is stuck during the navigation in Chromium",
where we cannot evaluate until the frame is done navigating.
In this case, use all other frames and just stub the failing ones
with "Snapshot is not available". Chances are, noone will even see
this frame because it's an invisible tracking iframe.
- Fill and click actions pass metadata to Progress.
- Progress reports success/failure through instrumentation.
- Tracer consumes ActionResult and ActionMetadata and records them.
Currently, only click and fill actions pass metadata to
contain the size of the change. Everything else should follow.
- Print parentGuid when it is not available for __create__.
Some bots show generic "something is undefined" error - let's
get better information about the failure.
- Ignore events on disposed objects outside of tests.
Some bots show this happening for "previewUpdated" - let's see
whether there are more important events that misbehave.
- We do not need the public BrowserType different from BrowserTypeBase anymore.
- Removing 'logName' parameter from runAbortableTask - it will
be used for metadata instead.
This patch:
- moves ffmpeg binaries from `//bin/` to `//third_party/ffmpeg`
- adds [COPYING.GPLv3](https://github.com/FFmpeg/FFmpeg/blob/master/COPYING.GPLv3)
ffmpeg license
- changes npm packaging to include `//third_party/ffmpeg` only in `playwright` and `playwrihgt-chromium` a
This is an alternative approach to #3698 that was setting up a custom
mapping between chromium revisions and our mirrored builds. For example, we were
taking chromium `792639` and re-packaging it to our CDN as Chromium 1000.
One big downside of this opaque mapping was inability to quickly
understand which Chromium is mirrored to CDN.
To solve this, this patch starts treating browser revision as a fractional number,
with and integer part being a chromium revision, and fractional
part being our build number. For example, we can generate builds `792639`, `792639.1`,
`792639.2` etc, all of which will pick Chromium `792639` and re-package it to our CDN.
In the Playwright code itself, there are a handful of places that treat
browser revision as integer, exclusively to compare revision with some particular
revision numbers. This code would still work as-is, but I changed these places
to use `parseFloat` instead of `parseInt` for correctness.
We used to do fetch() to decode the file buffer. However, this is
blocked by strict CSP policy. Instead, we can use explicit
string -> bytes conversion, and trade performance for CSP compliance.
As discussed offline, our testing scenarios assume running trusted
web content - so this warning is just a noise for this usecases.
When it comes to dealing with untrusted web content though, automation
authors need to make sure to not launch browsers under root in the first
place.
This is a large rework of selectors:
- Each BrowserContext now has a separate Selectors instance that has its own registrations.
Most of them share a single sharedSelectors instance, but contexts created for a connected
browser have their own instance.
- Connected browser now gets a RemoteBrowser object that encapsulates Selectors and Browser.
This Selectors object is registered with the api selectors.
- Public selectors.register api iterates over all registered Selectors channels
and registers in each of them.
- createSelector testing method migrated to ElementHandle._createSelectorForTest.
Sometimes I see "cannot call emit on the undefined" error on the bots.
This change adds some more logging, so we could potentially identify where
the issue comes from.
This introduces basic tracing enabled in our tests.
What is captured:
- network resources;
- snapshots at the start of most actions;
- snapshot after the test failure.
How this integrates with test runner:
- context fixture calls private method context._initSnapshotter() and uses Tracer to trace all events;
- all tests share a single test-results/trace-storage directory to store blobs;
- each test has its own trace file.
- npm run show-trace opens a bare-minimum trace viewer that renders snapshots.
Root index.js is only used for local development, so
assuming dev mode there is fine. This way we do not have
to worry about calling setUnderTest early enough.
One by one seems like a resonable minimum size for clicking.
It is not surprising to see a 1x1 native accessible control
that is covered by a custom control that handles input instead.
Due to wrong type usage, we ignored the origin while granting permissions.
Switching to generated types revealed this issue. We should follow up
with switching all dispatchers to the generated types.
This changes the behavior of slowmo to slow down user actions instead of every protocol command. This makes slowmo a lot more predictable. Without this, there is no way to set slowmo to a good value without incurring a huge delay at the start of your test when it sets things up.
This touches:
- noDefaultViewport;
- ignoreAllDefaultArgs;
- env;
- validateXYZ logic that was copying objects - we do not need that anymore;
- shuffles some converters closer to their usage.
This method waits for visible, hidden, stable or enabled state,
similar to the actionability checks performed before actions.
This gives a bit more control to the user. Some examples:
- Allows to wait for something to be stable before taking a screenshot.
- Allows to wait for the element to be hidden/detached after a specific action.
- Never write to console on the server side - we use stdout for
communication. This includes logPolitely and deprecate.
- Pass undefined instead of null in some BrowserContext methods.
- Use explicit _setFileChooserIntercepted instead of on/off magic.
This migrates Firefox to the protocol-based proxy implementation.
Benefits:
- supports secure web proxies (already supported by Chromium)
- unlocks support for SOCKS proxies with authentication
This starts downloading newer Chromium archives from our CDN, but
retains old endpoint for older Chromium revisions.
This backwards compatibility might help later on to implement
a browser bisecting tool.
References #3259
Everywhere in our api, possibly missing properties are nullable.
However, to make things easier for everyone, we just default to an
empty url instead, so that users do not have to null-check it.
Make sure executable exists before launching it. If it doesn't and
we were launched without custom executable path, print a helpful
instruction to run `npm i playwright` and get browsers downloaded.
Note: there's already a test that makes sure bad executable paths
are treated fairly: 9132d23b2b/test/launcher.jest.js (L54-L59)
This doesn't test missing default browser installation which I think is
fine.
Fixes#3161
WebKit WPE assumes `libglesv2.so` is available on the host system
and uses `dlopen` to open it.
This patch starts using `ldconfig -p` to check if the library
exists on the system.
References #2745
Renderer-based method DOM.getContentQuads and DOM.getBoxModel return
coordinates relative to the local root's viewport, but we need them relative
to the root viewport.
This adds one more protocol message __dispose__
to dispose a scope and all child objects.
Now, client side does not need a notion of scope anymore -
it just disposes the whole object subtree upon __dispose__.
Server, on the other hand, marks some objects as scopes
and disposes them manually, also asserting that all parents
are proper scopes.
This makes it easier to reason about our packages.
The only difference is what each package downloads.
When the browser is not downloaded, it will fail to launch.
Each browser gets a 'download' attribute in the browser.json file.
The original plan was to rnu some checks against libc version the
binary is compiled with, but these turn out to be a little complicated:
parsing out libc version from both static binary and host system
requires text processing, and it's hard to make sure it works reliably
across distributions.
Instead, let's start with a very particular check against running
Firefox on Ubuntu 16.04.
References #2745
Before typing/pressing, we focus the target element. WebKit
sometimes selects the value in this case. To unify the behavior
between the browsers we behave similar to human:
- when the input is already focused, we just type;
- when the input is not focused, we focus it, move caret
to the start (like if user clicked at the start to focus the input)
and then type.
Note this only affects inputs with non-empty value.
This establishes a single naming for all our blobs with browser
builds that we upload to CDN: `<browser-name>-<os-version>`
- `<browser-name>` is either `firefox` or `webkit`.
- `os-version` is the OS that was used to produce the build.
References #2745
This patch:
- specializes "linux" scripts into "Ubuntu 18.04" scripts
- renames all future linux blobs on CDN:
* `firefox-linux.zip => firefox-ubuntu-18.04.zip`
* `minibrowser-gtk.zip => minibrowser-gtk-ubuntu-18.04.zip`
* `minibrowser-wpe.zip => minibrowser-wpe-ubuntu-18.04.zip`
* `minibrowser-gtk-wpe.zip => minibrowser-gtk-wpe-ubuntu-18.04.zip`
- updates downloader to deal with the new names
References #2745
This patch detects Chromium crash with a sandboxing error and re-writes
the error to surface information nicely.
#### Error Before:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:324) UnhandledPromiseRejectionWarning: browserType.launch: Protocol error (Browser.getVersion): Target closed.
=========================== logs ===========================
[browser] <launching> /home/pwuser/.cache/ms-playwright/chromium-790602/chrome-linux/chrome --disable-background-networking --enable-features=NetworkService,NetworkServiceInProcess --disable-background-timer-throttling --disable-backgrounding-occluded-windows --disable-breakpad --disab
le-client-side-phishing-detection --disable-component-extensions-with-background-pages --disable-default-apps --disable-dev-shm-usage --disable-extensions --disable-features=TranslateUI,BlinkGenPropertyTrees,ImprovedCookieControls,SameSiteByDefaultCookies --disable-hang-monitor --disab
le-ipc-flooding-protection --disable-popup-blocking --disable-prompt-on-repost --disable-renderer-backgrounding --disable-sync --force-color-profile=srgb --metrics-recording-only --no-first-run --enable-automation --password-store=basic --use-mock-keychain --user-data-dir=/tmp/playwrig
ht_chromiumdev_profile-mjSfr2 --remote-debugging-pipe --headless --hide-scrollbars --mute-audio --no-startup-window
[browser] <launched> pid=401
[browser] [0722/170825.030020:FATAL:zygote_host_impl_linux.cc(117)] No usable sandbox! Update your kernel or see https://chromium.googlesource.com/chromium/src/+/master/docs/linux/suid_sandbox_development.md for more information on developing with the SUID sandbox. If you want to live
dangerously and need an immediate workaround, you can try using --no-sandbox.
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f853680 logging::LogMessage::~LogMessage()
[browser] #3 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #4 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #5 0x55ac4f45c9fa service_manager::Main()
[browser] #6 0x55ac4f40c361 content::ContentMain()
[browser] #7 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #8 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #9 0x55ac4ccc22e7 ChromeMain
[browser] #10 0x7f0f3d736b97 __libc_start_main
[browser] #11 0x55ac4ccc212a _start
[browser]
[browser] Received signal 6
[browser] #0 0x55ac4f8c7be9 base::debug::CollectStackTrace()
[browser] #1 0x55ac4f841c13 base::debug::StackTrace::StackTrace()
[browser] #2 0x55ac4f8c7785 base::debug::(anonymous namespace)::StackDumpSignalHandler()
[browser] #3 0x7f0f437b3890 (/lib/x86_64-linux-gnu/libpthread-2.27.so+0x1288f)
[browser] #4 0x7f0f3d753e97 gsignal
[browser] #5 0x7f0f3d755801 abort
[browser] #6 0x55ac4f8c66e5 base::debug::BreakDebugger()
[browser] #7 0x55ac4f853aeb logging::LogMessage::~LogMessage()
[browser] #8 0x55ac4df2307e content::ZygoteHostImpl::Init()
[browser] #9 0x55ac4f40dd47 content::ContentMainRunnerImpl::Initialize()
[browser] #10 0x55ac4f45c9fa service_manager::Main()
[browser] #11 0x55ac4f40c361 content::ContentMain()
[browser] #12 0x55ac4f45b5bd headless::(anonymous namespace)::RunContentMain()
[browser] #13 0x55ac4f45b2bc headless::HeadlessShellMain()
[browser] #14 0x55ac4ccc22e7 ChromeMain
[browser] #15 0x7f0f3d736b97 __libc_start_main
[browser] #16 0x55ac4ccc212a _start
[browser] r8: 0000000000000000 r9: 00007ffd38a863b0 r10: 0000000000000008 r11: 0000000000000246
[browser] r12: 00007ffd38a87680 r13: 00007ffd38a86610 r14: 00007ffd38a87690 r15: aaaaaaaaaaaaaaaa
[browser] di: 0000000000000002 si: 00007ffd38a863b0 bp: 00007ffd38a86600 bx: 00007ffd38a86e44
[browser] dx: 0000000000000000 ax: 0000000000000000 cx: 00007f0f3d753e97 sp: 00007ffd38a863b0
[browser] ip: 00007f0f3d753e97 efl: 0000000000000246 cgf: 002b000000000033 erf: 0000000000000000
[browser] trp: 0000000000000000 msk: 0000000000000000 cr2: 0000000000000000
[browser] [end of stack trace]
[browser] Calling _exit(1). Core file will not be generated.
============================================================
Note: use DEBUG=pw:api environment variable and rerun to capture Playwright logs.Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:39)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:52:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:87:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:60:25)
at async /home/pwuser/tmp/a.js:4:19
(node:324) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:324) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
#### Error After:
```sh
pwuser@23592d09b3bd:~/tmp$ node a.js
(node:222) UnhandledPromiseRejectionWarning: browserType.launch: Chromium sandboxing failed!
================================
To workaround sandboxing issues, do either of the following:
- (preferred): Configure environment to support sandboxing: https://github.com/microsoft/playwright/blob/master/docs/troubleshooting.md
- (alternative): Launch Chromium without sandbox using 'chromiumSandbox: false' option
================================
Error
at /home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:131:63
at new Promise (<anonymous>)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crConnection.js:130:16)
at CRSession.send (/home/pwuser/tmp/node_modules/playwright/lib/helper.js:78:31)
at Function.connect (/home/pwuser/tmp/node_modules/playwright/lib/chromium/crBrowser.js:54:27)
at Chromium._connectToTransport (/home/pwuser/tmp/node_modules/playwright/lib/server/chromium.js:53:38)
at Chromium._innerLaunch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:89:36)
at async ProgressController.run (/home/pwuser/tmp/node_modules/playwright/lib/progress.js:75:28)
at async Chromium.launch (/home/pwuser/tmp/node_modules/playwright/lib/server/browserType.js:61:25)
at async /home/pwuser/tmp/a.js:4:19
(node:222) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise reject
ion, use the CLI flag `--unhandled-rejections=strict` (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 2)
(node:222) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
```
References #2745
Currently, Ctrl-C while extracting browser might yield users in
a bad place.
This patch adds a marker file inside browser directory to make
sure that browser extraction completed.
Note: this was already attempted in #2489, but was eventually
reverted in #2534.
References #2660
We now have types for SerializedValue/SerializedArgument. This will
allow us to avoid double parse/serialize for evaluation arguments/results.
Drive-by: typing exposed a bug in ElementHandle.dispatchEvent().
Drive-by: fix electron issues, exposed by the test using
waitForNavigation.
Drive-by: mark some tests skip(CHANNEL) that were mistakenly
marked skip(USES_HOOKS).
Missing dependencies is #1 problem with launching on Linux.
This patch starts validating browser dependencies before launching
browser on Linux. In case of a missing dependency, we will abandon
launching with an error that lists all missing libs.
References #2745
This also changes timeout error format to
"page.click: Timeout 5000ms exceeded", so that all errors
can be similarly prefixed with api name.
We can now have different api names in different clients,
and our protocol is more reasonable.
We always have to reject promises with some error. Otherwise,
our error-rewriting logic in try-catch miserably fails.
With this patch, attempt to launch Firefox when it's missing
dependencies will actually result in a thrown exception with
pretty logs. Without this patch, Playwright throws internal error.
This includes page CDPSession, backgroundPages() and serviceWorkers().
This has also revealed an issue with closing order between the context
and the service worker.
We now use a few helper.waitForEvent calls to wait for internal
events kNavigationEvent and kLifecycleEvent. With these events,
we should be able to replicate logic over rpc.
Instead of checking lifecycle events on every change, we
notify precisely when lifecycle event in the subtree
is satisfied. This allows FrameTask to be later switched
to event-based approach, and will easily translate to the
rpc client.
We should not stall selector actions because of dialogs
and properly timeout instead. For this, we should not await
the handle.dispose() call because it will never happen
while dialog is shown.
Also, log information about dialogs to make it easier to debug.
We now commit protocol.ts files during the roll.
New utils/roll_browser.js helps with that.
This makes our installation very shallow:
- build installer;
- download browsers.
Firefox and WebKit require native promises to provide awaitPromise
functionality. When the Promise is overwritten, all evaluations
in the main world produce wrong Promise, so we wrap with async
function to get a native promise instead.
We now query selector and take textContent synchronously. This
avoids any issues with async processing: node being recycled,
detached, etc.
More methods will follow with the same atomic pattern.
Drive-by: fixed selector engine names being sometimes case-sensitive
and sometimes not.
We currently return undefined whenever we had an error trying
return the evaluation result by error. The most common error
is "execution context destroyed".
This produces very unexpected undefined from methods that do not
ever expect undefined. Instead, we should throw because we were
not able to return the result.
We currently have dispatchEventTask and waitForSelectorTask.
However, most selector-based operations make sense as tasks, to ensure
atomic execution, e.g. textContent(selector) or focus(selector).
This will fight hydration, elements recycling and other async issues.
In preparation, decouple tasks from selectors parsing so that
we can have common infrastructure for tasks.
- Gave all possible dom errors distinct names, and throw them on the node side.
- Separated errors into FatalDOMError and RetargetableDOMError.
Fatal errors are unrecoverable. Retargetable errors
could be resolved by requerying the selector.
- This exposed a number of unhandled 'notconnected' cases.
- Added helper functions to handle errors and ensure TypeScript catches
unhandled ones.
Element screenshot now waits for the element to become visible and
throws on detach.
Both screenshot methods accept a timeout and capture logs using Progress.
Also, carefully handling exceptions and restoring the viewport.
WebKit and Firefox are only able to continue redirects.
Firefox is faking it on the backend, so you can't even stall it.
Instead, we just do not fire routes for redirects on all browsers,
to avoid surprises.
In addition to `PLAYWRIGHT_DOWNLOAD_HOST` env variable, this patch adds a per-browser
configuration:
- `PLAYWRIGHT_CHROMIUM_DOWNLOAD_HOST`
- `PLAYWRIGHT_FIREFOX_DOWNLOAD_HOST`
- `PLAYWRIGHT_WEBKIT_DOWNLOAD_HOST`
This reverts 2 commits:
- "fix(installer): create tmp directory inside `browserPath` (#2498)"
commit 946b4efa3b.
- "feat: support atomic installation of browsers (#2489)"
commit 3de0c087bc.
This addresses installation issues we see in some CI environments.
Currently, we fail when the predicate throws on the first call,
and timeout when it fails on any other call.
There are two possible ways to handle throwing predicates:
- Fail waitForFunction if predicate throws once. This is good
since it gives you the error faster.
- Tolerate predicate exceptions. This is good because you do
not have to worry about non-initialized state during load.
This change implements the former.
Currently, Ctrl-C while extracting browser might yield users in
a bad place.
This patch extracts browsers in a temp directory that is later
moved to a installer registry.
During remote -> local transition, these two events come in unpredictable order, so we try to handle both cases. Also, remote frame detach was not handled at all.
- unifies polling timeouts with everything else,
based on the client time instead of the server time;
- prepares polling tasks for cancellation token
behavior.
Unfortunately, RerunnableTask had to be rewritten almost
entirely.