[Pkg-mozext-commits] [adblock-plus-element-hiding-helper] 17/28: Issue 2879 - Move element selection into the content process

David Prévot taffit at moszumanska.debian.org
Fri Aug 4 21:15:15 UTC 2017


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository adblock-plus-element-hiding-helper.

commit 9351e06550fcc9b8afee6bf89aa0535018696139
Author: Wladimir Palant <trev at adblockplus.org>
Date:   Thu Nov 17 09:07:21 2016 +0100

    Issue 2879 - Move element selection into the content process
---
 lib/aardvark.js        | 559 ++++++++-----------------------------------------
 lib/child/commands.js  | 147 +++++++++++++
 lib/child/main.js      |   3 +-
 lib/child/nodeInfo.js  |  20 --
 lib/child/selection.js | 315 ++++++++++++++++++++++++++++
 lib/child/utils.js     | 119 +++++++++++
 lib/main.js            |   3 +-
 lib/windowWrapper.js   |   7 +-
 metadata.gecko         |   1 -
 9 files changed, 671 insertions(+), 503 deletions(-)

diff --git a/lib/aardvark.js b/lib/aardvark.js
index bca6be7..0a24177 100644
--- a/lib/aardvark.js
+++ b/lib/aardvark.js
@@ -8,29 +8,45 @@ let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 let {Prefs} = require("prefs");
 
-// Make sure to stop selection when we are uninstalled
-onShutdown.add(() => Aardvark.quit());
+let messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+                       .getService(Ci.nsIMessageListenerManager)
+                       .QueryInterface(Ci.nsIMessageBroadcaster);
 
 // To be replaced when selection starts
 function E(id) {return null;}
 
-let messageCounter = 0;
+messageManager.addMessageListener("ElemHideHelper:SelectionStarted",
+                                  selectionStarted);
+messageManager.addMessageListener("ElemHideHelper:SelectionSucceeded",
+                                  selectionSucceeded);
+messageManager.addMessageListener("ElemHideHelper:SelectionStopped",
+                                  selectionStopped);
+onShutdown.add(() =>
+{
+  messageManager.removeMessageListener("ElemHideHelper:SelectionStarted",
+                                       selectionStarted);
+  messageManager.removeMessageListener("ElemHideHelper:SelectionSucceeded",
+                                       selectionSucceeded);
+  messageManager.removeMessageListener("ElemHideHelper:SelectionStopped",
+                                       selectionStopped);
+
+  selectionStopped();
+});
 
-/*********************************
- * Minimal element creation code *
- *********************************/
+function selectionStarted(message)
+{
+  Aardvark.selectionStarted();
+}
+
+function selectionSucceeded(message)
+{
+  Aardvark.selectionSucceeded(message.data);
+}
 
