OLD | NEW |
(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 } |
OLD | NEW |