Bug 1040653 - Make eyedropper work with e10s. r=mratcliffe

This commit is contained in:
Heather Arthur 2014-11-07 16:46:00 +01:00
parent a63486fa3f
commit c2a7709c61
9 changed files with 280 additions and 93 deletions

View File

@ -0,0 +1,20 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
addMessageListener("Eyedropper:RequestContentScreenshot", sendContentScreenshot);
function sendContentScreenshot() {
let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
let width = content.innerWidth;
let height = content.innerHeight;
canvas.width = width;
canvas.height = height;
canvas.mozOpaque = true;
let ctx = canvas.getContext("2d");
ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
sendAsyncMessage("Eyedropper:Screenshot", canvas.toDataURL());
}

View File

@ -5,6 +5,7 @@
const {Cc, Ci, Cu} = require("chrome");
const {rgbToHsl} = require("devtools/css-color").colorUtils;
const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js");
const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
Cu.import("resource://gre/modules/Services.jsm");
@ -112,6 +113,8 @@ function Eyedropper(chromeWindow, opts = { copyOnSelect: true }) {
this._chromeWindow = chromeWindow;
this._chromeDocument = chromeWindow.document;
this._OS = XULRuntime.OS;
this._dragging = true;
this.loaded = false;
@ -126,6 +129,10 @@ function Eyedropper(chromeWindow, opts = { copyOnSelect: true }) {
width: CANVAS_WIDTH, // width of canvas to draw zoomed area onto
height: CANVAS_WIDTH // height of canvas
};
let mm = this._contentTab.linkedBrowser.messageManager;
mm.loadFrameScript("resource:///modules/devtools/eyedropper/eyedropper-child.js", true);
EventEmitter.decorate(this);
}
@ -167,6 +174,30 @@ Eyedropper.prototype = {
return rgb;
},
get _contentTab() {
return this._chromeWindow.gBrowser.selectedTab;
},
/**
* Fetch a screenshot of the content.
*
* @return {promise}
* Promise that resolves with the screenshot as a dataURL
*/
getContentScreenshot: function() {
let deferred = promise.defer();
let mm = this._contentTab.linkedBrowser.messageManager;
function onScreenshot(message) {
mm.removeMessageListener("Eyedropper:Screenshot", onScreenshot);
deferred.resolve(message.data);
}
mm.addMessageListener("Eyedropper:Screenshot", onScreenshot);
mm.sendAsyncMessage("Eyedropper:RequestContentScreenshot");
return deferred.promise;
},
/**
* Start the eyedropper. Add listeners for a mouse move in the window to
* show the eyedropper.
@ -174,15 +205,31 @@ Eyedropper.prototype = {
open: function() {
if (this.isOpen) {
// the eyedropper is aready open, don't create another panel.
return;
return promise.resolve();
}
let deferred = promise.defer();
this.isOpen = true;
this._OS = XULRuntime.OS;
this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
this._showCrosshairs();
// Get screenshot of content so we can inspect colors
this.getContentScreenshot().then((dataURL) => {
this._contentImage = new this._chromeWindow.Image();
this._contentImage.src = dataURL;
// Wait for screenshot to load
this._contentImage.onload = () => {
// Then start showing the eyedropper UI
this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
deferred.resolve();
this.isStarted = true;
this.emit("started");
}
});
return deferred.promise;
},
/**
@ -209,6 +256,25 @@ Eyedropper.prototype = {
this._hideCursor();
},
/**
* Whether the coordinates are over the content or chrome.
*
* @param {number} clientX
* x-coordinate of mouse relative to browser window.
* @param {number} clientY
* y-coordinate of mouse relative to browser window.
*/
_isInContent: function(clientX, clientY) {
let box = this._contentTab.linkedBrowser.getBoundingClientRect();
if (clientX > box.left &&
clientX < box.right &&
clientY > box.top &&
clientY < box.bottom) {
return true;
}
return false;
},
/**
* Set the current coordinates to inspect from where a mousemove originated.
*
@ -216,21 +282,23 @@ Eyedropper.prototype = {
* Event for the mouse move.
*/
_setCoordinates: function(event) {
let inContent = this._isInContent(event.clientX, event.clientY);
let win = this._chromeWindow;
let x, y;
if (this._OS == "Linux") {
// event.clientX is off on Linux, so calculate it by hand
let windowX = win.screenX + (win.outerWidth - win.innerWidth);
x = event.screenX - windowX;
// offset of mouse from browser window
let x = event.clientX;
let y = event.clientY;
let windowY = win.screenY + (win.outerHeight - win.innerHeight);
y = event.screenY - windowY;
}
else {
x = event.clientX;
y = event.clientY;
if (inContent) {
// calculate the offset of the mouse from the content window
let box = this._contentTab.linkedBrowser.getBoundingClientRect();
x = x - box.left;
y = y - box.top;
this._zoomArea.contentWidth = box.width;
this._zoomArea.contentHeight = box.height;
}
this._zoomArea.inContent = inContent;
// don't let it inspect outside the browser window
x = Math.max(0, Math.min(x, win.outerWidth - 1));
@ -548,29 +616,66 @@ Eyedropper.prototype = {
* Draw the inspected area onto the canvas using the zoom level.
*/
_drawWindow: function() {
let { width, height, x, y } = this._zoomArea;
let { width, height, x, y, inContent,
contentWidth, contentHeight } = this._zoomArea;
let zoomedWidth = width / this.zoom;
let zoomedHeight = height / this.zoom;
let drawX = x - (zoomedWidth / 2);
let drawY = y - (zoomedHeight / 2);
let leftX = x - (zoomedWidth / 2);
let topY = y - (zoomedHeight / 2);
// draw the portion of the window we're inspecting
this._ctx.drawWindow(this._chromeWindow, drawX, drawY, zoomedWidth,
zoomedHeight, "white");
if (inContent) {
// draw from content source image "s" to destination rect "d"
let sx = leftX;
let sy = topY;
let sw = zoomedWidth;
let sh = zoomedHeight;
let dx = 0;
let dy = 0;
// we're at the content edge, so we have to crop the drawing
if (leftX < 0) {
sx = 0;
sw = zoomedWidth + leftX;
dx = -leftX;
}
else if (leftX + zoomedWidth > contentWidth) {
sw = contentWidth - leftX;
}
if (topY < 0) {
sy = 0;
sh = zoomedHeight + topY;
dy = -topY;
}
else if (topY + zoomedHeight > contentHeight) {
sh = contentHeight - topY;
}
let dw = sw;
let dh = sh;
// we don't want artifacts when we're inspecting the edges of content
if (leftX < 0 || topY < 0 ||
leftX + zoomedWidth > contentWidth ||
topY + zoomedHeight > contentHeight) {
this._ctx.fillStyle = "white";
this._ctx.fillRect(0, 0, width, height);
}
// draw from the screenshot to the eyedropper canvas
this._ctx.drawImage(this._contentImage, sx, sy, sw,
sh, dx, dy, dw, dh);
}
else {
// the mouse is over the chrome, so draw that instead of the content
this._ctx.drawWindow(this._chromeWindow, leftX, topY, zoomedWidth,
zoomedHeight, "white");
}
// now scale it
let sx = 0;
let sy = 0;
let sw = zoomedWidth;
let sh = zoomedHeight;
let dx = 0;
let dy = 0;
let dw = width;
let dh = height;
this._ctx.drawImage(this._canvas, sx, sy, sw, sh, dx, dy, dw, dh);
this._ctx.drawImage(this._canvas, 0, 0, zoomedWidth, zoomedHeight,
0, 0, width, height);
let rgb = this.centerColor;
this._colorPreview.style.backgroundColor = toColorString(rgb, "rgb");
@ -635,6 +740,7 @@ Eyedropper.prototype = {
this._removePanelListeners();
this._removeListeners();
this.isStarted = false;
this.isOpen = false;
this._isSelecting = false;

View File

@ -6,6 +6,7 @@
EXTRA_JS_MODULES.devtools.eyedropper += [
'commands.js',
'eyedropper-child.js',
'eyedropper.js'
]

View File

@ -1,5 +1,4 @@
[DEFAULT]
skip-if = e10s # Bug ?????? - devtools tests disabled with e10s
subsuite = devtools
support-files =
color-block.html

View File

@ -10,53 +10,69 @@ const DIV_COLOR = "#0000FF";
* - Opening eyedropper and pressing ESC closes the eyedropper
* - Opening eyedropper and clicking copies the center color
*/
function test() {
addTab(TESTCASE_URI).then(testEscape);
}
let test = asyncTest(function*() {
yield addTab(TESTCASE_URI);
function testEscape() {
info("added tab");
yield testEscape();
info("testing selecting a color");
yield testSelect();
});
function* testEscape() {
let dropper = new Eyedropper(window);
dropper.once("destroy", (event) => {
ok(true, "escape closed the eyedropper");
yield inspectPage(dropper, false);
// now test selecting a color
testSelect();
});
let destroyed = dropper.once("destroy");
pressESC();
yield destroyed;
inspectPage(dropper, false).then(pressESC);
ok(true, "escape closed the eyedropper");
}
function testSelect() {
function* testSelect() {
let dropper = new Eyedropper(window);
dropper.once("select", (event, color) => {
is(color, DIV_COLOR, "correct color selected");
});
let selected = dropper.once("select");
let copied = waitForClipboard(() => {}, DIV_COLOR);
// wait for DIV_COLOR to be copied to the clipboard then finish the test.
waitForClipboard(DIV_COLOR, () => {
inspectPage(dropper); // setup: inspect the page
}, finish, finish);
yield inspectPage(dropper);
let color = yield selected;
is(color, DIV_COLOR, "correct color selected");
// wait for DIV_COLOR to be copied to the clipboard
yield copied;
}
/* Helpers */
function inspectPage(dropper, click=true) {
dropper.open();
function* inspectPage(dropper, click=true) {
yield dropper.open();
let target = content.document.getElementById("test");
let win = content.window;
info("dropper opened");
EventUtils.synthesizeMouse(target, 20, 20, { type: "mousemove" }, win);
let target = document.documentElement;
let win = window;
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, 30, 30, { type: "mousemove" }, win);
// get location of the <div> in the content, offset from browser window
let box = gBrowser.selectedTab.linkedBrowser.getBoundingClientRect();
let x = box.left + 100;
let y = box.top + 100;
if (click) {
EventUtils.synthesizeMouse(target, 30, 30, {}, win);
}
});
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
yield dropperLoaded(dropper);
EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
if (click) {
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
}
}
function pressESC() {

View File

@ -31,26 +31,29 @@ function spawnTest() {
}
function inspectAndWaitForCopy() {
let deferred = promise.defer();
waitForClipboard(DIV_COLOR, () => {
return waitForClipboard(() => {
inspectPage(); // setup: inspect the page
}, deferred.resolve, deferred.reject);
return deferred.promise;
}, DIV_COLOR);
}
function inspectPage() {
let target = content.document.getElementById("test");
let win = content.window;
let target = document.documentElement;
let win = window;
EventUtils.synthesizeMouse(target, 20, 20, { type: "mousemove" }, win);
// get location of the <div> in the content, offset from browser window
let box = gBrowser.selectedTab.linkedBrowser.getBoundingClientRect();
let x = box.left + 100;
let y = box.top + 100;
let dropper = EyedropperManager.getInstance(window);
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, 30, 30, { type: "mousemove" }, win);
return dropperStarted(dropper).then(() => {
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
EventUtils.synthesizeMouse(target, 30, 30, {}, win);
});
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
});
})
}

View File

@ -10,8 +10,8 @@
#test {
margin: 100px;
background-color: blue;
width: 200px;
height: 200px;
width: 20px;
height: 20px;
}
</style>
</head>