-function createElement(doc, tagName, attrs, children)
+function selectionStopped(message)
 {
-  let el = doc.createElement(tagName);
-  if (attrs)
-    for (let key in attrs)
-      el.setAttribute(key, attrs[key]);
-  if (children)
-    for (let child of children)
-      el.appendChild(child)
-  return el;
-};
+  Aardvark.selectionStopped();
+}
 
 /**********************************
  * General element selection code *
@@ -40,90 +56,84 @@ let Aardvark = exports.Aardvark =
 {
   window: null,
   browser: null,
-  anchorElem: null,
-  selectedElem: null,
-  isUserSelected: false,
-  lockedAnchor: null,
-  commentElem: null,
+  rememberedWrapper: null,
   mouseX: -1,
   mouseY: -1,
-  prevSelectionUpdate: -1,
   commandLabelTimer: null,
   viewSourceTimer: null,
-  boxElem: null,
-  paintNode: null,
-  prevPos: null,
 
   start: function(wrapper)
   {
-    if (!this.canSelect(wrapper.browser))
-      return;
+    this.rememberedWrapper = wrapper;
+    let browser = wrapper.browser;
+    if ("selectedBrowser" in browser)
+      browser = browser.selectedBrowser;
+    messageManager.broadcastAsyncMessage(
+      "ElemHideHelper:StartSelection",
+      browser.outerWindowID
+    );
+  },
 
-    if (this.browser)
-      this.quit();
+  selectionStarted: function()
+  {
+    let wrapper = this.rememberedWrapper;
+    this.rememberedWrapper = null;
 
     this.window = wrapper.window;
     this.browser = wrapper.browser;
     E = id => wrapper.E(id);
 
-    this.browser.addEventListener("click", this.onMouseClick, true);
-    this.browser.addEventListener("DOMMouseScroll", this.onMouseScroll, true);
     this.browser.addEventListener("keypress", this.onKeyPress, true);
-    this.browser.addEventListener("mousemove", this.onMouseMove, true);
-    this.browser.addEventListener("select", this.quit, false);
-    this.browser.contentWindow.addEventListener("pagehide", this.onPageHide, true);
-
-    this.browser.contentWindow.focus();
-
-    let doc = this.browser.contentDocument;
-    let {elementMarkerClass} = require("main");
-    this.boxElem = 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"})
-      ])
-    ]);
+    this.browser.addEventListener("mousemove", this.onMouseMove, false);
+    this.browser.addEventListener("select", this.onTabSelect, false);
 
     this.initHelpBox();
 
     if (Prefs.showhelp)
       this.showMenu();
+  },
 
-    // Make sure to select some element immeditely (whichever is in the center of the browser window)
-    let [wndWidth, wndHeight] = this.getWindowSize(doc.defaultView);
-    this.isUserSelected = false;
-    this.onMouseMove({clientX: wndWidth / 2, clientY: wndHeight / 2, screenX: -1, screenY: -1, target: null});
+  selectionSucceeded: function(nodeInfo)
+  {
+    this.window.openDialog("chrome://elemhidehelper/content/composer.xul",
+        "_blank", "chrome,centerscreen,resizable,dialog=no", nodeInfo);
   },
 
-  canSelect: function(browser)
+  selectionStopped: function()
   {
-    if (!browser || !browser.contentWindow ||
-        !(browser.contentDocument instanceof Ci.nsIDOMHTMLDocument))
-    {
-      return false;
-    }
+    if (!this.browser)
+      return;
 
-    let location = browser.contentWindow.location;
-    if (location.href == "about:blank")
-      return false;
+    if (this.commandLabelTimer)
+      this.commandLabelTimer.cancel();
+    if (this.viewSourceTimer)
+      this.viewSourceTimer.cancel();
+    this.commandLabelTimer = null;
+    this.viewSourceTimer = null;
 
-    if (!Prefs.acceptlocalfiles &&
-        location.hostname == "" &&
-        location.protocol != "mailbox:" &&
-        location.protocol != "imap:" &&
-        location.protocol != "news:" &&
-        location.protocol != "snews:")
-    {
-      return false;
-    }
+    this.hideTooltips();
 
-    return true;
+    this.browser.removeEventListener("keypress", this.onKeyPress, true);
+    this.browser.removeEventListener("mousemove", this.onMouseMove, false);
+    this.browser.removeEventListener("select", this.onTabSelect, false);
+
+    this.window = null;
+    this.browser = null;
+    E = id => null;
   },
 
   doCommand: function(command, event)
   {
-    if (this[command](this.selectedElem))
+    let showFeedback;
+    if (this.hasOwnProperty(command))
+      showFeedback = this[command]();
+    else
+    {
+      showFeedback = (command != "select" && command != "quit");
+      messageManager.broadcastAsyncMessage("ElemHideHelper:Command", command);
+    }
+
+    if (showFeedback)
     {
       this.showCommandLabel(this.commands[command + "_key"], this.commands[command + "_altkey"], this.commands[command + "_label"]);
       if (event)
@@ -204,25 +214,6 @@ let Aardvark = exports.Aardvark =
     }
   },
 
-  onMouseClick: function(event)
-  {
-    if (event.button != 0 || event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
-      return;
-
-    this.doCommand("select", event);
-  },
-
-  onMouseScroll: function(event)
-  {
-    if (!event.shiftKey || event.altKey || event.ctrlKey || event.metaKey)
-      return;
-
-    if ("axis" in event && event.axis != event.VERTICAL_AXIS)
-      return;
-
-    this.doCommand(event.detail > 0 ? "wider" : "narrower", event);
-  },
-
   onKeyPress: function(event)
   {
     if (event.altKey || event.ctrlKey || event.metaKey)
@@ -246,91 +237,15 @@ let Aardvark = exports.Aardvark =
       this.doCommand(command, event);
   },
 
-  onPageHide: function(event)
-  {
-    this.doCommand("quit", null);
-  },
-
   onMouseMove: function(event)
   {
     this.mouseX = event.screenX;
     this.mouseY = event.screenY;
-
-    this.hideSelection();
-    if (!this.browser)
-    {
-      // hideSelection() called quit()
-      return;
-    }
-
-    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;
-    }
-
-    let elem = this.browser.contentDocument.elementFromPoint(x, y);
-    while (elem && "contentDocument" in elem && this.canSelect(elem))
-    {
-      let rect = elem.getBoundingClientRect();
-      x -= rect.left;
-      y -= rect.top;
-      elem = elem.contentDocument.elementFromPoint(x, y);
-    }
-
-    if (elem)
-    {
-      if (!this.lockedAnchor)
-        this.setAnchorElement(elem);
-      else
-      {
-        this.lockedAnchor = elem;
-        this.selectElement(this.selectedElem);
-      }
-    }
   },
 
-  onAfterPaint: function()
+  onTabSelect: function(event)
   {
-    // Don't update position too often
-    if (this.selectedElem && Date.now() - this.prevSelectionUpdate > 20)
-    {
-      let pos = this.getElementPosition(this.selectedElem);
-      if (!this.prevPos || this.prevPos.left != pos.left || this.prevPos.right != pos.right
-                        || this.prevPos.top != pos.top || this.prevPos.bottom != pos.bottom)
-      {
-        this.selectElement(this.selectedElem);
-      }
-    }
-  },
-
-  setAnchorElement: function(anchor)
-  {
-    this.anchorElem = anchor;
-
-    let newSelection = anchor;
-    if (this.isUserSelected)
-    {
-      // User chose an element via wider/narrower commands, keep the selection if
-      // out new anchor is still a child of that element
-      let e = newSelection;
-      while (e && e != this.selectedElem)
-        e = this.getParentElement(e);
-
-      if (e)
-        newSelection = this.selectedElem;
-      else
-        this.isUserSelected = false;
-    }
-
-    this.selectElement(newSelection);
+    this.doCommand("quit", null);
   },
 
   appendDescription: function(node, value, className)
@@ -342,147 +257,6 @@ let Aardvark = exports.Aardvark =
     node.appendChild(descr);
   },
 
-  /**************************
-   * Element marker display *
-   **************************/
-
-  getElementLabel: function(elem)
-  {
-    let tagName = elem.tagName.toLowerCase();
-    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;
-
-    return [tagName, addition];
-  },
-
-  selectElement: function(elem)
-  {
-    this.selectedElem = elem;
-    this.prevSelectionUpdate = Date.now();
-
-    let border = this.boxElem.getElementsByClassName("ehh-border")[0];
-    let label = this.boxElem.getElementsByClassName("ehh-label")[0];
-    let labelTag = this.boxElem.getElementsByClassName("ehh-labelTag")[0];
-    let labelAddition = this.boxElem.getElementsByClassName("ehh-labelAddition")[0];
-
-    if (this.boxElem.parentNode)
-      this.boxElem.parentNode.removeChild(this.boxElem);
-
-    let doc = this.browser.contentDocument;
-    let [wndWidth, wndHeight] = this.getWindowSize(doc.defaultView);
-
-    let pos = this.getElementPosition(elem);
-    this.boxElem.style.left = Math.min(pos.left - 1, wndWidth - 2) + "px";
-    this.boxElem.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] = this.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(this.boxElem);
-
-    this.paintNode = doc.defaultView;
-    if (this.paintNode)
-    {
-      this.prevPos = pos;
-      this.paintNode.addEventListener("MozAfterPaint", this.onAfterPaint, false);
-    }
-  },
-
-  hideSelection: function()
-  {
-    try
-    {
-      if (this.boxElem.parentNode)
-        this.boxElem.parentNode.removeChild(this.boxElem);
-    }
-    catch (e)
-    {
-      // Are we using CPOW whose process is gone? Quit!
-      // Clear some variables to prevent recursion (quit will call us again).
-      this.boxElem = {};
-      this.paintNode = null;
-      this.quit();
-      return;
-    }
-
-    if (this.paintNode)
-      this.paintNode.removeEventListener("MozAfterPaint", this.onAfterPaint, false);
-
-    this.paintNode = null;
-    this.prevPos = null;
-  },
-
-  getWindowSize: function(wnd)
-  {
-    return [wnd.innerWidth, wnd.document.documentElement.clientHeight];
-  },
-
-  getElementPosition: function(element)
-  {
-    // Restrict rectangle coordinates by the boundaries of a window's client area
-    function intersectRect(rect, wnd)
-    {
-      let [wndWidth, wndHeight] = this.getWindowSize(wnd);
-      rect.left = Math.max(rect.left, 0);
-      rect.top = Math.max(rect.top, 0);
-      rect.right = Math.min(rect.right, wndWidth);
-      rect.bottom = Math.min(rect.bottom, wndHeight);
-    }
-
-    let rect = element.getBoundingClientRect();
-    let wnd = element.ownerDocument.defaultView;
-
-    rect = {left: rect.left, top: rect.top,
-            right: rect.right, bottom: rect.bottom};
-    while (true)
-    {
-      intersectRect.call(this, rect, wnd);
-
-      if (!wnd.frameElement)
-        break;
-
-      // Recalculate coordinates to be relative to frame's parent window
-      let frameElement = wnd.frameElement;
-      wnd = frameElement.ownerDocument.defaultView;
-
-      let frameRect = frameElement.getBoundingClientRect();
-      let frameStyle = wnd.getComputedStyle(frameElement, null);
-      let relLeft = frameRect.left + parseFloat(frameStyle.borderLeftWidth) + parseFloat(frameStyle.paddingLeft);
-      let relTop = frameRect.top + parseFloat(frameStyle.borderTopWidth) + parseFloat(frameStyle.paddingTop);
-
-      rect.left += relLeft;
-      rect.right += relLeft;
-      rect.top += relTop;
-      rect.bottom += relTop;
-    }
-
-    return rect;
-  },
-
-  getParentElement: function(elem)
-  {
-    let result = elem.parentNode;
-    if (result && result.nodeType == Ci.nsIDOMElement.DOCUMENT_NODE && result.defaultView && result.defaultView.frameElement)
-      result = result.defaultView.frameElement;
-
-    if (result && result.nodeType != Ci.nsIDOMElement.ELEMENT_NODE)
-      return null;
-
-    return result;
-  },
-
   /***************************
    * Commands implementation *
    ***************************/
