| Index: lib/aardvark.js |
| =================================================================== |
| --- a/lib/aardvark.js |
| +++ b/lib/aardvark.js |
| @@ -3,132 +3,142 @@ |
| * version 2.0 (the "License"). You can obtain a copy of the License at |
| * http://mozilla.org/MPL/2.0/. |
| */ |
| 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; |
| - |
| -/********************************* |
| - * Minimal element creation code * |
| - *********************************/ |
| - |
| -function createElement(doc, tagName, attrs, children) |
| +messageManager.addMessageListener("ElemHideHelper:SelectionStarted", |
| + selectionStarted); |
| +messageManager.addMessageListener("ElemHideHelper:SelectionSucceeded", |
| + selectionSucceeded); |
| +messageManager.addMessageListener("ElemHideHelper:SelectionStopped", |
| + selectionStopped); |
| +onShutdown.add(() => |
| { |
| - 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; |
| -}; |
| + messageManager.removeMessageListener("ElemHideHelper:SelectionStarted", |
| + selectionStarted); |
| + messageManager.removeMessageListener("ElemHideHelper:SelectionSucceeded", |
| + selectionSucceeded); |
| + messageManager.removeMessageListener("ElemHideHelper:SelectionStopped", |
| + selectionStopped); |
| + |
| + selectionStopped(); |
| +}); |
| + |
| +function selectionStarted(message) |
| +{ |
| + Aardvark.selectionStarted(); |
| +} |
| + |
| +function selectionSucceeded(message) |
| +{ |
| + Aardvark.selectionSucceeded(message.data); |
| +} |
| + |
| +function selectionStopped(message) |
| +{ |
| + Aardvark.selectionStopped(); |
| +} |
| /********************************** |
| * General element selection code * |
| **********************************/ |
| 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}); |
| }, |
| - canSelect: function(browser) |
| + selectionSucceeded: function(nodeInfo) |
| { |
| - if (!browser || !browser.contentWindow || |
| - !(browser.contentDocument instanceof Ci.nsIDOMHTMLDocument)) |
| - { |
| - return false; |
| - } |
| + this.window.openDialog("chrome://elemhidehelper/content/composer.xul", |
| + "_blank", "chrome,centerscreen,resizable,dialog=no", nodeInfo); |
| + }, |
| - let location = browser.contentWindow.location; |
| - if (location.href == "about:blank") |
| - return false; |
| + selectionStopped: function() |
| + { |
| + if (!this.browser) |
| + return; |
| - if (!Prefs.acceptlocalfiles && |
| - location.hostname == "" && |
| - location.protocol != "mailbox:" && |
| - location.protocol != "imap:" && |
| - location.protocol != "news:" && |
| - location.protocol != "snews:") |
| - { |
| - return false; |
| - } |
| + if (this.commandLabelTimer) |
| + this.commandLabelTimer.cancel(); |
| + if (this.viewSourceTimer) |
| + this.viewSourceTimer.cancel(); |
| + this.commandLabelTimer = null; |
| + this.viewSourceTimer = null; |
| - return true; |
| + this.hideTooltips(); |
| + |
| + 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) |
| event.stopPropagation(); |
| } |
| if (event) |
| event.preventDefault(); |
| }, |
| @@ -199,35 +209,16 @@ let Aardvark = exports.Aardvark = |
| for (let i = 0; i < tooltips.length; i++) |
| { |
| let tooltip = E(tooltips[i]); |
| if (tooltip) |
| tooltip.hidePopup(); |
| } |
| }, |
| - 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) |
| return; |
| var command = null; |
| if (event.keyCode == event.DOM_VK_ESCAPE) |
| command = "quit"; |
| @@ -241,450 +232,68 @@ let Aardvark = exports.Aardvark = |
| if (commands[commands[i] + "_key"] == key || commands[commands[i] + "_altkey"] == key) |
| command = commands[i]; |
| } |
| if (command) |
| 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() |
| - { |
| - // 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) |
| + onTabSelect: function(event) |
| { |
| - 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) |
| { |
| var descr = this.window.document.createElement("description"); |
| descr.setAttribute("value", value); |
| if (className) |
| descr.setAttribute("class", className); |
| 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 * |
| ***************************/ |
| commands: [ |
| "select", |
| "wider", |
| "narrower", |
| "lock", |
| "quit", |
| "blinkElement", |
| "viewSource", |
| "viewSourceWindow", |
| "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; |
| } |
| sourceBox.hidePopup(); |
| while (sourceBox.firstElementChild) |
| sourceBox.removeChild(sourceBox.firstElementChild); |
| this.getOuterHtmlFormatted(elem, sourceBox); |
| - this.commentElem = elem; |
| let anchor = this.window.document.documentElement; |
| let x = this.mouseX; |
| let y = this.mouseY; |
| this.viewSourceTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
| this.viewSourceTimer.initWithCallback(function() |
| { |
| sourceBox.showPopup(anchor, x, y, "tooltip", "topleft", "topleft"); |
| @@ -819,10 +428,10 @@ let Aardvark = exports.Aardvark = |
| // Show help box |
| helpBox.showPopup(this.browser, -1, -1, "tooltip", "topleft", "topleft"); |
| return true; |
| } |
| } |
| // 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); |