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.onError = (details) {
|
||||||
FlutterError.presentError(details);
|
FlutterError.presentError(details);
|
||||||
print('CK_ERROR: ${details.exceptionAsString()}');
|
print('CK_ERROR: ${details.exceptionAsString()}');
|
||||||
print('CK_CTX: ${details.context.toString()}');
|
print('CK_CTX: ${details.context}');
|
||||||
print('CK_STACK: ${details.stack}');
|
print('CK_STACK: ${details.stack}');
|
||||||
};
|
};
|
||||||
// Enable configureApp will cause route problem.
|
// 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) =>
|
return MaterialPageRoute(builder: (BuildContext context) =>
|
||||||
RenewMiniOffice(int.parse(pathElements[2]))
|
RenewMiniOffice(int.parse(pathElements[2]))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (pathElements[1] == 'buy-service') {
|
if (pathElements[1] == 'buy-service' && pathElements.length >= 4) {
|
||||||
return MaterialPageRoute(builder: (BuildContext context) =>
|
return MaterialPageRoute(builder: (BuildContext context) =>
|
||||||
BuyService(int.parse(pathElements[2]), pathElements[3])
|
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",
|
"version": "1.0.0",
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"playwright": "^1.62.1"
|
"playwright": "^1.62.1",
|
||||||
|
"source-map": "^0.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
@@ -55,6 +56,15 @@
|
|||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20"
|
"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",
|
"license": "ISC",
|
||||||
"type": "commonjs",
|
"type": "commonjs",
|
||||||
"dependencies": {
|
"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