Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Unified Diff: lib/child/selection.js

Issue 29363476: Issue 2879 - Move element selection into the content process (Closed) Base URL: https://hg.adblockplus.org/elemhidehelper
Patch Set: Created Nov. 17, 2016, 1:17 p.m.
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: lib/child/selection.js
===================================================================
new file mode 100644
--- /dev/null
+++ b/lib/child/selection.js
@@ -0,0 +1,285 @@
+/*
saroyanm 2016/11/23 17:44:39 I thought that this code belongs to Aardvark ? Wha
Wladimir Palant 2016/11/24 14:02:01 Yes, E10S is the reason for this whole change. Any
+ * This Source Code is subject to the terms of the Mozilla Public License
+ * version 2.0 (the "License"). You can obtain a copy of the License at
+ * http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
+
+let messageManager = require("messageManager");
+let {
+ createElement, getWindowSize, getParentElement, getElementPosition
+} = require("./utils");
+
+let state = exports.state = {};
Wladimir Palant 2016/11/17 13:54:48 Original code was storing the selection state as p
saroyanm 2016/11/23 17:44:39 We are setting the properties of state in other fu
Wladimir Palant 2016/11/24 14:02:02 Setting them here doesn't make sense - the definit
+
+messageManager.addMessageListener("ElemHideHelper:StartSelection", startSelection);
+
+onShutdown.add(() =>
+{
+ messageManager.removeMessageListener("ElemHideHelper:StartSelection", startSelection);
+
+ stopSelection();
+});
+
+function startSelection(message)
+{
+ stopSelection();
+
+ let outerWindowID = message.data;
+ let wnd = Services.wm.getOuterWindowWithId(outerWindowID);
+ if (!wnd || !canSelect(wnd))
+ return;
+
+ state.window = wnd;
+ state.id = outerWindowID;
+
+ wnd.addEventListener("click", onMouseClick, true);
+ wnd.addEventListener("wheel", onMouseScroll, true);
saroyanm 2016/11/23 17:44:39 It's a deprecated event: https://developer.mozilla
Wladimir Palant 2016/11/24 14:02:02 That page is about the mousewheel event, not wheel
+ wnd.addEventListener("mousemove", onMouseMove, true);
+ wnd.addEventListener("pagehide", onPageHide, true);
+
+ wnd.focus();
+
+ let doc = wnd.document;
+ let {elementMarkerClass} = require("info");
+ state.boxElement = createElement(doc, "div", {"class": elementMarkerClass}, [
+ createElement(doc, "div", {"class": "ehh-border"}),
+ createElement(doc, "div", {"class": "ehh-label"}, [
+ createElement(doc, "span", {"class": "ehh-labelTag"}),
+ createElement(doc, "span", {"class": "ehh-labelAddition"})
+ ])
+ ]);
+
+ // Make sure to select some element immeditely (whichever is in the center of the browser window)
+ let [wndWidth, wndHeight] = getWindowSize(wnd);
+ state.isUserSelected = false;
+ onMouseMove({clientX: wndWidth / 2, clientY: wndHeight / 2, screenX: -1, screenY: -1, target: null});
+
+ messageManager.sendAsyncMessage("ElemHideHelper:SelectionStarted");
+}
+
+function stopSelection()
+{
+ if (!state.boxElement)
+ return;
+
+ hideSelection();
+
+ let wnd = state.window;
+ wnd.removeEventListener("click", onMouseClick, true);
+ wnd.removeEventListener("wheel", onMouseScroll, true);
+ wnd.removeEventListener("mousemove", onMouseMove, true);
+ wnd.removeEventListener("pagehide", onPageHide, true);
+
+ for (let key of Object.keys(state))
+ delete state[key];
+
+ messageManager.sendAsyncMessage("ElemHideHelper:SelectionStopped");
+}
+exports.stopSelection = stopSelection;
+
+function canSelect(wnd)
+{
+ let acceptlocalfiles;
saroyanm 2016/11/23 17:44:39 Nit: camelCase is missing
Wladimir Palant 2016/11/24 14:02:02 Done.
+ try
+ {
+ acceptlocalfiles = Services.prefs.getBoolPref("extensions.elemhidehelper.acceptlocalfiles");
saroyanm 2016/11/23 17:44:39 Nit: exceeding 80 chars.
Wladimir Palant 2016/11/24 14:02:01 Done.
+ }
+ catch (e)
+ {
+ acceptlocalfiles = false;
+ }
+
+ if (!acceptlocalfiles)
+ {
+ let localSchemes;
+ try
+ {
+ localSchemes = new Set(
+ Services.prefs.getCharPref("extensions.adblockplus.whitelistschemes")
+ .split(/\s+/)
+ );
Wladimir Palant 2016/11/17 13:54:48 This changes the original logic which was simply h
+ }
+ catch (e)
+ {
+ localSchemes = new Set();
+ }
+
+ if (localSchemes.has(wnd.location.protocol.replace(/:$/, "")))
+ return false;
+ }
+
+ return true;
+}
+
+function getElementLabel(elem)
+{
+ let tagName = elem.localName;
saroyanm 2016/11/23 17:44:39 localName is a deprecated property: https://develo
Wladimir Palant 2016/11/24 14:02:02 That would have been surprising - the docs say tha
+ let addition = "";
+ if (elem.id != "")
+ addition += ", id: " + elem.id;
+ if (elem.className != "")
+ addition += ", class: " + elem.className;
+ if (elem.style.cssText != "")
+ addition += ", style: " + elem.style.cssText;
saroyanm 2016/11/23 17:44:39 Weak suggestion: What about mentioning that it's n
Wladimir Palant 2016/11/24 14:02:02 Hasn't lead to any confusion so far, we can just a
+
+ return [tagName, addition];
+}
+
+function setAnchorElement(anchor)
saroyanm 2016/11/23 17:44:39 What is the anchor element ? I assume it's not rel
Wladimir Palant 2016/11/24 14:02:01 Nope. I've documented the state properties now, sh
+{
+ state.anchorElement = anchor;
+
+ let newSelection = anchor;
+ if (state.isUserSelected)
+ {
+ // User chose an element via wider/narrower commands, keep the selection if
+ // our new anchor is still a child of that element
+ let e = newSelection;
+ while (e && e != state.selectedElement)
+ e = getParentElement(e);
+
+ if (e)
+ newSelection = state.selectedElement;
+ else
+ state.isUserSelected = false;
+ }
+
+ selectElement(newSelection);
+}
+exports.setAnchorElement = setAnchorElement;
+
+function selectElement(elem)
+{
+ state.selectedElement = elem;
+ state.prevSelectionUpdate = Date.now();
+
+ let border = state.boxElement.querySelector(".ehh-border");
+ let label = state.boxElement.querySelector(".ehh-label");
+ let labelTag = state.boxElement.querySelector(".ehh-labelTag");
+ let labelAddition = state.boxElement.querySelector(".ehh-labelAddition");
+
+ let doc = state.window.document;
+ let [wndWidth, wndHeight] = getWindowSize(state.window);
+
+ let pos = getElementPosition(elem);
+ state.boxElement.style.left = Math.min(pos.left - 1, wndWidth - 2) + "px";
saroyanm 2016/11/23 17:44:38 Is this approach even working with RTL websites ?
Wladimir Palant 2016/11/24 14:02:01 Well, it works with RTL websites. Our UI is always
saroyanm 2016/11/25 16:18:13 Here you go: #4665
+ state.boxElement.style.top = Math.min(pos.top - 1, wndHeight - 2) + "px";
+ border.style.width = Math.max(pos.right - pos.left - 2, 0) + "px";
+ border.style.height = Math.max(pos.bottom - pos.top - 2, 0) + "px";
+
+ [labelTag.textContent, labelAddition.textContent] = getElementLabel(elem);
+
+ // If there is not enough space to show the label move it up a little
+ if (pos.bottom < wndHeight - 25)
+ label.className = "ehh-label";
+ else
+ label.className = "ehh-label onTop";
+
+ doc.documentElement.appendChild(state.boxElement);
+
+ state.prevPos = pos;
+ state.window.addEventListener("MozAfterPaint", onAfterPaint, false);
+}
+exports.selectElement = selectElement;
+
+function hideSelection()
+{
+ if (!Cu.isDeadWrapper(state.boxElement) && state.boxElement.parentNode)
+ state.boxElement.parentNode.removeChild(state.boxElement);
+
+ if (!Cu.isDeadWrapper(state.window))
+ state.window.removeEventListener("MozAfterPaint", onAfterPaint, false);
+}
+
+/******************
+ * Event handlers *
+ ******************/
+
+function onMouseClick(event)
+{
+ if (event.button != 0 || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
+ return;
+
+ require("./commands").select();
+ event.preventDefault();
+}
+
+function onMouseScroll(event)
+{
+ if (!event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
+ return;
+
+ let delta = event.deltaY || event.deltaX;
Wladimir Palant 2016/11/17 13:54:48 This was dead code originally, using DOMMouseScrol
saroyanm 2016/11/23 17:44:38 Acknowledged.
+ if (!delta)
+ return;
+
+ let commands = require("./commands");
saroyanm 2016/11/23 17:44:38 Detail: we already loading the module in onMouseCl
Wladimir Palant 2016/11/24 14:02:01 That would create a circular reference.
+ if (delta > 0)
+ commands.wider();
+ else
+ commands.narrower();
+ event.preventDefault();
+}
+
+function onMouseMove(event)
+{
+ hideSelection();
+
+ let x = event.clientX;
+ let y = event.clientY;
+
+ // We might have coordinates relative to a frame, recalculate relative to top window
+ let node = event.target;
+ while (node && node.ownerDocument && node.ownerDocument.defaultView && node.ownerDocument.defaultView.frameElement)
+ {
+ node = node.ownerDocument.defaultView.frameElement;
+ let rect = node.getBoundingClientRect();
+ x += rect.left;
+ y += rect.top;
+ }
+
+ // Get the element matching the coordinates, probably within a frame
+ let elem = state.window.document.elementFromPoint(x, y);
+ while (elem && "contentWindow" in elem && canSelect(elem.contentWindow))
+ {
+ let rect = elem.getBoundingClientRect();
+ x -= rect.left;
+ y -= rect.top;
+ elem = elem.contentWindow.document.elementFromPoint(x, y);
+ }
+
+ if (elem)
+ {
+ if (!state.lockedAnchor)
+ setAnchorElement(elem);
+ else
+ {
+ state.lockedAnchor = elem;
+ selectElement(state.selectedElement);
+ }
+ }
+}
+
+function onPageHide(event)
+{
+ stopSelection();
+}
+
+function onAfterPaint(event)
Wladimir Palant 2016/11/17 13:54:48 This code path is untested, I'm not sure whether i
saroyanm 2016/11/23 17:44:39 We do have "onMouseScroll" function, I thought it
Wladimir Palant 2016/11/24 14:02:02 Given that the purpose of this function is selecti
+{
+ // Don't update position too often
+ if (state.selectedElement && Date.now() - state.prevSelectionUpdate > 20)
+ {
+ let pos = getElementPosition(state.selectedElement);
+ if (!state.prevPos || state.prevPos.left != pos.left ||
+ state.prevPos.right != pos.right || state.prevPos.top != pos.top ||
+ state.prevPos.bottom != pos.bottom)
+ {
+ selectElement(state.selectedElement);
+ }
+ }
+}

Powered by Google App Engine
This is Rietveld