├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .travis.yml ├── README.md ├── doc ├── AMO-button_1.png ├── ChromeWebStore_BadgeWBorder_v2_206x58.png ├── meta.md ├── screenshot-1280x800.png ├── screenshot-chrome-1280x800.png ├── screenshot-chrome.png ├── screenshot-firefox-500.png ├── screenshot-firefox.png ├── screenshot.png ├── video-500x337.gif └── video.gif ├── extension ├── devtools_page │ ├── index.html │ ├── main.js │ └── z-index │ │ ├── ZRankingTableUI.js │ │ ├── index.html │ │ ├── main.css │ │ └── main.js ├── icons │ └── icon-90.png ├── manifest.json └── vendor │ └── webextension-polyfill │ └── .gitkeep ├── package-lock.json ├── package.json ├── testem.json ├── tests ├── .eslintrc.js └── devtools_page │ └── panes │ └── ZRankingTableUI.spec.js ├── tsconfig.json └── types └── ZRankingTableUI.d.ts /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | charset = utf-8 6 | indent_style = space 7 | indent_size = 2 8 | 9 | [*.{js,ts,json,css,sass,scss,html}] 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | *.d.ts 2 | /extension/vendor/* 3 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { 4 | browser: true 5 | }, 6 | extends: 'airbnb-base', 7 | globals: { 8 | "browser": false, 9 | }, 10 | rules: { 11 | "arrow-parens": [ 12 | "error", 13 | "always", 14 | ], 15 | "class-methods-use-this": [ 16 | "off", 17 | ], 18 | "comma-dangle": [ 19 | "error", 20 | "always-multiline", 21 | ], 22 | "import/extensions": [ 23 | "error", 24 | "always", 25 | ], 26 | "space-before-function-paren": [ 27 | "error", 28 | "always", 29 | ], 30 | "no-console": [ 31 | "error", 32 | { 33 | "allow": [ 34 | "warn", 35 | "error", 36 | ], 37 | }, 38 | ], 39 | "no-underscore-dangle": [ 40 | "error", 41 | { 42 | "allowAfterThis": true, 43 | }, 44 | ], 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ,* 2 | /.vscode/ 3 | /extension/vendor/* 4 | /node_modules/ 5 | /extension.zip 6 | /npm-debug.log 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "10" 4 | services: 5 | - xvfb 6 | script: 7 | - npm run lint 8 | - npm run test-travis 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DevTools z-index 2 | 3 | [![Build Status](https://travis-ci.org/ginpei/devtools-z-index.svg?branch=master)](https://travis-ci.org/ginpei/devtools-z-index) [![Greenkeeper badge](https://badges.greenkeeper.io/ginpei/devtools-z-index.svg)](https://greenkeeper.io/) 4 | 5 | → [日本語の紹介記事](https://ginpen.com/2018/06/20/devtools-z-index/) 6 | 7 | **Stop `z-index: 999999` !!** 8 | 9 | This adds "z-index" sub-pane of Elements panel for Chrome, "z-index" panel for Firefox. 10 | 11 | ### Install 12 | 13 | [![Download from Chrome Web Store](doc/ChromeWebStore_BadgeWBorder_v2_206x58.png)](https://chrome.google.com/webstore/detail/bcnpmhefiohkpmjacfoanhbjhikegmoe/) 14 | [![Download Firefox add-ons](doc/AMO-button_1.png)](https://addons.mozilla.org/en-US/firefox/addon/devtools-z-index/) 15 | 16 | ## What for? 17 | 18 | You may be shocked by finding how large numbers are used in your page. Unconsidered large numbers would be killed by another larger numbers, and those larger numbers also killed by much-larger numbers like war. That sucks. 19 | 20 | This extension offers a table that helps reduce those numbers. You can keep your CSS code clean, maintainable and peaceful. 21 | 22 | **No more `z-index: 2147483647` !!** 23 | 24 | ## Chrome extension 25 | 26 | https://chrome.google.com/webstore/detail/bcnpmhefiohkpmjacfoanhbjhikegmoe/ 27 | 28 | ![z-index pane in Elements panel, where you can find all elements with z-index](doc/screenshot.png) 29 | 30 | ![Click a selector to inspect the element in Elements panel](doc/video-500x337.gif) 31 | 32 | ## Firefox add-on 33 | 34 | https://addons.mozilla.org/en-US/firefox/addon/devtools-z-index/ 35 | 36 | ![z-index panel where you can find all elements with z-index](doc/screenshot-firefox-500.png) 37 | 38 | Since Firefox doesn't allow us to add nice Inspector (Elements) panel's pane, I added it as a panel. 39 | 40 | ## Code snippet 41 | 42 | Basic idea was in [Twitter](https://twitter.com/ginpei_jp/status/1006312787813908480) and Gist. 43 | 44 | https://gist.github.com/ginpei/073ab5d4679356f29585a9ae02277012 45 | 46 | ```js 47 | ((document, limit) => { 48 | const data = Array.from(document.querySelectorAll('*')) 49 | .map((el) => ({zIndex: Number(getComputedStyle(el).zIndex), element: el })) 50 | .filter(({ zIndex }) => !isNaN(zIndex)) 51 | .sort((r1, r2) => r2.zIndex - r1.zIndex) 52 | .slice(0, limit); 53 | console.table(data); 54 | })(document, 50); 55 | ``` 56 | 57 | ## Future feature 58 | 59 | Honestly, I'm not planning to update since I felt satisfied tough, it would be fun to add following features. 60 | 61 | - Fix: it finds a wrong element when some elements have the same selector (because it searches only by selector) 62 | - Show useful information like stacking context 63 | - Show something if an element's z-index is specified by style attribute 64 | - Ability to update z-index for preview, like DevTools Style sub-pane 65 | - Set better icon somehow 66 | 67 | ## Dev 68 | 69 | ### Development for Chrome 70 | 71 | 1. Open Extensions page 72 | - `chrome://extensions/` 73 | 2. Turn on "Developer mode" switch at the top right 74 | 3. Press "Load unpacked" button 75 | 4. Select `extension/` directory on this project 76 | 5. You'll see your extension card on the page 77 | 6. Close and open your DevTools to load 78 | 7. To reload your updates: 79 | 1. Modify code and resources 80 | 2. Press a reload button on your extension card 81 | 8. If your extension throws errors: 82 | 1. You'll see a Error button on your extension card 83 | 84 | ### Development for Firefox 85 | 86 | - [Temporary installation in Firefox | Firefox Extension Workshop](https://extensionworkshop.com/documentation/develop/temporary-installation-in-firefox/) 87 | 88 | 1. Open Firefox debug page: [`about:debugging#/runtime/this-firefox`](about:debugging#/runtime/this-firefox) 89 | 2. Click "Load Temporary Add-on" button under "Temporary Extensions" 90 | 3. Select the `manifest.json` 91 | 4. It should be loaded 92 | 93 | ### Publish for Chrome 94 | 95 | 1. Prepare a zip file to upload 96 | 1. Make sure `zip` is installed 97 | 1. `apt install zip` for WSL 98 | 2. Run `npm run build` 99 | 3. You will get a file `extension.zip` in the project root directory 100 | 2. Come to [Chrome Web Store - Developer Dashboard](https://chrome.google.com/webstore/devconsole/) 101 | 3. Select your extension 102 | 4. Left side panel > Build > Package 103 | 5. Press "Upload new package" button at top right 104 | 6. Upload the `extension.zip` 105 | 7. You'll be navigated to the "Store listing" page 106 | 8. In need, update fields there and press "Save draft" 107 | 9. Press "Submit for review", and proceed the steps 108 | 109 | ### Publish for Chrome 110 | 111 | 1. Make sure `zip` is installed 112 | 2. Come to [Add-on Developer Hub < Manage My Submissions](https://addons.mozilla.org/en-CA/developers/addons) 113 | 3. Select your extension 114 | 4. Press "Upload New Version" link button on left side, and proceed the steps 115 | 5. Upload the `extension.zip` 116 | 6. Continue submission steps 117 | 118 | ## License 119 | 120 | - MIT 121 | 122 | ## Contact 123 | 124 | - Ginpei Takanashi 125 | - Twitter [@ginpei_en](http://twitter.com/ginpei_en) 126 | - GitHub [@ginpei](https://github.com/ginpei/) / [devtools-z-index](https://github.com/ginpei/devtools-z-index) 127 | - [Ginpei.info](https://ginpei.info/) 128 | -------------------------------------------------------------------------------- /doc/AMO-button_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/AMO-button_1.png -------------------------------------------------------------------------------- /doc/ChromeWebStore_BadgeWBorder_v2_206x58.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/ChromeWebStore_BadgeWBorder_v2_206x58.png -------------------------------------------------------------------------------- /doc/meta.md: -------------------------------------------------------------------------------- 1 | # Meta 2 | 3 | - https://chrome.google.com/webstore/detail/bcnpmhefiohkpmjacfoanhbjhikegmoe/publish-accepted 4 | - https://addons.mozilla.org/en-US/firefox/addon/devtools-z-index/ 5 | - https://www.youtube.com/watch?v=5Q8QSUsUaxc 6 | 7 | ## Descriptions 8 | 9 | For Chrome: 10 | 11 | > Adds "z-index" pane to DevTools' Elements panel. It shows ranking of elements sorted by z-index value. This would help you to reduce z-index numbers. 12 | > 13 | > Stop `z-index: 999999` !! 14 | 15 | For Firefox: 16 | 17 | > Adds "z-index" panel to DevTools. It shows ranking of elements sorted by z-index value. This would help you to reduce z-index numbers. 18 | > 19 | > Stop `z-index: 999999` !! 20 | -------------------------------------------------------------------------------- /doc/screenshot-1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot-1280x800.png -------------------------------------------------------------------------------- /doc/screenshot-chrome-1280x800.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot-chrome-1280x800.png -------------------------------------------------------------------------------- /doc/screenshot-chrome.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot-chrome.png -------------------------------------------------------------------------------- /doc/screenshot-firefox-500.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot-firefox-500.png -------------------------------------------------------------------------------- /doc/screenshot-firefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot-firefox.png -------------------------------------------------------------------------------- /doc/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/screenshot.png -------------------------------------------------------------------------------- /doc/video-500x337.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/video-500x337.gif -------------------------------------------------------------------------------- /doc/video.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/doc/video.gif -------------------------------------------------------------------------------- /extension/devtools_page/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DevTools z-index 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /extension/devtools_page/main.js: -------------------------------------------------------------------------------- 1 | (() => { 2 | /** 3 | * @param {'updateTable' | 'clearTable'} type 4 | */ 5 | function sendMessage (type) { 6 | browser.runtime.sendMessage({ type }); 7 | } 8 | 9 | /** 10 | * @param {string} title 11 | * @param {string} themeName 12 | */ 13 | async function createSidebarPane (title, themeName) { 14 | const baseHtmlPath = '/devtools_page/z-index/index.html'; 15 | const htmlPath = `${baseHtmlPath}?themeName=${themeName}`; 16 | 17 | let pane; 18 | 19 | const sidebarAvailable = navigator.userAgent.match(' Chrome/'); 20 | if (sidebarAvailable) { 21 | pane = await browser.devtools.panels.elements.createSidebarPane(title); 22 | // Chrome has `setPage()` 🙂 23 | pane.setPage(htmlPath); 24 | } else { 25 | // Firefox does not have `setPage()` so... 😢 26 | pane = browser.devtools.panels.create(title, '', htmlPath); 27 | } 28 | 29 | return pane; 30 | } 31 | 32 | async function start () { 33 | const { themeName } = browser.devtools.panels; 34 | 35 | const pane = await createSidebarPane('z-index', themeName); 36 | pane.onShown.addListener(() => sendMessage('updateTable')); 37 | pane.onHidden.addListener(() => sendMessage('clearTable')); 38 | browser.devtools.panels.elements.onSelectionChanged.addListener(() => { 39 | sendMessage('updateTable'); 40 | }); 41 | } 42 | 43 | start(); 44 | })(); 45 | -------------------------------------------------------------------------------- /extension/devtools_page/z-index/ZRankingTableUI.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | 3 | /** @type {ZIndexRecord[]} */ 4 | const emptyZIndexRecordArray = []; 5 | 6 | class ZRankingTableUI { 7 | constructor () { 8 | /** @type {HTMLTableElement | null} */ 9 | this.elTable = null; 10 | 11 | /** @type {NonNullable} */ 12 | this._onClickListener = (event) => { 13 | if (!event.target || !(event.target instanceof HTMLElement)) { 14 | return; 15 | } 16 | 17 | const elSelector = event.target.closest('[data-selector]'); 18 | if (elSelector) { 19 | const selector = elSelector.getAttribute('data-selector') || ''; 20 | if (typeof this.onSelect === 'function') { 21 | this.onSelect(selector); 22 | } 23 | } 24 | }; 25 | 26 | /** @type {((selector: string) => void) | null} */ 27 | this.onSelect = null; 28 | } 29 | 30 | /** 31 | * @param {{ elTable: HTMLTableElement }} options 32 | */ 33 | start ({ elTable }) { 34 | this.elTable = elTable; 35 | this.elTable.addEventListener('click', this._onClickListener); 36 | } 37 | 38 | stop () { 39 | if (this.elTable) { 40 | this.elTable.removeEventListener('click', this._onClickListener); 41 | this.elTable = null; 42 | } 43 | } 44 | 45 | /** 46 | * @param {{ ranking?: ZIndexRecord[] }} options 47 | */ 48 | async updateTable ({ ranking = emptyZIndexRecordArray }) { 49 | if (!this.elTable) { 50 | return; 51 | } 52 | 53 | const html = ranking 54 | .map((row) => this._buildTableRowHtml(row)) 55 | .join(''); 56 | this.elTable.innerHTML = html; 57 | } 58 | 59 | /** 60 | * @param {ZIndexRecord} row 61 | */ 62 | _buildTableRowHtml (row) { 63 | const selector = [ 64 | row.tagName, 65 | row.id ? `#${row.id}` : '', 66 | row.classNames.length > 0 ? `.${row.classNames.join('.')}` : '', 67 | ].join(''); 68 | 69 | const selectorHtml = [ 70 | this._buildElementTitleHtml(row.tagName, 'tagName'), 71 | this._buildElementTitleHtml(row.id, 'id', '#'), 72 | this._buildElementTitleHtml(row.classNames.join('.'), 'classes', '.'), 73 | ].join(''); 74 | 75 | const html = ` 76 | 77 | ${row.zIndex} 78 | 79 | ${selectorHtml} 81 | 82 | 83 | `; 84 | return html; 85 | } 86 | 87 | /** 88 | * @param {string} text 89 | * @param {string} type 90 | */ 91 | _buildElementTitleHtml (text, type, prefix = '') { 92 | if (text) { 93 | return `${prefix}${text}`; 94 | } 95 | return ''; 96 | } 97 | } 98 | 99 | ZRankingTableUI.buildRanking = (d = document) => { 100 | const ranking = Array.from(d.querySelectorAll('*')) 101 | .map((el) => ({ 102 | classNames: Array.from(el.classList), 103 | id: el.id, 104 | tagName: el.tagName.toLowerCase(), 105 | zIndex: Number(getComputedStyle(el).zIndex), 106 | })) 107 | .filter(({ zIndex }) => !Number.isNaN(zIndex)) 108 | .sort((r1, r2) => r2.zIndex - r1.zIndex); 109 | return ranking; 110 | }; 111 | -------------------------------------------------------------------------------- /extension/devtools_page/z-index/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DevTools z-index 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
z-indexElement
18 |

Note: Inspecting element works only looking its selector for now. You would get a wrong element that can be selected by the same selector.

19 |

20 | Report problem 21 |

22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /extension/devtools_page/z-index/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Consolas, Lucida Console, Courier New, monospace; 3 | } 4 | 5 | a { 6 | color: rgb(15%, 15%, 15%); 7 | } 8 | 9 | .about { 10 | text-align: right; 11 | } 12 | 13 | .note { 14 | color: gray; 15 | font-size: italic; 16 | } 17 | 18 | .rankingTable { 19 | --border-value: 1px solid #aaa; 20 | 21 | border: var(--border-value); 22 | border-collapse: collapse; 23 | width: 100%; 24 | } 25 | .rankingTable > * > tr > * { 26 | border-left: var(--border-value); 27 | } 28 | .rankingTable > thead > tr { 29 | background-color: #f3f3f3; 30 | border: var(--border-value); 31 | } 32 | .rankingTable > thead > tr > th { 33 | font-weight: normal; 34 | text-align: left; 35 | white-space: nowrap; 36 | } 37 | .rankingTable > tbody > tr:nth-child(even) { 38 | background-color: hsl(214, 70%, 97%); 39 | } 40 | 41 | .rankingTableItem { 42 | } 43 | .rankingTableItem-zIndex, 44 | .rankingTableItem-element { 45 | padding: 0.2em 0.4em; 46 | } 47 | .rankingTableItem-zIndex { 48 | width: 60px; 49 | } 50 | .rankingTableItem-selector:hover { 51 | cursor: pointer; 52 | text-decoration: underline; 53 | } 54 | .rankingTableItem-tagName { 55 | color: rgb(136, 18, 128); 56 | } 57 | .rankingTableItem-id { 58 | color: rgb(26, 26, 166); 59 | } 60 | .rankingTableItem-classes { 61 | color: rgb(153, 69, 0); 62 | } 63 | tbody .rankingTableItem-zIndex { 64 | font-family: monospace; 65 | text-align: right; 66 | } 67 | tbody .rankingTableItem-element { 68 | font-style: italic; 69 | } 70 | 71 | /* wish the media query worked in the DevTool */ 72 | [data-theme-name="dark"] body { 73 | background-color: rgb(36, 36, 36); 74 | color: rgb(213, 213, 213); 75 | } 76 | [data-theme-name="dark"] a { 77 | color: rgb(211, 211, 211); 78 | } 79 | [data-theme-name="dark"] .rankingTable { 80 | --border-value: 1px solid rgb(85, 85, 85); 81 | } 82 | [data-theme-name="dark"] .rankingTable > thead > tr { 83 | background-color: #333; 84 | } 85 | [data-theme-name="dark"] .rankingTable > tbody > tr:nth-child(even) { 86 | background-color: rgb(11, 37, 68); 87 | } 88 | [data-theme-name="dark"] .rankingTableItem-tagName { 89 | color: rgb(93, 176, 215); 90 | } 91 | [data-theme-name="dark"] .rankingTableItem-id { 92 | color: rgb(242, 151, 102); 93 | } 94 | [data-theme-name="dark"] .rankingTableItem-classes { 95 | color: rgb(155, 187, 220); 96 | } 97 | [data-theme-name="dark"] .note { 98 | color: rgb(137, 137, 137); 99 | } 100 | -------------------------------------------------------------------------------- /extension/devtools_page/z-index/main.js: -------------------------------------------------------------------------------- 1 | /* globals chrome, ZRankingTableUI */ 2 | 3 | (() => { 4 | const tableUi = new ZRankingTableUI(); 5 | 6 | /** 7 | * @param {string} code 8 | */ 9 | function executeScript (code) { 10 | return new Promise((resolve, reject) => { 11 | // @ts-ignore 12 | chrome.devtools.inspectedWindow.eval(code, (result, status) => { 13 | if (status && status.isException) { 14 | reject(new Error(status.value)); 15 | } 16 | resolve(result); 17 | }); 18 | }); 19 | } 20 | 21 | /** 22 | * @returns {Promise} 23 | */ 24 | function getRanking () { 25 | const code = `(${ZRankingTableUI.buildRanking.toString()})()`; 26 | return executeScript(code); 27 | } 28 | 29 | async function updateTable () { 30 | const ranking = await getRanking(); 31 | tableUi.updateTable({ ranking }); 32 | } 33 | 34 | async function clearTable () { 35 | tableUi.updateTable({}); 36 | } 37 | 38 | function initColorScheme () { 39 | const paramPairs = window.location.search.slice(1).split('&'); 40 | const themeNameParam = paramPairs.find((v) => v.startsWith('themeName=')); 41 | if (!themeNameParam) { 42 | return; 43 | } 44 | 45 | const themeName = themeNameParam.split('=')[1]; 46 | document.documentElement.dataset.themeName = themeName; 47 | } 48 | 49 | /** 50 | * @param {string} selector 51 | */ 52 | function selectElement (selector) { 53 | const code = `inspect(document.querySelector('${selector}'));`; 54 | executeScript(code); 55 | } 56 | 57 | function start () { 58 | initColorScheme(); 59 | 60 | const elTable = /** @type {HTMLTableElement} */ (document.querySelector('#rankingTable-body')); 61 | tableUi.start({ elTable }); 62 | tableUi.onSelect = (selector) => { 63 | selectElement(selector); 64 | }; 65 | 66 | browser.runtime.onMessage.addListener(({ type }) => { 67 | if (type === 'updateTable') { 68 | updateTable(); 69 | } else if (type === 'clearTable') { 70 | clearTable(); 71 | } 72 | }); 73 | 74 | document.addEventListener('click', (event) => { 75 | if (!event.target || !(event.target instanceof HTMLElement)) { 76 | return; 77 | } 78 | 79 | const elLink = event.target.closest('a'); 80 | if (elLink) { 81 | event.preventDefault(); 82 | const url = elLink.href; 83 | window.open(url); 84 | } 85 | }); 86 | 87 | updateTable(); 88 | } 89 | 90 | start(); 91 | })(); 92 | -------------------------------------------------------------------------------- /extension/icons/icon-90.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/extension/icons/icon-90.png -------------------------------------------------------------------------------- /extension/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 3, 3 | "name": "DevTools z-index", 4 | "version": "1.2.0", 5 | "description": "Print all elements with z-index sorted by that number.", 6 | "author": "Ginpei Takanashi", 7 | "icons": { 8 | "90": "icons/icon-90.png" 9 | }, 10 | "devtools_page": "devtools_page/index.html", 11 | "browser_specific_settings": { 12 | "gecko": { 13 | "id": "{40c9a151-fca9-4252-aa05-cb0df43d18b7}" 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /extension/vendor/webextension-polyfill/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ginpei/devtools-z-index/b146976047e285fb4a281d14c7015ddfd646c5d0/extension/vendor/webextension-polyfill/.gitkeep -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "name": "devtools-z-index", 4 | "scripts": { 5 | "build": "npm run lint && npm run clean && npm run install-vendor && npm run pack", 6 | "clean": "rm extension.zip || true", 7 | "lint": "eslint extension && tsc && addons-linter extension", 8 | "install-vendor": "cp node_modules/webextension-polyfill/dist/browser-polyfill.min.js extension/vendor/webextension-polyfill/", 9 | "start": "# TODO", 10 | "pack": "cd extension && zip -r ../extension.zip . -x */.gitkeep", 11 | "postinstall": "npm run install-vendor", 12 | "test": "testem", 13 | "test-travis": "testem ci --launch Firefox" 14 | }, 15 | "dependencies": { 16 | "webextension-polyfill": "^0.10.0" 17 | }, 18 | "devDependencies": { 19 | "@types/chai": "^4.3.5", 20 | "@types/firefox-webext-browser": "^111.0.1", 21 | "@types/mocha": "^10.0.1", 22 | "@types/sinon": "^10.0.15", 23 | "@types/sinon-chai": "^3.2.9", 24 | "addons-linter": "^6.9.0", 25 | "eslint": "^8.43.0", 26 | "eslint-config-airbnb-base": "^15.0.0", 27 | "eslint-plugin-import": "^2.27.5", 28 | "sinon": "^15.2.0", 29 | "sinon-chai": "^3.7.0", 30 | "sinon-chrome": "^3.0.1", 31 | "testem": "^3.10.1", 32 | "typescript": "^5.1.3" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /testem.json: -------------------------------------------------------------------------------- 1 | { 2 | "framework": "mocha+chai", 3 | "src_files": [ 4 | "./node_modules/sinon/pkg/sinon.js", 5 | "./node_modules/sinon-chai/lib/sinon-chai.js", 6 | "./node_modules/sinon-chrome/bundle/sinon-chrome-apps.min.js", 7 | "extension/**/*.js", 8 | "!extension/vendor/webextension-polyfill/browser-polyfill.min.js", 9 | "!**/main.js", 10 | "tests/**/*.spec.js" 11 | ] 12 | } 13 | -------------------------------------------------------------------------------- /tests/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | mocha: true, 4 | }, 5 | globals: { 6 | chai: false, 7 | sinon: false, 8 | }, 9 | }; 10 | -------------------------------------------------------------------------------- /tests/devtools_page/panes/ZRankingTableUI.spec.js: -------------------------------------------------------------------------------- 1 | /* globals ZRankingTableUI */ 2 | 3 | /** 4 | * @typedef {HTMLElement & { textContent: string }} HTMLElementWithText 5 | */ 6 | 7 | describe('ZRankingTableUI', () => { 8 | const { expect } = chai; 9 | 10 | /** @type {ZRankingTableUI} */ 11 | let tableUi; 12 | 13 | /** @type {HTMLTableElement} */ 14 | let elTable; 15 | 16 | before(() => { 17 | elTable = document.createElement('table'); 18 | }); 19 | 20 | beforeEach(() => { 21 | tableUi = new ZRankingTableUI(); 22 | tableUi.start({ elTable }); 23 | tableUi.onSelect = sinon.spy(); 24 | }); 25 | 26 | describe('start()', () => { 27 | beforeEach(() => { 28 | tableUi.updateTable({ 29 | ranking: [ 30 | { 31 | tagName: 'div', 32 | id: 'penguin', 33 | classNames: ['foo', 'bar', 'boo'], 34 | zIndex: 1, 35 | }, 36 | ], 37 | }); 38 | const elSelector = /** @type {HTMLElement} */ (elTable.querySelector('[data-selector]')); 39 | elSelector.dispatchEvent(new Event('click', { bubbles: true })); 40 | }); 41 | 42 | it('start listening to click', () => { 43 | expect(tableUi.onSelect).to.be.callCount(1); 44 | }); 45 | }); 46 | 47 | describe('stop()', () => { 48 | beforeEach(() => { 49 | tableUi.updateTable({ 50 | ranking: [ 51 | { 52 | tagName: 'div', 53 | id: 'penguin', 54 | classNames: ['foo', 'bar', 'boo'], 55 | zIndex: 1, 56 | }, 57 | ], 58 | }); 59 | 60 | tableUi.stop(); 61 | 62 | const elSelector = /** @type {HTMLElement} */ (elTable.querySelector('[data-selector]')); 63 | elSelector.dispatchEvent(new Event('click', { bubbles: true })); 64 | }); 65 | 66 | it('stop listening to click', () => { 67 | expect(tableUi.onSelect).to.be.callCount(0); 68 | }); 69 | }); 70 | 71 | describe('updateTable', () => { 72 | beforeEach(() => { 73 | tableUi.updateTable({ 74 | ranking: [ 75 | { 76 | tagName: 'div', 77 | id: 'penguin', 78 | classNames: ['foo', 'bar', 'boo'], 79 | zIndex: 999, 80 | }, 81 | { 82 | tagName: 'span', 83 | id: '', 84 | classNames: ['hello'], 85 | zIndex: 1, 86 | }, 87 | { 88 | tagName: 'marquee', 89 | id: '', 90 | classNames: [], 91 | zIndex: -1, 92 | }, 93 | ], 94 | }); 95 | }); 96 | 97 | it('renders each row', () => { 98 | const elRowList = elTable.querySelectorAll('tbody tr'); 99 | expect(elRowList).to.have.lengthOf(3); 100 | }); 101 | 102 | it('renders a selector with ID and classes', () => { 103 | const elRowList = elTable.querySelectorAll('tbody tr'); 104 | 105 | const elZ = /** @type {HTMLElementWithText} */ (elRowList[0].querySelector('.rankingTableItem-zIndex')); 106 | expect(elZ.textContent.trim()).to.eql('999'); 107 | 108 | const elElement = /** @type {HTMLElementWithText} */ (elRowList[0].querySelector('.rankingTableItem-element')); 109 | expect(elElement.textContent.trim()).to.eql('div#penguin.foo.bar.boo'); 110 | }); 111 | 112 | it('renders a selector with only 1 class', () => { 113 | const elRowList = elTable.querySelectorAll('tbody tr'); 114 | 115 | const elZ = /** @type {HTMLElementWithText} */ (elRowList[1].querySelector('.rankingTableItem-zIndex')); 116 | expect(elZ.textContent.trim()).to.eql('1'); 117 | 118 | const elElement = /** @type {HTMLElementWithText} */ (elRowList[1].querySelector('.rankingTableItem-element')); 119 | expect(elElement.textContent.trim()).to.eql('span.hello'); 120 | }); 121 | 122 | it('renders a selector without ID and classes', () => { 123 | const elRowList = elTable.querySelectorAll('tbody tr'); 124 | 125 | const elZ = /** @type {HTMLElementWithText} */ (elRowList[2].querySelector('.rankingTableItem-zIndex')); 126 | expect(elZ.textContent.trim()).to.eql('-1'); 127 | 128 | const elElement = /** @type {HTMLElementWithText} */ (elRowList[2].querySelector('.rankingTableItem-element')); 129 | expect(elElement.textContent.trim()).to.eql('marquee'); 130 | }); 131 | 132 | it('updates rows', () => { 133 | tableUi.updateTable({ 134 | ranking: [ 135 | { 136 | tagName: 'x-link', 137 | id: '', 138 | classNames: ['extended'], 139 | zIndex: 0, 140 | }, 141 | ], 142 | }); 143 | const elRowList = elTable.querySelectorAll('tbody tr'); 144 | expect(elRowList).to.have.lengthOf(1); 145 | 146 | const el = /** @type {HTMLElementWithText} */ (elRowList[0].querySelector('.rankingTableItem-element')); 147 | expect(el.textContent.trim()).to.eql('x-link.extended'); 148 | }); 149 | 150 | it('removes all rows', () => { 151 | tableUi.updateTable({}); 152 | const elRowList = elTable.querySelectorAll('tbody tr'); 153 | expect(elRowList).to.have.lengthOf(0); 154 | }); 155 | }); 156 | 157 | describe('ZRankingTableUI.buildRanking()', () => { 158 | /** @type {HTMLIFrameElement} */ 159 | let elFrame; 160 | 161 | /** @type {Document} */ 162 | let d; 163 | 164 | /** @type {ZIndexRecord[]} */ 165 | let ranking; 166 | 167 | beforeEach(() => { 168 | elFrame = document.createElement('iframe'); 169 | elFrame.style.position = 'absolute'; 170 | elFrame.style.left = '-9999px'; 171 | elFrame.src = 'about:blank'; 172 | document.body.appendChild(elFrame); 173 | 174 | if (!elFrame.contentWindow) { 175 | throw new Error('elFrame.contentWindow not found'); 176 | } 177 | 178 | d = elFrame.contentWindow.document; 179 | 180 | const elStyle = document.createElement('style'); 181 | elStyle.innerHTML = ` 182 | #by-id { z-index: 1; } 183 | .by-class { z-index: 1; } 184 | x-by-tag-name { z-index: 1; } 185 | .auto-z-index { z-index: auto; } 186 | .zero-z-index { z-index: 0; } 187 | .minus-z-index { z-index: -1; } 188 | .lower-z-index { z-index: -10; } 189 | .high-z-index { z-index: 2; } 190 | .higher-z-index { z-index: 10; } 191 | `; 192 | d.head.appendChild(elStyle); 193 | 194 | d.body.innerHTML = ` 195 |
196 | 197 |
198 | 199 |
200 | 201 |
202 |
203 |
204 |
205 |
206 |
207 | 208 |
209 | 210 |
211 |
212 |
213 |
214 |
215 |
216 | `; 217 | 218 | ranking = ZRankingTableUI.buildRanking(d); 219 | }); 220 | 221 | afterEach(() => { 222 | document.body.removeChild(elFrame); 223 | }); 224 | 225 | it('gather elements ignoring "auto" and default', () => { 226 | expect(ranking).to.have.lengthOf(14); 227 | }); 228 | 229 | it('includes 0', () => { 230 | expect(ranking[8]).to.eql({ 231 | tagName: 'div', 232 | id: '', 233 | classNames: ['zero-z-index'], 234 | zIndex: 0, 235 | }); 236 | }); 237 | 238 | it('sorts by number, not as string', () => { 239 | expect(ranking[0]).to.eql({ 240 | tagName: 'div', 241 | id: '', 242 | classNames: ['higher-z-index'], 243 | zIndex: 10, 244 | }); 245 | expect(ranking[2]).to.eql({ 246 | tagName: 'div', 247 | id: '', 248 | classNames: ['high-z-index'], 249 | zIndex: 2, 250 | }); 251 | }); 252 | 253 | it('sorts minus', () => { 254 | expect(ranking[10]).to.eql({ 255 | tagName: 'div', 256 | id: '', 257 | classNames: ['minus-z-index'], 258 | zIndex: -1, 259 | }); 260 | expect(ranking[12]).to.eql({ 261 | tagName: 'div', 262 | id: '', 263 | classNames: ['lower-z-index'], 264 | zIndex: -10, 265 | }); 266 | }); 267 | }); 268 | }); 269 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "lib": [], /* Specify library files to be included in the compilation. */ 8 | "allowJs": true, /* Allow javascript files to be compiled. */ 9 | "checkJs": true, /* Report errors in .js files. */ 10 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 11 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 12 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 13 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 14 | // "outFile": "./", /* Concatenate and emit output to single file. */ 15 | // "outDir": "./", /* Redirect output structure to the directory. */ 16 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 17 | // "composite": true, /* Enable project compilation */ 18 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 19 | // "removeComments": true, /* Do not emit comments to output. */ 20 | "noEmit": true, /* Do not emit outputs. */ 21 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 22 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 23 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 24 | 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | 35 | /* Additional Checks */ 36 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 37 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 38 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 39 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 40 | 41 | /* Module Resolution Options */ 42 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 43 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 44 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 45 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 46 | // "typeRoots": [], /* List of folders to include type definitions from. */ 47 | // "types": [], /* Type declaration files to be included in compilation. */ 48 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 49 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 50 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 51 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 52 | 53 | /* Source Map Options */ 54 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 55 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 56 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 57 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 58 | 59 | /* Experimental Options */ 60 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 61 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /types/ZRankingTableUI.d.ts: -------------------------------------------------------------------------------- 1 | type ZIndexRecord = ReturnType< 2 | typeof ZRankingTableUI.buildRanking 3 | >[0]; 4 | --------------------------------------------------------------------------------