View File

@ -13,6 +13,13 @@ Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers
waitForExplicitFinish();
/**
* Define an async test based on a generator function
*/
function asyncTest(generator) {
return () => Task.spawn(generator).then(null, ok.bind(null, false)).then(finish);
}
function cleanup()
{
while (gBrowser.tabs.length > 1) {
@ -38,6 +45,19 @@ function addTab(uri) {
return deferred.promise;
}
function waitForClipboard(setup, expected) {
let deferred = promise.defer();
SimpleTest.waitForClipboard(expected, setup, deferred.resolve, deferred.reject);
return deferred.promise;
}
function dropperStarted(dropper) {
if (dropper.isStarted) {
return promise.resolve();
}
return dropper.once("started");
}
function dropperLoaded(dropper) {
if (dropper.loaded) {
return promise.resolve();

View File

@ -7,16 +7,24 @@
const PAGE_CONTENT = [
'<style type="text/css">',
' body {',
' background-color: #ff5;',
' padding: 50px',
' background-color: white;',
' padding: 0px',
' }',
' div {',
' width: 100px;',
' height: 100px;',
'',
' #div1 {',
' background-color: #ff5;',
' width: 20px;',
' height: 20px;',
' }',
'',
' #div2 {',
' margin-left: 20px;',
' width: 20px;',
' height: 20px;',
' background-color: #f09;',
' }',
'</style>',
'<body><div></div></body>'
'<body><div id="div1"></div><div id="div2"></div></body>'
].join("\n");
const ORIGINAL_COLOR = "rgb(255, 0, 153)"; // #f09
@ -30,9 +38,9 @@ let test = asyncTest(function*() {
content.document.body.innerHTML = PAGE_CONTENT;
let {toolbox, inspector, view} = yield openRuleView();
yield selectNode("div", inspector);
yield selectNode("#div2", inspector);
let property = getRuleViewProperty(view, "div", "background-color");
let property = getRuleViewProperty(view, "#div2", "background-color");
let swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
ok(swatch, "Color swatch is displayed for the bg-color property");
@ -112,24 +120,38 @@ function openEyedropper(view, swatch) {
}
function inspectPage(dropper, click=true) {
let target = content.document.body;
let win = content.window;
let target = document.documentElement;
let win = window;
EventUtils.synthesizeMouse(target, 10, 10, { type: "mousemove" }, win);
// get location of the content, offset from browser window
let box = gBrowser.selectedTab.linkedBrowser.getBoundingClientRect();
let x = box.left + 1;
let y = box.top + 1;
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, 20, 20, { type: "mousemove" }, win);
if (click) {
EventUtils.synthesizeMouse(target, 20, 20, {}, win);
}
return dropperStarted(dropper).then(() => {
EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
return dropperLoaded(dropper).then(() => {
EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
if (click) {
EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
}
});
});
}
function dropperStarted(dropper) {
if (dropper.isStarted) {
return promise.resolve();
}
return dropper.once("started");
}
function dropperLoaded(dropper) {
if (dropper.loaded) {
return promise.resolve();
}
return dropper.once("load");
}