├── .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 | [](https://travis-ci.org/ginpei/devtools-z-index) [](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 | [](https://chrome.google.com/webstore/detail/bcnpmhefiohkpmjacfoanhbjhikegmoe/)
14 | [](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 | 
29 |
30 | 
31 |
32 | ## Firefox add-on
33 |
34 | https://addons.mozilla.org/en-US/firefox/addon/devtools-z-index/
35 |
36 | 
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 | z-index |
14 | Element |
15 |
16 |
17 |
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 |
--------------------------------------------------------------------------------