├── test ├── css │ ├── base.css │ ├── base2.css │ ├── font.css │ ├── index.css │ └── skin.css ├── img │ └── logo.jpg ├── iframe │ ├── index.css │ └── index.html └── index.html ├── src ├── const │ └── debugMode.ts ├── ui │ ├── panel.css │ ├── panel.ts │ ├── options.ts │ ├── options.css │ ├── app.css │ ├── mockChromeAPI.ts │ ├── Options.svelte │ └── Panel.svelte ├── vite-env.d.ts ├── util │ ├── convUrlToAbs.ts │ ├── convTextToRules.ts │ ├── getFileContent.ts │ ├── cleanHTML.ts │ ├── cssHelper.ts │ ├── postTideCss.ts │ ├── convLinkToText.ts │ ├── generateRulesAll.ts │ ├── traversalCSSRuleList.ts │ └── filterRules.ts └── content.ts ├── public ├── devtools.html ├── static │ ├── icon │ │ ├── 128.png │ │ ├── 16.png │ │ └── 48.png │ ├── img │ │ └── intro.png │ └── js │ │ └── devtools.js ├── popup.html └── manifest.json ├── .vscode └── settings.json ├── svelte.config.js ├── panel.html ├── options.html ├── .github └── issue_template.md ├── .gitattributes ├── vite.config.ui.js ├── tsconfig.json ├── vite.config.content.js ├── .gitignore ├── LICENSE ├── package.json ├── README.md └── CHANGELOG.md /test/css/base.css: -------------------------------------------------------------------------------- 1 | body{ 2 | background: #ccc; 3 | } -------------------------------------------------------------------------------- /test/css/base2.css: -------------------------------------------------------------------------------- 1 | body { 2 | background: #aaa; 3 | } -------------------------------------------------------------------------------- /src/const/debugMode.ts: -------------------------------------------------------------------------------- 1 | export default import.meta.env.MODE === "development" -------------------------------------------------------------------------------- /src/ui/panel.css: -------------------------------------------------------------------------------- 1 | body{ 2 | font-family: Consolas,'Lucida Console',monospace; 3 | } -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | -------------------------------------------------------------------------------- /test/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/painty/CSS-Used-ChromeExt/HEAD/test/img/logo.jpg -------------------------------------------------------------------------------- /public/devtools.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /public/static/icon/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/painty/CSS-Used-ChromeExt/HEAD/public/static/icon/128.png -------------------------------------------------------------------------------- /public/static/icon/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/painty/CSS-Used-ChromeExt/HEAD/public/static/icon/16.png -------------------------------------------------------------------------------- /public/static/icon/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/painty/CSS-Used-ChromeExt/HEAD/public/static/icon/48.png -------------------------------------------------------------------------------- /public/static/img/intro.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/painty/CSS-Used-ChromeExt/HEAD/public/static/img/intro.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimFinalNewlines": true, 3 | "files.trimTrailingWhitespace": true, 4 | "files.insertFinalNewline": true 5 | } 6 | -------------------------------------------------------------------------------- /src/util/convUrlToAbs.ts: -------------------------------------------------------------------------------- 1 | function convUrlToAbs(baseURI: string | URL, url: string | URL) { 2 | return new URL(url, baseURI).href; 3 | } 4 | export default convUrlToAbs; 5 | -------------------------------------------------------------------------------- /src/ui/panel.ts: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import './panel.css' 3 | import Panel from './Panel.svelte' 4 | 5 | const app = new Panel({ 6 | target: document.getElementById('app'), 7 | }) 8 | 9 | export default app 10 | -------------------------------------------------------------------------------- /src/ui/options.ts: -------------------------------------------------------------------------------- 1 | import './app.css' 2 | import './options.css' 3 | import Options from './Options.svelte' 4 | 5 | const app = new Options({ 6 | target: document.getElementById('app'), 7 | }) 8 | 9 | export default app 10 | -------------------------------------------------------------------------------- /svelte.config.js: -------------------------------------------------------------------------------- 1 | import { vitePreprocess } from '@sveltejs/vite-plugin-svelte' 2 | 3 | export default { 4 | // Consult https://svelte.dev/docs#compile-time-svelte-preprocess 5 | // for more information about preprocessors 6 | preprocess: vitePreprocess(), 7 | } 8 | -------------------------------------------------------------------------------- /panel.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Options 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /test/iframe/index.css: -------------------------------------------------------------------------------- 1 | @media 2 | only screen and (-webkit-min-device-pixel-ratio: 1.5), 3 | only screen and (-o-min-device-pixel-ratio: 3/2), 4 | only screen and (min--moz-device-pixel-ratio: 1.5), 5 | only screen and (min-device-pixel-ratio: 1.5) { 6 | .logo { 7 | background-image: url(../img/logo.jpg); 8 | width: 128px; 9 | height: 128px; 10 | } 11 | } -------------------------------------------------------------------------------- /test/iframe/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS Used Test 6 | 7 | 8 | 9 | 10 |
iframeStart
11 | 12 |
iframeEnd
13 | 14 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 1. The url of page inspected. 2 | 3 | `https://example.com/page.html` 4 | 5 | 2. The element inspected. 6 | 7 | `div.somediv` 8 | 9 | 3. Expected result. 10 | 11 | ```css 12 | div.somediv{color:#333;} 13 | ``` 14 | 15 | 4. Actual result. 16 | 17 | ```css 18 | div.somediv{} 19 | ``` 20 | 21 | 5. (optional) Right click on the panel area, "inspect" and check if there's some suspicious logs in the console. -------------------------------------------------------------------------------- /public/popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | 13 |

Open Devtool -> Elements -> CSS UsedGithub

14 | 15 | 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /src/ui/options.css: -------------------------------------------------------------------------------- 1 | :root { 2 | color-scheme: light dark; 3 | color: rgba(255, 255, 255, 0.87); 4 | background-color: #242424; 5 | } 6 | 7 | @media (prefers-color-scheme: light) { 8 | :root { 9 | color: #213547; 10 | background-color: #ffffff; 11 | } 12 | 13 | a:hover { 14 | color: #a0ff74; 15 | } 16 | 17 | button { 18 | background-color: #f9f9f9; 19 | } 20 | } 21 | 22 | body { 23 | display: flex; 24 | place-items: center; 25 | justify-content: center; 26 | min-width: 500px; 27 | } -------------------------------------------------------------------------------- /vite.config.ui.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig } from 'vite' 3 | import { svelte } from '@sveltejs/vite-plugin-svelte' 4 | 5 | export default defineConfig({ 6 | plugins: [svelte()], 7 | esbuild: { 8 | charset: 'ascii', 9 | }, 10 | build: { 11 | target: 'es2020', 12 | copyPublicDir: true, 13 | emptyOutDir: true, 14 | rollupOptions: { 15 | input: { 16 | panel: resolve(__dirname, 'panel.html'), 17 | options: resolve(__dirname, 'options.html'), 18 | } 19 | }, 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/svelte/tsconfig.json", 3 | "compilerOptions": { 4 | "target": "es2020", 5 | "useDefineForClassFields": true, 6 | "module": "ESNext", 7 | "lib": ["ESNext", "DOM"], 8 | "moduleResolution": "Node", 9 | "strict": false, 10 | "resolveJsonModule": true, 11 | "allowJs": true, 12 | "checkJs": true, 13 | "isolatedModules": true, 14 | "esModuleInterop": true, 15 | "noEmit": true, 16 | "noUnusedLocals": true, 17 | "noUnusedParameters": true, 18 | "noImplicitReturns": true, 19 | "skipLibCheck": true 20 | }, 21 | "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.js", "src/**/*.svelte"] 22 | } 23 | -------------------------------------------------------------------------------- /src/util/convTextToRules.ts: -------------------------------------------------------------------------------- 1 | import postcss from 'postcss' 2 | import safe from 'postcss-safe-parser' 3 | 4 | async function convTextToRules(styleContent: string, href?: string) { 5 | const processor = postcss() 6 | // console.log('processor',processor); 7 | const result = await processor.process(styleContent, { 8 | from: href || 'cssFromUnknown', 9 | parser: safe, 10 | }) 11 | type cssNodeObj = { 12 | nodes: typeof result.root.nodes 13 | href?: string 14 | parentHref?: string 15 | media?: MediaList 16 | } 17 | const returnObj: cssNodeObj = { 18 | nodes: result.root.nodes, 19 | href, 20 | } 21 | return returnObj 22 | } 23 | 24 | export default convTextToRules 25 | -------------------------------------------------------------------------------- /vite.config.content.js: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path' 2 | import { defineConfig } from 'vite' 3 | import EnvironmentPlugin from 'vite-plugin-environment' 4 | 5 | export default defineConfig({ 6 | plugins: [EnvironmentPlugin(['NODE_ENV', 'LANG'])], 7 | esbuild: { 8 | charset: 'ascii', 9 | }, 10 | build: { 11 | target: 'es2020', 12 | copyPublicDir: false, 13 | emptyOutDir: false, 14 | // outDir:'./dist/', 15 | lib: { 16 | // Could also be a dictionary or array of multiple entry points 17 | entry: resolve(__dirname, "src/content.ts"), 18 | name: "getCssUsed", 19 | // the proper extensions will be added 20 | formats: ["umd"], 21 | fileName: (format, entryName)=>{ 22 | return 'assets/content.js' 23 | } 24 | }, 25 | minify : false, 26 | }, 27 | }) 28 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "CSS Used", 3 | "version": "3.0.0", 4 | "permissions": ["tabs"], 5 | "optional_permissions": ["storage"], 6 | "author": "Bob", 7 | "icons": { 8 | "16": "static/icon/16.png", 9 | "48": "static/icon/48.png", 10 | "128": "static/icon/128.png" 11 | }, 12 | "description": "Get all css rules used by the selected DOM and its descendants.", 13 | "devtools_page": "devtools.html", 14 | "options_page": "options.html", 15 | "manifest_version": 3, 16 | "content_scripts": [ 17 | { 18 | "matches": [""], 19 | "js": ["assets/content.js"], 20 | "all_frames": true 21 | } 22 | ], 23 | "action": { 24 | "default_icon": "static/icon/128.png", 25 | "default_title": "CSS Used For Chrome Devtool", 26 | "default_popup": "popup.html" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # Windows shortcuts 18 | *.lnk 19 | 20 | # ========================= 21 | # Operating System Files 22 | # ========================= 23 | 24 | # OSX 25 | # ========================= 26 | 27 | .DS_Store 28 | .AppleDouble 29 | .LSOverride 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | 38 | # Directories potentially created on remote AFP share 39 | .AppleDB 40 | .AppleDesktop 41 | Network Trash Folder 42 | Temporary Items 43 | .apdisk 44 | 45 | # Dependency directory 46 | node_modules 47 | 48 | # built 49 | dist 50 | build/asset/js/content.js 51 | .cache 52 | .vscode/launch.json 53 | -------------------------------------------------------------------------------- /src/util/getFileContent.ts: -------------------------------------------------------------------------------- 1 | import debugMode from '../const/debugMode' 2 | 3 | const getByFetch = (url: string): Promise => { 4 | return new Promise((resolve, reject) => { 5 | fetch(url, { 6 | method: 'GET', 7 | mode: 'no-cors', 8 | }) 9 | .then((resonse) => resonse.arrayBuffer()) 10 | .then((data) => { 11 | const decoder = new TextDecoder() 12 | resolve(decoder.decode(data)) 13 | }) 14 | .catch((error) => reject(error)) 15 | }) 16 | } 17 | 18 | const getByChromeAPI = (url: string): Promise => { 19 | return new Promise((resolve, _reject) => { 20 | chrome.runtime.sendMessage( 21 | { 22 | action: 'getResourceContent', 23 | url, 24 | }, 25 | (response) => { 26 | // console.log('response',response); 27 | resolve(response.content) 28 | } 29 | ) 30 | }) 31 | } 32 | 33 | export function getFileContent(url: string) { 34 | if (debugMode) { 35 | return getByFetch(url) 36 | } 37 | return getByChromeAPI(url) 38 | } 39 | -------------------------------------------------------------------------------- /test/css/font.css: -------------------------------------------------------------------------------- 1 | i { 2 | font-family: 'Cutive Mono'; 3 | } 4 | 5 | @media only screen and (max-width:480px) { 6 | @import url("https://fonts.googleapis.com/css?family=Cutive+Mono"); 7 | 8 | b { 9 | font-size: 20px; 10 | } 11 | } 12 | 13 | body { 14 | font-family: 'Cutive Mono'; 15 | } 16 | 17 | @font-face { 18 | font-family: 'icon-font'; 19 | src: url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.eot?id=141111232016"); 20 | src: url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.eot?id=141111232016#iefix") format('embedded-opentype'), 21 | url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.svg?id=141111232016") format('svg'), 22 | url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.woff?id=141111232016") format('woff'), 23 | url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.ttf?id=141111232016") format('truetype'); 24 | src: url("http://img.t.sinajs.cn/t6/style/images/common/font/swbficon.eot?id=141111232016") \9; 25 | font-weight: normal; 26 | font-style: normal; 27 | } 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Bobscript 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/util/cleanHTML.ts: -------------------------------------------------------------------------------- 1 | import convUrlToAbs from './convUrlToAbs' 2 | 3 | function makeTagReg(tagName:string){ 4 | return new RegExp('<'+tagName+'[\\s\\S]*?>[\\s\\S]*?<\/'+tagName+'>','gi') 5 | } 6 | 7 | export default function (dirty: string, doc: Document): string { 8 | return dirty 9 | .replace(makeTagReg('script'), '') 10 | .replace(makeTagReg('style'), '') 11 | .replace(//gi, '') 12 | .replace(/ on\w+=".*?"/gi, "") 13 | .replace(/(]+src=(['"]))(.*?)(\2.*?>)/g, function () { 14 | var src = convUrlToAbs(doc.location.href, arguments[3]) 15 | return arguments[1] + src + arguments[4] 16 | }) 17 | .replace(/(]+srcset=(['"]))(.*?)(\2.*?>)/g, function () { 18 | var srcset = arguments[3].split(/,\s*/) 19 | srcset.forEach(function (ele, index) { 20 | var src = ele.replace(/([^ ]*)(.*)/, function () { 21 | var _src = convUrlToAbs(doc.location.href, arguments[1]) 22 | return _src + ' ' + arguments[2] 23 | }) 24 | srcset[index] = src 25 | }) 26 | return arguments[1] + srcset.join(',') + arguments[4] 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /src/ui/app.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | font-synthesis: none; 8 | text-rendering: optimizeLegibility; 9 | -webkit-font-smoothing: antialiased; 10 | -moz-osx-font-smoothing: grayscale; 11 | -webkit-text-size-adjust: 100%; 12 | } 13 | 14 | a { 15 | font-weight: 500; 16 | color: #87d21d; 17 | text-decoration: inherit; 18 | } 19 | a:hover { 20 | color: #74b419; 21 | } 22 | 23 | body { 24 | font-family: inherit; 25 | font-size: inherit; 26 | margin: 0; 27 | min-height: 100vh; 28 | } 29 | 30 | h1 { 31 | font-size: 3.2em; 32 | line-height: 1.1; 33 | } 34 | 35 | .card { 36 | padding: 2em; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | /* cursor: pointer; */ 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #87d21d; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | outline: 4px auto -webkit-focus-ring-color; 56 | } 57 | 58 | button:disabled:hover{ 59 | cursor: not-allowed; 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "css-used-chrome-ext", 3 | "version": "3.0.0", 4 | "type": "module", 5 | "description": "A chrome extension to get all css rules used by the selected DOM and its descendants.", 6 | "scripts": { 7 | "dev:content": "npm run build:ui && vite build --watch --config vite.config.content.js", 8 | "dev:ui": "vite --config vite.config.ui.js", 9 | "build:content": "tsc && vite build --minify --config vite.config.content.js", 10 | "build:ui": "vite build --minify --config vite.config.ui.js", 11 | "build": "npm run build:ui && npm run build:content" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/painty/CSS-Used-ChromeExt.git" 16 | }, 17 | "keywords": [ 18 | "chrome-extension", 19 | "chrome-devtools", 20 | "css-usage", 21 | "css-rules" 22 | ], 23 | "author": "Bobscript", 24 | "license": "MIT", 25 | "bugs": { 26 | "url": "https://github.com/painty/CSS-Used-ChromeExt/issues" 27 | }, 28 | "homepage": "https://github.com/painty/CSS-Used-ChromeExt#readme", 29 | "dependencies": { 30 | "postcss": "^8.4.21", 31 | "postcss-safe-parser": "^6.0.0" 32 | }, 33 | "devDependencies": { 34 | "@sveltejs/vite-plugin-svelte": "^2.0.0", 35 | "@tsconfig/svelte": "^3.0.0", 36 | "@types/chrome": "^0.0.206", 37 | "@types/postcss-safe-parser": "^5.0.1", 38 | "svelte": "^3.54.0", 39 | "typescript": "^4.9.4", 40 | "vite": "^4.0.4", 41 | "vite-plugin-environment": "^1.1.3" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/util/cssHelper.ts: -------------------------------------------------------------------------------- 1 | export type cssObj = { 2 | normRule: string[] 3 | keyFram: string[] 4 | fontFace: string[] 5 | } 6 | 7 | import convTextToRules from './convTextToRules' 8 | 9 | const cssHelper = { 10 | mergeobjCss: function (a: cssObj, b: cssObj) { 11 | ['normRule', 'fontFace', 'keyFram'].forEach(function (ele) { 12 | if (!a[ele] || !b[ele]) { 13 | // console.log('NO '+ele); 14 | } 15 | a[ele] = a[ele].concat(b[ele]).filter(e=>e) 16 | }) 17 | }, 18 | normRuleNodeToText: function (node) { 19 | var s = '' 20 | node.nodes.forEach(function (ele) { 21 | if (ele.prop && ele.value) { 22 | var before = ele.raws.before.replace(/[\s]*/, '') 23 | s += 24 | before + 25 | ele.prop + 26 | ':' + 27 | ele.value + 28 | (ele.important ? '!important;' : ';') 29 | } 30 | }) 31 | return s 32 | }, 33 | keyFramNodeToText: function (node) { 34 | var s = '@' + node.name + ' ' + node.params + '{' 35 | node.nodes.forEach(function (_node) { 36 | s += _node.selector + '{' + cssHelper.normRuleNodeToText(_node) + '}' 37 | }) 38 | s += '}' 39 | return s 40 | }, 41 | fontFaceNodeToText: function (node) { 42 | var s = '@' + node.name + '{' 43 | s += cssHelper.normRuleNodeToText(node) 44 | s += '}' 45 | return s 46 | }, 47 | textToCss: async function (styleContent: string) { 48 | const parsedCss = await convTextToRules(styleContent) 49 | return parsedCss 50 | }, 51 | } 52 | 53 | export { cssHelper } 54 | -------------------------------------------------------------------------------- /src/util/postTideCss.ts: -------------------------------------------------------------------------------- 1 | function clean(s) { 2 | s = s.map(function (ele) { 3 | return ele.replace(/[\n\r]/g, ' '); 4 | }); 5 | s = s.join('\n'); 6 | // empty rules 7 | var reg1 = /^[^{}\n]*{\s*}/gm; 8 | // multiple empty lines 9 | var reg2 = /\n\n/g; 10 | // no import rule used 11 | var reg3 = /\/\*! @import.*\n\/\*! end @import \*\//g; 12 | 13 | while (s.match(reg1) !== null || s.match(reg2) !== null || s.match(reg3) !== null) { 14 | s = s.replace(reg1, ''); 15 | s = s.replace(reg2, '\n'); 16 | s = s.replace(reg3, ''); 17 | } 18 | //trim heading white space 19 | s = s.replace(/^\s*/mg, ''); 20 | return (s); 21 | } 22 | 23 | function fix(s) { 24 | 25 | s = clean(s); 26 | 27 | s = s.split(/\n+/); 28 | 29 | // remove the last comments line 30 | // which have no rules 31 | // endOfRuleLine:the end of the lastRule line number 32 | var endOfRuleLine = s.length; 33 | var fontFacePosition = s.indexOf('/*! CSS Used fontfaces */'); 34 | var keyFramsPosition = s.indexOf('/*! CSS Used keyframes */'); 35 | if (keyFramsPosition !== -1) { 36 | endOfRuleLine = keyFramsPosition; 37 | } else if (fontFacePosition !== -1) { 38 | endOfRuleLine = fontFacePosition; 39 | } 40 | while (s.length > 0 && s[endOfRuleLine - 1].match(/^\/\*! |^$/) !== null) { 41 | s.splice(endOfRuleLine - 1, 1); 42 | endOfRuleLine--; 43 | } 44 | var arr = [], 45 | regFrom = /^\/\*! CSS Used from: /; 46 | for (var i = 0; i < endOfRuleLine; i++) { 47 | if ((s[i].match(regFrom) !== null) && (i + 1 === endOfRuleLine || (s[i + 1].match(regFrom) !== null))) { 48 | continue; 49 | } else { 50 | arr.push(s[i]); 51 | } 52 | } 53 | // concat the latter fontface and keyframs part 54 | arr = arr.concat(s.slice(endOfRuleLine)); 55 | 56 | return arr.join('\n').replace(/(['"']?)\u5fae\u8f6f\u96c5\u9ed1\1/, '"Microsoft Yahei"'); //.replace(/(['"']?)宋体\1/,' simsun '); 57 | } 58 | 59 | export default fix -------------------------------------------------------------------------------- /src/ui/mockChromeAPI.ts: -------------------------------------------------------------------------------- 1 | import debugMode from '../const/debugMode' 2 | 3 | let chromeObj 4 | 5 | if (debugMode) { 6 | chromeObj = { 7 | devtools: { 8 | panels: { 9 | // themeName: 'default', 10 | themeName: 'dark', 11 | }, 12 | inspectedWindow: { 13 | tabId: 123, 14 | }, 15 | }, 16 | extension:{ 17 | isAllowedFileSchemeAccess:(fn)=>{ 18 | fn.call(null,true) 19 | } 20 | }, 21 | tabs:{ 22 | query:(_obj,fn)=>{ 23 | const arr=[{ 24 | id: 123, 25 | url: 'file:///Volumes/index.html' 26 | // url: 'http://localhost/index.html' 27 | }] 28 | fn.call(null,arr) 29 | } 30 | }, 31 | runtime: { 32 | _messageHandlerStack: [], 33 | onMessage: { 34 | addListener: (fn) => { 35 | // message, sender, sendResponse 36 | chromeObj.runtime._messageHandlerStack.push({ 37 | target: this, 38 | handler: fn, 39 | }) 40 | }, 41 | }, 42 | sendMessage: (message, responseFn) => { 43 | // self send and slef response 44 | chromeObj.runtime._messageHandlerStack.forEach((h) => { 45 | let sender: { tab?: { id: number } } = {} 46 | // if (message._from === 'devtools') {} 47 | if (message._from === 'content') { 48 | sender.tab = { 49 | id: 123, 50 | } 51 | } 52 | let handler = h.handler 53 | let sendResponse = (r) => { 54 | responseFn.call(null, r) 55 | } 56 | let isAsyncResponse = handler.call( 57 | chromeObj, 58 | message, 59 | sender, 60 | sendResponse 61 | ) 62 | if (isAsyncResponse === true) { 63 | // async response 64 | } else { 65 | } 66 | }) 67 | }, 68 | }, 69 | } 70 | } else { 71 | chromeObj = chrome 72 | } 73 | export default chromeObj 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CSS-Used 2 | 3 | ![CSS Used](http://ww1.sinaimg.cn/large/4e71f332gw1et7h243kgqj203k03ka9v.jpg) 4 | 5 | Get all css rules used by the selected DOM and its descendants. 6 | 7 | Get it at the Chrome Web Store : [CSS Used >>](https://chrome.google.com/webstore/detail/css-used/cdopjfddjlonogibjahpnmjpoangjfff) 8 | 9 | ## Overview 10 | 11 | Get all css rules used by the selected DOM and its descendants. Used to extrac only the used CSS. So the unused css will be left out. 12 | 13 | If the selected DOM is the body, the result will be all the used css by the whole page curently. 14 | 15 | ## Usage 16 | 17 | ![CSS-Used](https://user-images.githubusercontent.com/5387771/47267284-41b36a80-d574-11e8-9b83-c7896d428827.jpg) 18 | 19 | F12 open the Developer Tools, select the dom and active the "CSS Used" panel. The used CSS rules of the Selected dom and its descendants will be listed. 20 | 21 | You can click "Preview" to see the selected part with clean style rules. 22 | 23 | ## FAQ 24 | 25 | 1. No result 26 | 27 | 1. Check whether it's Chrome Web Store pages, which is `https://chrome.google.com/webstore/....`, which won't allow content script injection. 28 | 1. If it's a normal webpage, check site access permission https://github.com/painty/CSS-Used-ChromeExt/issues/13#issuecomment-687244215 29 | 1. If it's a local file, chrome won't allow local file access by default. You can turn on the "Allow access to file URLs" in the extension management page. 30 | 31 | 1. Preview not right 32 | 33 | As for the CSS rule like `.wrap p{...}`, if only `

` is selected, the result `.wrap p{...}` may not apply directly to `

`. 34 | Either changing this rule's selector to `p{...}` or giving the `

` a `.wrap` parent in the final HTML. 35 | 36 | 1. Not all the CSS is got 37 | 38 | 1. The result is generated based on the CURRENT HTML DOM. If a div doesn't exist in the document unless a specific user interaction, the result may miss out the style rules for the newly born div. 39 | 1. CSS custom properties (variables) are partially supported. Not working for declarations defined by $0's ancestor. Thinking it as a inheritable CSS property, as this tool won't handle inherit style. 40 | 41 | ## Changelog 42 | 43 | Go to the [Changelog page](CHANGELOG.md) 44 | -------------------------------------------------------------------------------- /src/util/convLinkToText.ts: -------------------------------------------------------------------------------- 1 | import convUrlToAbs from './convUrlToAbs' 2 | import { getFileContent } from './getFileContent' 3 | 4 | function getSavedSettings() { 5 | return new Promise((resolve) => { 6 | if (chrome && chrome.storage) { 7 | chrome.storage.sync.get( 8 | { 9 | convUrlToAbsolute: true, 10 | }, 11 | function (items) { 12 | resolve(items.convUrlToAbsolute) 13 | } 14 | ) 15 | } else { 16 | resolve(true) 17 | } 18 | }) 19 | } 20 | 21 | interface customCssObj { 22 | url: string 23 | cssraw: string 24 | } 25 | 26 | function makeRequest(url: string): Promise { 27 | const result: customCssObj = { url, cssraw: '' } 28 | chrome.runtime.sendMessage({ 29 | action: 'inform', 30 | info: 'Getting : ' + url, 31 | }) 32 | return new Promise(function (resolve) { 33 | getFileContent(url) 34 | .then((data) => { 35 | result.cssraw = data 36 | // console.log("Success:", url, data); 37 | getSavedSettings().then((willConvUrlToAbs) => { 38 | if (willConvUrlToAbs) { 39 | result.cssraw = result.cssraw.replace( 40 | /url\((['"]?)(.*?)\1\)/g, 41 | function (_a: string, p1: string, p2: string) { 42 | return `url(${p1}${convUrlToAbs(url, p2)}${p1})` 43 | } 44 | ) 45 | } 46 | resolve(result) 47 | chrome.runtime.sendMessage({ 48 | action: 'inform', 49 | info: 'Parsing : ' + url, 50 | }) 51 | }) 52 | }) 53 | .catch((error) => { 54 | console.log('CSS-Used: Fail to get: ' + url, error) 55 | result.cssraw = '' 56 | resolve(result) 57 | }) 58 | }) 59 | } 60 | 61 | function convLinkToText(links: string[]): Promise { 62 | var promises = [] 63 | return new Promise(function (resolve, reject) { 64 | if (links.length === 0) { 65 | resolve([]) 66 | } else { 67 | for (var i = 0; i < links.length; i++) { 68 | promises.push(makeRequest(links[i])) 69 | } 70 | Promise.all(promises) 71 | .then((result: customCssObj[]) => { 72 | resolve(result) 73 | }) 74 | .catch(function (err) { 75 | reject(err) 76 | }) 77 | } 78 | }) 79 | } 80 | 81 | export default convLinkToText 82 | -------------------------------------------------------------------------------- /test/css/index.css: -------------------------------------------------------------------------------- 1 | @import "base.css" screen, projection; 2 | 3 | @import url('base2.css') screen and (orientation:portrait); 4 | @import 'skin.css'; 5 | 6 | :AFTER { 7 | content: 'after'; 8 | background: #38c; 9 | color: #fff; 10 | font-size: 12px; 11 | opacity: 0; 12 | pointer-events: none; 13 | } 14 | 15 | html { 16 | height: 100%; 17 | } 18 | 19 | A { 20 | background: url(../img/logo.jpg); 21 | background-size: 100%; 22 | animation: teatnamekey; 23 | } 24 | 25 | BANNER {} 26 | 27 | @-webkit-keyframes teatnamekey { 28 | 0% { 29 | width: 10px; 30 | } 31 | 32 | 100% { 33 | height: 10px; 34 | } 35 | } 36 | 37 | @keyframes teatnamekey { 38 | 0% { 39 | width: 10px; 40 | } 41 | 42 | 100% { 43 | height: 10px; 44 | } 45 | } 46 | 47 | @media screen and (min-width:500px) { 48 | a { 49 | width: 16.666%; 50 | } 51 | } 52 | 53 | @media screen and (min-width:300px) { 54 | @media screen and (min-height:10px) { 55 | za { 56 | width: 16.666%; 57 | } 58 | 59 | b { 60 | height: 10px; 61 | } 62 | } 63 | 64 | za:nth-of-type(3n) { 65 | border-right: 1px solid #dcdddd; 66 | } 67 | 68 | za:nth-of-type(6n) { 69 | border-right: none; 70 | } 71 | } 72 | 73 | @import 'skin.css'; 74 | 75 | /*body,.S_page{background-image:url('http://ww3.sinaimg.cn/woriginal/4e71f332gw1e3r8b7kep9j.jpg');background-repeat:repeat;background-attachment:fixed;background-position:center top;}*/ 76 | 77 | body { 78 | font-family: "Cutive Mono"; 79 | } 80 | 81 | .font-style { 82 | font-family: 'Cutive Mono', icon-font, Inter, Avenir, Helvetica , Arial, sans-serif ; 83 | } 84 | .svg-bg { 85 | background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24'%3E%3Cpath fill='rgba(0,0,0,.54)' d='M20.49 19l-5.73-5.73C15.53 12.2 16 10.91 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.41 0 2.7-.47 3.77-1.24L19 20.49 20.49 19zM5 9.5C5 7.01 7.01 5 9.5 5S14 7.01 14 9.5 11.99 14 9.5 14 5 11.99 5 9.5z'/%3E%3C/svg%3E"); 86 | background-position: center; 87 | background-repeat: no-repeat; 88 | background-size: 20px; 89 | height: 20px; 90 | padding: 10px; 91 | width: 20px; 92 | font-size:0; 93 | } 94 | .not-focus{ 95 | width: 50px; 96 | height: 50px; 97 | border: 1px solid #000; 98 | } 99 | .not-focus:not(:focus){ 100 | background: rgb(10, 149, 86); 101 | } 102 | .not-focus:focus{ 103 | background: rgb(158, 51, 204); 104 | } 105 | @media print { 106 | i { 107 | font-size: 10pt; 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/util/generateRulesAll.ts: -------------------------------------------------------------------------------- 1 | import traversalCSSRuleList from './traversalCSSRuleList' 2 | import convTextToRules from './convTextToRules' 3 | import { cssHelper } from './cssHelper' 4 | import convUrlToAbs from './convUrlToAbs' 5 | 6 | type cssNodeObj = Awaited> 7 | 8 | function generateRulesAll( 9 | doc: Document, 10 | externalCssCache: { [index: cssNodeObj['href']]: cssNodeObj } 11 | ) { 12 | var x: number 13 | 14 | var objCss = { 15 | normRule: [], 16 | fontFace: [], 17 | keyFram: [], 18 | } 19 | 20 | var promises = [] 21 | 22 | return new Promise(function (resolve, reject) { 23 | // loop every styleSheets 24 | for (x = 0; x < doc.styleSheets.length; x++) { 25 | const styleSheet = doc.styleSheets[x] 26 | promises.push( 27 | new Promise(function (res) { 28 | var cssNodeArr: cssNodeObj 29 | if (styleSheet.href !== null) { 30 | // can be link tag 31 | cssNodeArr = externalCssCache[styleSheet.href] 32 | cssNodeArr.media = doc.styleSheets[x].media 33 | traversalCSSRuleList(doc, externalCssCache, cssNodeArr).then(res) 34 | } else if (styleSheet.ownerNode instanceof Element) { 35 | // style tag 36 | let html: string = styleSheet.ownerNode.innerHTML 37 | if (html === '') { 38 | // style may be in style-tag's cssRules but not show in innerHTML 39 | for ( 40 | let index = 0; 41 | index < doc.styleSheets[x].cssRules.length; 42 | index++ 43 | ) { 44 | const rule = doc.styleSheets[x].cssRules[index] 45 | html += rule.cssText 46 | } 47 | } 48 | // convert urls in style tag to abs 49 | html = html.replace( 50 | /url\((['"]?)(.*?)\1\)/g, 51 | function (_a, p1, p2) { 52 | return ( 53 | 'url(' + p1 + convUrlToAbs(doc.location.href, p2) + p1 + ')' 54 | ) 55 | } 56 | ) 57 | // the next operation is asynchronous 58 | // store the current x value 59 | let _x = x 60 | convTextToRules(html, doc.location.href).then((cssNodeObj) => { 61 | cssNodeObj.media = doc.styleSheets[_x].media 62 | traversalCSSRuleList(doc, externalCssCache, cssNodeObj).then(res) 63 | }) 64 | } else { 65 | // console.log('ProcessingInstruction', styleSheet.ownerNode); 66 | res({}) 67 | } 68 | }) 69 | ) 70 | } 71 | 72 | Promise.all(promises) 73 | .then(function (result) { 74 | result.forEach(function (ele) { 75 | cssHelper.mergeobjCss(objCss, ele) 76 | }) 77 | resolve(objCss) 78 | }) 79 | .catch(function (err) { 80 | reject(err) 81 | }) 82 | }) 83 | } 84 | export default generateRulesAll 85 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | **ver 3.0.0 | 15/01/2023** 2 | 3 | 1. Migrated to Manifest V3. 4 | 1. Resources are read from local caches, making it much faster and solves the [cross-origin issue](https://github.com/painty/CSS-Used-ChromeExt/issues/52). 5 | 1. Optimized style parsing in a more efficient way. 6 | 1. Dropped support for some outdated CSS, mostly -o- and -ms- prefixed properties. 7 | 1. Implemented a new UI powered by Svelte, with some visual tweaks. 8 | 9 | **ver 2.5.0 | 15/03/2021** 10 | 11 | 1. New. Option page. 12 | 2. New. Option to preserve relative URLs. [#31](https://github.com/painty/CSS-Used-ChromeExt/issues/31) 13 | 14 | **ver 2.4.3 | 04/03/2021** 15 | 16 | 1. Fix. Send to CodePen may not work. [#38](https://github.com/painty/CSS-Used-ChromeExt/issues/38) 17 | 18 | **ver 2.3.2 | 18/11/2018** 19 | 20 | 1. More friendly prompt text for first use or error display. 21 | 2. The add tabs permission is used to identify special urls. Such as chrome://newtab 22 | 23 | **ver 2.2.12 | 11/11/2018** 24 | 25 | 1. No new feature or bugfix. Just split the content.js into some modules. 26 | 27 | **ver 2.2.11 | 10/06/2018** 28 | 29 | 1. Fix. preview button doesn't work [#15](https://github.com/painty/CSS-Used-ChromeExt/issues/15) [#16](https://github.com/painty/CSS-Used-ChromeExt/pull/16) 30 | 2. specify protocol for img in Preview 31 | 32 | **ver 2.2.10 | 07/11/2018** 33 | 34 | 1. Better regex for cleaning empty rules. 35 | 2. Add. scrollbar style 36 | 37 | **ver 2.2.8 | 07/08/2018** 38 | 39 | 1. Fix. @import inside media query should be invalid. 40 | 41 | **ver 2.2.7 | 06/27/2018** 42 | 43 | 1. Add. Media property of css link/tag preserved. 44 | 2. Update dependencies 45 | 46 | "postcss": "6.0.23", 47 | "postcss-safe-parser": "3.0.1" 48 | 49 | **ver 2.2.6 | 08/30/2017** 50 | 51 | 1.Fix. css link with empty href 52 | 53 | **ver 2.2.5 | 08/25/2017** 54 | 55 | 1.Change Send-to-codepen's url from "http://" to "https://" 56 | 57 | **ver 2.2.4 | 08/23/2017** 58 | 59 | 1.Add. User-friendly way of requesting file access 60 | 61 | **ver 2.2.3 | 04/30/2017** 62 | 63 | 1.fix bug that the first comment is lost 64 | 2.won't import the invalid @import css 65 | 66 | **ver 2.2.2 | 04/29/2017** 67 | 68 | 1.Endless loop @import handling. eg: a.css import b.css and b.css import a.css 69 | 70 | **ver 2.2.1 | 04/27/2017** 71 | 72 | 1.Better tips for first time use 73 | 2.changed CSS parsing from Chrome to postcss 74 | 75 | **ver 2.1.1 | 04/25/2017** 76 | 77 | 1. Better external CSS Cache 78 | 79 | **ver 2.1.0 | 04/24/2017** 80 | 81 | 1. Now works with iframes 82 | 83 | **ver 2.0.0 | 04/24/2017** 84 | 85 | 1. A Break Though in speed improvement. 86 | 87 | **ver 1.5.0 | 04/23/2017** 88 | 89 | 1. Rewrite. to async way. Now CSS-Used could have a quicker UI response. Still be CAUTIOUS when selecting too many elements which may freeze the current tab. 90 | 2. Add. Two buttons: `Copy to clipboard` & `Send to codepen` 91 | 3. Better handling for pseudo element/class 92 | 93 | **ver 1.4.2 | 04/15/2017** 94 | 95 | 1. Add. calculating progress display. 96 | 97 | **ver 1.4.1 | 04/09/2017** 98 | 99 | 1. multiple pseudo class/element detection 100 | 101 | **ver 1.4.0 | 04/09/2017** 102 | 103 | 1. Add. media query support 104 | 2. Add. font-face support 105 | 3. Fix. unused stylesheet will not show in the result. 106 | 107 | **ver 1.3** 108 | 109 | 1. Convert all background image url to absolute path. 110 | 111 | **ver 1.2** 112 | 113 | 1. The link tag is preserved now. 114 | 115 | **ver 1.1.5** 116 | 117 | 1. Keyframe animation extract is now supported. 118 | -------------------------------------------------------------------------------- /src/content.ts: -------------------------------------------------------------------------------- 1 | import filterRules from './util/filterRules' 2 | import convLinkToText from './util/convLinkToText' 3 | import convTextToRules from './util/convTextToRules' 4 | import postTideCss from './util/postTideCss' 5 | import generateRulesAll from './util/generateRulesAll' 6 | import cleanHTML from './util/cleanHTML' 7 | 8 | type cssNodeObj = Awaited> 9 | 10 | const externalCssCache: { [index: string]: cssNodeObj } = {} 11 | //to store timers of testing if a html element matches a rule selector. 12 | const arrTimerOfTestingIfMatched: ReturnType[] = [] 13 | let doc = document 14 | async function getC($0: HTMLElement) { 15 | arrTimerOfTestingIfMatched.forEach(function (ele) { 16 | clearTimeout(ele) 17 | }) 18 | // reset to empty 19 | arrTimerOfTestingIfMatched.length = 0 20 | 21 | if ( 22 | $0 === null || 23 | typeof $0 === 'undefined' || 24 | typeof $0.nodeName === 'undefined' 25 | ) { 26 | return 27 | } 28 | 29 | if ($0.nodeName.match(/^ { 73 | // if href==='' , ele.getAttribute('href') !== ele.href 74 | const current = externalCssCache[ele.href] 75 | if ( 76 | ele.getAttribute('href') && 77 | (current === undefined || current.nodes.length === 0) 78 | ) { 79 | links.push(ele.href) 80 | } 81 | }) 82 | 83 | convLinkToText(links) 84 | .then(async (result) => { 85 | var promises: cssNodeObj[] = [] 86 | for (var i = 0; i < result.length; i++) { 87 | let ele = result[i], 88 | idx = i 89 | const rulesObj = await convTextToRules(ele.cssraw, links[idx]) 90 | promises.push(rulesObj) 91 | } 92 | return promises 93 | }) 94 | .catch(function (err) { 95 | console.error('CSS-Used: ', err) 96 | chrome.runtime.sendMessage({ 97 | action: 'inform', 98 | info: 'convLinkToText error, see detail in console', 99 | }) 100 | }) 101 | .then(function (result) { 102 | if (Array.isArray(result)) { 103 | result.forEach(function (rulesObj) { 104 | externalCssCache[rulesObj.href] = rulesObj 105 | }) 106 | } 107 | }) 108 | .then(function () { 109 | return generateRulesAll(doc, externalCssCache) 110 | }) 111 | .then(function (objCss) { 112 | // {fontFace : Array, keyFram : Array, normRule : Array} 113 | return filterRules($0, objCss, arrTimerOfTestingIfMatched) 114 | }) 115 | .then(function (data) { 116 | chrome.runtime.sendMessage({ 117 | action: 'celebrate', 118 | css: postTideCss(data), 119 | html: cleanHTML($0.outerHTML, doc), 120 | }) 121 | }) 122 | } 123 | 124 | chrome.runtime 125 | .sendMessage({ 126 | action: 'evalGetCssUsed', 127 | info: 'page loaded', 128 | }) 129 | .catch(() => { 130 | // console.log('error',error); 131 | }) 132 | 133 | export default getC 134 | -------------------------------------------------------------------------------- /public/static/js/devtools.js: -------------------------------------------------------------------------------- 1 | let panelVisible = false 2 | let isPageLoaded = true 3 | 4 | function getAllFramesUrl() { 5 | const framesURLArray = [] 6 | return new Promise((resolve) => { 7 | chrome.devtools.inspectedWindow.getResources((resources) => { 8 | for (var i = 0; i < resources.length; i++) { 9 | if ( 10 | resources[i].type === 'document' && 11 | resources[i].url.match(/^(https?:|file:\/)\/\//) !== null 12 | ) { 13 | framesURLArray.push(resources[i].url) 14 | } 15 | } 16 | resolve(framesURLArray) 17 | }) 18 | }) 19 | } 20 | 21 | function evalGetCssUsed(cancel = false) { 22 | if ((!cancel && !panelVisible) || !isPageLoaded) { 23 | return 24 | } 25 | getAllFramesUrl().then((arrFrameURL) => { 26 | // console.log('arrFrameURL', arrFrameURL) 27 | if (arrFrameURL.length === 0) { 28 | chrome.runtime.sendMessage({ 29 | action: 'inform', 30 | info: 'frameURLsEmpty', 31 | tabId: chrome.devtools.inspectedWindow.tabId, // to specify message from 32 | }) 33 | } 34 | arrFrameURL.forEach(function (ele) { 35 | chrome.devtools.inspectedWindow.eval( 36 | 'getCssUsed(' + (cancel ? '' : '$0') + ')', 37 | { 38 | frameURL: ele, 39 | useContentScriptContext: true, 40 | }, 41 | function (result, isException) { 42 | if (isException) { 43 | console.log("evalGetCssUsed isException: ",isException); 44 | } else { 45 | // console.log('evalGetCssUsed result: ',result); 46 | } 47 | } 48 | ) 49 | }) 50 | }) 51 | } 52 | 53 | chrome.devtools.panels.elements.onSelectionChanged.addListener(function () { 54 | evalGetCssUsed() 55 | // console.log('onSelectionChanged') 56 | chrome.runtime.sendMessage({ 57 | action: 'inform', 58 | info: 'onSelectionChanged', 59 | tabId: chrome.devtools.inspectedWindow.tabId, 60 | }).catch(console.log) 61 | }) 62 | 63 | chrome.devtools.network.onNavigated.addListener(function () { 64 | // console.log('onNavigated') 65 | isPageLoaded = false 66 | chrome.runtime.sendMessage({ 67 | action: 'inform', 68 | info: 'onNavigated', 69 | tabId: chrome.devtools.inspectedWindow.tabId, 70 | }) 71 | // initialText = `on Navigated.

  • Select another dom on the left
  • or
  • Reopen the Devtool
  • ` 72 | }) 73 | 74 | chrome.devtools.panels.elements.createSidebarPane( 75 | 'CSS Used', 76 | function (sidebar) { 77 | // sidebar.setHeight('calc(100vh - 48px)') 78 | sidebar.setPage('panel.html') 79 | sidebar.onShown.addListener(function (win) { 80 | // console.log('onShown') 81 | panelVisible = true 82 | evalGetCssUsed() 83 | chrome.runtime.sendMessage({ 84 | action: 'inform', 85 | info: 'onShown', 86 | tabId: chrome.devtools.inspectedWindow.tabId, 87 | }) 88 | }) 89 | sidebar.onHidden.addListener(function () { 90 | // console.log('onHidden') 91 | evalGetCssUsed(true) 92 | panelVisible = false 93 | chrome.runtime.sendMessage({ 94 | action: 'inform', 95 | info: 'onHidden', 96 | tabId: chrome.devtools.inspectedWindow.tabId, 97 | }) 98 | }) 99 | } 100 | ) 101 | 102 | // passing resources to content script 103 | chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { 104 | // console.log('sender,message', sender, message) 105 | // Messages from content scripts should have sender.tab set 106 | if (sender.tab && sender.tab.id === chrome.devtools.inspectedWindow.tabId) { 107 | if (message.action == 'getResourceContent') { 108 | chrome.devtools.inspectedWindow.getResources((resources) => { 109 | // console.log('resources', resources); 110 | const resourceMatched = resources.find((r) => r.url === message.url) 111 | resourceMatched.getContent((content, encoding) => { 112 | // https://developer.chrome.com/docs/extensions/reference/devtools_inspectedWindow/#method-getResources 113 | // encoding:Currently, only base64 is supported. 114 | // console.log(resourceMatched, encoding, content.length); 115 | sendResponse({ 116 | url: message.url, 117 | content, 118 | }) 119 | }) 120 | }) 121 | // https://stackoverflow.com/questions/44056271/chrome-runtime-onmessage-response-with-async-await 122 | return true 123 | } else if (message.action == 'evalGetCssUsed') { 124 | isPageLoaded = true 125 | evalGetCssUsed() 126 | } 127 | } else { 128 | // Messages from panel scripts 129 | } 130 | }) 131 | -------------------------------------------------------------------------------- /src/ui/Options.svelte: -------------------------------------------------------------------------------- 1 | 86 | 87 |
    88 |

    89 | 94 | 95 | 96 | CSS Used Options 97 |

    98 | 99 |
    100 |
    101 | 105 |
    106 |
    107 | * eg. background: url(./bg.jpg) -> 108 | background: url(https://example.com/assets/bg.jpg) 109 |
    110 |
    111 | * Without absolute url, the Preview/CodePen may not work, for the 112 | protocols and domains are different. 113 |
    114 |
    115 | * The inspected page need a refresh for changes to take effect. 116 |
    117 |
    118 | 119 |
    120 | 121 |
    122 | 123 |
    124 | 125 |

    126 | Source code on GitHub 131 |

    132 |
    133 | 134 | 167 | -------------------------------------------------------------------------------- /test/css/skin.css: -------------------------------------------------------------------------------- 1 | .S_spetxt, 2 | a.S_ficon:hover, 3 | a:hover .S_ficon, 4 | a.current .S_ficon { 5 | color: #fa7d3c; 6 | } 7 | 8 | .S_error { 9 | color: #f00; 10 | } 11 | 12 | .S_spetxt_bg { 13 | background-color: #fa7d3c; 14 | } 15 | 16 | .W_btn_prev:hover, 17 | .W_btn_next:hover { 18 | border-color: #fa7d3c; 19 | } 20 | 21 | .W_btn_prev:hover, 22 | .W_btn_next:hover, 23 | a.S_ficon:hover, 24 | a:hover .S_ficon, 25 | a.current .S_ficon, 26 | a.S_txt1:hover, 27 | a.current .S_txt1, 28 | a.S_txt2:hover, 29 | .SW_fun:hover .S_func1 { 30 | text-decoration: none; 31 | cursor: pointer; 32 | } 33 | 34 | .S_ficon_dis, 35 | a.S_ficon_dis:hover, 36 | a:hover .S_ficon_dis { 37 | cursor: default; 38 | opacity: 0.5; 39 | filter: alpha(opacity=50); 40 | } 41 | 42 | .WB_timeline .current em { 43 | color: #fff; 44 | } 45 | 46 | .send_weibo { 47 | background-image: none; 48 | } 49 | 50 | .WB_miniblog { 51 | padding-top: 50px; 52 | } 53 | 54 | .S_page .WB_miniblog { 55 | padding-top: 50px; 56 | } 57 | 58 | .S_page .WB_frame { 59 | background-color: transparent !important; 60 | } 61 | 62 | #js_skin_public_base { 63 | height: 42px; 64 | } 65 | 66 | body, 67 | .S_page { 68 | background-color: #b4d66b; 69 | background-image: none; 70 | } 71 | 72 | .send_weibo .ficon_swtxt { 73 | color: #78a11f; 74 | } 75 | 76 | body, 77 | legend, 78 | .W_input:focus, 79 | .S_txt1, 80 | .W_btn_b, 81 | .SW_fun .S_func1 { 82 | color: #333; 83 | text-decoration: none; 84 | } 85 | 86 | .S_txt1_bg { 87 | background-color: #333; 88 | } 89 | 90 | .S_txt1_br { 91 | border-color: #333; 92 | } 93 | 94 | .S_txt2, 95 | .W_input, 96 | .W_btn_b_disable, 97 | .W_btn_b_disable:hover { 98 | color: #808080; 99 | text-decoration: none; 100 | } 101 | 102 | .S_txt2_bg { 103 | background-color: #808080; 104 | } 105 | 106 | .S_txt2_br { 107 | border-color: #808080; 108 | } 109 | 110 | .S_ficon, 111 | .S_ficon_dis, 112 | a.S_ficon_dis:hover, 113 | a:hover .S_ficon_dis { 114 | color: #696e78; 115 | } 116 | 117 | .S_ficon_bg { 118 | background-color: #696e78; 119 | } 120 | 121 | .S_ficon_br { 122 | border-color: #696e78; 123 | } 124 | 125 | a, 126 | .S_link1, 127 | a.S_txt1:hover, 128 | a.current .S_txt1, 129 | a.S_txt2:hover, 130 | .SW_fun:hover .S_func1 { 131 | color: #76a513; 132 | } 133 | 134 | .S_link1_bg { 135 | background-color: #76a513; 136 | } 137 | 138 | .S_link1_br { 139 | border-color: #76a513; 140 | } 141 | 142 | .S_bg1, 143 | .SW_fun_bg:hover, 144 | .SW_fun_bg_active { 145 | background-color: #f2f2f5; 146 | } 147 | 148 | .S_bg1_c { 149 | color: #f2f2f5; 150 | } 151 | 152 | .S_bg1_br { 153 | border-color: #f2f2f5; 154 | } 155 | 156 | .W_btn_cardlink, 157 | .W_btn_tag_cur, 158 | .W_btn_tag_cur:hover { 159 | background-color: #f2f2f5 !important; 160 | } 161 | 162 | .S_bg2, 163 | blockquote, 164 | .W_btn_b, 165 | .W_input, 166 | .SW_fun_bg { 167 | background-color: #fff; 168 | } 169 | 170 | .S_bg2_c { 171 | color: #fff; 172 | } 173 | 174 | .S_bg2_br { 175 | color: #fff; 176 | border-color: #fff; 177 | } 178 | 179 | .S_bg3 { 180 | background-color: #eaeaec; 181 | } 182 | 183 | .S_line1, 184 | .W_btn_prev, 185 | .W_btn_next, 186 | .W_btn_b { 187 | border-color: #d9d9d9; 188 | } 189 | 190 | .W_btn_b_disable, 191 | .W_btn_b_disable:hover, 192 | .W_btn_tag_cur, 193 | .W_btn_tag_cur:hover { 194 | border-color: #d9d9d9 !important; 195 | } 196 | 197 | .S_line1_c { 198 | color: #d9d9d9; 199 | } 200 | 201 | .W_btn_b_disable .S_ficon { 202 | color: #d9d9d9 !important; 203 | } 204 | 205 | .S_line2 { 206 | border-color: #f2f2f5; 207 | } 208 | 209 | .S_line2_c { 210 | color: #f2f2f5; 211 | } 212 | 213 | .S_line3, 214 | .W_input, 215 | .send_weibo .input, 216 | .W_btn_b:hover { 217 | border-color: #ccc; 218 | } 219 | 220 | .W_input, 221 | .send_weibo .input { 222 | background-color: #fff; 223 | } 224 | 225 | .WB_left_nav .S_txt1, 226 | .WB_left_nav a.S_txt1:hover, 227 | .WB_left_nav .S_ficon, 228 | .WB_left_nav a:hover .S_ficon, 229 | .WB_left_nav a.S_ficon:hover { 230 | color: #fff; 231 | } 232 | 233 | .WB_left_nav .lev a:hover, 234 | .WB_left_nav .lev_curr, 235 | .WB_left_nav .lev_curr:hover, 236 | .WB_left_nav .levmore .more { 237 | background-color: #b0c97b; 238 | background-color: rgba(255, 255, 255, 0.2); 239 | *background-color: #b0c97b; 240 | } 241 | 242 | .WB_left_nav .lev_Box, 243 | .WB_left_nav fieldset { 244 | border-color: #b0c97b; 245 | border-color: rgba(255, 255, 255, 0.1); 246 | *border-color: #b0c97b; 247 | } 248 | 249 | .WB_frame { 250 | background-color: #9ebd5b; 251 | background-color: rgba(94, 118, 47, 0.25); 252 | *background-color: #9ebd5b; 253 | } 254 | 255 | .WB_timeline .S { 256 | color: #87a840; 257 | border-left-color: #87a840; 258 | } 259 | 260 | .WB_timeline .current em, 261 | .WB_timeline .S_dot, 262 | .W_gotop { 263 | background-color: #87a840; 264 | } 265 | 266 | .WB_miniblog { 267 | background: none; 268 | } 269 | 270 | .S_page .WB_miniblog { 271 | background: none; 272 | } -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CSS Used Test 6 | 7 | 8 | 14 | 26 | 27 | 28 | 29 |
    30 | Link 31 |
    32 |
    33 | Bold 34 |
    35 |
    36 | Italic 37 |
    38 |
    FontFamily
    39 |

    .S_spetxt

    40 |
    svg-bg
    41 |
    svg-bg-inpage
    42 | 43 | 44 |

    45 | Compare text below with result in devtools, with <html> tag 46 | selected. 47 |

    48 | 49 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /src/util/traversalCSSRuleList.ts: -------------------------------------------------------------------------------- 1 | import { cssHelper } from './cssHelper' 2 | import type { cssObj } from './cssHelper' 3 | import convUrlToAbs from './convUrlToAbs' 4 | import convLinkToText from './convLinkToText' 5 | import convTextToRules from './convTextToRules' 6 | 7 | type cssNodeObj = Awaited> 8 | 9 | function traversalCSSRuleList( 10 | doc: Document, 11 | externalCssCache: { [index: cssNodeObj['href']]: cssNodeObj }, 12 | cssNodeObj: cssNodeObj 13 | ): Promise { 14 | var promises = [] 15 | 16 | var objCss: cssObj = { 17 | normRule: [], 18 | keyFram: [], 19 | fontFace: [], 20 | } 21 | 22 | return new Promise(function (resolve, reject) { 23 | if (cssNodeObj === undefined || cssNodeObj.nodes.length === 0) { 24 | resolve(objCss) 25 | } else if (cssNodeObj.nodes.length > 0) { 26 | // annotion where the CSS rule from 27 | let strMediaText = '' 28 | if (cssNodeObj.media && cssNodeObj.media.length > 0) { 29 | strMediaText = `; media=${cssNodeObj.media.mediaText} ` 30 | } 31 | if (cssNodeObj.href === doc.location.href) { 32 | objCss.normRule.push(`/*! CSS Used from: Embedded ${strMediaText}*/`) 33 | } else if (cssNodeObj.href && !cssNodeObj.parentHref) { 34 | objCss.normRule.push( 35 | `/*! CSS Used from: ${cssNodeObj.href} ${strMediaText}*/` 36 | ) 37 | } 38 | } 39 | 40 | for (var i = 0; i < cssNodeObj.nodes.length; i++) { 41 | ;(function (CSSRuleListItem, i) { 42 | promises.push( 43 | new Promise(function (res) { 44 | var _objCss = { 45 | normRule: [], 46 | keyFram: [], 47 | fontFace: [], 48 | } 49 | if ( 50 | CSSRuleListItem.type === 'atrule' && 51 | CSSRuleListItem.name.match(/^(-(webkit|moz)-)?keyframes$/) 52 | ) { 53 | // CSSKeyframesRule 54 | _objCss.keyFram.push(CSSRuleListItem) 55 | res(_objCss) 56 | } else if ( 57 | CSSRuleListItem.type === 'atrule' && 58 | CSSRuleListItem.name === 'font-face' 59 | ) { 60 | // CSSFontFaceRule 61 | _objCss.fontFace.push(CSSRuleListItem) 62 | res(_objCss) 63 | } else if ( 64 | CSSRuleListItem.type === 'atrule' && 65 | CSSRuleListItem.name === 'media' 66 | ) { 67 | // CSSMediaRule 68 | traversalCSSRuleList(doc, externalCssCache, { 69 | nodes: CSSRuleListItem.nodes, 70 | }).then(function (obj) { 71 | _objCss.normRule.push( 72 | '\n@media ' + CSSRuleListItem.params + '{' 73 | ) 74 | cssHelper.mergeobjCss(_objCss, obj) 75 | _objCss.normRule.push('}') 76 | res(_objCss) 77 | }) 78 | } else if ( 79 | CSSRuleListItem.type === 'atrule' && 80 | CSSRuleListItem.name === 'import' 81 | ) { 82 | // CSSImportRule 83 | let isValidImport = true 84 | for (let j = 0; j < i; j++) { 85 | let rule = cssNodeObj.nodes[j] 86 | if ( 87 | rule.type === 'rule' || 88 | (rule.type === 'atrule' && 89 | rule.name.match(/^charset|import$/) === null) 90 | ) { 91 | isValidImport = false 92 | break 93 | } 94 | } 95 | if (!cssNodeObj.href) { 96 | // such as import inside media query 97 | isValidImport = false 98 | } 99 | let importParamsMatch = CSSRuleListItem.params.match( 100 | /^(url\((['"]?)(.*?)\2\)|(['"])(.*?)\4)\s*(.*)$/ 101 | ) 102 | let href = importParamsMatch[3] || importParamsMatch[5] || '' 103 | let media = importParamsMatch[6] 104 | if ( 105 | isValidImport && 106 | (href = convUrlToAbs(cssNodeObj.href, href)) && 107 | href !== cssNodeObj.parentHref 108 | ) { 109 | new Promise((resolve) => { 110 | if (externalCssCache[href] !== undefined) { 111 | resolve(externalCssCache[href]) 112 | } else { 113 | convLinkToText([href]) 114 | .then(function (result) { 115 | return convTextToRules(result[0].cssraw) 116 | }) 117 | .then(function (nodeArr) { 118 | nodeArr.href = href 119 | nodeArr.parentHref = cssNodeObj.href 120 | externalCssCache[href] = nodeArr 121 | resolve(nodeArr) 122 | }) 123 | } 124 | }) 125 | .then(function (nodeArr: cssNodeObj) { 126 | return traversalCSSRuleList(doc, externalCssCache, nodeArr) 127 | }) 128 | .then(function (obj) { 129 | if (obj.normRule.length > 0) { 130 | _objCss.normRule.push( 131 | ['/*! @import', href, media, '*/'] 132 | .join(' ') 133 | .replace(/ {2,}/g, ' ') 134 | ) 135 | media.length && 136 | _objCss.normRule.push('\n@media ' + media + '{') 137 | cssHelper.mergeobjCss(_objCss, obj) 138 | media.length && _objCss.normRule.push('}') 139 | _objCss.normRule.push('/*! end @import */') 140 | } else { 141 | cssHelper.mergeobjCss(_objCss, obj) 142 | } 143 | res(_objCss) 144 | }) 145 | } else { 146 | res(_objCss) 147 | } 148 | } else if ( 149 | CSSRuleListItem.type === 'rule' && 150 | CSSRuleListItem.selector !== '' 151 | ) { 152 | // the normal "CSSStyleRule" 153 | _objCss.normRule.push(CSSRuleListItem) 154 | res(_objCss) 155 | } else { 156 | res(_objCss) 157 | } 158 | }) 159 | ) 160 | })(cssNodeObj.nodes[i], i) 161 | } 162 | 163 | Promise.all(promises) 164 | .then(function (result) { 165 | result.forEach(function (ele) { 166 | cssHelper.mergeobjCss(objCss, ele) 167 | }) 168 | if (cssNodeObj.media && cssNodeObj.media.length > 0) { 169 | objCss.normRule.splice(1, 0, `@media ${cssNodeObj.media.mediaText}{`) 170 | objCss.normRule.push('}') 171 | } 172 | resolve(objCss) 173 | }) 174 | .catch(function (err) { 175 | reject(err) 176 | }) 177 | }) 178 | } 179 | 180 | export default traversalCSSRuleList 181 | -------------------------------------------------------------------------------- /src/util/filterRules.ts: -------------------------------------------------------------------------------- 1 | // this module is used to filter rules 2 | // by testing the dom and its descendants one by one. 3 | // each testing is wrapped by a settimeout timmer to make it async 4 | // because the testing can be a long time if too many. 5 | 6 | import debugMode from '../const/debugMode' 7 | import { cssHelper } from './cssHelper' 8 | 9 | // may match accoding to interaction 10 | const PseudoClass = 11 | 'active|checked|disabled|empty|enabled|focus|hover|in-range|invalid|link|out-of-range|target|valid|visited|focus-within|focus-visible|fullscreen', 12 | PseudoElement = 13 | '((-(webkit|moz)-)?(scrollbar(-(button|thumb|corner|track(-piece)?))?))|-webkit-(details-marker|resizer)|after|before|first-letter|first-line|placeholder|selection', 14 | MaxPossiblePseudoLength = 30, 15 | REG0 = new RegExp( 16 | '^(:(' + PseudoClass + ')|::?(' + PseudoElement + '))+$', 17 | '' 18 | ), 19 | REG1 = new RegExp( 20 | '( |^)(:(' + PseudoClass + ')|::?(' + PseudoElement + '))+( |$)', 21 | 'ig' 22 | ), 23 | REG2 = new RegExp( 24 | '\\((:(' + PseudoClass + ')|::?(' + PseudoElement + '))+\\)', 25 | 'ig' 26 | ), 27 | REG3 = new RegExp( 28 | '(:(' + PseudoClass + ')|::?(' + PseudoElement + '))+', 29 | 'ig' 30 | ) 31 | 32 | function filterRules($0: HTMLElement, objCss, taskTimerRecord) { 33 | var promises = [] 34 | var matched = [] 35 | var keyFramUsed = [] 36 | var fontFaceUsed = [] 37 | 38 | const descendantsCount = $0.querySelectorAll('*').length 39 | 40 | return new Promise(function (resolve, reject) { 41 | // loop every dom 42 | objCss.normRule.forEach(function (rule, idx) { 43 | promises.push( 44 | new Promise(async function (res) { 45 | var timer = setTimeout(function () { 46 | if (idx % 1000 === 0) { 47 | let nRule = objCss.normRule.length 48 | chrome.runtime.sendMessage({ 49 | action: 'inform', 50 | info: `The selected dom has ${descendantsCount} descendants.\nPage rules are about ${nRule}.\nTraversing the ${idx}th rule...`, 51 | }) 52 | } 53 | 54 | if (typeof rule === 'string') { 55 | res(rule) 56 | return 57 | } else { 58 | var selMatched = [] 59 | var arrSel = rule.selectors.filter(function (v, i, self) { 60 | return self.indexOf(v) === i 61 | }) 62 | arrSel.forEach(function (sel) { 63 | if (selMatched.indexOf(sel) !== -1) { 64 | return 65 | } 66 | // these pseudo class/elements can apply to any ele 67 | // but wont apply now 68 | // eg. :active{xxx} 69 | // only works when clicked on and actived 70 | if (sel.length < MaxPossiblePseudoLength && sel.match(REG0)) { 71 | selMatched.push(sel) 72 | } else { 73 | let errorArray = [] 74 | let replacedSel = sel 75 | .replace(REG1, ' * ') 76 | .replace(REG2, '(*)') 77 | .replace(REG3, '') 78 | .replace(/:not\(\*\)/ig, '') 79 | 80 | try { 81 | if ( 82 | $0.matches(replacedSel) || 83 | $0.querySelectorAll(replacedSel).length !== 0 84 | ) { 85 | selMatched.push(sel) 86 | } 87 | } catch (e) { 88 | errorArray.push({ 89 | selector: replacedSel, 90 | error: e, 91 | }) 92 | } 93 | if (debugMode) { 94 | console.warn('selector match error: ', errorArray) 95 | } 96 | } 97 | }) 98 | if (selMatched.length !== 0) { 99 | // remove duplicate selector 100 | var cssText = selMatched 101 | .filter(function (v, i, self) { 102 | return self.indexOf(v) === i 103 | }) 104 | .join(',') 105 | cssText += '{' + cssHelper.normRuleNodeToText(rule) + '}' 106 | res(cssText) 107 | rule.nodes.forEach(function (ele) { 108 | if ( 109 | ele.prop && 110 | ele.prop.match(/^(-(webkit|moz)-)?animation(-name)?$/i) !== 111 | null 112 | ) { 113 | keyFramUsed = keyFramUsed.concat( 114 | ele.value.split(/ *, */).map(function (ele) { 115 | return ele.split(' ')[0] 116 | }) 117 | ) 118 | } 119 | }) 120 | 121 | if (rule && rule.nodes) { 122 | for (let index = 0; index < rule.nodes.length; index++) { 123 | const declaration = rule.nodes[index] 124 | if (declaration && declaration.prop === 'font-family') { 125 | fontFaceUsed = [ 126 | ...fontFaceUsed, 127 | ...declaration.value 128 | .split(/ *, */) 129 | .filter((e: string) => !!e), 130 | ] 131 | } 132 | } 133 | } 134 | return 135 | } 136 | } 137 | res('') 138 | }, 0) 139 | taskTimerRecord.push(timer) 140 | }) 141 | ) 142 | }) 143 | 144 | Promise.all(promises) 145 | .then(function (result) { 146 | keyFramUsed = keyFramUsed.filter(function (v, i, self) { 147 | return self.indexOf(v) === i 148 | }) 149 | fontFaceUsed = fontFaceUsed.filter(function (v, i, self) { 150 | return self.indexOf(v) === i 151 | }) 152 | result.forEach(function (ele) { 153 | // typeof ele:string 154 | if (ele.length > 0) { 155 | matched.push(ele) 156 | } 157 | }) 158 | var frameCommentMarkUsed = false 159 | keyFramUsed.forEach(function (ele) { 160 | objCss.keyFram.forEach(function (e) { 161 | if (ele === e.params) { 162 | if (!frameCommentMarkUsed) { 163 | matched.push('/*! CSS Used keyframes */') 164 | frameCommentMarkUsed = true 165 | } 166 | matched.push(cssHelper.keyFramNodeToText(e)) 167 | } 168 | }) 169 | }) 170 | var fontCommentMarkUsed = false 171 | fontFaceUsed.forEach(function (ele) { 172 | objCss.fontFace.forEach(function (e) { 173 | e.nodes.forEach(function (n) { 174 | if ( 175 | n.prop === 'font-family' && 176 | ele.replace(/^(['"])?(.*)\1$/, '$2') === 177 | n.value.replace(/^(['"])?(.*)\1$/, '$2') 178 | ) { 179 | if (!fontCommentMarkUsed) { 180 | matched.push('/*! CSS Used fontfaces */') 181 | fontCommentMarkUsed = true 182 | } 183 | matched.push(cssHelper.fontFaceNodeToText(e)) 184 | } 185 | }) 186 | }) 187 | }) 188 | resolve(matched) 189 | }) 190 | .catch(function (err) { 191 | reject(err) 192 | }) 193 | }) 194 | } 195 | 196 | export default filterRules 197 | -------------------------------------------------------------------------------- /src/ui/Panel.svelte: -------------------------------------------------------------------------------- 1 | 177 | 178 |
    179 |
    180 | CSS Used by $0 and its descendants: 181 | 182 |
    183 |
    184 |