Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Go to firefox.com/developer. Paste the script into the console. Profit! 😎
/*
(!) Please make sure you've read the disclamer below! (!)
If you want to see how can you transform the Firefox Developer Edition landing
page into a fancy animation just
- navigate to http://firefox.com/developer *
- open the developer tools (F12)
- copy and paste this code into the console
- hit enter, and enjoy the show!
- you can follow the commentary via the console log
All the edits showcased can be (and has been initally) done manually fairly easily
with the developer tools. We figured we'll put this all together in a script
so you everyone can follow the steps, have fun and remix the Developer Edition
landing page how _they_ liked.
To read more on the new features in Dev. Edition 48, check out:
https://hacks.mozilla.org/2016/05/developer-edition-48-firebug-features-editable-storage-inspector-improvements-and-more/
* this script has been tested on Firefox 48 (Developer Edition), but should
run fine on other browsers as well, pending they support the advanced
ECMAScript 2015 features used throughout.
DISCLAIMER: PLEASE DO NOT COPY AND PASTE ARBITRARY CODE INTO YOUR DEVELOPER
TOOLS CONSOLE, NOT EVEN THIS ONE. ONLY DO THIS IF YOU TRUST THE SCRIPT AND
KNOW WHAT IT'S DOING! PASTING ARBITRARY CODE INTO THE DEVELOPER TOOLS ON
PAGES LIKE FACEBOOK MIGHT GET YOU INTO ALL SORTS OF NASTY SITUATIONS LIKE
YOU UNINTENTIONALLY SPAMMING YOUR FRIENDS WITH JUNK OR HAVING YOUR PERSONAL
DATA SUBMITTED TO THIRD PARTIES.
*/
(function() {
'use strict';
/* Some helpers for the fancy timing and console output
(not required to get this stuff working from the devtools)
*/
// change this to modify the overall speed of the presentation
// 1.0 is the default (unchanged), use 0-1.0 to speed up, >0 to slow down
// this setting doesn't affect the speed of the ending animation
this.speed = 1.25;
this.timeline = Promise.resolve();
this.then = (resolve, reject) => {
this.timeline = this.timeline.then(resolve, reject);
return this;
};
this.wait = (ms) => {
return this.then( _ =>
new Promise( resolve => setTimeout(resolve, ms*this.speed) )
);
};
this.addStyle = (css) => {
this.then( _ => new Promise(resolve => {
css = typeof css === 'function' ? css.call(this) : css;
let rules = css
.split(/\/\*\:/)
.filter( e=>e.trim().length )
.map( e=>e.split(/\*\//,2) )
.map(
e=> ({
delay: parseInt(e[0].split(/,/)[0]),
label: (e[0].split(/,/)[1]||'').trim(),
css: e[1]
})
);
if (rules && rules.length > 1) {
let rulesPromise = Promise.resolve();
rules.forEach(r => {
rulesPromise = rulesPromise.then(_ => {
if (r.label.length) {
console.log(' * ' + r.label);
}
let styletag = document.createElement('style');
styletag.type = 'text/css';
styletag.appendChild(document.createTextNode(r.css));
document.head.appendChild(styletag);
return (new Promise(ruleResolve => setTimeout(ruleResolve, r.delay*this.speed)));
});
});
resolve(rulesPromise);
} else {
let styletag = document.createElement('style');
styletag.type = 'text/css';
styletag.appendChild(document.createTextNode(css));
document.head.appendChild(styletag);
resolve(Promise.resolve());
}
}));
return this;
};
this.log = (...args) => {
if (!args.length) return;
if (args.length === 1 && typeof args[0] === 'function') {
this.then( _ => console.log( args[0].call(this) ) );
} else {
this.then( _ => console.log(...args) );
}
return this;
};
// The (non-exhaustive) list of improvements we've compiled
// from the Dev. Edition blogpost
let featureStrings =
`
Drag & Drop positioned elements|for absolute/fix positioned elements
Edit cookies & local/sessionStorage |in the DevTools Storage panel
Inspect Map & Set objects|in the inspector sidebar
Graphing GC retaining paths|in the Memory tool
DOM structure panel|ported from Firebug
Intelligent autocomplete|in the CSS properties panel
Multiline property editor|in the CSS properties panel
Updated animation inspector|in the DevTools Animation panel
New tree & aggregate views|in the Memory tool
Angle units quickswitch|in the CSS properties panel
Unregistering service workers|in about:debugging
DevTools Firebug theme|ported to the native DevTools
Extended network request logs|in the DevTools console
Improved visuals and shortcuts|in the Markup view
Add-on quick reloading|in about:debugging
Implemented console.clear()|in the DevTools console
Various visual & performance improvements|throughout the DevTools
"Create New Node" button|in the Markup view
`;
// some paced intro
this
.log('Creating a cool animation with Firefox Developer Tools').wait(2000)
.log('by hacking the Firefox Dev Edition landing page!').wait(2000)
.log('All this in just a few easy steps!').wait(2000)
.log('Oh, and check the script\'s source for the gory details! ;)')
.wait(3000)
.log('Let\'s go!')
.wait(1000);
// here we compile a list of features.
// we could just edit the source manually via the inspector, but automating
// this is always a good idea in case you have to modify some parts.
//
// you could always whip something up in the console, and just console.log
// it, you can even use copy() in Firefox to copy the contents of a variable
// or the results of a JS expression to the clipboard, e.g.:
//
// copy("1,2,3".split(/,/).join('\n'))
//
this
.log('First we need to compile a list of new features...')
.then( _ => {
this.features = featureStrings.trim()
.split(/$/m)
.map(
l => l.split('|')
)
.reduce(
(obj, pair) => {
obj.titles.push(pair[0].trim());
obj.comments.push(pair[1].trim());
return obj;
},
{ titles:[], comments:[] }
);
this.features.length = this.features.titles.length;
})
.wait(2000)
.log( _ => // deferred log message, msg text evaluated asynchronously
' | ' + this.features.titles.slice(0,4).join(', ') + '...'
)
.wait(2000)
.log( _ =>
this.features.length + '+ new features in Dev Edition 48, whoah!'
)
.wait(3000);
// here we just edit some of the styles.
//
// the CSS panel in the web inspector is a great way to start,
// but at some point you may want to copy over the results of
// your experimentation to the CSS tab into a newly created
// stylesheet.
//
this
.log('Okay, adding some CSS overrides to prepare the page...')
.addStyle(`
/*: 1500, removing unneccessary clutter */
#masthead aside {
display: none;
}
/*: 800 */
#tabzilla {
visibility: hidden;
}
/*: 800 */
#nav-main {
visibility: hidden;
}
/*: 800 */
#download-button-desktop-alpha, #download-button-desktop-alpha~* {
visibility: hidden;
}
/*: 1500, moving logo container to be fix positioned */
nav+h2 {
position: absolute;
width: 100%;
padding: 0px !important;
margin: 0px !important;
}
/*: 1000, arranging logo container */
nav+h2 {
display: flex;
align-items: center;
justify-content: center;
}
/*: 1000 */
nav+h2 {
height: 275px;
}
#wrapper {
height: 310px;
overflow: hidden;
}
/*: 1000 */
.intro {
opacity: .25;
}
/*: 1500, resizing & fine-tuning logo */
nav+h2 img {
margin-left: -32px;
width: 240px;
height: auto;
}
/*: 1000, hiding the logo for now */
nav+h2 {
opacity: 0;
}
/*: 1000 */
.intro {
opacity: 1;
}
`)
.wait(2000);
// we'll just add the feature strings as spans to the intro heading's h1/h2-s
//
// you can do this manually via the Web Inspector, or via JS
//
// did you know you could use $0 to reference the currently highlighted element
// in the Console?
// That is, you select the element in the Web Inspector, then typing
//
// $0.innerHTML = 'my text'
//
// in the console will let you change the element's content easily (without
// having to fiddle with querySelector and its friends)
//
this
.log('Inserting feature strings...')
.then( _ => {
document.querySelector('.intro.container>h2').innerHTML = this.features.comments.map(l=>`<span>${l}</span>`).join('');
})
.wait(1500)
.then( _ => {
document.querySelector('.intro.container>h1').innerHTML = this.features.titles.map(l=>`<span>${l}</span>`).join('');
})
.wait(2000);
// now that we've dumped a bunch of elements into the document, we need to
// show and hide them accordingly (only one at a time).
// we could, of course, do this via JavaScript entirely, but it's a better
// practice to have this in our CSS, declaratively.
//
// below, we prepare the CSS for that will hide all the elements, and we'll
// create some CSS rules that can selectively enable one at a time.
//
this
.log('Some CSS to handle feature strings...')
.addStyle(`
.intro.container>h1 span, .intro.container>h2 span {
display: none;
}
.intro.container[data-feature] {
opacity: 0;
}
#wrapper .intro.container h1 {
line-height: 1em;
height: 2.1em;
margin-top: -4px;
vertical-align: bottom;
display: flex;
align-items: center;
justify-content: center;
}
`)
.wait(2000);
// the problem with repetitive CSS is... well, that it's repetitive.
// we could use a preprocessor, like SASS to generate our individual rules
// responsible for showing/hiding the elements, something like:
//
// @for $i from 1 to 20 {
// .intro.container[data-feature="#{$i}"]>h1 span:nth-of-type(#{$i}),
// .intro.container[data-feature="#{$i}"]>h2 span:nth-of-type(#{$i}) { display: inline; }
// }
//
// but that's a hassle, and also, we're fiddling in the browser, which has no
// knowledge of SCSS semantics whatsoever - but we could *generate* that code,
// from, yes you guessed it right: the console, via JavaScript.
// actually, if you check the code below, it's not much different than the SCSS
// code, except maybe that it iterates our feature list (making sure we always
// have the correct number of rules generated)
//
// you could use copy() here as well, to copy the final result into a new
// stylesheet on the CSS tab.
//
function generateFeatureListCSS() {
return this.features.titles.reduce( (css, _, n) => css + `
.intro.container[data-feature="${n}"] > h1 span:nth-of-type(${n}),
.intro.container[data-feature="${n}"] > h2 span:nth-of-type(${n}) { display: inline; }
`, '');
}
this.log('Now we need to create some (rather repetitive) CSS...')
.wait(1500)
.log('...but we can just do that in the Console! ;)').wait(2000)
.log('With some creative JavaScripting:')
.wait(1000)
.log( _ =>
generateFeatureListCSS
.toString()
.split(/\r?\n/)
.map( r => ' | '+r )
.join('\n')
)
.addStyle( generateFeatureListCSS )
.wait(2000)
.log('Woohoo!')
.wait(2000)
.log('Who needs SASS when you got template strings?! :P')
.wait(3000);
// now all that remains is actually animating this stuff.
// you could go for an all declarative (pure CSS), or all-JS solution, below
// we used a hybrid approach - we add some CSS animations for the outro,
// and animate the flashing feature list via JS and the Web Animation API,
// you can find the JS for that further below.
//
this
.log('Preparing feature animation...')
.addStyle(`
@keyframes fadein {
0% { opacity: 0 }
33% { opacity: 1 }
50% { opacity: 1 }
100% { opacity: 0 }
}
@keyframes sizein {
0% { width: 36px }
33% { width: 360px }
100% { width: 320px }
}
@keyframes blurout {
0% {
-webkit-filter: blur(0px);
filter: blur(0px);
opacity: 1;
}
100% {
-webkit-filter: blur(120px);
filter: blur(120px);
opacity: 0;
}
}
@keyframes whiteout {
0% {
background-color: rgba(255, 255, 255, 0);
}
15% {
background-color: rgba(255, 255, 255, 0);
}
30% {
background-color: rgba(255, 255, 255, 1);
}
80% {
background-color: rgba(0, 0, 0, 1);
}
}
#wrapper.end-demo .intro.container h1,
#wrapper.end-demo .intro.container h2 {
animation: 3s ease-in 0s blurout;
opacity: 0;
}
#wrapper.end-demo {
animation: 6s ease 0s whiteout;
background-color: rgba(0,0,0,1);
}
#wrapper.end-demo nav+h2 {
animation: 6s ease-in 1s fadein;
}
#wrapper.end-demo nav+h2 img {
animation: 6s ease-out 1s sizein;
}
#wrapper.end-demo nav+h2:after {
content: '48';
color: white;
font-weight: 900;
font-size: 80px;
position: relative;
top: -6px;
left: 8px;
letter-spacing: -2px;
}
`)
.wait(1000)
.log('All done!')
.wait(1000)
.log('Let\'s wrap up with some Web Animation API magic! ;)')
.wait(2000)
.then(
showFeatures.bind(this, 2000, .8, 100)
);
let introContainer = document.querySelector('.intro.container');
function showFeatures(initialTime, scale, minTime) {
let f, time;
let cb = ( _ => {
if (typeof f === 'undefined') {
f = 0;
time = initialTime;
} else {
++f;
time = time<minTime? minTime : time*scale;
}
if (f < this.features.titles.length) {
this.log(' * ' + this.features.titles[f]);
flashFeature(f, time).then(cb);
} else {
if (f === this.features.titles.length) {
setTimeout(_ => document.querySelector('#wrapper').classList.add('end-demo'), 10);
}
if (f < this.features.titles.length*3) flashFeature(f%this.features.titles.length, time).then(cb);
}
});
console.log('Animating features...');
setTimeout(cb, 0);
}
function flashFeature(feature, animateTime) {
introContainer.dataset.feature = feature;
return introContainer.animate([
{ opacity: 0 },
{ opacity: 1 },
{ opacity: 0 }
], animateTime).finished;
}
}).call({});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.