@@ -499,177 +273,13 @@ let Aardvark = exports.Aardvark =
     "showMenu"
   ],
 
-  wider: function(elem)
-  {
-    if (!elem)
-      return false;
-
-    let newElem = this.getParentElement(elem);
-    if (!newElem)
-      return false;
-
-    this.isUserSelected = true;
-    this.selectElement(newElem);
-    return true;
-  },
-
-  narrower: function(elem)
-  {
-    if (elem)
-    {
-      // Search selected element in the parent chain, starting with the anchor element.
-      // We need to select the element just before the selected one.
-      let e = this.anchorElem;
-      let newElem = null;
-      while (e && e != elem)
-      {
-        newElem = e;
-        e = this.getParentElement(e);
-      }
-
-      if (!e || !newElem)
-        return false;
-
-      this.isUserSelected = true;
-      this.selectElement(newElem);
-      return true;
-    }
-    return false;
-  },
-
-  lock: function(elem)
-  {
-    if (!elem)
-      return false;
-
-    if (this.lockedAnchor)
-    {
-      this.setAnchorElement(this.lockedAnchor);
-      this.lockedAnchor = null;
-    }
-    else
-      this.lockedAnchor = this.anchorElem;
-
-    return true;
-  },
-
-  quit: function()
-  {
-    if (!this.browser)
-      return false;
-
-    if ("blinkTimer" in this)
-      this.stopBlinking();
-
-    if (this.commandLabelTimer)
-      this.commandLabelTimer.cancel();
-    if (this.viewSourceTimer)
-      this.viewSourceTimer.cancel();
-    this.commandLabelTimer = null;
-    this.viewSourceTimer = null;
-
-    this.hideSelection();
-    this.hideTooltips();
-
-    this.browser.removeEventListener("click", this.onMouseClick, true);
-    this.browser.removeEventListener("DOMMouseScroll", this.onMouseScroll, true);
-    this.browser.removeEventListener("keypress", this.onKeyPress, true);
-    this.browser.removeEventListener("mousemove", this.onMouseMove, true);
-    this.browser.removeEventListener("select", this.quit, false);
-    this.browser.contentWindow.removeEventListener("pagehide", this.onPageHide, true);
-
-    this.anchorElem = null;
-    this.selectedElem = null;
-    this.window = null;
-    this.browser = null;
-    this.commentElem = null;
-    this.lockedAnchor = null;
-    this.boxElem = null;
-    E = id => null;
-    return false;
-  },
-
-  select: function(elem)
-  {
-    if (!elem || !this.window)
-      return false;
-
-    let messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]
-                           .getService(Ci.nsIMessageBroadcaster);
-    let messageId = ++messageCounter;
-    let callback = (message) =>
-    {
-      let response = message.data;
-      if (response.messageId != messageId)
-        return;
-
-      messageManager.removeMessageListener(
-        "ElemHideHelper:GetNodeInfo:Response",
-        callback
-      );
-
-      if (!response.nodeData)
-        return;
-
-      this.window.openDialog("chrome://elemhidehelper/content/composer.xul",
-          "_blank", "chrome,centerscreen,resizable,dialog=no", response);
-      this.quit();
-    };
-
-    messageManager.addMessageListener(
-      "ElemHideHelper:GetNodeInfo:Response",
-      callback
-    );
-    messageManager.broadcastAsyncMessage(
-      "ElemHideHelper:GetNodeInfo",
-      messageId,
-      {
-        element: elem
-      }
-    );
-    return false;
-  },
-
-  blinkElement: function(elem)
-  {
-    if (!elem)
-      return false;
-
-    if ("blinkTimer" in this)
-      this.stopBlinking();
-
-    let counter = 0;
-    this.blinkElem = elem;
-    this.blinkOrigValue = elem.style.visibility;
-    this.blinkTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    this.blinkTimer.initWithCallback(function()
-    {
-      counter++;
-      elem.style.visibility = (counter % 2 == 0 ? "visible" : "hidden");
-      if (counter == 6)
-        Aardvark.stopBlinking();
-    }, 250, Ci.nsITimer.TYPE_REPEATING_SLACK);
-
-    return true;
-  },
-
-  stopBlinking: function()
-  {
-    this.blinkTimer.cancel();
-    this.blinkElem.style.visibility = this.blinkOrigValue;
-
-    delete this.blinkElem;
-    delete this.blinkOrigValue;
-    delete this.blinkTimer;
-  },
-
   viewSource: function(elem)
   {
     if (!elem)
       return false;
 
     var sourceBox = E("ehh-viewsource");
-    if (sourceBox.state == "open" && this.commentElem == elem)
+    if (sourceBox.state == "open")
     {
       sourceBox.hidePopup();
       return true;
@@ -679,7 +289,6 @@ let Aardvark = exports.Aardvark =
     while (sourceBox.firstElementChild)
       sourceBox.removeChild(sourceBox.firstElementChild);
     this.getOuterHtmlFormatted(elem, sourceBox);
-    this.commentElem = elem;
 
     let anchor = this.window.document.documentElement;
     let x = this.mouseX;
@@ -824,5 +433,5 @@ let Aardvark = exports.Aardvark =
 
 // Makes sure event handlers like Aardvark.onKeyPress always have the correct
 // this pointer set.
-for (let method of ["onMouseClick", "onMouseScroll", "onKeyPress", "onPageHide", "onMouseMove", "onAfterPaint", "quit"])
+for (let method of ["onKeyPress", "onMouseMove", "onTabSelect"])
   Aardvark[method] = Aardvark[method].bind(Aardvark);
diff --git a/lib/child/commands.js b/lib/child/commands.js
new file mode 100644
index 0000000..067ce37
--- /dev/null
+++ b/lib/child/commands.js
@@ -0,0 +1,147 @@
+/*
+ * 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 messageManager = require("messageManager");
+let {getNodeInfo} = require("./nodeInfo");
+let {
+  state, selectElement, setAnchorElement, stopSelection
+} = require("./selection");
+let {getParentElement} = require("./utils");
+
+messageManager.addMessageListener("ElemHideHelper:Command", onCommand);
+
+onShutdown.add(() =>
+{
+  messageManager.removeMessageListener("ElemHideHelper:Command", onCommand);
+});
+
+function onCommand(message)
+{
+  let command = message.data;
+  if (typeof exports[command] == "function")
+    exports[command]();
+}
+
+function quit()
+{
+  stopSelection();
+}
+exports.quit = quit;
+
+function select()
+{
+  if (!state.selectedElement)
+    return;
+
+  messageManager.sendAsyncMessage(
+    "ElemHideHelper:SelectionSucceeded",
+    getNodeInfo(state.selectedElement)
+  );
+  stopSelection();
+}
+exports.select = select;
+
+function wider()
+{
+  if (!state.selectedElement)
+    return;
+
+  let newElement = getParentElement(state.selectedElement);
+  if (!newElement)
+    return;
+
+  state.isUserSelected = true;
+  selectElement(newElement);
+}
+exports.wider = wider;
+
+function narrower()
+{
+  if (!state.selectedElement)
+    return;
+
+  // Search selected element in the parent chain, starting with the ancho
+  // element. We need to select the element just before the selected one.
+  let e = state.anchorElement;
+  let newElement = null;
+  while (e && e != state.selectedElement)
+  {
+    newElement = e;
+    e = getParentElement(e);
+  }
+
+  if (!e || !newElement)
+    return;
+
+  state.isUserSelected = true;
+  selectElement(newElement);
+}
+exports.narrower = narrower;
+
+function lock()
+{
+  if (!state.selectedElement)
+    return;
+
+  if (state.lockedAnchor)
+  {
+    setAnchorElement(state.lockedAnchor);
+    state.lockedAnchor = null;
+  }
+  else
+    state.lockedAnchor = state.anchorElement;
+}
+exports.lock = lock;
+
+let blinkState = null;
+
+function stopBlinking()
+{
+  blinkState.timer.cancel();
+  if (!Cu.isDeadWrapper(blinkState.element))
+    blinkState.element.style.visibility = blinkState.origVisibility;
+  blinkState = null;
+}
+
+function doBlink()
+{
+  if (Cu.isDeadWrapper(blinkState.element))
+  {
+    stopBlinking();
+    return;
+  }
+
+  blinkState.counter++;
+  blinkState.element.style.setProperty(
+    "visibility",
+    (blinkState.counter % 2 == 0 ? "visible" : "hidden"),
+    "important"
+  );
+  if (blinkState.counter == 6)
+    stopBlinking();
+}
+
+function blinkElement()
+{
+  if (!state.selectedElement)
+    return;
+
+  if (blinkState)
+    stopBlinking();
+
+  blinkState = {
+    counter: 0,
+    element: state.selectedElement,
+    origVisibility: state.selectedElement.style.visibility,
+    timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer)
+  };
+
+  blinkState.timer.initWithCallback(doBlink, 250,
+      Ci.nsITimer.TYPE_REPEATING_SLACK);
+}
+exports.blinkElement = blinkElement;
diff --git a/lib/child/main.js b/lib/child/main.js
index bb52cff..747d7d4 100644
--- a/lib/child/main.js
+++ b/lib/child/main.js
@@ -7,5 +7,6 @@
 "use strict";
 
 require("./actor");
-require("./nodeInfo");
+require("./commands");
 require("./preview");
+require("./selection");
diff --git a/lib/child/nodeInfo.js b/lib/child/nodeInfo.js
index 0a835cf..93232df 100644
--- a/lib/child/nodeInfo.js
+++ b/lib/child/nodeInfo.js
@@ -8,30 +8,10 @@
 
 let {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
-let messageManager = require("messageManager");
-
 let processID = Services.appinfo.processID;
 let maxNodeID = 0;
 let nodes = new Map();
 
-messageManager.addMessageListener("ElemHideHelper:GetNodeInfo", onGetNodeInfo);
-onShutdown.add(() =>
-{
-  messageManager.removeMessageListener("ElemHideHelper:GetNodeInfo",
-                                       onGetNodeInfo);
-});
-
-function onGetNodeInfo(message)
-{
-  if (Cu.isCrossProcessWrapper(message.objects.element))
-    return;
-
-  let nodeInfo = getNodeInfo(message.objects.element);
-  nodeInfo.messageId = message.data;
-  messageManager.sendAsyncMessage("ElemHideHelper:GetNodeInfo:Response",
-                                  nodeInfo);
-}
-
 function getNodeInfo(node)
 {
   let nodeData = getNodeData(node);
diff --git a/lib/child/selection.js b/lib/child/selection.js
new file mode 100644
index 0000000..6e23b4f
--- /dev/null
+++ b/lib/child/selection.js
@@ -0,0 +1,315 @@
+/*
+ * 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");
+
+/**
+ * @typedef State
+ * @type {Object}
+ * @property {Window} window
+ *   The top-level window that we are selecting in.
+ * @property {Element} boxElement
+ *   The element marking the current selection.
+ * @property {Element} anchorElement
+ *   The element that received the last mouse event.
+ * @property {Element} selectedElement
+ *   The element currently selected (usually anchorElement or a parent element
+ *   of it).
+ * @property {boolean} isUserSelected
+ *   Will be true if the user narrowed down the selection to a specific element
+ *   using wider/narrower commands.
+ * @property {Element} lockedAnchor
+ *   When selection is locked, this property will be updated by mouse events
+ *   instead of anchorElement.
+ * @property {number} prevSelectionUpdate
+ *   Time of previous selection update, used for rate limiting.
+ * @property {Object} prevPos
+ *   Position and size of selected element during previous selection update.
+ */
+
+/**
+ * Current selection state. This object will be empty if no selection is
+ * currently in progress.
+ *
+ * @type {State}
+ */
+let state = exports.state = {};
+
+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;
+
+  wnd.addEventListener("click", onMouseClick, true);
+  wnd.addEventListener("wheel", onMouseScroll, true);
+  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;
+  try
+  {
+    let pref = "extensions.elemhidehelper.acceptlocalfiles";
+    acceptLocalFiles = Services.prefs.getBoolPref(pref);
+  }
+  catch (e)
+  {
+    acceptLocalFiles = false;
+  }
+
+  if (!acceptLocalFiles)
+  {
+    let localSchemes;
+    try
+    {
+      localSchemes = new Set(
+        Services.prefs.getCharPref("extensions.adblockplus.whitelistschemes")
+                      .split(/\s+/)
+      );
+    }
+    catch (e)
+    {
+      localSchemes = new Set();
+    }
+
+    if (localSchemes.has(wnd.location.protocol.replace(/:$/, "")))
+      return false;
+  }
+
+  return true;
+}
+
+function getElementLabel(elem)
+{
+  let tagName = elem.localName;
+  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;
+
+  return [tagName, addition];
+}
+
+function setAnchorElement(anchor)
+{
+  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";
+  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;
+  if (!delta)
+    return;
+
+  let commands = require("./commands");
+  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)
+{
+  // 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);
+    }
+  }
+}
diff --git a/lib/child/utils.js b/lib/child/utils.js
new file mode 100644
index 0000000..d5b1561
--- /dev/null
+++ b/lib/child/utils.js
@@ -0,0 +1,119 @@
+/*
+ * 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";
+
+/**
+ * Element creation helper, allows defining attributes and child elements in one
+ * go.
+ * @param {Document} doc
+ *   Document to create the element in
+ * @param {string} tagName
+ *   Tag name of the new element
+ * @param {Object.<string, string>} [attrs]
+ *   Attributes to set on the element
+ * @param {Array.<Node>} [children]
+ *   Child nodes to add to the element
+ * @return {Element}
+ *   Element that was created
+ */
+function createElement(doc, tagName, attrs, children)
+{
+  let el = doc.createElement(tagName);
+  if (attrs)
+    for (let key in attrs)
+      el.setAttribute(key, attrs[key]);
+  if (children)
+    for (let child of children)
+      el.appendChild(child)
+  return el;
+}
+exports.createElement = createElement;
+
+/**
+ * Calculates the document size for a window.
+ * @return {Array.<number>}
+ *   Width and height of the document loaded into the window
+ */
+function getWindowSize(/**Window*/ wnd)
+{
+  return [wnd.innerWidth, wnd.document.documentElement.clientHeight];
+}
+exports.getWindowSize = getWindowSize;
+
+/**
+ * Determines the parent element for a document node, if any. Will ascend into
+ * parent frames if necessary.
+ */
+function getParentElement(/**Node*/ elem) /**Element*/
+{
+  let result = elem.parentNode;
+  if (result && result.nodeType == result.DOCUMENT_NODE && result.defaultView && result.defaultView.frameElement)
+    result = result.defaultView.frameElement;
+
+  if (result && result.nodeType != result.ELEMENT_NODE)
+    return null;
+
+  return result;
+}
+exports.getParentElement = getParentElement;
+
+/**
+ * Modifies a rectangle with coordinates relative to a window's client area
+ * to make sure it doesn't exceed that client area.
+ * @param {Object} rect
+ *   Rectangle with properties left, top, right, bottom.
+ * @param {Window} wnd
+ *   Window to restrict the rectangle to.
+ */
+function intersectRect(rect, wnd)
+{
+  let [wndWidth, wndHeight] = getWindowSize(wnd);
+  rect.left = Math.max(rect.left, 0);
+  rect.top = Math.max(rect.top, 0);
+  rect.right = Math.min(rect.right, wndWidth);
+  rect.bottom = Math.min(rect.bottom, wndHeight);
+}
+
+/**
+ * Calculates the element's position within the top frame. This will consider
+ * the element being clipped by frame boundaries.
+ * @return {Object}
+ *   Object with properties left, top, width, height denoting the element's
+ *   position and size within the top frame.
+ */
+function getElementPosition(/**Element*/ element)
+{
+  let rect = element.getBoundingClientRect();
+  let wnd = element.ownerDocument.defaultView;
+
+  rect = {left: rect.left, top: rect.top,
+          right: rect.right, bottom: rect.bottom};
+  while (true)
+  {
+    intersectRect(rect, wnd);
+
+    if (!wnd.frameElement)
+      break;
+
+    // Recalculate coordinates to be relative to frame's parent window
+    let frameElement = wnd.frameElement;
+    wnd = frameElement.ownerDocument.defaultView;
+
+    let frameRect = frameElement.getBoundingClientRect();
+    let frameStyle = wnd.getComputedStyle(frameElement, null);
+    let relLeft = frameRect.left + parseFloat(frameStyle.borderLeftWidth) + parseFloat(frameStyle.paddingLeft);
+    let relTop = frameRect.top + parseFloat(frameStyle.borderTopWidth) + parseFloat(frameStyle.paddingTop);
+
+    rect.left += relLeft;
+    rect.right += relLeft;
+    rect.top += relTop;
+    rect.bottom += relTop;
+  }
+
+  return rect;
+}
+exports.getElementPosition = getElementPosition;
diff --git a/lib/main.js b/lib/main.js
index eb053f8..ad1c493 100644
--- a/lib/main.js
+++ b/lib/main.js
@@ -27,7 +27,6 @@ let elementMarkerClass = null;
 
   elementMarkerClass = String.fromCharCode.apply(String, rnd);
 }
