Skip to content

Instantly share code, notes, and snippets.

@paulrouget
Created March 21, 2012 09:38
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save paulrouget/2145830 to your computer and use it in GitHub Desktop.
Save paulrouget/2145830 to your computer and use it in GitHub Desktop.
Firefox Magnifier
/*
TODO:
- zoom level menu
- need to find a way to re-start the update
- add color tools
- integrate better in Firefox
- crosshair has a 1px offset
*/
function Magnifier(aWindow)
{
this.chromeWin = aWindow;
this.tabbrowser = aWindow.gBrowser;
this.chromeDoc = aWindow.document;
// Bind!!!
this.onPopupShown = this.onPopupShown.bind(this);
this.onPopupHiding = this.onPopupHiding.bind(this);
this.onButtonClick = this.onButtonClick.bind(this);
this.update = this.update.bind(this);
this.onMouseMove = this.onMouseMove.bind(this);
this.onMouseClick = this.onMouseClick.bind(this);
this.onTabSelect = this.onTabSelect.bind(this);
this.onGridClicked = this.onGridClicked.bind(this);
this.onGridKeyPressed = this.onGridKeyPressed.bind(this);
this.isOpen = false;
this._init();
}
Magnifier.prototype = {
_init: function magnifier__init() {
this.zoomWindow = {
x: 0,
y: 0,
cx: null,
cy: null,
width: 304 / 8 + 1,
height: 304 / 8 + 1,
zoom: 8,
}
this.buildButton();
this.buildPanel();
this.buildEyeDropper();
this.tabbrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect, false);
},
destroy: function magnifier_destroy() {
this.panel.hidePopup();
this.panel.parentNode.removeChild(this.panel);
this.button.parentNode.removeChild(this.button);
},
setupZoom: function magnifier_setupZoom() {
this.canvas.width = this.zoomWindow.width;
this.canvas.height = this.zoomWindow.height;
let csswidth = (this.zoomWindow.width * this.zoomWindow.zoom) + "px";
let cssheight = (this.zoomWindow.height * this.zoomWindow.zoom) + "px";
this.canvas.style.width = this.grid.style.width = csswidth;
this.canvas.style.height = this.grid.style.height = cssheight;
this.grid.style.backgroundSize = this.zoomWindow.zoom + "px " + this.zoomWindow.zoom + "px";
this.crosshair.style.height = this.crosshair.style.width = (this.zoomWindow.zoom - 1) + "px";
this.moveCrosshair(this.zoomWindow.width / 2, this.zoomWindow.height / 2);
},
moveCrosshair: function magnifier_moveCrosshair(x, y) {
this.zoomWindow.cx = ~~x;
this.zoomWindow.cy = ~~y;
this.crosshair.style.left = (this.zoomWindow.zoom * this.zoomWindow.cx) + "px";
this.crosshair.style.top = (this.zoomWindow.zoom * this.zoomWindow.cy) + "px";
},
/* ---------- UI builders ---------- */
buildEyeDropper: function magnifier_eyeDropper() {
let hbox = this.chromeDoc.createElement("hbox");
this.colorbox = this.chromeDoc.createElement("box");
this.colorbox.setAttribute("style", "width: 24px; border: 1px solid white");
this.colortext = this.chromeDoc.createElement("textbox");
this.colortext.className = "devtools-searchinput";
hbox.appendChild(this.colorbox);
hbox.appendChild(this.colortext);
this.panel.appendChild(hbox);
},
buildPanel: function magnifier_buildPanel() {
this.panel = this.chromeDoc.createElement("panel");
this.panel.id = "devtools-magnifier-panel";
this.panel.setAttribute("noautofocus", true);
this.panel.setAttribute("noautohide", true);
//this.panel.setAttribute("backdrag", true);
this.panel.setAttribute("level", "floating");
this.panel.setAttribute("style", (<r><![CDATA[
border-radius: 4px;
-moz-appearance: none;
background: #485665;
padding: 10px;
]]></r>)+"");
//this.panel.setAttribute("type", "arrow");
this.panel.addEventListener("popupshown", this.onPopupShown, true);
this.panel.addEventListener("popuphiding", this.onPopupHiding, true);
this.chromeDoc.querySelector("#mainPopupSet").appendChild(this.panel);
let box = this.chromeDoc.createElement("box");
box.setAttribute("style", "display:block; position: relative");
this.panel.appendChild(box);
let XHTML = "http://www.w3.org/1999/xhtml";
this.canvas = this.chromeDoc.createElementNS(XHTML, "canvas");
this.ctx = this.canvas.getContext("2d");
this.canvas.setAttribute("style", "image-rendering:-moz-crisp-edges;background-color:purple");
box.appendChild(this.canvas);
this.grid = this.chromeDoc.createElement("box");
this.grid.setAttribute("style", (<r><![CDATA[
display: block;
position: absolute;
top: 0; left: 0;
]]></r>)+"");
box.appendChild(this.grid);
this.grid.addEventListener("click", this.onGridClicked, true);
//this.panel.addEventListener("keypress", function(){alert(1)}, true); /* TODO: doesn't work */
this.crosshair = this.chromeDoc.createElement("box");
this.crosshair.setAttribute("style", (<r><![CDATA[
display: block;
position: absolute;
border-width: 2px;
border-style: solid;
-moz-box-sizing: content-box;
margin-left: -1px;
margin-top: -1px;
pointer-events: none;
]]></r>)+"");
box.appendChild(this.crosshair);
this.setupZoom();
},
updateGridColor: function magnifier_updateGridColor() {
let g = this.color.r + this.color.g + this.color.b;
g = g / 3;
let c = g < 128 ? "rgba(255,255,255," : "rgba(0,0,0,";
this.crosshair.style.borderColor = c + "0.5)";
if (this.lastGridColor && this.lastGridColor == c) return;
this.chromeWin.clearTimeout(this.gridColorTimeout);
this.lastGridColor = c;
let self = this;
this.gridColorTimeout = this.chromeWin.setTimeout(function() {
self.grid.style.backgroundImage = "-moz-linear-gradient(left, " + c +"0.15) 1px, transparent 1px)," +
"-moz-linear-gradient(top, " + c + "0.15) 1px, transparent 1px)";
}, 250);
},
buildButton: function magnifier_buildButton() {
let button = this.chromeDoc.createElement("toolbarbutton");
button.id = "devtools-magnifier-button";
button.className = "chromeclass-toolbar-additional";
button.setAttribute("style", "list-style-image: url(http://i.imgur.com/4kwxV.png);");
button.onclick = this.onButtonClick;
this.chromeDoc.querySelector("#urlbar").appendChild(button);
return this.button = button;
},
/* ---------- Content copy ---------- */
startRenderingLoop: function() {
this.isRendering = true;
this.update();
},
stopRenderingLoop: function() {
this.isRendering = false;
},
update: function magnifier_update() {
let win = this.tabbrowser.selectedBrowser.contentWindow;
if (win) { // Why do I need to do that?
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
this.ctx.drawWindow(win,
this.zoomWindow.x + win.scrollX, this.zoomWindow.y + win.scrollY,
this.zoomWindow.width, this.zoomWindow.height, "white");
}
if (this.isRendering) this.chromeWin.mozRequestAnimationFrame(this.update);
this.updateColor();
},
updateColor: function magnifier_updateColor() {
let pixel = this.ctx.getImageData(this.zoomWindow.cx, this.zoomWindow.cy, 1, 1).data;
let r = ~~pixel[0];
let g = ~~pixel[1];
let b = ~~pixel[2];
this.color = {r:r, g:g, b:b};
this.colorName = "rgb(" + r + "," + g + "," + b + ")";
this.colorbox.style.backgroundColor = this.colorName;
this.colortext.value = this.colorName;
this.updateGridColor();
},
/* ---------- Events and callbacks ---------- */
attachPageEvents: function magnifier_attachPageEvents() {
let browser = this.tabbrowser.selectedBrowser;
browser.addEventListener("mousemove", this.onMouseMove, true);
browser.addEventListener("click", this.onMouseClick, true);
},
deattachPageEvents: function magnifier_deattachPageEvents() {
let browser = this.tabbrowser.selectedBrowser;
browser.removeEventListener("mousemove", this.onMouseMove, true);
browser.removeEventListener("click", this.onMouseClick, true);
},
onPopupShown: function magnifier_onPopupShown() {
this.isOpen = true;
this.startRenderingLoop();
this.attachPageEvents();
},
onPopupHiding: function magnifier_onPopupHiding() {
this.isOpen = false;
this.stopRenderingLoop();
this.deattachPageEvents();
},
onButtonClick: function magnifier_onButtonClick() {
if (this.isOpen) {
this.panel.hidePopup();
} else {
this.panel.openPopup();
}
},
onTabSelect: function magnifier_onTabSelect() {
if (this.isOpen)
this.panel.hidePopup();
},
onMouseMove: function magnifier_onMouseMove(e) {
this.zoomWindow.x = e.clientX - this.zoomWindow.width / 2;
this.zoomWindow.y = e.clientY - this.zoomWindow.height / 2;
},
onMouseClick: function magnifier_onMouseClick(e) {
if (!this.isRendering) return;
this.stopRenderingLoop();
e.preventDefault();
e.stopPropagation();
},
onGridClicked: function magnifier_onGridClicked(e) {
if (this.isRendering) return;
let x = (~~e.clientX - this.grid.parentNode.boxObject.x) / this.zoomWindow.zoom;
let y = (~~e.clientY - this.grid.parentNode.boxObject.y) / this.zoomWindow.zoom;
this.moveCrosshair(x, y);
this.updateColor();
},
onGridKeyPressed: function magnifier_onGridKeyPressed(e) {
if (this.isRendering) return;
alert(1);
switch (e.keyCode) {
case this.chromeWin.KeyEvent.DOM_VK_LEFT:
if (this.zoomWindow.cx)
this.moveCrosshair(this.zoomWindow.cx--, this.zoomWindow.cy);
break;
case this.chromeWin.KeyEvent.DOM_VK_RIGHT:
if (this.zoomWindow.cx < this.zoomWindow.width)
this.moveCrosshair(this.zoomWindow.cx++, this.zoomWindow.cy);
break;
case this.chromeWin.KeyEvent.DOM_VK_UP:
if (this.zoomWindow.cy)
this.moveCrosshair(this.zoomWindow.cx, this.zoomWindow.cy--);
break;
case this.chromeWin.KeyEvent.DOM_VK_DOWN:
if (this.zoomWindow.cy < this.zoomWindow.height)
this.moveCrosshair(this.zoomWindow.cx, this.zoomWindow.cy++);
break;
}
this.updateColor();
},
}
// main()
try{if (window.magnifier) window.magnifier.destroy();}catch(e){}
window.magnifier = new Magnifier(window);
@grssam
Copy link

grssam commented Mar 27, 2012

It would be really nice to have an add-on for this. I am converting this gist into a restart-less add-on. Will take a couple of days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment