Web Inspector

Allows you to inspect web pages

当前为 2024-12-16 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Web Inspector
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.8
  5. // @description Allows you to inspect web pages
  6. // @author https://gf.qytechs.cn/en/users/85040-dan-wl-danwl
  7. // @license MIT
  8. // @match *://*/*
  9. // @run-at document-start
  10. // @grant none
  11. // ==/UserScript==
  12.  
  13. // MIT License
  14.  
  15. // Copyright(c) 2024 DanWL
  16.  
  17. // Permission is hereby granted, free of charge, to any person obtaining a copy
  18. // of this software and associated documentation files(the "Software"), to deal
  19. // in the Software without restriction, including without limitation the rights
  20. // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
  21. // copies of the Software, and to permit persons to whom the Software is
  22. // furnished to do so, subject to the following conditions:
  23.  
  24. // The above copyright notice and this permission notice shall be included in all
  25. // copies or substantial portions of the Software.
  26.  
  27. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  28. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  29. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
  30. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  31. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  32. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  33. // SOFTWARE.
  34.  
  35.  
  36. // This userscript defines a function on the web page you visit
  37. // For the function to do anything, use this bookmarklet:
  38. // javascript:(function(){WEB_INSPECTOR();})();
  39. // A bookmarklet is essentially a regular browser bookmark/favorite but with a JavaScript url
  40.  
  41. (() => {
  42. function el(tagName, className) {
  43. const ret = document.createElement(tagName);
  44.  
  45. if (className) {
  46. ret.className = className;
  47. }
  48.  
  49. return ret;
  50. }
  51.  
  52. function spanNode(className, innerTextOrText){
  53. const span = el('span', className);
  54.  
  55. if (typeof innerTextOrText === 'string') {
  56. span.innerText = innerTextOrText;
  57. }
  58. else if (innerTextOrText instanceof Text) {
  59. append(span, innerTextOrText);
  60. }
  61.  
  62. return span;
  63. }
  64.  
  65. function textNode(txt) {
  66. return document.createTextNode(txt);
  67. }
  68.  
  69. function append(parent, nodes) {
  70. // enables much better minimising
  71.  
  72. if (!Array.isArray(nodes)) {
  73. nodes = [nodes];
  74. }
  75.  
  76. nodes.forEach((node) => {
  77. parent.appendChild(node);
  78. });
  79. }
  80.  
  81. function htmlSymbol(symbol) {
  82. return spanNode('html-symbol', symbol);
  83. }
  84.  
  85. function createTagNameNode(tagName) {
  86. return spanNode('tag-name', tagName);
  87. }
  88.  
  89. function createTagAttributeValueNode(attribute) {
  90. const isLink = ['href', 'src'].includes(attribute.name);
  91. const isStyle = attribute.name === 'style';
  92. const span = spanNode('tag-attribute-value');
  93.  
  94. if (isLink) {
  95. append(span, [textNode('"'), createLink(attribute.value, attribute.value), textNode('"')]);
  96. }
  97. else if (isStyle) {
  98. append(span, [textNode('"'), parseStyle(attribute.ownerElement.style), textNode('"')]);
  99. }
  100. else {
  101. append(span, textNode(JSON.stringify(attribute.value)));
  102. }
  103.  
  104. return span;
  105. }
  106.  
  107. function createPlainTextNode(node) {
  108. // TODO html entities highlighting
  109.  
  110. return spanNode('text', textNode(applyHTMLWhitespaceRules(node.textContent)));
  111. }
  112.  
  113. function elementDoesNotNeedToBeClosed(tagName) {
  114. // https://developer.mozilla.org/en-US/docs/Web/HTML/Element
  115.  
  116. return ['base', 'link', 'meta', 'hr', 'br', 'wbr', 'area', 'img', 'track', 'embed', 'source', 'input'].includes(tagName);
  117. }
  118.  
  119. function applyHTMLWhitespaceRules(text) {
  120. // https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model/Whitespace
  121.  
  122. return text
  123. .replace(/^[\t ]+/mg, '')
  124. .replace(/[\t\r\n]/g, ' ')
  125. .replace(/ {2,}/g, ' ');
  126. }
  127.  
  128. function createSpacer(spacing) {
  129. const spacer = el('pre', 'spacer');
  130.  
  131. spacer.innerHTML = spacing;
  132.  
  133. return spacer;
  134. }
  135.  
  136. function createIndentSpacer(indentLevel) {
  137. const space = '\t';
  138. let spacing = '';
  139.  
  140. while (indentLevel > 0) {
  141. spacing += space;
  142. indentLevel--;
  143. }
  144.  
  145. const spacer = createSpacer(spacing);
  146.  
  147. spacer.className += ' indentation';
  148.  
  149. return spacer
  150. }
  151.  
  152. function createLink(url, displayText) {
  153. const link = el('a');
  154.  
  155. link.href = url;
  156. link.target = '_blank';
  157.  
  158. append(link, textNode(displayText));
  159.  
  160. return link;
  161. }
  162.  
  163. function createExpandCollapseBtn(element) {
  164. // https://www.amp-what.com &#9660
  165.  
  166. const btn = el('button', 'expand-collapse-button');
  167.  
  168. btn.innerHTML = '▼';
  169. btn.onclick = function() {
  170. if (element.className.match(/ collapsed /)) {
  171. element.className = element.className.replace(/ collapsed /, '');
  172. }
  173. else {
  174. element.className += ' collapsed ';
  175. }
  176. };
  177.  
  178. return btn;
  179. }
  180.  
  181. function parseHTML(node, parent, parentTagName, indentLevel) {
  182. // https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
  183. // using instanceof doesnt always work
  184.  
  185. const isElement = node.nodeType === Node.ELEMENT_NODE;
  186. const isText = node.nodeType === Node.TEXT_NODE;
  187. const isComment = node.nodeType === Node.COMMENT_NODE;
  188. const isDoctype = node.nodeType === Node.DOCUMENT_TYPE_NODE;
  189.  
  190. const addLeadingSpaces = indentLevel > 0;
  191.  
  192. function addNewLineSpacing() {
  193. append(parent, el('br'));
  194.  
  195. if (addLeadingSpaces) {
  196. append(parent, createIndentSpacer(indentLevel));
  197. }
  198.  
  199. const spacing = createSpacer(' ');
  200.  
  201. append(parent, spacing);
  202.  
  203. return spacing;
  204. }
  205.  
  206. if (isElement) {
  207. const spacing = addNewLineSpacing();
  208. const tagNode = spanNode('tag');
  209. const tagName = node.tagName.toLowerCase();
  210. const elementIsSelfClosing = elementDoesNotNeedToBeClosed(tagName);
  211. const style = getComputedStyle(node);
  212.  
  213. // FIXME isHidden detection isn't fully correct https://developer.mozilla.org/en-US/docs/Web/CSS/visibility
  214. const isHidden = style.display === 'none' || style.visibility !== 'visible';
  215.  
  216. if (isHidden) {
  217. tagNode.className += ' hidden-tag';
  218. }
  219.  
  220. append(tagNode, [htmlSymbol('<'), createTagNameNode(tagName)]);
  221.  
  222. const nodeAttributes = node.attributes;
  223.  
  224. if (nodeAttributes.length) {
  225. const tagAttributesNode = spanNode('tag-attributes');
  226.  
  227. for (const attribute of nodeAttributes) {
  228. append(tagAttributesNode, [htmlSymbol(' '), spanNode('tag-attribute-name', attribute.name), htmlSymbol('='), createTagAttributeValueNode(attribute)]);
  229. }
  230.  
  231. append(tagNode, tagAttributesNode);
  232. }
  233.  
  234. append(tagNode, htmlSymbol((elementIsSelfClosing ? ' /' : '') + '>'));
  235.  
  236. // frameDoc for iframe or deprecated tags like frame which include other web pages
  237. // iframe.childNodes.length is always 0
  238.  
  239. let frameDoc;
  240.  
  241. try {
  242. frameDoc = (node.contentDocument || (node.contentWindow && node.contentWindow.document));
  243. }
  244. catch(err) {
  245. // unable to inspect iframe content
  246. // likely a SecurityError
  247. }
  248.  
  249. const childNodes = frameDoc ? frameDoc.childNodes : node.childNodes;
  250.  
  251. if (childNodes.length > 0) {
  252. const tagInnerNode = spanNode('tag-inner');
  253.  
  254. if (isHidden || frameDoc) {
  255. // initialise to collapsed, dont make it collapse again unless done so by user
  256. tagInnerNode.className += ' collapsed ';
  257. }
  258.  
  259. switch(tagName) {
  260. case 'style': {
  261. append(tagInnerNode, parseStyle(node.sheet, 0));
  262. break;
  263. }
  264. case 'script': {
  265. append(tagInnerNode, parseScript(node));
  266. break;
  267. }
  268. default: {
  269. for (const child of childNodes) {
  270. parseHTML(child, tagInnerNode, tagName, indentLevel + 1);
  271. }
  272. }
  273. }
  274.  
  275. append(tagNode, tagInnerNode);
  276. }
  277.  
  278. if (!elementIsSelfClosing) {
  279. if (tagNode.querySelectorAll('.tag, .css, .script').length > 0) {
  280. append(tagNode, el('br'));
  281.  
  282. if (addLeadingSpaces) {
  283. append(tagNode, createIndentSpacer(indentLevel));
  284. }
  285. const expandCollapseBtn = createExpandCollapseBtn(tagNode.querySelector('.tag-inner'));
  286.  
  287. spacing.insertAdjacentElement('afterend', expandCollapseBtn);
  288. expandCollapseBtn.insertAdjacentHTML('afterend', '<pre class="spacer"> </pre>');
  289. append(tagNode, spacing);
  290. }
  291.  
  292. append(tagNode, [htmlSymbol('</'), createTagNameNode(tagName), htmlSymbol('>')]);
  293. }
  294.  
  295. append(parent, tagNode);
  296. }
  297. else if (isText) {
  298. append(parent, createPlainTextNode(node));
  299. }
  300. else if (isComment) {
  301. addNewLineSpacing();
  302.  
  303. append(parent, spanNode('comment', textNode('<!-- ' + node.textContent + '-->')));
  304. }
  305. else if (isDoctype) {
  306. addNewLineSpacing();
  307.  
  308. append(parent, spanNode('document-type', '<!DOCTYPE ' + node.nodeName + '>'));
  309. }
  310. else {
  311. console.log('isElement', isElement);
  312. console.log(node instanceof HTMLElement)
  313. window._node = node;
  314. console.error(node);
  315. throw new Error('unexpected node');
  316. }
  317. }
  318.  
  319. function validateIndentLevel(indentLevel) {
  320. if (indentLevel === undefined || isNaN(indentLevel)) {
  321. // any of these + 1 gives NaN
  322. return true;
  323. }
  324.  
  325. if (typeof indentLevel === 'number' && isFinite(indentLevel) && indentLevel >= 0) {
  326. return true;
  327. }
  328.  
  329. throw new Error('indentLevel must be a number >= 0, undefined or NaN');
  330. }
  331.  
  332. function cssSymbol(symbol) {
  333. return spanNode('css-symbol', textNode(symbol));
  334. }
  335.  
  336. function atRuleNameNode(name) {
  337. return spanNode('css-at-rule-name', textNode(name));
  338. }
  339.  
  340. function cssSelectorText(selectorText) {
  341. // parsing selector text is very complex
  342. // so just leave it as it is for now
  343. // https://www.npmjs.com/package/css-selector-parser
  344. // https://github.com/mdevils/css-selector-parser/blob/master/src/parser.ts
  345.  
  346. return spanNode('css-full-selector', textNode(selectorText));
  347. }
  348.  
  349. function previewCSSColorNode(property, value) {
  350. if (!property.match(/(^|-)color$/)) {
  351. // properties with a color as a value are either 'color' or end with '-color'
  352. return;
  353. }
  354.  
  355. if (property.match(/^-/)) {
  356. // could be a css varable which might not be a color value
  357. return;
  358. }
  359.  
  360. if (value.match(/^(-|var\()/i)) {
  361. // cant easily preview variable colors
  362. return;
  363. }
  364.  
  365. if (value.match(/^(currentcolor|inherit|initial|revert|revert-layer|unset)$/i)) {
  366. // cant easily preview global colors
  367. return;
  368. }
  369.  
  370. // the outline adds contrast
  371. // getComputedStyle(preview) gives empty string so use the very new css invert function
  372. // https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/invert
  373.  
  374. const span = spanNode('css-color-preview-container');
  375. const preview = spanNode('css-color-preview');
  376. const previewInner = spanNode();
  377.  
  378. preview.style.outlineColor = value;
  379. previewInner.style.backgroundColor = value;
  380.  
  381. append(preview, previewInner);
  382. append(span, [createSpacer(' '), preview]);
  383.  
  384. return span;
  385. }
  386.  
  387. function parseStyle(cssStyleDeclaration, indentLevel) {
  388. validateIndentLevel(indentLevel);
  389.  
  390. // https://developer.mozilla.org/en-US/docs/Web/API/CSS_Object_Model
  391. // https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_nesting/Using_CSS_nesting#nested_declarations_rule
  392. // https://developer.mozilla.org/en-US/docs/Web/API/CSSRule
  393.  
  394. const style = spanNode('css');
  395.  
  396. function addNewLineSpacing(parent, indentLevel, spacer) {
  397. if (!isFinite(indentLevel)) {
  398. return;
  399. }
  400.  
  401. append(parent, [el('br'), createIndentSpacer(indentLevel), spacer ? spacer : createSpacer(' ')]);
  402. }
  403.  
  404. function parseDeclaration(property, value, indentLevel, isLastDeclaration) {
  405. validateIndentLevel(indentLevel);
  406.  
  407. const decNode = spanNode('css-declaration');
  408. const propNode = spanNode('css-declaration-property', textNode(property));
  409. const valNode = spanNode('css-declaration-value', textNode(value));
  410. const colorPreviewNode = previewCSSColorNode(property, value);
  411.  
  412. addNewLineSpacing(decNode, indentLevel);
  413.  
  414. append(decNode, [propNode, cssSymbol(': ')]);
  415.  
  416. if (colorPreviewNode) {
  417. append(valNode, colorPreviewNode);
  418. }
  419.  
  420. append(decNode, [valNode, cssSymbol(';')]);
  421.  
  422. if (!isFinite(indentLevel) && !isLastDeclaration) {
  423. append(decNode, cssSymbol(' '));
  424. }
  425.  
  426. return decNode;
  427. }
  428.  
  429. function parseRuleCSSRules(rule, indentLevel) {
  430. if (!rule.cssRules.length) {
  431. return textNode('');
  432. }
  433.  
  434. const ruleRulesNode = spanNode();
  435.  
  436. for (const ruleRule of rule.cssRules) {
  437. parseRule(ruleRulesNode, ruleRule, indentLevel + 1);
  438. }
  439.  
  440. return ruleRulesNode;
  441. }
  442.  
  443. function parseRule(parentElement, rule, indentLevel) {
  444. validateIndentLevel(indentLevel);
  445.  
  446. const ruleNode = spanNode();
  447. const braceLeadingNode = spanNode('css-brace-leading');
  448. const braceContentNode = spanNode('css-brace-content');
  449. const spacer = createSpacer(' ');
  450.  
  451. function insertExpandCollapseBtn() {
  452. spacer.insertAdjacentElement('beforebegin', createExpandCollapseBtn(braceContentNode));
  453. spacer.innerHTML = ' ';
  454. }
  455.  
  456. addNewLineSpacing(ruleNode, indentLevel, spacer);
  457.  
  458. switch (rule.constructor.name) {
  459. case 'CSSStyleRule': {
  460. insertExpandCollapseBtn();
  461.  
  462. ruleNode.className = 'style-rule';
  463.  
  464. append(braceLeadingNode, cssSelectorText(rule.selectorText));
  465. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  466. append(braceContentNode, [parseRuleCSSRules(rule, indentLevel), parseStyle(rule.style, indentLevel)]);
  467. addNewLineSpacing(braceContentNode, indentLevel);
  468. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  469.  
  470. break;
  471. }
  472. case 'CSSImportRule': {
  473. ruleNode.className = 'import-rule';
  474.  
  475. const url = spanNode();
  476. const layer = spanNode(0, textNode(rule.layerName === null ? '' : (' ' + (rule.layerName ? `layer(${rule.layerName})` : rule.layerName))));
  477. const supports = spanNode(0, textNode(rule.supportsText === null ? '' : ` supports(${rule.supportsText})`));
  478.  
  479. append(url, [textNode('url("'), createLink(rule.styleSheet.href, rule.href), textNode('")')]);
  480. append(ruleNode, [atRuleNameNode('@import '), url, layer, supports, spanNode(0, textNode(rule.media.mediaText))]);
  481.  
  482. break;
  483. }
  484. case 'CSSMediaRule': {
  485. insertExpandCollapseBtn();
  486.  
  487. ruleNode.className = 'media-rule';
  488.  
  489. append(braceLeadingNode, [atRuleNameNode('@media '), textNode(rule.conditionText)]);
  490. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  491. append(braceContentNode, parseRuleCSSRules(rule, indentLevel));
  492. addNewLineSpacing(braceContentNode, indentLevel);
  493. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  494.  
  495. break;
  496. }
  497. case 'CSSFontFaceRule': {
  498. insertExpandCollapseBtn();
  499.  
  500. ruleNode.className = 'font-face-rule';
  501.  
  502. append(braceLeadingNode, atRuleNameNode('@font-face'));
  503. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  504. append(braceContentNode, parseStyle(rule.style, indentLevel + 1));
  505. addNewLineSpacing(braceContentNode, indentLevel);
  506. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  507.  
  508. break;
  509. }
  510. case 'CSSPageRule': {
  511. insertExpandCollapseBtn();
  512.  
  513. ruleNode.className = 'page-rule';
  514.  
  515. append(braceLeadingNode, atRuleNameNode('@page'));
  516.  
  517. if (rule.selectorText) {
  518. append(braceLeadingNode, [cssSymbol(' '), cssSelectorText(rule.selectorText)]);
  519. }
  520.  
  521. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  522. append(braceContentNode, [parseRuleCSSRules(rule, indentLevel), parseStyle(rule.style, indentLevel + 1)]);
  523. addNewLineSpacing(braceContentNode, indentLevel);
  524. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  525.  
  526. break;
  527. }
  528. case 'CSSNamespaceRule': {
  529. ruleNode.className = 'namespace-rule';
  530.  
  531. append(ruleNode, atRuleNameNode('@namespace '));
  532.  
  533. if (rule.prefix) {
  534. append(ruleNode, rule.prefix + ' ');
  535. }
  536.  
  537. append(rule, [textNode('url("'), createLink(rule.namespaceURI, rule.namespaceURI), textNode('")')]);
  538.  
  539. break;
  540. }
  541. case 'CSSKeyframesRule': {
  542. insertExpandCollapseBtn();
  543.  
  544. ruleNode.className = 'keyframes-rule';
  545.  
  546. append(braceLeadingNode, [atRuleNameNode('@keyframes '), textNode(rule.name)]);
  547. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  548. append(braceContentNode, parseRuleCSSRules(rule, indentLevel + 1));
  549. addNewLineSpacing(braceContentNode, indentLevel);
  550. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  551.  
  552. break;
  553. }
  554. case 'CSSKeyframeRule': {
  555. insertExpandCollapseBtn();
  556.  
  557. ruleNode.className = 'keyframe-rule';
  558.  
  559. append(braceLeadingNode, textNode(rule.keyText));
  560. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  561. append(braceContentNode, parseStyle(rule.style, indentLevel + 1));
  562. addNewLineSpacing(braceContentNode, indentLevel);
  563. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  564.  
  565. break;
  566. }
  567. case 'CSSCounterStyleRule': {
  568. insertExpandCollapseBtn();
  569.  
  570. ruleNode.className = 'counter-style-rule';
  571.  
  572. append(braceLeadingNode, [atRuleNameNode('@counter-style '), textNode(rule.name)]);
  573. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  574. [
  575. ['system', rule.system],
  576. ['symbols', rule.symbols],
  577. ['additiveSymbols', rule.additiveSymbols],
  578. ['negative', rule.negative],
  579. ['prefix', rule.prefix],
  580. ['suffix', rule.suffix],
  581. ['range', rule.range],
  582. ['pad', rule.pad],
  583. ['speak-as', rule.speakAs],
  584. ['fallback', rule.fallback]
  585. ].forEach((declaration) => {
  586. if (declaration[1]) {
  587. append(braceContentNode, parseDeclaration(declaration[0], declaration[1], indentLevel + 1));
  588. }
  589. });
  590. addNewLineSpacing(braceContentNode, indentLevel);
  591. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  592.  
  593. break;
  594. }
  595. case 'CSSSupportsRule': {
  596. insertExpandCollapseBtn();
  597.  
  598. ruleNode.className = 'supports-rule';
  599.  
  600. append(braceLeadingNode, [atRuleNameNode('@supports '), textNode(rule.conditionText)]);
  601. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  602. append(braceContentNode, parseRuleCSSRules(rule, indentLevel + 1));
  603. addNewLineSpacing(braceContentNode, indentLevel);
  604. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  605.  
  606. break;
  607. }
  608. case 'CSSFontFeatureValuesRule': {
  609. ruleNode.className = 'font-feature-values-rule';
  610.  
  611. // TODO test this in a browser that supports CSSFontFeatureValuesRule
  612. // not supported in librewolf 133.0-1 on linux
  613.  
  614. // https://developer.mozilla.org/en-US/docs/Web/API/CSSFontFeatureValuesRule
  615. // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-feature-values
  616. // https://developer.mozilla.org/en-US/docs/Web/CSS/@font-feature-values/font-display
  617.  
  618. console.warn(rule)
  619. console.warn('unclear how to parse CSSFontFeatureValuesRule, using unformatted');
  620.  
  621. append(ruleNode, textNode(rule.cssText));
  622.  
  623. /*
  624. ruleNode.appendChild(textNode('@font-feature-values '));
  625. ruleNode.appendChild(rule.fontFamily);
  626. ruleNode.appendChild(cssSymbol(' {'));
  627.  
  628. // who knows
  629.  
  630. ruleNode.appendChild(cssSymbol('}'));
  631. */
  632.  
  633. break;
  634. }
  635. case 'CSSFontPaletteValuesRule': {
  636. ruleNode.className = 'font-palette-values-rule';
  637.  
  638. // TODO test this in a browser that supports CSSFontPaletteValuesRule
  639. // not supported in librewolf 133.0-1 on linux
  640.  
  641. console.warn(rule)
  642. console.warn('unclear how to parse CSSFontFeatureValuesRule, using unformatted');
  643.  
  644. append(ruleNode, textNode(rule.cssText));
  645. /*
  646. ruleNode.appendChild(textNode('@font-palette-values '));
  647. ruleNode.appendChild(rule.name);
  648. ruleNode.appendChild(cssSymbol(' {'));
  649.  
  650. ruleNode.appendChild(parseDeclaration('font-family', rule.fontFamily, indentLevel + 1));
  651. ruleNode.appendChild(parseDeclaration('base-palette', rule.basePalette, indentLevel + 1));
  652.  
  653. // no idea how this will behave
  654. // https://developer.mozilla.org/en-US/docs/Web/API/CSSFontPaletteValuesRule
  655. // https://developer.mozilla.org/en-US/docs/Web/API/CSSFontPaletteValuesRule/overrideColors
  656. // may need special treatment for formatting
  657. ruleNode.appendChild(parseDeclaration('override-colors', rule.overrideColors, indentLevel + 1, true))
  658.  
  659. ruleNode.appendChild(cssSymbol('}'));
  660. */
  661. }
  662. case 'CSSLayerBlockRule': {
  663. insertExpandCollapseBtn();
  664.  
  665. ruleNode.className = 'layer-block-rule';
  666.  
  667. append(braceLeadingNode, [atRuleNameNode('@layer '), textNode(rule.name)]);
  668. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  669. append(braceContentNode, parseRuleCSSRules(rule, indentLevel + 1));
  670. addNewLineSpacing(braceContentNode, indentLevel);
  671. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  672.  
  673. break;
  674. }
  675. case 'CSSLayerBlockRule': {
  676. ruleNode.className = 'layer-block-rule';
  677.  
  678. append(ruleNode, atRuleNameNode('@layer '));
  679.  
  680. rule.nameList.forEach((name, i) => {
  681. append(ruleNode, textNode(name));
  682.  
  683. if (i + 1 < this.length) {
  684. append(ruleNode, cssSymbol(', '));
  685. }
  686. });
  687.  
  688. append(ruleNode, cssSymbol(';'));
  689.  
  690. break;
  691. }
  692. case 'CSSPropertyRule': {
  693. insertExpandCollapseBtn();
  694.  
  695. ruleNode.className = 'property-rule';
  696.  
  697. append(braceLeadingNode, [atRuleNameNode('@property '), textNode(rule.name)]);
  698. append(ruleNode, [braceLeadingNode, cssSymbol(' {')]);
  699. append(braceContentNode, [parseDeclaration('syntax', rule.syntax, indentLevel + 1), parseDeclaration('inherits', '' + rule.inherits, indentLevel + 1), parseDeclaration('initial-value', rule.initialValue, indentLevel + 1, true)]);
  700. addNewLineSpacing(braceContentNode, indentLevel);
  701. append(ruleNode, [braceContentNode, cssSymbol('}')]);
  702.  
  703. break;
  704. }
  705. default: {
  706. ruleNode.className = 'unexpected-rule';
  707.  
  708. // should not need to explicitly handle CSSGroupingRule because other rule types inherit from it
  709. // should not need to explicitly handle CSSNestedDeclarations because other rules pass a cssStyleDeclaration
  710.  
  711. console.warn(rule);
  712. console.warn('unexpected css rule type, using unformatted');
  713.  
  714. append(ruleNode, textNode(rule.cssText));
  715.  
  716. break;
  717. }
  718. }
  719.  
  720. parentElement.appendChild(ruleNode);
  721. }
  722.  
  723. if (cssStyleDeclaration instanceof CSSStyleSheet) {
  724. const ruleRulesNode = spanNode();
  725.  
  726. for (const rule of cssStyleDeclaration.cssRules) {
  727. parseRule(ruleRulesNode, rule, indentLevel);
  728. }
  729.  
  730. append(style, ruleRulesNode);
  731. }
  732. else {
  733. // previously use a for of before to filter out all style declarations
  734. // need to know if there is a next declaration for formatting purposes
  735. // element.style has numbered indexes for styles actually declared on the element
  736.  
  737. for (let i = 0; ; ) {
  738. const prop = cssStyleDeclaration[i];
  739.  
  740. if (!prop) {
  741. break;
  742. }
  743.  
  744. i++;
  745.  
  746. const hasNext = !!cssStyleDeclaration[i];
  747.  
  748. append(style, parseDeclaration(prop, cssStyleDeclaration.getPropertyValue(prop), indentLevel + 1, !hasNext));
  749. }
  750. }
  751.  
  752. return style;
  753. }
  754.  
  755. function parseScript(node) {
  756. // TODO formatting, highlighting
  757.  
  758. return spanNode('script', textNode(node.textContent.trim()));
  759. }
  760.  
  761. function hideCollapsed() {
  762. let a = '.collapsed';
  763. let b = a;
  764.  
  765. ['br', '.spacer', '.spacer'].forEach((c) => {
  766. b += ' + ' + c;
  767. a += ', ' + b;
  768. });
  769.  
  770. return a + ' {\n\tdisplay: none;\n}';
  771. }
  772.  
  773. function color(selector, color) {
  774. return `${selector} {\n\tcolor: ${color};\n}`;
  775. }
  776.  
  777. function getStyle() {
  778. return `body {
  779. margin: 1em;
  780. padding: 0;
  781. display: block;
  782. font-family: monospace;
  783. font-size: 1em;
  784. line-height: 1.2em;
  785. tab-size: 2;
  786. background: #232327;
  787. word-break: break-word;
  788. }
  789.  
  790. .spacer {
  791. margin: 0;
  792. padding: 0;
  793. border: 0;
  794. outline: 0;
  795. display: inline-block;
  796. }
  797.  
  798. ${hideCollapsed()}
  799.  
  800. .expand-collapse-button {
  801. margin: 0;
  802. padding: 0;
  803. border: 0;
  804. width: fit-content;
  805. cursor: pointer;
  806. background: inherit;
  807. color: #88888a;
  808. }
  809.  
  810. .expand-collapse-button:has(+ .spacer + .tag > .collapsed), .expand-collapse-button:has(+ .spacer + .css-brace-leading + .css-symbol + .css-brace-content.collapsed) {
  811. rotate: 270deg;
  812. }
  813.  
  814. .document-type {
  815. font-style: italic;
  816. }
  817.  
  818. ${color('.html-symbol', '#7c7c7e')}
  819.  
  820. ${color('.document-type', '#72baf9')}
  821.  
  822. ${color('.comment', '#90EE90')}
  823.  
  824. ${color('.text', 'white')}
  825.  
  826. ${color('.tag-name', '#72baf9')}
  827.  
  828. ${color('.tag-attribute-name', '#fb7be5')}
  829.  
  830. ${color('.tag-attribute-value', '#9b79d4')}
  831.  
  832. .tag-attribute-value a {
  833. color: inherit;
  834. text-decoration: underline;
  835. }
  836.  
  837. ${color('.tag.hidden-tag .html-symbol', '#6e6e6e')}
  838.  
  839. ${color('.tag.hidden-tag .tag-name', '#929294')}
  840.  
  841. ${color('.tag.hidden-tag .tag-attribute-name', '#676768')}
  842.  
  843. ${color('.tag.hidden-tag .tag-attribute-value', '#939394')}
  844.  
  845. ${color('.css', 'white')}
  846.  
  847. ${color('.css-symbol', '#7c7c7e')}
  848.  
  849. ${color('.css-at-rule-name', '#72baf9')}
  850.  
  851. ${color('.css-declaration-property', '#80d36f')}
  852.  
  853. ${color('.css-declaration-value', '#fb7be5')}
  854.  
  855. .css-color-preview {
  856. display: inline-block;
  857. width: 1em;
  858. height: 1em;
  859. outline-width: 2px;
  860. outline-style: solid;
  861. filter: invert(100%);
  862. }
  863.  
  864. ${color('.script', 'white')}`;
  865. }
  866.  
  867. function main(outputWindow) {
  868. const meta1 = el('meta');
  869. const meta2 = el('meta');
  870. const title = el('title');
  871. const style = el('style');
  872. const output = el('span');
  873.  
  874. meta1.setAttribute('charset', 'utf-8');
  875.  
  876. meta2.setAttribute('name', 'viewport');
  877. meta2.setAttribute('content', 'width=device-width, initial-scale=1, minimum-scale=1');
  878.  
  879. title.innerHTML = 'Web Inspector - ' + document.title;
  880.  
  881. style.innerHTML = getStyle();
  882.  
  883. for (const node of document.childNodes) {
  884. parseHTML(node, output, undefined, 0);
  885. }
  886.  
  887. if (output.firstElementChild.tagName === 'BR') {
  888. // remove unnecessary spacing at top
  889. output.firstElementChild.remove();
  890. }
  891.  
  892. outputWindow.document.write('<!DOCTYPE html><html><head></head><body></body></html>');
  893.  
  894. append(outputWindow.document.head, meta1);
  895. append(outputWindow.document.head, meta2);
  896. append(outputWindow.document.head, title)
  897. append(outputWindow.document.head, style);
  898. append(outputWindow.document.body, output);
  899. }
  900.  
  901. window.WEB_INSPECTOR = function() {
  902. try {
  903. // try to open in a new window
  904. // if popups are blocked, replace the current webpage with the web inspector
  905.  
  906. const outputWindow = open('about:blank') || window;
  907.  
  908. main(outputWindow);
  909.  
  910. outputWindow.onload = function() {
  911. main(outputWindow);
  912. };
  913. }
  914. catch(err) {
  915. prompt('Error while using Web Inspector:', err);
  916. }
  917. };
  918. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址