|
74 | 74 | }); |
75 | 75 | } |
76 | 76 |
|
| 77 | + function resetLoadingState() { |
| 78 | + promptText.style.display = 'block'; |
| 79 | + toolbar.style.display = 'none'; |
| 80 | + statusBar.style.display = 'none'; |
| 81 | + canvas.style.display = 'none'; |
| 82 | + } |
| 83 | + |
77 | 84 | function showLoadingError(text) { |
78 | 85 | promptText.style.display = 'block'; |
79 | 86 | toolbar.style.display = 'none'; |
|
465 | 472 | toolbar.style.display = 'flex'; |
466 | 473 | statusBar.style.display = 'flex'; |
467 | 474 | canvas.style.display = 'block'; |
| 475 | + updateHash(code, map); |
468 | 476 | const sm = parseSourceMap(map); |
469 | 477 |
|
470 | 478 | // Populate the file picker |
|
1531 | 1539 | // Older browsers |
1532 | 1540 | darkMedia.addListener(onDarkModeChange) |
1533 | 1541 | } |
| 1542 | + |
| 1543 | + //////////////////////////////////////////////////////////////////////////////// |
| 1544 | + // Shareable URLs |
| 1545 | + |
| 1546 | + const utf8ToUTF16 = x => decodeURIComponent(escape(x)); |
| 1547 | + const utf16ToUTF8 = x => unescape(encodeURIComponent(x)); |
| 1548 | + |
| 1549 | + function loadFromHash() { |
| 1550 | + try { |
| 1551 | + // Reads a string in length-prefix form separated by a null character. This |
| 1552 | + // format is used because it's simple and also more compact than JSON. |
| 1553 | + const readBuffer = () => { |
| 1554 | + const zero = hash.indexOf('\0'); |
| 1555 | + if (zero < 0) throw 'No null character'; |
| 1556 | + const start = zero + 1; |
| 1557 | + const end = start + (0 | hash.slice(0, zero)); |
| 1558 | + const buffer = hash.slice(start, end); |
| 1559 | + if (end > hash.length) throw 'Invalid length'; |
| 1560 | + hash = hash.slice(end); |
| 1561 | + return buffer; |
| 1562 | + }; |
| 1563 | + |
| 1564 | + // Extract the length-prefixed data |
| 1565 | + let hash = atob(location.hash.slice(1)); |
| 1566 | + const code = readBuffer(); |
| 1567 | + const map = readBuffer(); |
| 1568 | + if (hash !== '') throw 'Unexpected extra data'; |
| 1569 | + |
| 1570 | + finishLoading(utf8ToUTF16(code), utf8ToUTF16(map)); |
| 1571 | + } catch (e) { |
| 1572 | + // Clear out an invalid hash and reset the UI |
| 1573 | + if (location.hash !== '') { |
| 1574 | + try { |
| 1575 | + history.replaceState({}, '', location.pathname); |
| 1576 | + } catch (e) { |
| 1577 | + } |
| 1578 | + } |
| 1579 | + resetLoadingState(); |
| 1580 | + } |
| 1581 | + } |
| 1582 | + |
| 1583 | + function updateHash(code, map) { |
| 1584 | + try { |
| 1585 | + const btoaLength = n => 4 * ((n + 2) / 3 | 0) |
| 1586 | + const kMaxURLDisplayChars = 32 * 1024; // Chrome limits URLs to 32kb in size |
| 1587 | + const url = new URL(location.href); |
| 1588 | + url.hash = '#'; // Clear the data in the hash but leave the hash prefix |
| 1589 | + const urlLength = url.href.length; |
| 1590 | + |
| 1591 | + // Do a cheap check to see if the URL will be too long |
| 1592 | + let codeLength = `${code.length}\0`; |
| 1593 | + let mapLength = `${map.length}\0`; |
| 1594 | + let finalLength = urlLength + btoaLength(codeLength.length + code.length + mapLength.length + map.length) |
| 1595 | + if (finalLength >= kMaxURLDisplayChars) throw 'URL estimate too long'; |
| 1596 | + |
| 1597 | + // Do the expensive check to see if the URL will be too long |
| 1598 | + code = utf16ToUTF8(code); |
| 1599 | + map = utf16ToUTF8(map); |
| 1600 | + codeLength = `${code.length}\0`; |
| 1601 | + mapLength = `${map.length}\0`; |
| 1602 | + finalLength = urlLength + btoaLength(codeLength.length + code.length + mapLength.length + map.length) |
| 1603 | + if (finalLength >= kMaxURLDisplayChars) throw 'URL too long'; |
| 1604 | + |
| 1605 | + // Only pay the cost of building the string now that we know it'll work |
| 1606 | + const hash = '#' + btoa(`${codeLength}${code}${mapLength}${map}`); |
| 1607 | + if (location.hash !== hash) { |
| 1608 | + history.pushState({}, '', hash); |
| 1609 | + } |
| 1610 | + } catch (e) { |
| 1611 | + // Push an empty hash instead if it's too big for a URL |
| 1612 | + if (location.hash !== '') { |
| 1613 | + try { |
| 1614 | + history.pushState({}, '', location.pathname); |
| 1615 | + } catch (e) { |
| 1616 | + } |
| 1617 | + } |
| 1618 | + } |
| 1619 | + } |
| 1620 | + |
| 1621 | + loadFromHash(); |
| 1622 | + addEventListener('popstate', () => loadFromHash()); |
1534 | 1623 | })(); |
1535 | 1624 |
|
1536 | 1625 | const exampleJS = `// Generated by CoffeeScript 2.5.1 |
|
0 commit comments