Bug 573041 - Clicker code should happen on content side [r=mfinkle]

This commit is contained in:
Vivien Nicolas 2010-06-21 22:16:37 +02:00
Родитель 0734dd52ca
Коммит 3a2b991011
5 изменённых файлов: 170 добавлений и 219 удалений

Просмотреть файл

@ -317,8 +317,8 @@ Point.prototype = {
function Rect(x, y, w, h) {
this.left = x;
this.top = y;
this.right = x+w;
this.bottom = y+h;
this.right = x + w;
this.bottom = y + h;
};
Rect.fromRect = function fromRect(r) {

Просмотреть файл

@ -407,6 +407,7 @@ var BrowserUI = {
// listen returns messages from content
messageManager.addMessageListener("Browser:SaveAs:Return", this);
messageManager.addMessageListener("Browser:Highlight", this);
// listening mousedown for automatically dismiss some popups (e.g. larry)
window.addEventListener("mousedown", this, true);
@ -422,7 +423,7 @@ var BrowserUI = {
messageManager.addMessageListener("DOMContentLoaded", function() {
// We only want to delay one time
messageManager.removeMessageListener("DOMContentLoaded", arguments.callee, true);
// We unhide the panelUI so the XBL and settings can initialize
Elements.panelUI.hidden = false;
@ -780,6 +781,7 @@ var BrowserUI = {
receiveMessage: function receiveMessage(aMessage) {
let browser = aMessage.target;
let json = aMessage.json;
switch (aMessage.name) {
case "DOMTitleChanged":
this._titleChanged(browser);
@ -795,7 +797,6 @@ var BrowserUI = {
this._updateIcon(Browser.selectedBrowser.mIconURL);
break;
case "Browser:SaveAs:Return":
let json = aMessage.json;
if (json.type != Ci.nsIPrintSettings.kOutputFormatPDF)
return;
@ -821,6 +822,15 @@ var BrowserUI = {
catch(e) {}
gObserverService.notifyObservers(download, "dl-done", null);
break;
case "Browser:Highlight":
let rects = [];
for (let i = 0; i < json.rects.length; i++) {
let rect = json.rects[i];
rects.push(new Rect(rect.left, rect.top, rect.width, rect.height));
}
TapHighlightHelper.show(rects);
break;
}
return {};
@ -982,6 +992,48 @@ var BrowserUI = {
}
};
var TapHighlightHelper = {
get _overlay() {
delete this._overlay;
return this._overlay = document.getElementById("content-overlay");
},
show: function show(aRects) {
let bv = Browser._browserView;
let union = aRects.reduce(function(a, b) {
return a.expandToContain(b);
}, new Rect(0, 0, 0, 0)).map(bv.browserToViewport);
let vis = Browser.getVisibleRect();
let canvasArea = vis.intersect(union);
let overlay = this._overlay;
overlay.width = canvasArea.width;
overlay.style.width = canvasArea.width + "px";
overlay.height = canvasArea.height;
overlay.style.height = canvasArea.height + "px";
let ctx = overlay.getContext("2d");
ctx.save();
ctx.translate(-canvasArea.left, -canvasArea.top);
bv.browserToViewportCanvasContext(ctx);
overlay.style.left = canvasArea.left + "px";
overlay.style.top = canvasArea.top + "px";
ctx.fillStyle = "rgba(0, 145, 255, .5)";
for (let i = aRects.length - 1; i >= 0; i--) {
let rect = aRects[i];
ctx.fillRect(rect.left, rect.top, rect.width, rect.height);
}
ctx.restore();
overlay.style.display = "block";
},
hide: function hide() {
this._overlay.style.display = "none";
}
}
var PageActions = {
get _permissionManager() {
delete this._permissionManager;

Просмотреть файл

@ -1232,36 +1232,6 @@ var Browser = {
}
},
getContentClientRects: function getContentClientRects(contentElem) {
// XXX don't copy getBoundingContentRect
let browser = Browser._browserView.getBrowser();
if (!browser)
return null;
let offset = BrowserView.Util.getContentScrollOffset(browser);
let nativeRects = contentElem.getClientRects();
// step out of iframes and frames, offsetting scroll values
let rect;
let cw = browser.contentWindow;
for (let frame = contentElem.ownerDocument.defaultView; frame != cw; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
rect = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
}
let result = [];
let r;
for (let i = nativeRects.length - 1; i >= 0; i--) {
r = nativeRects[i];
result.push(new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height));
}
return result;
},
getBoundingContentRect: function getBoundingContentRect(contentElem) {
let document = contentElem.ownerDocument;
while(document.defaultView.frameElement)
@ -1817,166 +1787,62 @@ const BrowserSearch = {
/** Watches for mouse events in chrome and sends them to content. */
function ContentCustomClicker(browserView) {
this._browserView = browserView;
this._overlay = document.getElementById("content-overlay");
this._overlayTimeout = 0;
this._width = 0;
this._height = 0;
}
ContentCustomClicker.prototype = {
/** Dispatch a mouse event with chrome client coordinates. */
_dispatchMouseEvent: function _dispatchMouseEvent(element, name, cX, cY) {
let browser = this._browserView.getBrowser();
if (browser) {
let [x, y] = Browser.transformClientToBrowser(cX, cY);
let cwu = BrowserView.Util.getBrowserDOMWindowUtils(browser);
let scrollX = {}, scrollY = {};
cwu.getScrollXY(false, scrollX, scrollY);
_dispatchMouseEvent: function _dispatchMouseEvent(aName, aX, aY) {
let browser = this._browserView.getBrowser();
let [x, y] = Browser.transformClientToBrowser(aX, aY);
browser.messageManager.sendAsyncMessage(aName, { x: x, y: y });
},
// the element can be out of the cX/cY point because of the touch radius
// ignore the redirection if the element is a HTMLHtmlElement (bug 562981)
let rect = Browser.getBoundingContentRect(element);
if (!rect.isEmpty() && !(element instanceof HTMLHtmlElement) &&
((x < rect.left || (x > rect.left + rect.width)) || (y < rect.top || (y > rect.top + rect.height)))) {
mouseDown: function mouseDown(aX, aY) {
// Ensure that the content process has gets an activate event
let browser = this._browserView.getBrowser();
let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
try {
fl.activateRemoteFrame();
} catch (e) {}
this._dispatchMouseEvent("Browser:MouseDown", aX, aY);
},
let point = rect.center();
x = point.x;
y = point.y;
}
mouseUp: function mouseUp(aX, aY) {
},
x = x - scrollX.value;
y = y - scrollY.value;
cwu.sendMouseEvent(name, x, y, 0, 1, 0, true);
}
},
panBegin: function panBegin() {
TapHighlightHelper.hide();
let browser = this._browserView.getBrowser();
browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
},
/** Returns a node if selecting this node causes a focus. */
_getFocusable: function _getFocusable(node) {
if (node && node.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label"))
return node;
return null;
},
singleClick: function singleClick(aX, aY, aModifiers) {
TapHighlightHelper.hide();
this._dispatchMouseEvent("Browser:MouseUp", aX, aY);
// TODO e10s: handle modifiers for clicks
//
// if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
// let uri = Util.getHrefForElement(element);
// if (uri)
// Browser.addTab(uri, false);
// }
},
_showCanvas: function _showCanvas(cX, cY) {
// This code is sensitive to performance. Please profile changes you make to
// keep this running fast.
let bv = this._browserView;
let overlay = this._overlay;
let ctx = overlay.getContext("2d");
let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
let element = this._getFocusable(Browser.elementFromPoint(elementX, elementY));
if (!element)
return;
doubleClick: function doubleClick(aX1, aY1, aX2, aY2) {
TapHighlightHelper.hide();
let browser = this._browserView.getBrowser();
browser.messageManager.sendAsyncMessage("Browser:MouseCancel", {});
let rects = Browser.getContentClientRects(element);
let union = rects.reduce(function(a, b) {
return a.expandToContain(b);
}, new Rect(0, 0, 0, 0)).map(bv.browserToViewport);
const kDoubleClickRadius = 32;
let vis = Browser.getVisibleRect();
let canvasArea = vis.intersect(union);
this._ensureSize(canvasArea.width, canvasArea.height);
let maxRadius = kDoubleClickRadius * Browser._browserView.getZoomLevel();
let isClickInRadius = (Math.abs(aX1 - aX2) < maxRadius && Math.abs(aY1 - aY2) < maxRadius);
if (isClickInRadius && !Browser.zoomToPoint(aX1, aY1))
Browser.zoomFromPoint(aX1, aY1);
},
ctx.save();
ctx.translate(-canvasArea.left, -canvasArea.top);
bv.browserToViewportCanvasContext(ctx);
overlay.style.left = canvasArea.left + "px";
overlay.style.top = canvasArea.top + "px";
ctx.fillStyle = "rgba(0, 145, 255, .5)";
let rect;
let i;
for (i = rects.length - 1; i >= 0; i--) {
rect = rects[i];
ctx.fillRect(rect.left, rect.top, rect.width, rect.height);
}
ctx.restore();
overlay.style.display = "block";
},
/** Stop highlighting current element. */
_hideCanvas: function _hideCanvas() {
let overlay = this._overlay;
overlay.style.display = "none";
overlay.getContext("2d").clearRect(0, 0, this._width, this._height);
if (this._overlayTimeout) {
clearTimeout(this._overlayTimeout);
this._overlayTimeout = 0;
}
},
/** Make sure canvas is at least width x height. */
_ensureSize: function _ensureSize(width, height) {
if (this._width <= width) {
this._width = width;
this._overlay.width = width;
}
if (this._height <= height) {
this._height = height;
this._overlay.height = height;
}
},
mouseDown: function mouseDown(cX, cY) {
if (!this._overlayTimeout)
this._overlayTimeout = setTimeout(function(self) { self._showCanvas(cX, cY); }, kTapOverlayTimeout, this);
},
mouseUp: function mouseUp(cX, cY) {
},
panBegin: function panBegin() {
this._hideCanvas();
},
singleClick: function singleClick(cX, cY, modifiers) {
this._hideCanvas();
let [elementX, elementY] = Browser.transformClientToBrowser(cX, cY);
let element = Browser.elementFromPoint(elementX, elementY);
if (modifiers == 0) {
if (element instanceof HTMLOptionElement)
element = element.parentNode;
if (gPrefService.getBoolPref("formhelper.enabled")) {
if (FormHelper.canShowUIFor(element) && FormHelper.open(element))
return;
}
else if (SelectHelper.canShowUIFor(element)) {
SelectHelper.show(element);
return;
}
gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
let self = this;
Util.executeSoon(function() {
self._dispatchMouseEvent(element, "mousedown", cX, cY);
self._dispatchMouseEvent(element, "mouseup", cX, cY);
});
}
else if (modifiers == Ci.nsIDOMNSEvent.CONTROL_MASK) {
let uri = Util.getHrefForElement(element);
if (uri)
Browser.addTab(uri, false, Browser.selectedTab);
}
},
doubleClick: function doubleClick(cX1, cY1, cX2, cY2) {
this._hideCanvas();
const kDoubleClickRadius = 32;
let maxRadius = kDoubleClickRadius * Browser._browserView.getZoomLevel();
let isClickInRadius = (Math.abs(cX1 - cX2) < maxRadius && Math.abs(cY1 - cY2) < maxRadius);
if (isClickInRadius && !Browser.zoomToPoint(cX1, cY1))
Browser.zoomFromPoint(cX1, cY1);
},
toString: function toString() {
return "[ContentCustomClicker] { }";
}
toString: function toString() {
return "[ContentCustomClicker] { }";
}
};
/** Watches for mouse events in chrome and sends them to content. */

Просмотреть файл

@ -259,7 +259,7 @@
<scrollbox id="content-scrollbox" style="overflow: hidden;" class="window-width" flex="1">
<!-- Content viewport -->
<html:div id="tile-container" style="overflow: hidden;">
<html:canvas id="content-overlay" style="display: none; position: absolute; z-index: 1000;">
<html:canvas id="content-overlay" style="display: none; position: absolute; z-index: 1000; left: 0; top: 0;">
</html:canvas>
</html:div>
</scrollbox>

Просмотреть файл

@ -6,8 +6,6 @@ const kTapOverlayTimeout = 200;
let Cc = Components.classes;
let Ci = Components.interfaces;
let gIOService = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
let gFocusManager = Cc["@mozilla.org/focus-manager;1"]
.getService(Ci.nsIFocusManager);
let gPrefService = Cc["@mozilla.org/preferences-service;1"]
@ -145,6 +143,7 @@ const ElementTouchHelper = {
}
};
/**
* @param x,y Browser coordinates
* @return Element at position, null if no active browser or no element found
@ -162,8 +161,8 @@ function elementFromPoint(x, y) {
while (elem && (elem instanceof HTMLIFrameElement || elem instanceof HTMLFrameElement)) {
// adjust client coordinates' origin to be top left of iframe viewport
let rect = elem.getBoundingClientRect();
x = x - rect.left;
y = y - rect.top;
x -= rect.left;
y -= rect.top;
let windowUtils = elem.contentDocument.defaultView.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
elem = ElementTouchHelper.getClosest(windowUtils, x, y);
}
@ -171,20 +170,19 @@ function elementFromPoint(x, y) {
return elem;
}
function getBoundingContentRect(contentElem) {
if (!contentElem)
function getBoundingContentRect(aElement) {
if (!aElement)
return new Rect(0, 0, 0, 0);
let document = contentElem.ownerDocument;
let document = aElement.ownerDocument;
while(document.defaultView.frameElement)
document = document.defaultView.frameElement.ownerDocument;
let offset = Util.getScrollOffset(content);
let r = contentElem.getBoundingClientRect();
let r = aElement.getBoundingClientRect();
// step out of iframes and frames, offsetting scroll values
for (let frame = contentElem.ownerDocument.defaultView; frame != content; frame = frame.parent) {
for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
let rect = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
@ -195,6 +193,29 @@ function getBoundingContentRect(contentElem) {
return new Rect(r.left + offset.x, r.top + offset.y, r.width, r.height);
}
function getContentClientRects(aElement) {
let offset = Util.getScrollOffset(content);
let nativeRects = aElement.getClientRects();
// step out of iframes and frames, offsetting scroll values
for (let frame = aElement.ownerDocument.defaultView; frame != content; frame = frame.parent) {
// adjust client coordinates' origin to be top left of iframe viewport
let rect = frame.frameElement.getBoundingClientRect();
let left = frame.getComputedStyle(frame.frameElement, "").borderLeftWidth;
let top = frame.getComputedStyle(frame.frameElement, "").borderTopWidth;
offset.add(rect.left + parseInt(left), rect.top + parseInt(top));
}
let result = [];
for (let i = nativeRects.length - 1; i >= 0; i--) {
let r = nativeRects[i];
result.push({ left: r.left + offset.x,
top: r.top + offset.y,
width: r.width,
height: r.height
});
}
return result;
};
/** Reponsible for sending messages about viewport size changes and painting. */
function Coalescer() {
@ -536,8 +557,7 @@ ContentFormManager.prototype = {
let currentElement = this.getCurrent();
let rect = currentElement.getCaretRect();
return null;
},
}
};
@ -678,9 +698,9 @@ FormNavigator.prototype = {
function Content() {
addMessageListener("Browser:Blur", this);
addMessageListener("Browser:Focus", this);
addMessageListener("Browser:Mousedown", this);
addMessageListener("Browser:Mouseup", this);
addMessageListener("Browser:CancelMouse", this);
addMessageListener("Browser:MouseDown", this);
addMessageListener("Browser:MouseUp", this);
addMessageListener("Browser:MouseCancel", this);
addMessageListener("Browser:SaveAs", this);
this._coalescer = new Coalescer();
@ -693,16 +713,16 @@ function Content() {
this._progressController.start();
this._contentFormManager = new ContentFormManager();
this._mousedownTimeout = new Util.Timeout();
}
Content.prototype = {
receiveMessage: function receiveMessage(aMessage) {
let json = aMessage.json;
let x = json.x;
let y = json.y;
switch (aMessage.name) {
case "Browser:Blur":
case "Browser:Blur":
docShell.isOffScreenBrowser = false;
this._selected = false;
break;
@ -712,31 +732,44 @@ Content.prototype = {
this._selected = true;
break;
case "Browser:Mousedown":
this._mousedownTimeout.once(kTapOverlayTimeout, function() {
case "Browser:MouseDown":
if (this._overlayTimeout)
return;
this._overlayTimeout = content.document.defaultView.setTimeout(function() {
let element = elementFromPoint(x, y);
gFocusManager.setFocus(element, Ci.nsIFocusManager.FLAG_NOSCROLL);
});
if (!element || !element.mozMatchesSelector("*:link,*:visited,*:link *,*:visited *,*[role=button],button,input,option,select,textarea,label"))
return;
let rects = getContentClientRects(element);
sendSyncMessage("Browser:Highlight", { rects: rects });
}, kTapOverlayTimeout);
break;
case "Browser:Mouseup":
this._mousedownTimeout.flush();
case "Browser:MouseUp":
let element = elementFromPoint(x, y);
if (!this._contentFormManager.formAssist(element)) {
// the element can be out of the cX/cY point because of the touch radius
// ignore the redirection if the element is a HTMLHtmlElement (bug 562981)
let rect = getBoundingContentRect(element);
if (!rect.isEmpty() && !(element instanceof HTMLHtmlElement) &&
((x < rect.left || (x > rect.left + rect.width)) || (y < rect.top || (y > rect.top + rect.height)))) {
let point = rect.center();
x = point.x;
y = point.y;
}
// XXX e10s bug 566288
// if (!this._contentFormManager.formAssist(element)) {
this._sendMouseEvent("mousedown", element, x, y);
this._sendMouseEvent("mouseup", element, x, y);
}
break;
// }
case "Browser:CancelMouse":
this._mousedownTimeout.clear();
// XXX there must be a better way than this to cancel the mouseover/focus?
this._sendMouseEvent("mouseup", null, -1000, -1000);
try {
content.document.activeElement.blur();
case "Browser:MouseCancel":
if (this._overlayTimeout) {
content.document.defaultView.clearTimeout(this._overlayTimeout);
this._overlayTimeout = 0;
}
catch(e) {}
break;
case "Browser:SaveAs":