Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions docs/binance-futures-blank-page-diagnosis.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Binance Futures startup stall observed on 2026-10-02

This is incident evidence, not a standing diagnosis or permission to change a
browser profile. The assistant performed no refresh, extension change, cache
deletion, proxy change, or trading action during the investigation.

## Page purpose

`/zh-CN/futures/` is a real contract-entry route. The observed Binance application
registers `futures` with route ID `743f`. Its page module delegates to module
`41906`, which selects a valid previous UM contract or defaults to `BTCUSDT`.
An indefinitely empty page is not its intended final state.

Sources: [entry module](https://bin.bnbstatic.com/static/chunks/page-743f.ce7935bd.js),
[contract selection](https://bin.bnbstatic.com/static/chunks/78955.382ce295.js).
The precise downstream history push/replace implementation was not inspected.

## Observed failure and recovery

Both a concrete USUSDT trading page and the contract-entry page had HTTP 200
documents and completed load events, but remained in startup. The router was
not ready, its transition remained pending, and the trading page had no native
orderbook or chart container. A small userscript panel does not establish host
application readiness.

The current [framework](https://bin.bnbstatic.com/static/common/framework.2922525e.js)
waits for route readiness before hydration. Its registered theme `beforeResolve`
hook records `get-cache-theme-config-start`, awaits configuration, then records
the end marker and advances the route. In the
[main bundle](https://bin.bnbstatic.com/static/main.37a180b9.js), module `99940`
first awaits a localForage cache read. A cache miss loads a theme JavaScript
chunk. The cache wait has no application-level deadline; webpack's separate
script loader does have a 120-second deadline.

The user subsequently reported that the pages had recovered. The currently
loaded documents contained the following timing records; values are milliseconds
from each document's navigation start:

| Document | Time origin | Event | Start | End |
| --- | --- | --- | ---: | ---: |
| USUSDT | 1790915945589.4 | Theme configuration | 924 | 1214752 |
| USUSDT | 1790915945589.4 | Language cache branch | 856 | 1214836 |
| Futures home | 1790915960355 | Language startup gate | 647 | 1200211 |

The USUSDT theme gate and home language gate ended about 225 ms apart in wall
time. USUSDT then had a ready router, native orderbook, chart container, and
canvas. A subsequent theme read took 5 ms. A fresh read-only
`indexedDB.databases()` metadata query completed in 3 ms and confirmed the theme
database existed, without reading its records.

Earlier, an IndexedDB metadata query remained pending at a subsequent check.
Intervening document navigations invalidated its remote object, so its eventual
outcome cannot be recovered or equated with the later successful query. These
samples span different document lifetimes; do not describe the entire session
as one uninterrupted navigation or claim that nobody refreshed it.

The strongest supported conclusion is a stalled theme/configuration route gate,
with browser-local storage readiness implicated by the concurrent cache waits
and recovery. The precise storage driver, blocking connection, rejected versus
pending internal theme operation, and browser defect remain unproven. A rejected
async hook that never calls its continuation can also leave a route pending.

## Network comparison

macOS HTTP, HTTPS, and SOCKS settings pointed to `127.0.0.1:7890`.
One bounded request for the exact main bundle through that proxy returned HTTP
200 in 0.904 seconds. A direct request timed out during connection establishment
after 15 seconds. These results establish only those two shell request paths;
they do not certify every browser request or exclude an earlier transient
network failure. There is no evidence here that disabling the proxy is a fix.

## Related upstream reports

- [localForage #821](https://github.com/localForage/localForage/issues/821)
describes a first `getItem()` that never settles after cross-tab object-store
changes. [PR #807](https://github.com/localForage/localForage/pull/807) fixed a
connection/version-change problem. The maintainer
[released 1.10.0](https://github.com/localForage/localForage/pull/807#issuecomment-901439908)
and a reporter [confirmed the fix](https://github.com/localForage/localForage/pull/807#issuecomment-901563757).
The observed Binance bundle already contains localForage 1.10.0 and
`onversionchange` connection closing. Missing this old fix is not a supported
explanation for the current incident.
- [#685](https://github.com/localForage/localForage/issues/685) reports silent
`indexedDB.open()` hangs and community recovery after Chrome restart. Another
reporter [found site-data clearing also unresponsive](https://github.com/localForage/localForage/issues/685#issuecomment-381134641).
The [maintainer did not establish a root cause](https://github.com/localForage/localForage/issues/685#issuecomment-381339640).
- [#912](https://github.com/localForage/localForage/issues/912#issuecomment-883958597)
contains a Chrome multi-tab pending-operation report. An earlier comment
[associates clearing data across open tabs with a hang](https://github.com/localForage/localForage/issues/912#issuecomment-566066515).
These are community observations, not confirmed remedies for Binance.
- [Chrome Page Lifecycle guidance](https://developer.chrome.com/docs/web-platform/page-lifecycle-api)
recommends closing IndexedDB connections before freezing so other same-origin
tabs are not affected. [MDN's blocked event documentation](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event)
explains version-change blocking. Neither proves a freeze or lock occurred here.

The cited GitHub discussions were inspected with their full comment threads,
follow-ups, and linked fix. Direct Binance Reddit threads were inaccessible
during this investigation and are not treated as verified supporting evidence.

## Next occurrence

Preserve the failed page, record document time origin and the route/cache marks,
and compare browser storage readiness with bounded network evidence before
changing settings. Collect from navigation start if the goal is to identify the
specific unresolved storage operation. Browser restart is a community recovery
experiment, not a proven durable fix; clearing site data is not the first step.
Do not patch Binance's router or call its continuation from a userscript.

The separate userscript bug was broad route recognition (`home` became `HOME`)
and Strategy31's non-trading status lifecycle. Fixing those defects does not
establish that the host startup stall is fixed.
4 changes: 2 additions & 2 deletions docs/binance-strategy27-events-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ V10 live projection. The VPS remains the only market-data and event-analysis
authority. The userscript opens no Binance market-data WebSocket, uses no
Binance API key, and does not recalculate the four force groups.

Version 0.6.6 retains this installation's private gateway configuration and
Version 0.6.7 retains this installation's private gateway configuration and
provides a shared read-only transport. Strategy29 owns its own summary panel,
lifecycle and panel position. The existing `strategy27GatewayOrigin` and
`strategy27GatewayAuthSecret` storage keys remain the single credential source;
Expand Down Expand Up @@ -169,7 +169,7 @@ ADR 032 in CorsairQuant owns the server-side rule and transport contract. The
browser does not reconstruct candidates from ordinary events or recalculate
market evidence. The client, lifecycle, panel, native chart layer and optional-job
controller are wired into the entrypoint and tested together. The source and
generated install artifact are version 0.6.6 with identical metadata headers.
generated install artifact are version 0.6.7 with identical metadata headers.
The generated artifact passes syntax, release-contract and isolated execution
checks, including candidate delivery, paired entities, clear and context stop.
Binance operator-page validation remains outstanding. Server/gateway rollout
Expand Down
4 changes: 2 additions & 2 deletions docs/binance-strategy29-bollinger-development.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ already-loaded native chart candles. The summary reads the authenticated
unified loopback gateway; it does not call Binance market-data or account APIs,
submit orders, rotate hidden charts, or add remote events as chart drawings.

Install Strategy29 0.5.6 with orderbook 2.7.199 or later, or use it alone.
Install CorsairQuant signal client 0.6.6 for the remote summary.
Install Strategy29 0.5.7 with orderbook 2.7.199 or later, or use it alone.
Install CorsairQuant signal client 0.6.7 for the remote summary.
Do not combine it with the embedded observer in orderbook 2.7.198.
After updating/disabling the old script, reload the page. An embedded observer
is an explicit conflict: Strategy29 stops and displays an upgrade/reload notice.
Expand Down
7 changes: 5 additions & 2 deletions docs/binance-strategy31-volume-reversal-development.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# Strategy31 volume reversal signals

Source: `src/binance-strategy31-volume-reversal/`. Installer:
`scripts/binance-strategy31-volume-reversal.user.js` (0.1.0).
Install alongside CorsairQuant signal client 0.6.6, which owns private gateway
`scripts/binance-strategy31-volume-reversal.user.js` (0.1.1).
Install alongside CorsairQuant signal client 0.6.7, which owns private gateway
authentication. The shared bridge explicitly advertises the `strategy31` capability.

CorsairQuant confirms consecutive closed red/green candles when green base volume
Expand All @@ -14,6 +14,9 @@ Hidden documents suspend requests; navigation and settings revisions invalidate
late responses. Repeated snapshots reconcile one arrow per stable event ID.
Interval changes preserve the native data-completion subscription. Unsupported
periods and non-USDT markets pause observation until a supported chart is selected.
Non-trading routes remove the status and retire pending requests and chart
ownership. Route observation remains available after a terminal failure solely
to remove presentation on departure; it does not restart failed business work.
A transient native candle snapshot inconsistency retains existing arrows and
waits for the next sample; malformed signal contracts still stop the observer.
Loading earlier
Expand Down
10 changes: 10 additions & 0 deletions docs/userscript-validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,16 @@ read the Node version from `.nvmrc`.
`scripts/*.user.js` file and checked directly.
- Route and symbol identity come from the documented pathname/route contract.
Do not infer a symbol from a page title, stale DOM, or a neighboring panel.
- The shared route parser activates scripts only for
`/[locale/]futures/<asset>USDT` and `<asset>USDC` perpetual routes, with an
optional trailing slash. The asset must be nonempty; Unicode letters, numbers,
and underscores retain their exchange identity. This is the scripts' supported
route syntax, not a live exchange listing check. Individual strategies retain
their narrower USDT market and timeframe requirements. Other quote assets,
dated contracts, landing pages, `home`, `quiz`, multi-chart pages, calculators,
and wallet routes do not activate panels or business requests.
Keep the broad metadata match and lightweight route watcher so SPA navigation
from a landing page can activate a supported contract and remove it on return.
- Binance identifiers share the Unicode letter/number/underscore character
contract in `src/shared/binance-symbol.js`. Include Chinese, one-character,
numeric-only, and numeric-prefixed assets when validating route, depth, order
Expand Down
3 changes: 2 additions & 1 deletion e2e/binance-orderbook/helpers/userscript-page.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function readScenarioEvidence(page) {
return evidenceByPage.get(page) || null;
}

export async function openUserscriptScenario(page, scenario, { beforeOrderbook = '', afterOrderbook = '' } = {}) {
export async function openUserscriptScenario(page, scenario, { beforeOrderbook = '', afterOrderbook = '', afterNavigation } = {}) {
const errors = [];
page.on('pageerror', (error) => errors.push(String(error?.stack || error)));
const userscriptSource = await readFile(USERSCRIPT_PATH, 'utf8');
Expand Down Expand Up @@ -128,6 +128,7 @@ export async function openUserscriptScenario(page, scenario, { beforeOrderbook =
});

await page.goto(`https://www.binance.com/zh-CN/futures/${scenario.currentSymbol}`);
if (afterNavigation) await afterNavigation(page);
await page.locator('#jh-binance-close-qty-multiplier-panel').waitFor({ state: 'visible' });
await page.locator('#jh-binance-ladder-body').waitFor({ state: 'visible' });
await page.evaluate(() => new Promise((resolve) => {
Expand Down
77 changes: 77 additions & 0 deletions e2e/binance-orderbook/specs/futures-route-scope.pw.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { test, expect } from '../test.js';
import { createCancelScenario } from '../scenarios/cancel-current-symbol.js';
import { openUserscriptScenario, readFixtureState } from '../helpers/userscript-page.js';
import { installScenarioClock, pauseScenarioClock } from '../helpers/scenario-clock.js';

test('user can enter a trading route after the orderbook installer starts on home', async ({ page }) => {
// Given the real installer starts on home while an isolated native host is available.
await page.route('**/*', route => route.abort('blockedbyclient'));
await installScenarioClock(page);
const businessRequests = [];
page.on('request', request => {
if (new URL(request.url()).pathname.includes('/fapi/')) businessRequests.push(request.url());
});

// When another non-trading route precedes entry into a supported contract without reinjection.
const host = await openUserscriptScenario(page, createCancelScenario({ currentSymbol: 'USUSDT' }), {
beforeOrderbook: "history.replaceState({}, '', '/zh-CN/futures/home');",
afterNavigation: async () => {
await expect(page.locator('#jh-binance-close-qty-multiplier-panel')).toHaveCount(0);
expect(await page.evaluate(() => typeof window.__TM_CLOSE_LONG_DEBUG__)).toBe('undefined');
expect(businessRequests).toEqual([]);
await page.evaluate(() => history.pushState({}, '', '/zh-CN/futures/quiz'));
await expect(page.locator('#jh-binance-close-qty-multiplier-panel')).toHaveCount(0);
await page.evaluate(() => history.pushState({}, '', '/zh-CN/futures/USUSDT'));
},
});
await pauseScenarioClock(page);

// Then the installer starts exactly once and creates its trading panel.
await expect(page.locator('#jh-binance-close-qty-multiplier-panel')).toBeVisible();
await expect(page.locator('#jh-binance-close-qty-multiplier-panel')).toHaveCount(1);
await page.evaluate(() => { window.routeTestFetch = window.fetch; window.routeTestWebSocket = window.WebSocket; });

// When repeated route notifications accompany leaving and returning to trading.
await page.evaluate(() => {
history.pushState({}, '', '/zh-CN/futures/home');
history.pushState({}, '', '/zh-CN/futures/USUSDT');
dispatchEvent(new PopStateEvent('popstate'));
dispatchEvent(new PopStateEvent('popstate'));
});
await page.clock.runFor(1000);

// Then the original network observers and single panel remain owned by one runtime.
await expect(page.locator('#jh-binance-close-qty-multiplier-panel')).toHaveCount(1);
expect(await page.evaluate(() => [window.fetch === window.routeTestFetch, window.WebSocket === window.routeTestWebSocket])).toEqual([true, true]);
expect((await readFixtureState(page)).events.filter(event => ['order-submitted', 'cancel-requested'].includes(event.type))).toEqual([]);
expect(host.errors).toEqual([]);
});

test('user sees the orderbook panel only on supported trading routes across SPA navigation', async ({ page }) => {
// Given the generated installer has mounted on a supported contract in an isolated host.
await page.route('**/*', route => route.abort('blockedbyclient'));
await installScenarioClock(page);
const host = await openUserscriptScenario(page, createCancelScenario());
await pauseScenarioClock(page);
const panel = page.locator('#jh-binance-close-qty-multiplier-panel');
await expect(panel).toBeVisible();
const tradingPath = await page.evaluate(() => location.pathname);

// When SPA navigation visits each non-trading futures page with stale trading DOM still present.
for (const path of ['/zh-CN/futures/home', '/zh-CN/futures/', '/zh-CN/futures/quiz', '/zh-CN/futures/multipleChart', '/zh-CN/futures/multi-symbols', `${tradingPath}/calculator`]) {
await page.evaluate(path => history.pushState({}, '', path), path);
await page.clock.runFor(1000);
// Then the off-route panel is removed even though the old orderbook DOM remains.
await expect(panel).toHaveCount(0);
}

// When the user returns to the original supported contract.
await page.evaluate(path => history.pushState({}, '', path), tradingPath);
await page.clock.runFor(1000);

// Then one panel resumes without any order placement or cancellation.
await expect(panel).toBeVisible();
await expect(panel).toHaveCount(1);
expect((await readFixtureState(page)).events.filter(event => ['order-submitted', 'cancel-requested'].includes(event.type))).toEqual([]);
expect(host.errors).toEqual([]);
});
4 changes: 2 additions & 2 deletions scripts/binance-coinmarketcap-data.user.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// @namespace binance.coinmarketcap.data
// @icon data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
// @icon64 data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2064%2064%22%3E%3Crect%20width%3D%2264%22%20height%3D%2264%22%20rx%3D%2214%22%20fill%3D%22%23f0b90b%22%2F%3E%3Ctext%20x%3D%2232%22%20y%3D%2249%22%20text-anchor%3D%22middle%22%20font-family%3D%22Arial%2C%20sans-serif%22%20font-size%3D%2242%22%20font-weight%3D%22800%22%20fill%3D%22%23111827%22%3EJ%3C%2Ftext%3E%3C%2Fsvg%3E
// @version 0.1.17
// @version 0.1.18
// @author jackhai9
// @description 在 Binance 合约页面显示当前币种的 CoinMarketCap 中文页关键估值与供应量数据
// @match https://www.binance.com/*/futures/*
Expand All @@ -25,7 +25,7 @@

// src/shared/binance-futures-route.js
var FUTURES_TRADING_PATH_RE = /^\/(?:[a-z]{2}(?:-[A-Za-z]{2})?\/)?futures\/([^/]+)\/?$/;
var TRADING_SYMBOL_RE = new RegExp(`^[${BINANCE_SYMBOL_CHARACTERS}]{3,}$`, "u");
var TRADING_SYMBOL_RE = new RegExp(`^[${BINANCE_SYMBOL_CHARACTERS}]+(?:USDT|USDC)$`, "iu");
function parseFuturesTradingSymbolFromPathname(pathname) {
const normalized = String(pathname || "").split(/[?#]/, 1)[0];
const match = normalized.match(FUTURES_TRADING_PATH_RE);
Expand Down
Loading
Loading