| OLD | NEW | 
|---|
| 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 | 
| 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the | 
| 12  * GNU General Public License for more details. | 12  * GNU General Public License for more details. | 
| 13  * | 13  * | 
| 14  * You should have received a copy of the GNU General Public License | 14  * You should have received a copy of the GNU General Public License | 
| 15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 15  * along with Adblock Plus.  If not, see <http://www.gnu.org/licenses/>. | 
| 16  */ | 16  */ | 
| 17 | 17 | 
| 18 /** @module cssInjection */ | 18 /** @module cssInjection */ | 
| 19 | 19 | 
| 20 "use strict"; | 20 "use strict"; | 
| 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 | 30 | 
| 30 const userStyleSheetsSupported = "extensionTypes" in browser && | 31 // Chromium's support for tabs.removeCSS is still a work in progress and the | 
| 31                                  "CSSOrigin" in browser.extensionTypes; | 32 // API is likely to be different from Firefox's; for now we just don't use it | 
|  | 33 // at all, even if it's available. | 
|  | 34 // See https://crbug.com/608854 | 
|  | 35 const styleSheetRemovalSupported = info.platform == "gecko"; | 
|  | 36 | 
| 32 const selectorGroupSize = 1024; | 37 const selectorGroupSize = 1024; | 
| 33 | 38 | 
|  | 39 let userStyleSheetsSupported = true; | 
|  | 40 | 
| 34 function* splitSelectors(selectors) | 41 function* splitSelectors(selectors) | 
| 35 { | 42 { | 
| 36   // Chromium's Blink engine supports only up to 8,192 simple selectors, and | 43   // Chromium's Blink engine supports only up to 8,192 simple selectors, and | 
| 37   // even fewer compound selectors, in a rule. The exact number of selectors | 44   // even fewer compound selectors, in a rule. The exact number of selectors | 
| 38   // that would work depends on their sizes (e.g. "#foo .bar" has a size of 2). | 45   // that would work depends on their sizes (e.g. "#foo .bar" has a size of 2). | 
| 39   // Since we don't know the sizes of the selectors here, we simply split them | 46   // Since we don't know the sizes of the selectors here, we simply split them | 
| 40   // into groups of 1,024, based on the reasonable assumption that the average | 47   // into groups of 1,024, based on the reasonable assumption that the average | 
| 41   // selector won't have a size greater than 8. The alternative would be to | 48   // selector won't have a size greater than 8. The alternative would be to | 
| 42   // calculate the sizes of the selectors and divide them up accordingly, but | 49   // calculate the sizes of the selectors and divide them up accordingly, but | 
| 43   // this approach is more efficient and has worked well in practice. In theory | 50   // this approach is more efficient and has worked well in practice. In theory | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
| 54     yield selectorGroup.join(", ") + " {display: none !important;}"; | 61     yield selectorGroup.join(", ") + " {display: none !important;}"; | 
| 55 } | 62 } | 
| 56 | 63 | 
| 57 function createStyleSheet(selectors) | 64 function createStyleSheet(selectors) | 
| 58 { | 65 { | 
| 59   return Array.from(createRules(selectors)).join("\n"); | 66   return Array.from(createRules(selectors)).join("\n"); | 
| 60 } | 67 } | 
| 61 | 68 | 
| 62 function addStyleSheet(tabId, frameId, styleSheet) | 69 function addStyleSheet(tabId, frameId, styleSheet) | 
| 63 { | 70 { | 
| 64   browser.tabs.insertCSS(tabId, { | 71   try | 
| 65     code: styleSheet, | 72   { | 
| 66     cssOrigin: "user", | 73     browser.tabs.insertCSS(tabId, { | 
| 67     frameId, | 74       code: styleSheet, | 
| 68     matchAboutBlank: true, | 75       cssOrigin: "user", | 
| 69     runAt: "document_start" | 76       frameId, | 
| 70   }); | 77       matchAboutBlank: true, | 
|  | 78       runAt: "document_start" | 
|  | 79     }); | 
|  | 80   } | 
|  | 81   catch (error) | 
|  | 82   { | 
|  | 83     if (!/\bcssOrigin\b/.test(error.message)) | 
|  | 84       throw error; | 
|  | 85 | 
|  | 86     userStyleSheetsSupported = false; | 
|  | 87   } | 
|  | 88 | 
|  | 89   return userStyleSheetsSupported; | 
| 71 } | 90 } | 
| 72 | 91 | 
| 73 function removeStyleSheet(tabId, frameId, styleSheet) | 92 function removeStyleSheet(tabId, frameId, styleSheet) | 
| 74 { | 93 { | 
|  | 94   if (!styleSheetRemovalSupported) | 
|  | 95     return; | 
|  | 96 | 
| 75   browser.tabs.removeCSS(tabId, { | 97   browser.tabs.removeCSS(tabId, { | 
| 76     code: styleSheet, | 98     code: styleSheet, | 
| 77     cssOrigin: "user", | 99     cssOrigin: "user", | 
| 78     frameId, | 100     frameId, | 
| 79     matchAboutBlank: true | 101     matchAboutBlank: true | 
| 80   }); | 102   }); | 
| 81 } | 103 } | 
| 82 | 104 | 
| 83 function updateFrameStyles(tabId, frameId, selectors, groupName) | 105 function updateFrameStyles(tabId, frameId, selectors, groupName) | 
| 84 { | 106 { | 
| 85   let styleSheet = null; | 107   let styleSheet = null; | 
| 86   if (selectors.length > 0) | 108   if (selectors.length > 0) | 
| 87     styleSheet = createStyleSheet(selectors); | 109     styleSheet = createStyleSheet(selectors); | 
| 88 | 110 | 
| 89   let frame = ext.getFrame(tabId, frameId); | 111   let frame = ext.getFrame(tabId, frameId); | 
| 90   if (!frame.injectedStyleSheets) | 112   if (!frame.injectedStyleSheets) | 
| 91     frame.injectedStyleSheets = new Map(); | 113     frame.injectedStyleSheets = new Map(); | 
| 92 | 114 | 
| 93   let oldStyleSheet = frame.injectedStyleSheets.get(groupName); | 115   let oldStyleSheet = frame.injectedStyleSheets.get(groupName); | 
| 94 | 116 | 
| 95   // Ideally we would compare the old and new style sheets and skip this code | 117   // Ideally we would compare the old and new style sheets and skip this code | 
| 96   // if they're the same, but the old style sheet can be a leftover from a | 118   // if they're the same, but the old style sheet can be a leftover from a | 
| 97   // previous instance of the frame. We must add the new style sheet | 119   // previous instance of the frame. We must add the new style sheet | 
| 98   // regardless. | 120   // regardless. | 
| 99 | 121 | 
| 100   // Add the new style sheet first to keep previously hidden elements from | 122   // Add the new style sheet first to keep previously hidden elements from | 
| 101   // reappearing momentarily. | 123   // reappearing momentarily. | 
| 102   if (styleSheet) | 124   if (styleSheet && !addStyleSheet(tabId, frameId, styleSheet)) | 
| 103     addStyleSheet(tabId, frameId, styleSheet); | 125     return false; | 
| 104 | 126 | 
| 105   // Sometimes the old and new style sheets can be exactly the same. In such a | 127   // Sometimes the old and new style sheets can be exactly the same. In such a | 
| 106   // case, do not remove the "old" style sheet, because it is in fact the new | 128   // case, do not remove the "old" style sheet, because it is in fact the new | 
| 107   // style sheet now. | 129   // style sheet now. | 
| 108   if (oldStyleSheet && oldStyleSheet != styleSheet) | 130   if (oldStyleSheet && oldStyleSheet != styleSheet) | 
| 109     removeStyleSheet(tabId, frameId, oldStyleSheet); | 131     removeStyleSheet(tabId, frameId, oldStyleSheet); | 
| 110 | 132 | 
| 111   frame.injectedStyleSheets.set(groupName, styleSheet); | 133   frame.injectedStyleSheets.set(groupName, styleSheet); | 
|  | 134   return true; | 
| 112 } | 135 } | 
| 113 | 136 | 
| 114 port.on("elemhide.getSelectors", (message, sender) => | 137 port.on("elemhide.getSelectors", (message, sender) => | 
| 115 { | 138 { | 
| 116   let selectors = []; | 139   let selectors = []; | 
| 117   let emulatedPatterns = []; | 140   let emulatedPatterns = []; | 
| 118   let trace = devtools && devtools.hasPanel(sender.page); | 141   let trace = devtools && devtools.hasPanel(sender.page); | 
| 119   let inline = !userStyleSheetsSupported; | 142   let inline = !userStyleSheetsSupported; | 
| 120 | 143 | 
| 121   if (!checkWhitelisted(sender.page, sender.frame, | 144   if (!checkWhitelisted(sender.page, sender.frame, | 
| 122                         RegExpFilter.typeMap.DOCUMENT | | 145                         RegExpFilter.typeMap.DOCUMENT | | 
| 123                         RegExpFilter.typeMap.ELEMHIDE)) | 146                         RegExpFilter.typeMap.ELEMHIDE)) | 
| 124   { | 147   { | 
| 125     let hostname = extractHostFromFrame(sender.frame); | 148     let hostname = extractHostFromFrame(sender.frame); | 
| 126     let specificOnly = checkWhitelisted(sender.page, sender.frame, | 149     let specificOnly = checkWhitelisted(sender.page, sender.frame, | 
| 127                                         RegExpFilter.typeMap.GENERICHIDE); | 150                                         RegExpFilter.typeMap.GENERICHIDE); | 
| 128 | 151 | 
| 129     selectors = ElemHide.getSelectorsForDomain( | 152     selectors = ElemHide.getSelectorsForDomain( | 
| 130       hostname, | 153       hostname, | 
| 131       specificOnly ? ElemHide.SPECIFIC_ONLY : ElemHide.ALL_MATCHING | 154       specificOnly ? ElemHide.SPECIFIC_ONLY : ElemHide.ALL_MATCHING | 
| 132     ); | 155     ); | 
| 133 | 156 | 
| 134     for (let filter of ElemHideEmulation.getRulesForDomain(hostname)) | 157     for (let filter of ElemHideEmulation.getRulesForDomain(hostname)) | 
| 135       emulatedPatterns.push({selector: filter.selector, text: filter.text}); | 158       emulatedPatterns.push({selector: filter.selector, text: filter.text}); | 
| 136   } | 159   } | 
| 137 | 160 | 
| 138   if (!inline) | 161   if (!inline && !updateFrameStyles(sender.page.id, sender.frame.id, | 
| 139     updateFrameStyles(sender.page.id, sender.frame.id, selectors, "standard"); | 162                                     selectors, "standard")) | 
|  | 163   { | 
|  | 164     inline = true; | 
|  | 165   } | 
| 140 | 166 | 
| 141   let response = {trace, inline, emulatedPatterns}; | 167   let response = {trace, inline, emulatedPatterns}; | 
| 142   if (trace || inline) | 168   if (trace || inline) | 
| 143     response.selectors = selectors; | 169     response.selectors = selectors; | 
| 144 | 170 | 
|  | 171   // If we can't remove user style sheets using tabs.removeCSS, we'll only keep | 
|  | 172   // adding them, which could cause problems with emulation filters as | 
|  | 173   // described in issue #5864. Instead, we can just ask the content script to | 
|  | 174   // add styles for emulation filters inline. | 
|  | 175   if (!styleSheetRemovalSupported) | 
|  | 176     response.inlineEmulated = true; | 
|  | 177 | 
| 145   return response; | 178   return response; | 
| 146 }); | 179 }); | 
| 147 | 180 | 
| 148 port.on("elemhide.injectSelectors", (message, sender) => | 181 port.on("elemhide.injectSelectors", (message, sender) => | 
| 149 { | 182 { | 
| 150   updateFrameStyles(sender.page.id, sender.frame.id, message.selectors, | 183   updateFrameStyles(sender.page.id, sender.frame.id, message.selectors, | 
| 151                     message.groupName); | 184                     message.groupName); | 
| 152 }); | 185 }); | 
| OLD | NEW | 
|---|