| Index: chrome/content/elemHideEmulation.js | 
| =================================================================== | 
| --- a/chrome/content/elemHideEmulation.js | 
| +++ b/chrome/content/elemHideEmulation.js | 
| @@ -14,16 +14,17 @@ | 
| * You should have received a copy of the GNU General Public License | 
| * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| */ | 
|  | 
| /* globals filterToRegExp */ | 
|  | 
| "use strict"; | 
|  | 
| +const MIN_INVOCATION_INTERVAL = 3000; | 
| const abpSelectorRegexp = /:-abp-([\w-]+)\(/i; | 
|  | 
| let reportError = () => {}; | 
|  | 
| function splitSelector(selector) | 
| { | 
| if (selector.indexOf(",") == -1) | 
| return [selector]; | 
| @@ -194,16 +195,21 @@ const incompletePrefixRegexp = /[\s>+~]$ | 
| function HasSelector(selectors) | 
| { | 
| this._innerSelectors = selectors; | 
| } | 
|  | 
| HasSelector.prototype = { | 
| requiresHiding: true, | 
|  | 
| +  get dependsOnStyles() | 
| +  { | 
| +    return this._innerSelectors.some(selector => selector.dependsOnStyles); | 
| +  }, | 
| + | 
| *getSelectors(prefix, subtree, styles) | 
| { | 
| for (let element of this.getElements(prefix, subtree, styles)) | 
| yield [makeSelector(element, ""), element]; | 
| }, | 
|  | 
| /** | 
| * Generator function returning selected elements. | 
| @@ -242,16 +248,17 @@ function PropsSelector(propertyExpressio | 
| else | 
| regexpString = filterToRegExp(propertyExpression); | 
|  | 
| this._regexp = new RegExp(regexpString, "i"); | 
| } | 
|  | 
| PropsSelector.prototype = { | 
| preferHideWithSelector: true, | 
| +  dependsOnStyles: true, | 
|  | 
| *findPropsSelectors(styles, prefix, regexp) | 
| { | 
| for (let style of styles) | 
| if (regexp.test(style.style)) | 
| for (let subSelector of style.subSelectors) | 
| yield prefix + subSelector; | 
| }, | 
| @@ -337,26 +344,42 @@ ElemHideEmulation.prototype = { | 
| if (suffix == null) | 
| return null; | 
|  | 
| selectors.push(...suffix); | 
|  | 
| return selectors; | 
| }, | 
|  | 
| +  _lastInvocation: 0, | 
| + | 
| +  /** | 
| +   * Processes the current document and applies all rules to it. | 
| +   * @param {CSSStyleSheet[]} [stylesheets] | 
| +   *    The list of new stylesheets that have been added to the document and | 
| +   *    made reprocessing necessary. This parameter shouldn't be passed in for | 
| +   *    the initial processing, all of document's stylesheets will be considered | 
| +   *    then and all rules, including the ones not dependent on styles. | 
| +   */ | 
| addSelectors(stylesheets) | 
| { | 
| +    this._lastInvocation = Date.now(); | 
| + | 
| let selectors = []; | 
| let selectorFilters = []; | 
|  | 
| let elements = []; | 
| let elementFilters = []; | 
|  | 
| let cssStyles = []; | 
|  | 
| +    let stylesheetOnlyChange = !!stylesheets; | 
| +    if (!stylesheets) | 
| +      stylesheets = this.window.document.styleSheets; | 
| + | 
| for (let stylesheet of stylesheets) | 
| { | 
| // Explicitly ignore third-party stylesheets to ensure consistent behavior | 
| // between Firefox and Chrome. | 
| if (!this.isSameOrigin(stylesheet)) | 
| continue; | 
|  | 
| let rules = stylesheet.cssRules; | 
| @@ -370,16 +393,22 @@ ElemHideEmulation.prototype = { | 
|  | 
| cssStyles.push(stringifyStyle(rule)); | 
| } | 
| } | 
|  | 
| let {document} = this.window; | 
| for (let pattern of this.patterns) | 
| { | 
| +      if (stylesheetOnlyChange && | 
| +          !pattern.selectors.some(selector => selector.dependsOnStyles)) | 
| +      { | 
| +        continue; | 
| +      } | 
| + | 
| for (let selector of evaluate(pattern.selectors, | 
| 0, "", document, cssStyles)) | 
| { | 
| if (pattern.selectors.some(s => s.preferHideWithSelector) && | 
| !pattern.selectors.some(s => s.requiresHiding)) | 
| { | 
| selectors.push(selector); | 
| selectorFilters.push(pattern.text); | 
| @@ -394,21 +423,40 @@ ElemHideEmulation.prototype = { | 
| } | 
| } | 
| } | 
|  | 
| this.addSelectorsFunc(selectors, selectorFilters); | 
| this.hideElemsFunc(elements, elementFilters); | 
| }, | 
|  | 
| +  _stylesheetQueue: null, | 
| + | 
| onLoad(event) | 
| { | 
| let stylesheet = event.target.sheet; | 
| if (stylesheet) | 
| -      this.addSelectors([stylesheet]); | 
| +    { | 
| +      if (!this._stylesheetQueue && | 
| +          Date.now() - this._lastInvocation < MIN_INVOCATION_INTERVAL) | 
| +      { | 
| +        this._stylesheetQueue = []; | 
| +        this.window.setTimeout(() => | 
| +        { | 
| +          let stylesheets = this._stylesheetQueue; | 
| +          this._stylesheetQueue = null; | 
| +          this.addSelectors(stylesheets); | 
| +        }, MIN_INVOCATION_INTERVAL - (Date.now() - this._lastInvocation)); | 
| +      } | 
| + | 
| +      if (this._stylesheetQueue) | 
| +        this._stylesheetQueue.push(stylesheet); | 
| +      else | 
| +        this.addSelectors([stylesheet]); | 
| +    } | 
| }, | 
|  | 
| apply() | 
| { | 
| this.getFiltersFunc(patterns => | 
| { | 
| this.patterns = []; | 
| for (let pattern of patterns) | 
| @@ -416,14 +464,14 @@ ElemHideEmulation.prototype = { | 
| let selectors = this.parseSelector(pattern.selector); | 
| if (selectors != null && selectors.length > 0) | 
| this.patterns.push({selectors, text: pattern.text}); | 
| } | 
|  | 
| if (this.patterns.length > 0) | 
| { | 
| let {document} = this.window; | 
| -        this.addSelectors(document.styleSheets); | 
| +        this.addSelectors(); | 
| document.addEventListener("load", this.onLoad.bind(this), true); | 
| } | 
| }); | 
| } | 
| }; | 
|  |