|
1 | 1 | // ==UserScript== |
2 | | -// @name GM_xmlhttpRequest Exhaustive Test Harness v2 |
| 2 | +// @name GM_xmlhttpRequest Exhaustive Test Harness v3 |
3 | 3 | // @namespace tm-gmxhr-test |
4 | | -// @version 1.2.0 |
| 4 | +// @version 1.2.1 |
5 | 5 | // @description Comprehensive in-page tests for GM_xmlhttpRequest: normal, abnormal, and edge cases with clear pass/fail output. |
6 | 6 | // @author you |
7 | 7 | // @match *://*/*?GM_XHR_TEST_SC |
8 | 8 | // @grant GM_xmlhttpRequest |
9 | 9 | // @connect httpbun.com |
10 | | -// @connect ipv4.download.thinkbroadband.com |
11 | 10 | // @connect nonexistent-domain-abcxyz.test |
| 11 | +// @connect raw.githubusercontent.com |
| 12 | +// @connect translate.googleapis.com |
12 | 13 | // @noframes |
13 | 14 | // ==/UserScript== |
14 | 15 |
|
@@ -60,6 +61,30 @@ const enableTool = true; |
60 | 61 | return el; |
61 | 62 | } |
62 | 63 |
|
| 64 | + // value type helper |
| 65 | + const typing = (x) => { |
| 66 | + let t = x === null ? "null" : typeof x; |
| 67 | + if (!x) t = `<${t}>`; |
| 68 | + if (t === "object") { |
| 69 | + try { |
| 70 | + return x[Symbol.toStringTag] || "object"; |
| 71 | + } catch (e) {} |
| 72 | + } |
| 73 | + return t; |
| 74 | + }; |
| 75 | + |
| 76 | + const statusCode = (response) => { |
| 77 | + return (+response.readyState + +response.status / 1000).toFixed(3); |
| 78 | + }; |
| 79 | + |
| 80 | + const resPrint = (r) => { |
| 81 | + const a = statusCode(r); |
| 82 | + const b1 = "response" in r ? typing(r.response) : "missing"; |
| 83 | + const b2 = "responseText" in r ? typing(r.responseText) : "missing"; |
| 84 | + const b3 = "responseXML" in r ? typing(r.responseXML) : "missing"; |
| 85 | + return `${a};r=${b1};t=${b2};x=${b3}`; |
| 86 | + }; |
| 87 | + |
63 | 88 | // ---------- Test Panel ---------- |
64 | 89 | const panel = h( |
65 | 90 | "div", |
@@ -1259,6 +1284,274 @@ const enableTool = true; |
1259 | 1284 | assertDeepEq(readyStateList, fetch ? [2, 4] : [1, 2, 3, 4], "status 200"); |
1260 | 1285 | }, |
1261 | 1286 | }, |
| 1287 | + { |
| 1288 | + name: "General Sequence", |
| 1289 | + async run(fetch) { |
| 1290 | + const resultList = []; |
| 1291 | + const url = `https://raw.githubusercontent.com/mdn/content/54fd6eaad3924076e0b546e7eff1f6f466f6139f/.editorconfig?d=${Date.now()}`; |
| 1292 | + await new Promise((resolve, reject) => |
| 1293 | + GM_xmlhttpRequest({ |
| 1294 | + method: "GET", |
| 1295 | + url, |
| 1296 | + fetch, |
| 1297 | + nocache: true, |
| 1298 | + onreadystatechange: function (response) { |
| 1299 | + resultList.push("onreadystatechange " + resPrint(response)); |
| 1300 | + }, |
| 1301 | + onload: function (response) { |
| 1302 | + resultList.push("onload " + resPrint(response)); |
| 1303 | + }, |
| 1304 | + onloadend: function (response) { |
| 1305 | + resultList.push("onloadend " + resPrint(response)); |
| 1306 | + resolve(); |
| 1307 | + }, |
| 1308 | + onerror: () => reject(), |
| 1309 | + ontimeout: () => reject(), |
| 1310 | + }) |
| 1311 | + ); |
| 1312 | + if (!fetch) { |
| 1313 | + assertDeepEq( |
| 1314 | + resultList, |
| 1315 | + [ |
| 1316 | + "onreadystatechange 1.000;r=missing;t=missing;x=missing", |
| 1317 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1318 | + "onreadystatechange 3.200;r=missing;t=missing;x=missing", |
| 1319 | + "onreadystatechange 4.200;r=string;t=string;x=XMLDocument", |
| 1320 | + "onload 4.200;r=string;t=string;x=XMLDocument", |
| 1321 | + "onloadend 4.200;r=string;t=string;x=XMLDocument", |
| 1322 | + ], |
| 1323 | + "standard-type GMXhr OK" |
| 1324 | + ); |
| 1325 | + } else { |
| 1326 | + assertDeepEq( |
| 1327 | + resultList, |
| 1328 | + [ |
| 1329 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1330 | + "onreadystatechange 4.200;r=string;t=string;x=XMLDocument", |
| 1331 | + "onload 4.200;r=string;t=string;x=XMLDocument", |
| 1332 | + "onloadend 4.200;r=string;t=string;x=XMLDocument", |
| 1333 | + ], |
| 1334 | + "fetch-type GMXhr OK" |
| 1335 | + ); |
| 1336 | + } |
| 1337 | + }, |
| 1338 | + }, |
| 1339 | + { |
| 1340 | + name: "Progress & JSON Fallback", |
| 1341 | + async run(fetch) { |
| 1342 | + const resultSet = new Set(); |
| 1343 | + let progressCount = 0; |
| 1344 | + const url = `https://raw.githubusercontent.com/dscape/spell/3f1d4dd2a6dfcad65578eadaf29cae1800a1be13/test/resources/big.txt?d=${Date.now()}`; |
| 1345 | + await new Promise((resolve, reject) => |
| 1346 | + GM_xmlhttpRequest({ |
| 1347 | + method: "GET", |
| 1348 | + url, |
| 1349 | + fetch, |
| 1350 | + nocache: true, |
| 1351 | + responseType: "json", |
| 1352 | + onreadystatechange: function (response) { |
| 1353 | + resultSet.add("onreadystatechange " + resPrint(response)); |
| 1354 | + }, |
| 1355 | + onprogress: function (response) { |
| 1356 | + resultSet.add("onprogress " + resPrint(response)); |
| 1357 | + progressCount++; |
| 1358 | + }, |
| 1359 | + onload: function (response) { |
| 1360 | + resultSet.add("onload " + resPrint(response)); |
| 1361 | + }, |
| 1362 | + onloadend: function (response) { |
| 1363 | + resultSet.add("onloadend " + resPrint(response)); |
| 1364 | + resolve(); |
| 1365 | + }, |
| 1366 | + onerror: () => reject(), |
| 1367 | + ontimeout: () => reject(), |
| 1368 | + }) |
| 1369 | + ); |
| 1370 | + const resultList = [...resultSet]; |
| 1371 | + if (!fetch) { |
| 1372 | + assertEq(progressCount >= 2, true, "progressCount ok"); |
| 1373 | + assertDeepEq( |
| 1374 | + resultList, |
| 1375 | + [ |
| 1376 | + "onreadystatechange 1.000;r=missing;t=missing;x=missing", |
| 1377 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1378 | + "onreadystatechange 3.200;r=missing;t=missing;x=missing", |
| 1379 | + "onprogress 3.200;r=missing;t=missing;x=missing", |
| 1380 | + "onprogress 4.200;r=missing;t=missing;x=missing", |
| 1381 | + "onreadystatechange 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1382 | + "onload 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1383 | + "onloadend 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1384 | + ], |
| 1385 | + "standard-type GMXhr OK" |
| 1386 | + ); |
| 1387 | + } else { |
| 1388 | + assertEq(progressCount >= 2, true, "progressCount ok"); |
| 1389 | + assertDeepEq( |
| 1390 | + resultList, |
| 1391 | + [ |
| 1392 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1393 | + "onprogress 3.200;r=missing;t=missing;x=missing", |
| 1394 | + "onreadystatechange 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1395 | + "onload 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1396 | + "onloadend 4.200;r=<undefined>;t=string;x=XMLDocument", |
| 1397 | + ], |
| 1398 | + "fetch-type GMXhr OK" |
| 1399 | + ); |
| 1400 | + } |
| 1401 | + }, |
| 1402 | + }, |
| 1403 | + { |
| 1404 | + name: "Progress & JSON Object", |
| 1405 | + async run(fetch) { |
| 1406 | + const resultSet = new Set(); |
| 1407 | + let progressCount = 0; |
| 1408 | + const url = `https://raw.githubusercontent.com/json-iterator/test-data/0bce379832b475a6c21726ce37f971f8d849513b/large-file.json?d=${Date.now()}`; |
| 1409 | + await new Promise((resolve, reject) => |
| 1410 | + GM_xmlhttpRequest({ |
| 1411 | + method: "GET", |
| 1412 | + url, |
| 1413 | + fetch, |
| 1414 | + nocache: true, |
| 1415 | + responseType: "json", |
| 1416 | + onreadystatechange: function (response) { |
| 1417 | + resultSet.add("onreadystatechange " + resPrint(response)); |
| 1418 | + }, |
| 1419 | + onprogress: function (response) { |
| 1420 | + resultSet.add("onprogress " + resPrint(response)); |
| 1421 | + progressCount++; |
| 1422 | + }, |
| 1423 | + onload: function (response) { |
| 1424 | + resultSet.add("onload " + resPrint(response)); |
| 1425 | + }, |
| 1426 | + onloadend: function (response) { |
| 1427 | + resultSet.add("onloadend " + resPrint(response)); |
| 1428 | + resolve(); |
| 1429 | + }, |
| 1430 | + onerror: () => reject(), |
| 1431 | + ontimeout: () => reject(), |
| 1432 | + }) |
| 1433 | + ); |
| 1434 | + const resultList = [...resultSet]; |
| 1435 | + if (!fetch) { |
| 1436 | + assertEq(progressCount >= 2, true, "progressCount ok"); |
| 1437 | + assertDeepEq( |
| 1438 | + resultList, |
| 1439 | + [ |
| 1440 | + "onreadystatechange 1.000;r=missing;t=missing;x=missing", |
| 1441 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1442 | + "onreadystatechange 3.200;r=missing;t=missing;x=missing", |
| 1443 | + "onprogress 3.200;r=missing;t=missing;x=missing", |
| 1444 | + "onprogress 4.200;r=missing;t=missing;x=missing", |
| 1445 | + "onreadystatechange 4.200;r=object;t=string;x=XMLDocument", |
| 1446 | + "onload 4.200;r=object;t=string;x=XMLDocument", |
| 1447 | + "onloadend 4.200;r=object;t=string;x=XMLDocument", |
| 1448 | + ], |
| 1449 | + "standard-type GMXhr OK" |
| 1450 | + ); |
| 1451 | + } else { |
| 1452 | + assertEq(progressCount >= 2, true, "progressCount ok"); |
| 1453 | + assertDeepEq( |
| 1454 | + resultList, |
| 1455 | + [ |
| 1456 | + "onreadystatechange 2.200;r=missing;t=missing;x=missing", |
| 1457 | + "onprogress 3.200;r=missing;t=missing;x=missing", |
| 1458 | + "onreadystatechange 4.200;r=object;t=string;x=XMLDocument", |
| 1459 | + "onload 4.200;r=object;t=string;x=XMLDocument", |
| 1460 | + "onloadend 4.200;r=object;t=string;x=XMLDocument", |
| 1461 | + ], |
| 1462 | + "fetch-type GMXhr OK" |
| 1463 | + ); |
| 1464 | + } |
| 1465 | + }, |
| 1466 | + }, |
| 1467 | + { |
| 1468 | + name: "response.getAllResponseHeaders() [without headers in request]", |
| 1469 | + async run(fetch) { |
| 1470 | + let resultHeaders = null; |
| 1471 | + const getHeaders = (e) => { |
| 1472 | + if (!e) return new Headers(); |
| 1473 | + var n = e.split("\r\n").map(function (t) { |
| 1474 | + var e = t.split(":"); |
| 1475 | + return [e[0].trim(), e[1].trim()]; |
| 1476 | + }); |
| 1477 | + return new Headers(n); |
| 1478 | + }; |
| 1479 | + const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=ja&dj=1&dt=t&dt=rm&q=%E3%81%8F%E3%82%8B%EF%BC%81%EF%BC%81%0A%E3%81%8F%E3%82%8B%E2%80%A6%0A%E3%81%8D%E3%81%9F%EF%BC%81%EF%BC%81%0Alets+go%0A%E3%81%8F%E3%82%8B%EF%BC%81%0Ayes%21`; |
| 1480 | + await new Promise((resolve, reject) => |
| 1481 | + GM_xmlhttpRequest({ |
| 1482 | + method: "GET", |
| 1483 | + url, |
| 1484 | + responseType: "blob", |
| 1485 | + headers: {}, |
| 1486 | + fetch, |
| 1487 | + onload: function (response) { |
| 1488 | + resultHeaders = getHeaders(response.responseHeaders); |
| 1489 | + }, |
| 1490 | + onloadend: function (response) { |
| 1491 | + resolve(); |
| 1492 | + }, |
| 1493 | + onerror: () => reject(), |
| 1494 | + ontimeout: () => reject(), |
| 1495 | + }) |
| 1496 | + ); |
| 1497 | + const headers = resultHeaders; |
| 1498 | + assertEq(headers.get("content-type"), "application/json; charset=utf-8", "content-type ok"); |
| 1499 | + assertEq( |
| 1500 | + headers.get("reporting-endpoints").replace(/context=[-+\w]+/, "context=eJzj4tD"), |
| 1501 | + 'default="/_/TranslateApiHttp/web-reports?context=eJzj4tD"', |
| 1502 | + "reporting-endpoints ok" |
| 1503 | + ); |
| 1504 | + assertEq(headers.get("cross-origin-opener-policy"), "same-origin", "cross-origin-opener-policy ok"); |
| 1505 | + assertEq(headers.get("content-encoding") !== "deflate", true, "content-encoding ok"); |
| 1506 | + }, |
| 1507 | + }, |
| 1508 | + { |
| 1509 | + name: "response.getAllResponseHeaders() [with headers in request]", |
| 1510 | + async run(fetch) { |
| 1511 | + let resultHeaders = null; |
| 1512 | + const getHeaders = (e) => { |
| 1513 | + if (!e) return new Headers(); |
| 1514 | + var n = e.split("\r\n").map(function (t) { |
| 1515 | + var e = t.split(":"); |
| 1516 | + return [e[0].trim(), e[1].trim()]; |
| 1517 | + }); |
| 1518 | + return new Headers(n); |
| 1519 | + }; |
| 1520 | + const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=ja&dj=1&dt=t&dt=rm&q=%E3%81%8F%E3%82%8B%EF%BC%81%EF%BC%81%0A%E3%81%8F%E3%82%8B%E2%80%A6%0A%E3%81%8D%E3%81%9F%EF%BC%81%EF%BC%81%0Alets+go%0A%E3%81%8F%E3%82%8B%EF%BC%81%0Ayes%21`; |
| 1521 | + await new Promise((resolve, reject) => |
| 1522 | + GM_xmlhttpRequest({ |
| 1523 | + method: "GET", |
| 1524 | + url, |
| 1525 | + responseType: "blob", |
| 1526 | + headers: { |
| 1527 | + "Accept-Encoding": "deflate", |
| 1528 | + }, |
| 1529 | + fetch, |
| 1530 | + onload: function (response) { |
| 1531 | + resultHeaders = getHeaders(response.responseHeaders); |
| 1532 | + }, |
| 1533 | + onloadend: function (response) { |
| 1534 | + resolve(); |
| 1535 | + }, |
| 1536 | + onerror: () => reject(), |
| 1537 | + ontimeout: () => reject(), |
| 1538 | + }) |
| 1539 | + ); |
| 1540 | + const headers = resultHeaders; |
| 1541 | + assertEq(headers.get("content-type"), "application/json; charset=utf-8", "content-type ok"); |
| 1542 | + assertEq( |
| 1543 | + headers.get("reporting-endpoints").replace(/context=[-+\w]+/, "context=eJzj4tD"), |
| 1544 | + 'default="/_/TranslateApiHttp/web-reports?context=eJzj4tD"', |
| 1545 | + "reporting-endpoints ok" |
| 1546 | + ); |
| 1547 | + assertEq(headers.get("cross-origin-opener-policy"), "same-origin", "cross-origin-opener-policy ok"); |
| 1548 | + assertEq( |
| 1549 | + headers.get("content-encoding") === "deflate" || headers.get("content-encoding") === null, |
| 1550 | + true, |
| 1551 | + "content-encoding ok" |
| 1552 | + ); |
| 1553 | + }, |
| 1554 | + }, |
1262 | 1555 | { |
1263 | 1556 | name: "Response headers line endings", |
1264 | 1557 | async run(fetch) { |
|
0 commit comments