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

Side by Side 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: Addressed comments and marked extension as E10S-compatible Created Nov. 24, 2016, 2 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/child/nodeInfo.js ('k') | lib/child/utils.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This Source Code is subject to the terms of the Mozilla Public License
3 * version 2.0 (the "License"). You can obtain a copy of the License at
4 * http://mozilla.org/MPL/2.0/.
5 */
6
7 "use strict";
8
9 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
10
11 let messageManager = require("messageManager");
12 let {
13 createElement, getWindowSize, getParentElement, getElementPosition
14 } = require("./utils");
15
16 /**
17 * @typedef State
18 * @type {Object}
19 * @property {Window} window
20 * The top-level window that we are selecting in.
21 * @property {Element} boxElement
22 * The element marking the current selection.
23 * @property {Element} anchorElement
24 * The element that received the last mouse event.
25 * @property {Element} selectedElement
26 * The element currently selected (usually anchorElement or a parent element
27 * of it).
28 * @property {boolean} isUserSelected
29 * Will be true if the user narrowed down the selection to a specific element
30 * using wider/narrower commands.
31 * @property {Element} lockedAnchor
32 * When selection is locked, this property will be updated by mouse events
33 * instead of anchorElement.
34 * @property {number} prevSelectionUpdate
35 * Time of previous selection update, used for rate limiting.
36 * @property {Object} prevPos
37 * Position and size of selected element during previous selection update.
38 */
39
40 /**
41 * Current selection state. This object will be empty if no selection is
42 * currently in progress.
43 *
44 * @type {State}
45 */
46 let state = exports.state = {};
47
48 messageManager.addMessageListener("ElemHideHelper:StartSelection", startSelectio n);
49
50 onShutdown.add(() =>
51 {
52 messageManager.removeMessageListener("ElemHideHelper:StartSelection", startSel ection);
53
54 stopSelection();
55 });
56
57 function startSelection(message)
58 {
59 stopSelection();
60
61 let outerWindowID = message.data;
62 let wnd = Services.wm.getOuterWindowWithId(outerWindowID);
63 if (!wnd || !canSelect(wnd))
64 return;
65
66 state.window = wnd;
67
68 wnd.addEventListener("click", onMouseClick, true);
69 wnd.addEventListener("wheel", onMouseScroll, true);
70 wnd.addEventListener("mousemove", onMouseMove, true);
71 wnd.addEventListener("pagehide", onPageHide, true);
72
73 wnd.focus();
74
75 let doc = wnd.document;
76 let {elementMarkerClass} = require("info");
77 state.boxElement = createElement(doc, "div", {"class": elementMarkerClass}, [
78 createElement(doc, "div", {"class": "ehh-border"}),
79 createElement(doc, "div", {"class": "ehh-label"}, [
80 createElement(doc, "span", {"class": "ehh-labelTag"}),
81 createElement(doc, "span", {"class": "ehh-labelAddition"})
82 ])
83 ]);
84
85 // Make sure to select some element immeditely (whichever is in the center of the browser window)
86 let [wndWidth, wndHeight] = getWindowSize(wnd);
87 state.isUserSelected = false;
88 onMouseMove({clientX: wndWidth / 2, clientY: wndHeight / 2, screenX: -1, scree nY: -1, target: null});
89
90 messageManager.sendAsyncMessage("ElemHideHelper:SelectionStarted");
91 }
92
93 function stopSelection()
94 {
95 if (!state.boxElement)
96 return;
97
98 hideSelection();
99
100 let wnd = state.window;
101 wnd.removeEventListener("click", onMouseClick, true);
102 wnd.removeEventListener("wheel", onMouseScroll, true);
103 wnd.removeEventListener("mousemove", onMouseMove, true);
104 wnd.removeEventListener("pagehide", onPageHide, true);
105
106 for (let key of Object.keys(state))
107 delete state[key];
108
109 messageManager.sendAsyncMessage("ElemHideHelper:SelectionStopped");
110 }
111 exports.stopSelection = stopSelection;
112
113 function canSelect(wnd)
114 {
115 let acceptLocalFiles;
116 try
117 {
118 let pref = "extensions.elemhidehelper.acceptlocalfiles";
119 acceptLocalFiles = Services.prefs.getBoolPref(pref);
120 }
121 catch (e)
122 {
123 acceptLocalFiles = false;
124 }
125
126 if (!acceptLocalFiles)
127 {
128 let localSchemes;
129 try
130 {
131 localSchemes = new Set(
132 Services.prefs.getCharPref("extensions.adblockplus.whitelistschemes")
133 .split(/\s+/)
134 );
135 }
136 catch (e)
137 {
138 localSchemes = new Set();
139 }
140
141 if (localSchemes.has(wnd.location.protocol.replace(/:$/, "")))
142 return false;
143 }
144
145 return true;
146 }
147
148 function getElementLabel(elem)
149 {
150 let tagName = elem.localName;
151 let addition = "";
152 if (elem.id != "")
153 addition += ", id: " + elem.id;
154 if (elem.className != "")
155 addition += ", class: " + elem.className;
156 if (elem.style.cssText != "")
157 addition += ", style: " + elem.style.cssText;
158
159 return [tagName, addition];
160 }
161
162 function setAnchorElement(anchor)
163 {
164 state.anchorElement = anchor;
165
166 let newSelection = anchor;
167 if (state.isUserSelected)
168 {
169 // User chose an element via wider/narrower commands, keep the selection if
170 // our new anchor is still a child of that element
171 let e = newSelection;
172 while (e && e != state.selectedElement)
173 e = getParentElement(e);
174
175 if (e)
176 newSelection = state.selectedElement;
177 else
178 state.isUserSelected = false;
179 }
180
181 selectElement(newSelection);
182 }
183 exports.setAnchorElement = setAnchorElement;
184
185 function selectElement(elem)
186 {
187 state.selectedElement = elem;
188 state.prevSelectionUpdate = Date.now();
189
190 let border = state.boxElement.querySelector(".ehh-border");
191 let label = state.boxElement.querySelector(".ehh-label");
192 let labelTag = state.boxElement.querySelector(".ehh-labelTag");
193 let labelAddition = state.boxElement.querySelector(".ehh-labelAddition");
194
195 let doc = state.window.document;
196 let [wndWidth, wndHeight] = getWindowSize(state.window);
197
198 let pos = getElementPosition(elem);
199 state.boxElement.style.left = Math.min(pos.left - 1, wndWidth - 2) + "px";
200 state.boxElement.style.top = Math.min(pos.top - 1, wndHeight - 2) + "px";
201 border.style.width = Math.max(pos.right - pos.left - 2, 0) + "px";
202 border.style.height = Math.max(pos.bottom - pos.top - 2, 0) + "px";
203
204 [labelTag.textContent, labelAddition.textContent] = getElementLabel(elem);
205
206 // If there is not enough space to show the label move it up a little
207 if (pos.bottom < wndHeight - 25)
208 label.className = "ehh-label";
209 else
210 label.className = "ehh-label onTop";
211
212 doc.documentElement.appendChild(state.boxElement);
213
214 state.prevPos = pos;
215 state.window.addEventListener("MozAfterPaint", onAfterPaint, false);
216 }
217 exports.selectElement = selectElement;
218
219 function hideSelection()
220 {
221 if (!Cu.isDeadWrapper(state.boxElement) && state.boxElement.parentNode)
222 state.boxElement.parentNode.removeChild(state.boxElement);
223
224 if (!Cu.isDeadWrapper(state.window))
225 state.window.removeEventListener("MozAfterPaint", onAfterPaint, false);
226 }
227
228 /******************
229 * Event handlers *
230 ******************/
231
232 function onMouseClick(event)
233 {
234 if (event.button != 0 || event.shiftKey || event.altKey || event.ctrlKey || ev ent.metaKey)
235 return;
236
237 require("./commands").select();
238 event.preventDefault();
239 }
240
241 function onMouseScroll(event)
242 {
243 if (!event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
244 return;
245
246 let delta = event.deltaY || event.deltaX;
247 if (!delta)
248 return;
249
250 let commands = require("./commands");
251 if (delta > 0)
252 commands.wider();
253 else
254 commands.narrower();
255 event.preventDefault();
256 }
257
258 function onMouseMove(event)
259 {
260 hideSelection();
261
262 let x = event.clientX;
263 let y = event.clientY;
264
265 // We might have coordinates relative to a frame, recalculate relative to top window
266 let node = event.target;
267 while (node && node.ownerDocument && node.ownerDocument.defaultView && node.ow nerDocument.defaultView.frameElement)
268 {
269 node = node.ownerDocument.defaultView.frameElement;
270 let rect = node.getBoundingClientRect();
271 x += rect.left;
272 y += rect.top;
273 }
274
275 // Get the element matching the coordinates, probably within a frame
276 let elem = state.window.document.elementFromPoint(x, y);
277 while (elem && "contentWindow" in elem && canSelect(elem.contentWindow))
278 {
279 let rect = elem.getBoundingClientRect();
280 x -= rect.left;
281 y -= rect.top;
282 elem = elem.contentWindow.document.elementFromPoint(x, y);
283 }
284
285 if (elem)
286 {
287 if (!state.lockedAnchor)
288 setAnchorElement(elem);
289 else
290 {
291 state.lockedAnchor = elem;
292 selectElement(state.selectedElement);
293 }
294 }
295 }
296
297 function onPageHide(event)
298 {
299 stopSelection();
300 }
301
302 function onAfterPaint(event)
303 {
304 // Don't update position too often
305 if (state.selectedElement && Date.now() - state.prevSelectionUpdate > 20)
306 {
307 let pos = getElementPosition(state.selectedElement);
308 if (!state.prevPos || state.prevPos.left != pos.left ||
309 state.prevPos.right != pos.right || state.prevPos.top != pos.top ||
310 state.prevPos.bottom != pos.bottom)
311 {
312 selectElement(state.selectedElement);
313 }
314 }
315 }
OLDNEW
« no previous file with comments | « lib/child/nodeInfo.js ('k') | lib/child/utils.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld