Index: lib/objectTabs.js |
=================================================================== |
--- a/lib/objectTabs.js |
+++ b/lib/objectTabs.js |
@@ -14,485 +14,99 @@ |
* You should have received a copy of the GNU General Public License |
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
*/ |
/** |
* @fileOverview Code responsible for showing and hiding object tabs. |
*/ |
+let {Prefs} = require("prefs"); |
+let {Utils} = require("utils"); |
+ |
/** |
- * Class responsible for showing and hiding object tabs. |
- * @class |
+ * Random element class, to be used for object tabs displayed on top of the |
+ * plugin content. |
+ * @type string |
*/ |
-var objTabs = |
+let classVisibleTop = null; |
+ |
+/** |
+ * Random element class, to be used for object tabs displayed at the bottom of |
+ * the plugin content. |
+ * @type string |
+ */ |
+let classVisibleBottom = null; |
+ |
+/** |
+ * Random element class, to be used for object tabs that are hidden. |
+ * @type string |
+ */ |
+let classHidden = null; |
+ |
+Utils.addChildMessageListener("AdblockPlus:GetObjectTabsStatus", function() |
{ |
- /** |
- * Number of milliseconds to wait until hiding tab after the mouse moves away. |
- * @type Integer |
- */ |
- HIDE_DELAY: 1000, |
+ let {UI} = require("ui"); |
- /** |
- * Flag used to trigger object tabs initialization first time object tabs are |
- * used. |
- * @type Boolean |
- */ |
- initialized: false, |
+ return !!(Prefs.enabled && Prefs.frameobjects && UI.overlay && classHidden); |
+}); |
- /** |
- * Will be set to true while initialization is in progress. |
- * @type Boolean |
- */ |
- initializing: false, |
+Utils.addChildMessageListener("AdblockPlus:GetObjectTabsTexts", function() |
+{ |
+ let {UI} = require("ui"); |
- /** |
- * Parameters for _showTab, to be called once initialization is complete. |
- */ |
- delayedShowParams: null, |
+ return { |
+ label: UI.overlay.attributes.objtabtext, |
+ tooltip: UI.overlay.attributes.objtabtooltip, |
+ classVisibleTop, classVisibleBottom, classHidden |
+ }; |
+}); |
- /** |
- * Randomly generated class to be used for visible object tabs on top of object. |
- * @type String |
- */ |
- objTabClassVisibleTop: null, |
+Utils.addChildMessageListener("AdblockPlus:BlockItem", function(item) |
+{ |
+ let {UI} = require("ui"); |
+ UI.blockItem(UI.currentWindow, null, item); |
+}); |
- /** |
- * Randomly generated class to be used for visible object tabs at the bottom of the object. |
- * @type String |
- */ |
- objTabClassVisibleBottom: null, |
- |
- /** |
- * Randomly generated class to be used for invisible object tabs. |
- * @type String |
- */ |
- objTabClassHidden: null, |
- |
- /** |
- * Document element the object tab is currently being displayed for. |
- * @type Element |
- */ |
- currentElement: null, |
- |
- /** |
- * Windows that the window event handler is currently registered for. |
- * @type Window[] |
- */ |
- windowListeners: null, |
- |
- /** |
- * Panel element currently used as object tab. |
- * @type Element |
- */ |
- objtabElement: null, |
- |
- /** |
- * Time of previous position update. |
- * @type Integer |
- */ |
- prevPositionUpdate: 0, |
- |
- /** |
- * Timer used to update position of the object tab. |
- * @type nsITimer |
- */ |
- positionTimer: null, |
- |
- /** |
- * Timer used to delay hiding of the object tab. |
- * @type nsITimer |
- */ |
- hideTimer: null, |
- |
- /** |
- * Used when hideTimer is running, time when the tab should be hidden. |
- * @type Integer |
- */ |
- hideTargetTime: 0, |
- |
- /** |
- * Initializes object tabs (generates random classes and registers stylesheet). |
- */ |
- _initCSS: function() |
+function init() |
+{ |
+ function processCSSData(event) |
{ |
- function processCSSData(request) |
- { |
- if (onShutdown.done) |
- return; |
- |
- let data = request.responseText; |
- |
- let rnd = []; |
- let offset = "a".charCodeAt(0); |
- for (let i = 0; i < 60; i++) |
- rnd.push(offset + Math.random() * 26); |
- |
- this.objTabClassVisibleTop = String.fromCharCode.apply(String, rnd.slice(0, 20)); |
- this.objTabClassVisibleBottom = String.fromCharCode.apply(String, rnd.slice(20, 40)); |
- this.objTabClassHidden = String.fromCharCode.apply(String, rnd.slice(40, 60)); |
- |
- let {Utils} = require("utils"); |
- let url = Utils.makeURI("data:text/css," + encodeURIComponent(data.replace(/%%CLASSVISIBLETOP%%/g, this.objTabClassVisibleTop) |
- .replace(/%%CLASSVISIBLEBOTTOM%%/g, this.objTabClassVisibleBottom) |
- .replace(/%%CLASSHIDDEN%%/g, this.objTabClassHidden))); |
- Utils.styleService.loadAndRegisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET); |
- onShutdown.add(function() |
- { |
- Utils.styleService.unregisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET); |
- }); |
- |
- this.initializing = false; |
- this.initialized = true; |
- |
- if (this.delayedShowParams) |
- this._showTab.apply(this, this.delayedShowParams); |
- } |
- |
- this.delayedShowParams = arguments; |
- |
- if (!this.initializing) |
- { |
- this.initializing = true; |
- |
- // Load CSS asynchronously |
- try { |
- let request = new XMLHttpRequest(); |
- request.mozBackgroundRequest = true; |
- request.open("GET", "chrome://adblockplus/content/objtabs.css"); |
- request.overrideMimeType("text/plain"); |
- |
- request.addEventListener("load", processCSSData.bind(this, request), false); |
- request.send(null); |
- } |
- catch (e) |
- { |
- Cu.reportError(e); |
- this.initializing = false; |
- } |
- } |
- }, |
- |
- /** |
- * Called to show object tab for an element. |
- */ |
- showTabFor: function(/**Element*/ element) |
- { |
- // Object tabs aren't usable in Fennec |
- let {application} = require("info"); |
- if (application == "fennec" || application == "fennec2" || |
- application == "adblockbrowser") |
+ if (onShutdown.done) |
return; |
- let {Prefs} = require("prefs"); |
- if (!Prefs.frameobjects) |
- return; |
+ let data = event.target.responseText; |
- if (this.hideTimer) |
+ let rnd = []; |
+ let offset = "a".charCodeAt(0); |
+ for (let i = 0; i < 60; i++) |
+ rnd.push(offset + Math.random() * 26); |
+ |
+ classVisibleTop = String.fromCharCode.apply(String, rnd.slice(0, 20)); |
+ classVisibleBottom = String.fromCharCode.apply(String, rnd.slice(20, 40)); |
+ classHidden = String.fromCharCode.apply(String, rnd.slice(40, 60)); |
+ |
+ let url = Utils.makeURI("data:text/css," + encodeURIComponent(data.replace(/%%CLASSVISIBLETOP%%/g, classVisibleTop) |
+ .replace(/%%CLASSVISIBLEBOTTOM%%/g, classVisibleBottom) |
+ .replace(/%%CLASSHIDDEN%%/g, classHidden))); |
+ Utils.styleService.loadAndRegisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET); |
+ onShutdown.add(function() |
{ |
- this.hideTimer.cancel(); |
- this.hideTimer = null; |
- } |
+ Utils.styleService.unregisterSheet(url, Ci.nsIStyleSheetService.USER_SHEET); |
+ }); |
+ } |
- if (this.objtabElement) |
- this.objtabElement.style.setProperty("opacity", "1", "important"); |
- |
- if (this.currentElement != element) |
- { |
- this._hideTab(); |
- |
- let {Policy} = require("contentPolicy"); |
- let {RequestNotifier} = require("requestNotifier"); |
- let data = RequestNotifier.getDataForNode(element, true, "OBJECT"); |
- if (data) |
- { |
- if (this.initialized) |
- this._showTab(element, data[1]); |
- else |
- this._initCSS(element, data[1]); |
- } |
- } |
- }, |
- |
- /** |
- * Called to hide object tab for an element (actual hiding happens delayed). |
- */ |
- hideTabFor: function(/**Element*/ element) |
+ // Load CSS asynchronously |
+ try |
{ |
- if (element != this.currentElement || this.hideTimer) |
- return; |
- |
- this.hideTargetTime = Date.now() + this.HIDE_DELAY; |
- this.hideTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
- this.hideTimer.init(this, 40, Ci.nsITimer.TYPE_REPEATING_SLACK); |
- }, |
- |
- /** |
- * Makes the tab element visible. |
- * @param {Element} element |
- * @param {RequestEntry} data |
- */ |
- _showTab: function(element, data) |
+ let request = new XMLHttpRequest(); |
+ request.mozBackgroundRequest = true; |
+ request.open("GET", "chrome://adblockplus/content/objtabs.css"); |
+ request.overrideMimeType("text/plain"); |
+ request.addEventListener("load", processCSSData, false); |
+ request.send(null); |
+ } |
+ catch (e) |
{ |
- let {UI} = require("ui"); |
- if (!UI.overlay) |
- return; |
- |
- let doc = element.ownerDocument.defaultView.top.document; |
- |
- this.objtabElement = doc.createElementNS("http://www.w3.org/1999/xhtml", "a"); |
- this.objtabElement.textContent = UI.overlay.attributes.objtabtext; |
- this.objtabElement.setAttribute("title", UI.overlay.attributes.objtabtooltip); |
- this.objtabElement.setAttribute("href", data.location); |
- this.objtabElement.setAttribute("class", this.objTabClassHidden); |
- this.objtabElement.style.setProperty("opacity", "1", "important"); |
- this.objtabElement.nodeData = data; |
- |
- this.currentElement = element; |
- |
- // Register paint listeners for the relevant windows |
- this.windowListeners = []; |
- let wnd = element.ownerDocument.defaultView; |
- while (wnd) |
- { |
- wnd.addEventListener("MozAfterPaint", objectWindowEventHandler, false); |
- this.windowListeners.push(wnd); |
- wnd = (wnd.parent != wnd ? wnd.parent : null); |
- } |
- |
- // Register mouse listeners on the object tab |
- this.objtabElement.addEventListener("mouseover", objectTabEventHander, false); |
- this.objtabElement.addEventListener("mouseout", objectTabEventHander, false); |
- this.objtabElement.addEventListener("click", objectTabEventHander, true); |
- |
- // Insert the tab into the document and adjust its position |
- doc.documentElement.appendChild(this.objtabElement); |
- if (!this.positionTimer) |
- { |
- this.positionTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); |
- this.positionTimer.init(this, 200, Ci.nsITimer.TYPE_REPEATING_SLACK); |
- } |
- this._positionTab(); |
- }, |
- |
- /** |
- * Hides the tab element. |
- */ |
- _hideTab: function() |
- { |
- this.delayedShowParams = null; |
- |
- if (this.objtabElement) |
- { |
- // Prevent recursive calls via popuphidden handler |
- let objtab = this.objtabElement; |
- this.objtabElement = null; |
- this.currentElement = null; |
- |
- if (this.hideTimer) |
- { |
- this.hideTimer.cancel(); |
- this.hideTimer = null; |
- } |
- |
- if (this.positionTimer) |
- { |
- this.positionTimer.cancel(); |
- this.positionTimer = null; |
- } |
- |
- try { |
- objtab.parentNode.removeChild(objtab); |
- } catch (e) {} |
- objtab.removeEventListener("mouseover", objectTabEventHander, false); |
- objtab.removeEventListener("mouseout", objectTabEventHander, false); |
- objtab.nodeData = null; |
- |
- for (let wnd of this.windowListeners) |
- wnd.removeEventListener("MozAfterPaint", objectWindowEventHandler, false); |
- this.windowListeners = null; |
- } |
- }, |
- |
- /** |
- * Updates position of the tab element. |
- */ |
- _positionTab: function() |
- { |
- // Test whether element is still in document |
- let elementDoc = null; |
- try |
- { |
- elementDoc = this.currentElement.ownerDocument; |
- } catch (e) {} // Ignore "can't access dead object" error |
- if (!elementDoc || !this.currentElement.offsetWidth || !this.currentElement.offsetHeight || |
- !elementDoc.defaultView || !elementDoc.documentElement) |
- { |
- this._hideTab(); |
- return; |
- } |
- |
- let objRect = this._getElementPosition(this.currentElement); |
- |
- let className = this.objTabClassVisibleTop; |
- let left = objRect.right - this.objtabElement.offsetWidth; |
- let top = objRect.top - this.objtabElement.offsetHeight; |
- if (top < 0) |
- { |
- top = objRect.bottom; |
- className = this.objTabClassVisibleBottom; |
- } |
- |
- if (this.objtabElement.style.left != left + "px") |
- this.objtabElement.style.setProperty("left", left + "px", "important"); |
- if (this.objtabElement.style.top != top + "px") |
- this.objtabElement.style.setProperty("top", top + "px", "important"); |
- |
- if (this.objtabElement.getAttribute("class") != className) |
- this.objtabElement.setAttribute("class", className); |
- |
- this.prevPositionUpdate = Date.now(); |
- }, |
- |
- /** |
- * Calculates element's position relative to the top frame and considering |
- * clipping due to scrolling. |
- * @return {{left: Number, top: Number, right: Number, bottom: Number}} |
- */ |
- _getElementPosition: function(/**Element*/ element) |
- { |
- // Restrict rectangle coordinates by the boundaries of a window's client area |
- function intersectRect(rect, wnd) |
- { |
- // Cannot use wnd.innerWidth/Height because they won't account for scrollbars |
- let doc = wnd.document; |
- let wndWidth = doc.documentElement.clientWidth; |
- let wndHeight = doc.documentElement.clientHeight; |
- if (doc.compatMode == "BackCompat") // clientHeight will be bogus in quirks mode |
- wndHeight = Math.max(doc.documentElement.offsetHeight, doc.body.offsetHeight) - wnd.scrollMaxY - 1; |
- |
- 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; |
- |
- let style = wnd.getComputedStyle(element, null); |
- let offsets = [ |
- parseFloat(style.borderLeftWidth) + parseFloat(style.paddingLeft), |
- parseFloat(style.borderTopWidth) + parseFloat(style.paddingTop), |
- parseFloat(style.borderRightWidth) + parseFloat(style.paddingRight), |
- parseFloat(style.borderBottomWidth) + parseFloat(style.paddingBottom) |
- ]; |
- |
- rect = {left: rect.left + offsets[0], top: rect.top + offsets[1], |
- right: rect.right - offsets[2], bottom: rect.bottom - offsets[3]}; |
- 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; |
- }, |
- |
- doBlock: function() |
- { |
- let {UI} = require("ui"); |
- let {Utils} = require("utils"); |
- let chromeWindow = Utils.getChromeWindow(this.currentElement.ownerDocument.defaultView); |
- UI.blockItem(chromeWindow, this.currentElement, this.objtabElement.nodeData); |
- }, |
- |
- /** |
- * Called whenever a timer fires. |
- * @param {nsISupport} subject |
- * @param {string} topic |
- * @param {string} data |
- */ |
- observe: function(subject, topic, data) |
- { |
- if (subject == this.positionTimer) |
- { |
- // Don't update position if it was already updated recently (via MozAfterPaint) |
- if (Date.now() - this.prevPositionUpdate > 100) |
- this._positionTab(); |
- } |
- else if (subject == this.hideTimer) |
- { |
- let now = Date.now(); |
- if (now >= this.hideTargetTime) |
- this._hideTab(); |
- else if (this.hideTargetTime - now < this.HIDE_DELAY / 2) |
- this.objtabElement.style.setProperty("opacity", (this.hideTargetTime - now) * 2 / this.HIDE_DELAY, "important"); |
- } |
+ Cu.reportError(e); |
} |
-}; |
- |
-onShutdown.add(objTabs._hideTab.bind(objTabs)); |
- |
-/** |
- * Function called whenever the mouse enters or leaves an object. |
- */ |
-function objectMouseEventHander(/**Event*/ event) |
-{ |
- if (!event.isTrusted) |
- return; |
- |
- if (event.type == "mouseover") |
- objTabs.showTabFor(event.target); |
- else if (event.type == "mouseout") |
- objTabs.hideTabFor(event.target); |
} |
- |
-/** |
- * Function called for paint events of the object tab window. |
- */ |
-function objectWindowEventHandler(/**Event*/ event) |
-{ |
- if (!event.isTrusted) |
- return; |
- |
- // Don't trigger update too often, avoid overusing CPU on frequent page updates |
- if (event.type == "MozAfterPaint" && Date.now() - objTabs.prevPositionUpdate > 20) |
- objTabs._positionTab(); |
-} |
- |
-/** |
- * Function called whenever the mouse enters or leaves an object tab. |
- */ |
-function objectTabEventHander(/**Event*/ event) |
-{ |
- if (onShutdown.done || !event.isTrusted) |
- return; |
- |
- if (event.type == "click" && event.button == 0) |
- { |
- event.preventDefault(); |
- event.stopPropagation(); |
- |
- objTabs.doBlock(); |
- } |
- else if (event.type == "mouseover") |
- objTabs.showTabFor(objTabs.currentElement); |
- else if (event.type == "mouseout") |
- objTabs.hideTabFor(objTabs.currentElement); |
-} |
-exports.objectMouseEventHander = objectMouseEventHander; |
+init(); |