| 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(); | 
|  |