| LEFT | RIGHT | 
|    1 /* |    1 /* | 
|    2  * This file is part of Adblock Plus <https://adblockplus.org/>, |    2  * This file is part of Adblock Plus <https://adblockplus.org/>, | 
|    3  * Copyright (C) 2006-present eyeo GmbH |    3  * Copyright (C) 2006-present eyeo GmbH | 
|    4  * |    4  * | 
|    5  * Adblock Plus is free software: you can redistribute it and/or modify |    5  * Adblock Plus is free software: you can redistribute it and/or modify | 
|    6  * it under the terms of the GNU General Public License version 3 as |    6  * it under the terms of the GNU General Public License version 3 as | 
|    7  * published by the Free Software Foundation. |    7  * published by the Free Software Foundation. | 
|    8  * |    8  * | 
|    9  * Adblock Plus is distributed in the hope that it will be useful, |    9  * Adblock Plus is distributed in the hope that it will be useful, | 
|   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of |   10  * but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
|   21  |   21  | 
|   22 const {RegExpFilter} = require("filterClasses"); |   22 const {RegExpFilter} = require("filterClasses"); | 
|   23 const {ElemHide} = require("elemHide"); |   23 const {ElemHide} = require("elemHide"); | 
|   24 const {ElemHideEmulation} = require("elemHideEmulation"); |   24 const {ElemHideEmulation} = require("elemHideEmulation"); | 
|   25 const {checkWhitelisted} = require("whitelisting"); |   25 const {checkWhitelisted} = require("whitelisting"); | 
|   26 const {extractHostFromFrame} = require("url"); |   26 const {extractHostFromFrame} = require("url"); | 
|   27 const {port} = require("messaging"); |   27 const {port} = require("messaging"); | 
|   28 const devtools = require("devtools"); |   28 const devtools = require("devtools"); | 
|   29 const info = require("info"); |   29 const info = require("info"); | 
|   30  |   30  | 
|   31 const cssOriginSupported = "extensionTypes" in browser && |   31 // Chromium's support for tabs.removeCSS is still a work in progress and the | 
|   32                            "CSSOrigin" in browser.extensionTypes; |   32 // API is likely to be different from Firefox's; for now we just don't use it | 
|   33 const userStyleSheetsSupported = cssOriginSupported || |   33 // at all, even if it's available. | 
|   34                                  (info.platform == "chromium" && |   34 // See https://crbug.com/608854 | 
|   35                                   parseInt(info.platformVersion, 10) >= 64); |   35 const styleSheetRemovalSupported = info.platform == "gecko"; | 
|   36  |   36  | 
|   37 function hideElements(tabId, frameId, selectors) |   37 const selectorGroupSize = 1024; | 
 |   38  | 
 |   39 let userStyleSheetsSupported = true; | 
 |   40  | 
 |   41 function* splitSelectors(selectors) | 
|   38 { |   42 { | 
|   39   let options = { |   43   // Chromium's Blink engine supports only up to 8,192 simple selectors, and | 
|   40     code: selectors.join(", ") + "{display: none !important;}", |   44   // even fewer compound selectors, in a rule. The exact number of selectors | 
|   41     frameId, |   45   // that would work depends on their sizes (e.g. "#foo .bar" has a size of 2). | 
|   42     matchAboutBlank: true, |   46   // Since we don't know the sizes of the selectors here, we simply split them | 
|   43     runAt: "document_start" |   47   // into groups of 1,024, based on the reasonable assumption that the average | 
|   44   }; |   48   // selector won't have a size greater than 8. The alternative would be to | 
|   45  |   49   // calculate the sizes of the selectors and divide them up accordingly, but | 
|   46   if (cssOriginSupported) |   50   // this approach is more efficient and has worked well in practice. In theory | 
|   47     options.cssOrigin = "user"; |   51   // this could still lead to some selectors not working on Chromium, but it is | 
|   48  |   52   // highly unlikely. | 
|   49   browser.tabs.insertCSS(tabId, options); |   53   // See issue #6298 and https://crbug.com/804179 | 
 |   54   for (let i = 0; i < selectors.length; i += selectorGroupSize) | 
 |   55     yield selectors.slice(i, i + selectorGroupSize); | 
|   50 } |   56 } | 
|   51  |   57  | 
|   52 port.on("elemhide.getSelectors", (msg, sender) => |   58 function* createRules(selectors) | 
 |   59 { | 
 |   60   for (let selectorGroup of splitSelectors(selectors)) | 
 |   61     yield selectorGroup.join(", ") + " {display: none !important;}"; | 
 |   62 } | 
 |   63  | 
 |   64 function createStyleSheet(selectors) | 
 |   65 { | 
 |   66   return Array.from(createRules(selectors)).join("\n"); | 
 |   67 } | 
 |   68  | 
 |   69 function addStyleSheet(tabId, frameId, styleSheet) | 
 |   70 { | 
 |   71   try | 
 |   72   { | 
 |   73     browser.tabs.insertCSS(tabId, { | 
 |   74       code: styleSheet, | 
 |   75       cssOrigin: "user", | 
 |   76       frameId, | 
 |   77       matchAboutBlank: true, | 
 |   78       runAt: "document_start" | 
 |   79     }); | 
 |   80   } | 
 |   81   catch (error) | 
 |   82   { | 
 |   83     userStyleSheetsSupported = false; | 
 |   84   } | 
 |   85  | 
 |   86   return userStyleSheetsSupported; | 
 |   87 } | 
 |   88  | 
 |   89 function removeStyleSheet(tabId, frameId, styleSheet) | 
 |   90 { | 
 |   91   if (!styleSheetRemovalSupported) | 
 |   92     return; | 
 |   93  | 
 |   94   browser.tabs.removeCSS(tabId, { | 
 |   95     code: styleSheet, | 
 |   96     cssOrigin: "user", | 
 |   97     frameId, | 
 |   98     matchAboutBlank: true | 
 |   99   }); | 
 |  100 } | 
 |  101  | 
 |  102 function updateFrameStyles(tabId, frameId, selectors, groupName) | 
 |  103 { | 
 |  104   let styleSheet = null; | 
 |  105   if (selectors.length > 0) | 
 |  106     styleSheet = createStyleSheet(selectors); | 
 |  107  | 
 |  108   let frame = ext.getFrame(tabId, frameId); | 
 |  109   if (!frame.injectedStyleSheets) | 
 |  110     frame.injectedStyleSheets = new Map(); | 
 |  111  | 
 |  112   let oldStyleSheet = frame.injectedStyleSheets.get(groupName); | 
 |  113  | 
 |  114   // Ideally we would compare the old and new style sheets and skip this code | 
 |  115   // if they're the same, but the old style sheet can be a leftover from a | 
 |  116   // previous instance of the frame. We must add the new style sheet | 
 |  117   // regardless. | 
 |  118  | 
 |  119   // Add the new style sheet first to keep previously hidden elements from | 
 |  120   // reappearing momentarily. | 
 |  121   if (styleSheet && !addStyleSheet(tabId, frameId, styleSheet)) | 
 |  122     return false; | 
 |  123  | 
 |  124   // Sometimes the old and new style sheets can be exactly the same. In such a | 
 |  125   // case, do not remove the "old" style sheet, because it is in fact the new | 
 |  126   // style sheet now. | 
 |  127   if (oldStyleSheet && oldStyleSheet != styleSheet) | 
 |  128     removeStyleSheet(tabId, frameId, oldStyleSheet); | 
 |  129  | 
 |  130   frame.injectedStyleSheets.set(groupName, styleSheet); | 
 |  131   return true; | 
 |  132 } | 
 |  133  | 
 |  134 port.on("elemhide.getSelectors", (message, sender) => | 
|   53 { |  135 { | 
|   54   let selectors = []; |  136   let selectors = []; | 
|   55   let emulatedPatterns = []; |  137   let emulatedPatterns = []; | 
|   56   let trace = devtools && devtools.hasPanel(sender.page); |  138   let trace = devtools && devtools.hasPanel(sender.page); | 
|   57   let inject = !userStyleSheetsSupported; |  139   let inline = !userStyleSheetsSupported; | 
|   58  |  140  | 
|   59   if (!checkWhitelisted(sender.page, sender.frame, |  141   if (!checkWhitelisted(sender.page, sender.frame, | 
|   60                         RegExpFilter.typeMap.DOCUMENT | |  142                         RegExpFilter.typeMap.DOCUMENT | | 
|   61                         RegExpFilter.typeMap.ELEMHIDE)) |  143                         RegExpFilter.typeMap.ELEMHIDE)) | 
|   62   { |  144   { | 
|   63     let hostname = extractHostFromFrame(sender.frame); |  145     let hostname = extractHostFromFrame(sender.frame); | 
|   64     let specificOnly = checkWhitelisted(sender.page, sender.frame, |  146     let specificOnly = checkWhitelisted(sender.page, sender.frame, | 
|   65                                         RegExpFilter.typeMap.GENERICHIDE); |  147                                         RegExpFilter.typeMap.GENERICHIDE); | 
|   66  |  148  | 
|   67     selectors = ElemHide.getSelectorsForDomain( |  149     selectors = ElemHide.getSelectorsForDomain( | 
|   68       hostname, |  150       hostname, | 
|   69       specificOnly ? ElemHide.SPECIFIC_ONLY : ElemHide.ALL_MATCHING |  151       specificOnly ? ElemHide.SPECIFIC_ONLY : ElemHide.ALL_MATCHING | 
|   70     ); |  152     ); | 
|   71  |  153  | 
|   72     for (let filter of ElemHideEmulation.getRulesForDomain(hostname)) |  154     for (let filter of ElemHideEmulation.getRulesForDomain(hostname)) | 
|   73       emulatedPatterns.push({selector: filter.selector, text: filter.text}); |  155       emulatedPatterns.push({selector: filter.selector, text: filter.text}); | 
|   74   } |  156   } | 
|   75  |  157  | 
|   76   if (!inject && selectors.length > 0) |  158   if (!inline && !updateFrameStyles(sender.page.id, sender.frame.id, | 
|   77     hideElements(sender.page.id, sender.frame.id, selectors); |  159                                     selectors, "standard")) | 
 |  160   { | 
 |  161     inline = true; | 
 |  162   } | 
|   78  |  163  | 
|   79   let response = {trace, inject, emulatedPatterns}; |  164   let response = {trace, inline, emulatedPatterns}; | 
|   80   if (trace || inject) |  165   if (trace || inline) | 
|   81     response.selectors = selectors; |  166     response.selectors = selectors; | 
 |  167  | 
 |  168   // If we can't remove user style sheets using tabs.removeCSS, we'll only keep | 
 |  169   // adding them, which could cause problems with emulation filters as | 
 |  170   // described in issue #5864. Instead, we can just ask the content script to | 
 |  171   // add styles for emulation filters inline. | 
 |  172   if (!styleSheetRemovalSupported) | 
 |  173     response.inlineEmulated = true; | 
|   82  |  174  | 
|   83   return response; |  175   return response; | 
|   84 }); |  176 }); | 
|   85  |  177  | 
|   86 port.on("elemhide.injectSelectors", (msg, sender) => |  178 port.on("elemhide.injectSelectors", (message, sender) => | 
|   87 { |  179 { | 
|   88   hideElements(sender.page.id, sender.frame.id, msg.selectors); |  180   updateFrameStyles(sender.page.id, sender.frame.id, message.selectors, | 
 |  181                     message.groupName); | 
|   89 }); |  182 }); | 
| LEFT | RIGHT |