-exports.elementMarkerClass = elementMarkerClass;
 
 // Load CSS asynchronously
 let request = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
@@ -48,6 +47,8 @@ request.send(null);
 
 // Load our process script
 let info = require("info");
+info.elementMarkerClass = elementMarkerClass;
+
 let processScript = info.addonRoot + "lib/child/bootstrap.js?" +
     elementMarkerClass + "&" +
     "info=" + encodeURIComponent(JSON.stringify(info));
diff --git a/lib/windowWrapper.js b/lib/windowWrapper.js
index 06bc124..0da8065 100644
--- a/lib/windowWrapper.js
+++ b/lib/windowWrapper.js
@@ -80,15 +80,12 @@ WindowWrapper.prototype =
 
     this.popupHiddenHandler(event);
 
-    let enabled = Aardvark.canSelect(this.browser);
-    let running = (enabled && this.browser == Aardvark.browser);
+    let running = this.browser == Aardvark.browser;
 
     let [labelStart, labelStop] = getMenuItem();
     let item = popup.ownerDocument.createElement("menuitem");
     item.setAttribute("label", running ? labelStop : labelStart);
     item.setAttribute("class", "elemhidehelper-item");
-    if (!enabled)
-      item.setAttribute("disabled", "true");
 
     if (typeof key == "undefined")
       this.configureKey(event.currentTarget);
@@ -142,6 +139,6 @@ WindowWrapper.prototype =
     if ("@adblockplus.org/abp/public;1" in Cc && this.browser != Aardvark.browser)
       Aardvark.start(this);
     else
-      Aardvark.quit();
+      Aardvark.doCommand("quit", null);
   }
 };
diff --git a/metadata.gecko b/metadata.gecko
index c80f396..3bb752d 100644
--- a/metadata.gecko
+++ b/metadata.gecko
@@ -3,7 +3,6 @@ id=elemhidehelper at adblockplus.org
 basename=elemhidehelper
 version=1.3.10
 author=Wladimir Palant
-needMultiprocessShims=false
 
 [contributors]
 1=Rob Brown

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mozext/adblock-plus-element-hiding-helper.git



More information about the Pkg-mozext-commits mailing list