Executing JavaScript to fix GUI components incorrect Nesting
-
So someone paying attention to Twitter noticed my aggravation with the GUI, specifically tabs above GUI controls (YOU should have control OVER tabs, not vice-versa!) and gave me a couple of helpful replies.
So I enabled experimental stuff:
vivaldi://experiments
"Allow for using CSS modifications"Then I enabled inspecting the browser's GUI's DOM by modifying the shortcut (I never use "pinning"):
--debug-packed-apps --silent-debugger-extension-apiOkay great! Now I can see the DOM for the GUI.
PROBLEM!
The DOM is a bit of a mess!
I could easily modify the GUI properly (simply setting the CSS property order) if I had the ability to correct the DOM.
- Where is the XML file that I can correct the DOM for Vivaldi's GUI?
- How do I execute my own JavaScript to move the elements around as a less desirable alternative?
I'm a power user and my toolbars in Waterfox Classic look like the
I maintain.
I thought about it: I may need to do both. I don't want a minimalist toolbar and having to press F4 to see buttons I should see on the primary toolbar.
Additionally I searched for the DOM in * of:
C:\Users--user--\AppData\Local\Vivaldi
C:\MEDIA\INTERNET\VivaldiSo I'm going to take a wild guess that for some reason it's encoded in a binary file. :-|
-
@jabcreations said in Executing JavaScript to fix GUI components incorrect Nesting:
Where is the XML file that I can correct the DOM for Vivaldi's GUI?
Vivaldi's UI is built using React. I don't know if it is possible to edit the React files, but I don't think I have ever seen anyone do it.
Or how do I execute my own JavaScript to move the elements around as a less desirable alternative?
This is the typical way we modify Vivaldi's UI when CSS is not enough. You can find instructions for it in the Modding Vivaldi thread (under the "Adding functionality" section).
Note: JavaScript modifications are not officially supported and will disappear after each Vivaldi update, so make sure that you back up your scripts in a location outside of the Vivaldi installation directory!
-
@komposten So I discovered that Vivaldi was installing itself to the AppData directory and had to correct that!
Once I manually corrected that I create a shortcut:
C:\MEDIA\INTERNET\Vivaldi\4.0\Application\vivaldi.exe --debug-packed-apps --silent-debugger-extension-api
So now I'm able to inspect the GUI's DOM with it running in the correct directory.
So now I modify the following file:
C:\MEDIA\INTERNET\Vivaldi\4.0\Application\4.0.2312.33\resources\vivaldi\browser.html
I add the following line in multiple places (one instance only per test):
<script src="custom.js"></script>
This contains the following code:
window.onload = function(event) { document.getElementById('main').appendChild(document.getElementById('tabs-tabbar-container')); alert('hello?!?!?'); }
The file is clearly not executing, with or without the function. So I'm clearly missing something about how it's intended to be executed. There are several JavaScript files that use pure JavaScript (no framework or library nonsense).
What am I missing?
-
So this guy had to do some excessive stuff to get it working (tested his script and it works):
https://forum.vivaldi.net/topic/56809/tabs-below-address-bar-and-bookmark-bar/7
I imagine he's waiting for the element to be added to the DOM? I'll look in to it more in-depth later though basically if that is the case I'll clean up his code and make it so people can much more easily move buttons to where they need to be.
-
Yeah, you have to wait for the element to exist before you can add stuff to it. This is how most of my scripts work:
// Run waitForTabNav() 300 ms after this JS file is loaded setTimeout(waitForTabNav, 300); function waitForTabNav() { // If tabNav() fails (returns false), wait another 300 ms if (!tabNav()) { setTimeout(waitForTabNav, 300); } } // If all buttons exist, move them, else return false function tabNav() { const tabs = document.getElementById('tabs-container'); const forward = document.querySelector('.toolbar-addressbar .button-toolbar[title~=next]'); let back = document.querySelector('.toolbar-addressbar .button-toolbar button[title~=previous]'); let reload = document.querySelector('.toolbar-addressbar .button-toolbar button[title~=Reload]'); const resize = document.querySelector('#tabs-container div.resize'); if (tabs && forward && back && reload && resize) { back = back.parentElement; reload = reload.parentElement; tabs.insertBefore(forward, resize); tabs.insertBefore(reload, forward); tabs.insertBefore(back, reload); return true; } return false; };
-
And this is what my browser.html looks like:
<!doctype html> <html> <head> <meta charset="UTF-8"> <title>Vivaldi</title> <link rel="stylesheet" href="style/common.css"> <link rel="stylesheet" href="chrome://vivaldi-data/css-mods/css"> <style> body { background-color: #d2d2d2; background-image: url('resources/vivaldi-splash-icon.svg'); background-size: 16%; background-position: center; background-repeat: no-repeat; } @media (prefers-color-scheme: dark) { body { background-color: #2d2d2d; } } </style> <link rel="stylesheet" href="style/custom.css"> </head> <body> <div id="app"></div> <script src="background-common-bundle.js"></script> <script src="vendor-bundle.js"></script> <script src="settings-bundle.js"></script> <script src="urlbar-bundle.js"></script> <script src="components-bundle.js"></script> <script src="bundle.js"></script> <!-- Custom JS mods --> <script src="style/custom.js"></script> <script src="style/search_engine_backup.js"></script> </body> </html>
-
@jabcreations If you just want to test JS mods, use console.log
-
@jabcreations Vivaldi DOM is rendered dynamically. But why do you need to change it? It is often better to use some CSS hacks.
Moving elements with JavaScript
https://code-boxx.com/move-html-element-javascript/Keep in mind, this code must be executed after #browser is created!
And, to handle showing and hiding of UI elements, you should use a mutation observer.
-
Great stuff! I love JavaScript though I do wish I knew how to directly edit the XML directly without needing to use JavaScript though having JavaScript as an option certainly opens up some possibilities!
I use Waterfox Classic and likely will for a while longer though I have a habit of opening a second browser up for things like social media sometimes, got to keep it segregated from my work! I have a ton of custom JavaScript functions on my platform that could help expedite cleaning up the GUI. We could do some sweet things like slowly transitioning the background-color using CSS transitions. But first I need text labels on the right and to move those buttons from the sidebar to the main toolbar. I don't know if Vivaldi would adopt my XML revision for their GUI so one day I'll basically hire some folks to build my own engine and the XML will be able to be customized separately. It's not difficult to do code the right way though the world is a mess, I didn't even know about new error() and the fact that it works in IE6 until late 2020 for crying out loud!
This will have to be a side project for now though as I'm trying to launch my business this week so really bad timing. Though I'm hopeful that I'll be able to pull it off with some time. The replies have been super helpful, thank you! I'll continue to post on this thread as I progress to emulate what I have in Waterfox Classic.
-
@code3 said in Executing JavaScript to fix GUI components incorrect Nesting:
@jabcreations Vivaldi DOM is rendered dynamically. But why do you need to change it? It is often better to use some CSS hacks.
Moving elements with JavaScript
https://code-boxx.com/move-html-element-javascript/Keep in mind, this code must be executed after #browser is created!
And, to handle showing and hiding of UI elements, you should use a mutation observer.
I've developed my own web platform from scratch: I don't do hacks, I do quality. You'll find position: absolute; on 90% of all elements on every single WordPress theme in example because of how poorly it's coded. Vivaldi's HTML isn't terrible though there are some glaring issues that prevent a natural ease of use via CSS. That is the point of fixing the element nesting: I'm not going to simply build this script for myself! In the beginning I will of course though it's better to be able to have people help collaborate with you. If you inspect the DOM on my site's default XML template you'll find it's ridiculously optimized so my customers can easily change things like flex order instead of making a mess with position: absolute. I love Waterfox Classic though the XML for the GUI is absolutely atrocious! Fix with hacks? No, it's BUILT with hacks! The world doesn't need more poor quality anythings.
-
So I finally had a few minutes to work on improving the existing code and moving the panel buttons to the Main toolbar where they belong. Now my posting this is more of a save point as there is more work to be done. The HTML used isn't very consistent (though not as terrible as Mozilla's) and the buttons need some CSS to fix the oddball horizontal and/or vertical positioning.
That being the buttons get moved and in an alphabetical order. I could have stuck the buttons moving in to a loop though I suspect that would have only complicated things and this is running locally on the computer so no need to optimize for downloads. I'll continue to work on this here and there. There aren't even text labels being added yet so meh. I plan on creating an easy to edit array for options so people can quickly edit a few things (e.g. do or do not want text labels).
Here is the code thus far:
//Use this to highlight something to ensure you're targeting the correct element: //$('[data-id="bookmarks"]')[0].style.backgroundColor = '#f0f'; //Common JavaScript functions to ease development; imported from http://www.jabcreations.com/ function $(o) { var a = true; try {document.querySelectorAll(o);} catch(err) {a = false; error_report(new Error('The parameter "'+o+'" is not a valid CSS selector.')); sound.notice();} return (a && document.querySelectorAll && document.querySelectorAll(o)) ? document.querySelectorAll(o) : false; } function class_(c) { var r = false; if (typeof c != 'string') {error_report(new Error('Can not change class; the parameter \''+((typeof c == 'string') ? c : c.toString())+'\' is not valid.'));} else if (document.getElementsByClassName(c)) {r = document.getElementsByClassName(c);} return r; } function id_(id) { var r = false; if (typeof id != 'string') {error_report(new Error('The id_ parameter "'+id.toString()+'" is not a string.'));} else if (id.length == 0) {error_report(new Error('The id_ parameter was an empty string.'));} else if (document.getElementById(id)) {r = document.getElementById(id);} return r; } /**************** ****************/ function toolbar_tabs_move() { const toolbar_tabs = document.getElementById('tabs-tabbar-container'); const toolbar_address = document.querySelector('.UrlBar'); const toolbar_bookmarks = document.querySelector('.bookmark-bar'); const insertAfter = toolbar_bookmarks ? toolbar_bookmarks : toolbar_address; if (toolbar_tabs && insertAfter) { toolbar_tabs.remove(); insertAfter.insertAdjacentElement('afterend', toolbar_tabs); } //Change backgroundColor: var toolbar_main = class_('toolbar toolbar-droptarget toolbar-mainbar toolbar-large')[0];//.style.backgroundColor = '#f0f'; //Move Bookmarks button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="bookmarks"]')[0]); toolbar_main.appendChild(d); //Move Calendar button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="calendar"]')[0]); toolbar_main.appendChild(d); //Move Contacts button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="contacts"]')[0]); toolbar_main.appendChild(d); //Move Downloads button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="downloads"]')[0]); toolbar_main.appendChild(d); //Move Feeds button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="feeds"]')[0]); toolbar_main.appendChild(d); //Move History button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="history"]')[0]); toolbar_main.appendChild(d); //Move Mail button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="mail"]')[0]); toolbar_main.appendChild(d); //Move Mail Status button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[class*="MailStatusButton"]')[0]); toolbar_main.appendChild(d); //Move Notes button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="notes"]')[0]); toolbar_main.appendChild(d); //Move Profiles button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[class*="profile-popup"]')[0]); toolbar_main.appendChild(d); //Move Tabs button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="tabs"]')[0]); toolbar_main.appendChild(d); //Hide useless F4 buttons panel: id_('switch').style.display = 'none'; } window.onload = function(event) { let interval_init = setInterval(() => { const browser = document.getElementById('browser'); if (browser) {clearInterval(interval_init); toolbar_tabs_move();} }, 300); }
-
After roughly three hours I've made huge success in cleaning up the GUI!
Click for full non-resized image:
Improvements:
- Fixed the toolbars for power users not interested in arthritis, not masochist minimalists.
- Moved most if not all toolbar buttons to the Main toolbar.
- Added Italics effect for hover state to be more compatible with various themes though if this finds parity with Waterfox Classic I'll likely create a Black Theme and not focus on compatibility.
- Manually cleaned up many buttons to better center icons (see my previous post above to see how non-consistent they were).
- Fixed some button text that broke to a second line.
- Set fixed button width to 100px to make the buttons appear less "choppy".
- Centered the Page Title with the (full) File menu.
- All toolbar buttons still have their full functionality.
- Moved the Address bar on to the Bookmarks toolbar with bookmarks to the right.
- Fixed Search panel to move the down button to the right.
- Tabs are of course the fourth / bottom toolbar as the content of tabs do NOT have control over YOUR browsing sessions.
Current Issues (4.0.2312.33 · Stable · x64):
- People with different screen resolutions (or who don't maximize their browsers because they hate web designers) might need to make some adjustments such as the Page Title
margin-left
bit (I use 2560x1440 with my current setup). - Settings ⇨ Address Bar ⇨ Show Full Address does NOT work!
- The Address Bar does NOT have a Go button!
- The Search Bar does NOT show the search engine icon (only displayed drop down)!
- The F4 panel still appears after closing the panel!
- Browser lacks a Dark Mode theme (e.g. default background-color should be black, not gray).
Fresh Install:
This requires two parts: the CSS and JavaScript bits. I've commented in both sets of code to make it easier to find items and tweak them (e.g. you don't want some, most or all text labels on buttons).
CSS:
The following code is ADDED to the existing file located at:
C:\MEDIA\INTERNET\Vivaldi\4.0\Application\4.0.2312.33\resources\vivaldi\style\common.css
/*** Toolbar 1: Centers Page Title, don't use below screen resolution/maximized window size of 1152. ***/ .horizontal-menu-pagetitle {margin-left: -375px !important;} /*** Toolbar 2: Create more consistent button layout for Main buttons: ***/ .toolbar-mainbar .toolbar-mainbar .button-toolbar {width: 100px;} .button-toolbar > button {padding-right: 4px;} .button-toolbar > button + span {white-space: nowrap;} .button-toolbar > button > span + span:nth-of-type(2) {padding-right: 4px;}/* background-color: #f0f;*/ .button-toolbar:hover > button > span:nth-last-of-type(1) {font-style: italic;} /*** Toolbar 2: Search Field relative center (2560 screen; adjust accordingly): ***/ .UrlBar-SearchField {margin-left: 10%;}
JavaScript:
Step 1/4:Find the following file (your install is likely in the Program Files garbage dump folder if on Windows):
C:\MEDIA\INTERNET\Vivaldi\4.0\Application\4.0.2312.33\resources\vivaldi\browser.html
Step 2/4:
After this line:
<div id="app" />
Add the following line:
<script src="custom.js"></script>
Step 3/4:
The following code is CREATED in a NEW FILE located at:
C:\MEDIA\INTERNET\Vivaldi\4.0\Application\4.0.2312.33\resources\vivaldi\custom.js
Step 4/4:
Add the following to the file you just created in step 3://Use this to highlight something to ensure you're targeting the correct element: //$('[data-id="bookmarks"]')[0].style.backgroundColor = '#f0f'; function $(o) { var a = true; try {document.querySelectorAll(o);} catch(err) {a = false; error_report(new Error('The parameter "'+o+'" is not a valid CSS selector.')); sound.notice();} return (a && document.querySelectorAll && document.querySelectorAll(o)) ? document.querySelectorAll(o) : false; } function class_(c) { var r = false; if (typeof c != 'string') {error_report(new Error('Can not change class; the parameter \''+((typeof c == 'string') ? c : c.toString())+'\' is not valid.'));} else if (document.getElementsByClassName(c)) {r = document.getElementsByClassName(c);} return r; } function id_(id) { var r = false; if (typeof id != 'string') {error_report(new Error('The id_ parameter "'+id.toString()+'" is not a string.'));} else if (id.length == 0) {error_report(new Error('The id_ parameter was an empty string.'));} else if (document.getElementById(id)) {r = document.getElementById(id);} return r; } /**************** ****************/ function gui_fixes() { const toolbar_tabs = document.getElementById('tabs-tabbar-container'); const toolbar_address = document.querySelector('.UrlBar'); const toolbar_bookmarks = document.querySelector('.bookmark-bar'); const insertAfter = toolbar_bookmarks ? toolbar_bookmarks : toolbar_address; if (toolbar_tabs && insertAfter) { toolbar_tabs.remove(); insertAfter.insertAdjacentElement('afterend', toolbar_tabs); } //Add text label for Back Button: var d = document.createElement('span'); d.appendChild(document.createTextNode('Back')); $('[title^="Go to previous"]')[0].getElementsByTagName('button')[0].appendChild(d); //Add text label for Back Button: var d = document.createElement('span'); d.appendChild(document.createTextNode('Forward')); $('[title^="Go to next"]')[0].getElementsByTagName('button')[0].appendChild(d); //Add text label for Reload Button: var d = document.createElement('span'); d.appendChild(document.createTextNode('Reload')); $('[title^="Reload"]')[0].appendChild(d); //Add text label for Home Button: var d = document.createElement('span'); d.appendChild(document.createTextNode('Home')); $('[title="Go to homepage"]')[0].appendChild(d); //Move Address bar to Bookmarks Toolbar: class_('bookmark-bar default')[0].insertBefore(class_('UrlBar-AddressField')[0], class_('bookmark-bar default')[0].firstChild) class_('UrlBar-AddressField')[0].style.minWidth = '800px'; //Main Toolbar: var toolbar_main = class_('toolbar toolbar-droptarget toolbar-mainbar toolbar-large')[0]; //Move Bookmarks button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="bookmarks"]')[0]); toolbar_main.appendChild(d); $('[data-id="bookmarks"]')[0].getElementsByTagName('svg')[0].style.marginTop = '12px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Bookmarks')); $('[data-id="bookmarks"]')[0].appendChild(d); //Move Calendar button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="calendar"]')[0]); toolbar_main.appendChild(d); $('[data-id="calendar"]')[0].getElementsByTagName('svg')[0].style.margin = '12px 0 0 8px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Calendar')); $('[data-id="calendar"]')[0].appendChild(d); //Move Contacts button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="contacts"]')[0]); toolbar_main.appendChild(d); var d = document.createElement('span'); d.appendChild(document.createTextNode('Contacts')); $('[data-id="contacts"]')[0].appendChild(d); //Move Downloads button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="downloads"]')[0]); toolbar_main.appendChild(d); $('[data-id="downloads"]')[0].getElementsByTagName('svg')[0].style.margin = '12px 0 0 8px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Downloads')); $('[data-id="downloads"]')[0].insertBefore(d, $('[data-id="downloads"]')[0].getElementsByTagName('div')[0]) //$('[data-id="downloads"]')[0].appendChild(d); //toolbar_main.appendChild(d); //Move Feeds button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="feeds"]')[0]); toolbar_main.appendChild(d); var d = document.createElement('span'); d.appendChild(document.createTextNode('Feeds')); $('[data-id="feeds"]')[0].appendChild(d); //Move History button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="history"]')[0]); toolbar_main.appendChild(d); $('[data-id="history"]')[0].getElementsByTagName('svg')[0].style.margin = '8px 0 0 8px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('History')); $('[data-id="history"]')[0].appendChild(d); //Move Mail button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="mail"]')[0]); toolbar_main.appendChild(d); $('[data-id="mail"]')[0].getElementsByTagName('svg')[0].style.margin = '8px 0 0 0'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Mail')); $('[data-id="mail"]')[0].appendChild(d); //Move Mail Status button: var d1 = document.createElement('div'); d1.setAttribute('class','button-toolbar'); d1.appendChild($('[class*="MailStatusButton"]')[0].firstChild); var d2 = document.createElement('span'); d2.appendChild(document.createTextNode('Mail Status')); d1.getElementsByTagName('button')[0].appendChild(d2); toolbar_main.appendChild(d1); //Move New Tab button: var d1 = document.createElement('div'); d1.setAttribute('class','button-toolbar'); d1.appendChild($('[class*="newtab"]')[0].firstChild); var d2 = document.createElement('span'); d2.appendChild(document.createTextNode('New Tab')); d1.getElementsByTagName('button')[0].appendChild(d2); toolbar_main.appendChild(d1); //Move Notes button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="notes"]')[0]); toolbar_main.appendChild(d); $('[data-id="notes"]')[0].getElementsByTagName('svg')[0].style.margin = '4px 0 0 4px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Notes')); $('[data-id="notes"]')[0].appendChild(d); //Move Profiles button: var d1 = document.createElement('div'); d1.setAttribute('class','button-toolbar'); d1.appendChild($('[class*="profile-popup"]')[0].getElementsByTagName('button')[0]); var d2 = document.createElement('span'); d2.appendChild(document.createTextNode('Profiles')); d1.getElementsByTagName('button')[0].appendChild(d2); toolbar_main.appendChild(d1); //Move Settings button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[class="preferences panelbtn"]')[0]); toolbar_main.appendChild(d); var d = document.createElement('span'); d.appendChild(document.createTextNode('Settings')); $('[class="preferences panelbtn"]')[0].appendChild(d); //Move Tabs button: var d = document.createElement('div'); d.setAttribute('class','button-toolbar'); d.appendChild($('[data-id="tabs"]')[0]); toolbar_main.appendChild(d); $('[data-id="tabs"]')[0].getElementsByTagName('svg')[0].style.margin = '8px 0 0 8px'; var d = document.createElement('span'); d.appendChild(document.createTextNode('Tabs')); $('[data-id="tabs"]')[0].appendChild(d); //Fix Search field select menu: $('[class*="UrlBar-SearchField"]')[0].appendChild($('[class*="SearchEngineSelect-Icon"]')[0]); //Fix sidebar not using 100% width: class_('panel-collapse-guard')[0].style.maxWidth = '100%'; } window.onload = function(event) { let interval_init = setInterval(() => { const browser = document.getElementById('browser'); if (browser) {clearInterval(interval_init); gui_fixes();} }, 50); }
This really cleans up the GUI and makes the buttons not only one-click accessible though large enough that you don't have to pause your primary thought process (e.g. whatever you're working on) to try and click on a small button. I don't feel you should have to click twice to do what you should be able to do in a single click. I also feel that buttons shouldn't be splashed all over the place all willy-nilly.
Even if someone loathes this GUI layout I hope the cleaned up code makes it much easier for others to fix and modify the GUI to meet their needs. My primary browser is Waterfox Classic and I actually intend to create my own browser one day that won't need manual fixes to the DOM to customize regardless whether you're a power user or masochist minimalist who hates not having arthritis or getting any work done.
For now this will allow me to replace Firefox as my secondary browser since I'll actually have control over things.
If the following things were added to Vivaldi I'd swap Vivaldi and Waterfox Classic as primary and secondary browsers:
- Go button.
- Paste & Go button.
- Paste & Search button.
- Fix the Address Bar not showing the full address!
- Fully hide the sidebar when it's not open.
- Active search engine's favicon on the left side of the Search bar.
- Search button for the Search bar (not the two-click stuff).
- A button per each web developer tool (e.g. Console, Elements, Network).