fix(runtime): onGenerateRoute pathElements 越界 RangeError
- buy-service/renew-minioffice 加长度检查 (pathElements.length >= 4/3) - 根因: Flutter web 深链接渐进式路径解析, onGenerateRoute 被调用多次, pathElements 递增 ([,buy-service] -> [,buy-service,1] -> 完整), 前几次路径短但 pathElements[1]已匹配 -> int.parse([2])/[3] 越界 - 直接访问/刷新 /buy-service/x/y, /renew-minioffice/x 时崩溃 - 恢复 buy_service 实验代码 (data 校验 + mounted 保留)
This commit is contained in:
@@ -36,7 +36,7 @@ void main() {
|
||||
FlutterError.onError = (details) {
|
||||
FlutterError.presentError(details);
|
||||
print('CK_ERROR: ${details.exceptionAsString()}');
|
||||
print('CK_CTX: ${details.context.toString()}');
|
||||
print('CK_CTX: ${details.context}');
|
||||
print('CK_STACK: ${details.stack}');
|
||||
};
|
||||
// Enable configureApp will cause route problem.
|
||||
@@ -171,12 +171,12 @@ class MyApp extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
}
|
||||
if (pathElements[1] == 'renew-minioffice') {
|
||||
if (pathElements[1] == 'renew-minioffice' && pathElements.length >= 3) {
|
||||
return MaterialPageRoute(builder: (BuildContext context) =>
|
||||
RenewMiniOffice(int.parse(pathElements[2]))
|
||||
);
|
||||
}
|
||||
if (pathElements[1] == 'buy-service') {
|
||||
if (pathElements[1] == 'buy-service' && pathElements.length >= 4) {
|
||||
return MaterialPageRoute(builder: (BuildContext context) =>
|
||||
BuyService(int.parse(pathElements[2]), pathElements[3])
|
||||
);
|
||||
|
||||
16
tools/e2e/diag_ctx.js
Normal file
16
tools/e2e/diag_ctx.js
Normal file
@@ -0,0 +1,16 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(8000);
|
||||
for (let i=0;i<logs.length;i++){
|
||||
if(logs[i].startsWith('CK_ERROR')){console.log(logs[i].slice(0,120));
|
||||
const ctx=logs.slice(i+1,i+4).find(l=>l.startsWith('CK_CTX'));
|
||||
if(ctx) console.log(' CTX:', ctx.slice(0,150));
|
||||
}
|
||||
}
|
||||
await browser.close();
|
||||
})();
|
||||
13
tools/e2e/diag_deep.js
Normal file
13
tools/e2e/diag_deep.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(7000);
|
||||
const ctx = logs.filter(l => l.startsWith('CK_CTX'));
|
||||
if (ctx.length) console.log(ctx[0].slice(0, 600));
|
||||
else console.log('无 CK_CTX');
|
||||
await browser.close();
|
||||
})();
|
||||
14
tools/e2e/diag_exp.js
Normal file
14
tools/e2e/diag_exp.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(7000);
|
||||
const range = logs.filter(l => l.includes('RangeError'));
|
||||
const err = logs.filter(l => l.includes('CK_BUY_ERR'));
|
||||
console.log('RangeError 数:', range.length, err.length ? '| CK_BUY_ERR:'+err[0].slice(0,100) : '');
|
||||
if (range.length) console.log(range[0].slice(0,120));
|
||||
await browser.close();
|
||||
})();
|
||||
15
tools/e2e/diag_paths.js
Normal file
15
tools/e2e/diag_paths.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(7000);
|
||||
console.log('=== path elements 打印(看是否有短路径)===');
|
||||
logs.filter(l => l.includes('path elements')).forEach(l => console.log(' ' + l.slice(0,80)));
|
||||
console.log('=== RangeError 之前的最后几条 console ===');
|
||||
const idx = logs.findIndex(l => l.includes('RangeError'));
|
||||
if (idx >= 0) logs.slice(Math.max(0,idx-4), idx).forEach(l => console.log(' ' + l.slice(0,90)));
|
||||
await browser.close();
|
||||
})();
|
||||
14
tools/e2e/diag_routes.js
Normal file
14
tools/e2e/diag_routes.js
Normal file
@@ -0,0 +1,14 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
for (const route of ['/download', '/contact-stores', '/me', '/orders', '/buy-service/1/ministore']) {
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
let range = 0;
|
||||
page.on('console', m => { if (m.text().includes('RangeError')) range++; });
|
||||
await page.goto('http://127.0.0.1:8099'+route, { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(6000);
|
||||
console.log(`${range>0?'✗ '+range+' RangeError':'✓ OK'} ${route}`);
|
||||
await page.close();
|
||||
}
|
||||
await browser.close();
|
||||
})();
|
||||
13
tools/e2e/diag_seq.js
Normal file
13
tools/e2e/diag_seq.js
Normal file
@@ -0,0 +1,13 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(7000);
|
||||
const seq = logs.filter(l => l.startsWith('CK_BS_') || l.includes('RangeError'));
|
||||
console.log('时序:');
|
||||
seq.slice(0,10).forEach(l => console.log(' ' + l.slice(0,90)));
|
||||
await browser.close();
|
||||
})();
|
||||
19
tools/e2e/diag_stack.js
Normal file
19
tools/e2e/diag_stack.js
Normal file
@@ -0,0 +1,19 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(7000);
|
||||
const stack = logs.filter(l => l.startsWith('CK_STACK'));
|
||||
if (stack.length) {
|
||||
// 找 .dart 源文件帧(非 main.dart.js)
|
||||
const dartFrames = stack[0].split('\n').filter(l => l.includes('.dart') && !l.includes('main.dart.js'));
|
||||
console.log('源文件帧:', dartFrames.length ? dartFrames.slice(0,5).join('\n ') : '(无,全是 main.dart.js)');
|
||||
// 也看前 3 个 main.dart.js 帧
|
||||
const jsFrames = stack[0].split('\n').filter(l => l.includes('main.dart.js')).slice(0,4);
|
||||
console.log('main.dart.js 帧:', jsFrames.join('\n '));
|
||||
}
|
||||
await browser.close();
|
||||
})();
|
||||
11
tools/e2e/diag_then.js
Normal file
11
tools/e2e/diag_then.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { chromium } = require('playwright');
|
||||
(async () => {
|
||||
const browser = await chromium.launch({ headless: true });
|
||||
const page = await (await browser.newContext({viewport:{width:1280,height:900}})).newPage();
|
||||
const logs = [];
|
||||
page.on('console', m => logs.push(m.text()));
|
||||
await page.goto('http://127.0.0.1:8099/buy-service/1/ministore', { waitUntil: 'load' }).catch(()=>{});
|
||||
await page.waitForTimeout(8000);
|
||||
logs.filter(l => l.includes('CK_BUY_THEN') || l.includes('RangeError') || l.includes('CK_ERROR')).slice(0,5).forEach(l => console.log(l.slice(0,160)));
|
||||
await browser.close();
|
||||
})();
|
||||
46
tools/e2e/main.dart.js
Normal file
46
tools/e2e/main.dart.js
Normal file
@@ -0,0 +1,46 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
|
||||
// A map containing the URLs for the bootstrap scripts in debug.
|
||||
let _scriptUrls = {
|
||||
"mapper": "stack_trace_mapper.js",
|
||||
"requireJs": "require.js"
|
||||
};
|
||||
|
||||
// Create a TrustedTypes policy so we can attach Scripts...
|
||||
let _ttPolicy;
|
||||
if (window.trustedTypes) {
|
||||
_ttPolicy = trustedTypes.createPolicy("flutter-tools-bootstrap", {
|
||||
createScriptURL: (url) => {
|
||||
let scriptUrl = _scriptUrls[url];
|
||||
if (!scriptUrl) {
|
||||
console.error("Unknown Flutter Web bootstrap resource!", url);
|
||||
}
|
||||
return scriptUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Creates a TrustedScriptURL for a given `scriptName`.
|
||||
// See `_scriptUrls` and `_ttPolicy` above.
|
||||
function getTTScriptUrl(scriptName) {
|
||||
let defaultUrl = _scriptUrls[scriptName];
|
||||
return _ttPolicy ? _ttPolicy.createScriptURL(scriptName) : defaultUrl;
|
||||
}
|
||||
|
||||
// Attach source mapping.
|
||||
var mapperEl = document.createElement("script");
|
||||
mapperEl.defer = true;
|
||||
mapperEl.async = false;
|
||||
mapperEl.src = getTTScriptUrl("mapper");
|
||||
document.head.appendChild(mapperEl);
|
||||
|
||||
// Attach require JS.
|
||||
var requireEl = document.createElement("script");
|
||||
requireEl.defer = true;
|
||||
requireEl.async = false;
|
||||
requireEl.src = getTTScriptUrl("requireJs");
|
||||
// This attribute tells require JS what to load as main (defined below).
|
||||
requireEl.setAttribute("data-main", "main_module.bootstrap");
|
||||
document.head.appendChild(requireEl);
|
||||
12
tools/e2e/package-lock.json
generated
12
tools/e2e/package-lock.json
generated
@@ -9,7 +9,8 @@
|
||||
"version": "1.0.0",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"playwright": "^1.62.1"
|
||||
"playwright": "^1.62.1",
|
||||
"source-map": "^0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
@@ -55,6 +56,15 @@
|
||||
"engines": {
|
||||
"node": ">=20"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.8.0.tgz",
|
||||
"integrity": "sha512-d8EqvL+k/SOXCreS/SUzg2ciyHqBBLcN/yuRjFsbvVhHTE2pgei7oAhmPM7kWFbkX6OSMQfUq4KbkF3au9lhYQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"license": "ISC",
|
||||
"type": "commonjs",
|
||||
"dependencies": {
|
||||
"playwright": "^1.62.1"
|
||||
"playwright": "^1.62.1",
|
||||
"source-map": "^0.8.0"
|
||||
}
|
||||
}
|
||||
|
||||
15
tools/e2e/parse_map.js
Normal file
15
tools/e2e/parse_map.js
Normal file
@@ -0,0 +1,15 @@
|
||||
const fs = require('fs');
|
||||
const { SourceMapConsumer } = require('source-map');
|
||||
(async () => {
|
||||
const mapJson = await (await fetch('http://127.0.0.1:8099/main.dart.js.map')).text().catch(()=>null);
|
||||
if (!mapJson) { console.log('无 sourcemap'); return; }
|
||||
const smc = await new SourceMapConsumer(mapJson);
|
||||
// 解析 299:254 (line:col)
|
||||
const pos = smc.originalPositionFor({ line: 299, column: 254 });
|
||||
console.log('299:254 →', pos.source, pos.line + ':' + pos.column, '|', pos.name);
|
||||
// 也试 299 的前后几行
|
||||
for (const c of [200, 100, 50]) {
|
||||
const p = smc.originalPositionFor({ line: 299, column: c });
|
||||
if (p.source) console.log(`299:${c} →`, p.source.split('/').slice(-2).join('/') + ':' + p.line);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user