| 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 | 
| 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 "use strict"; | 18 "use strict"; | 
| 19 | 19 | 
| 20 const {port} = require("messaging"); | 20 const {RegExpFilter, | 
| 21 const {DevLogger} = require("devLogger"); | 21        WhitelistFilter, | 
|  | 22        ElemHideFilter} = require("../adblockpluscore/lib/filterClasses"); | 
|  | 23 const {SpecialSubscription} = | 
|  | 24   require("../adblockpluscore/lib/subscriptionClasses"); | 
|  | 25 const {FilterStorage} = require("../adblockpluscore/lib/filterStorage"); | 
|  | 26 const {defaultMatcher} = require("../adblockpluscore/lib/matcher"); | 
|  | 27 const {FilterNotifier} = require("../adblockpluscore/lib/filterNotifier"); | 
|  | 28 const {extractHostFromFrame} = require("./url"); | 
|  | 29 const {port} = require("./messaging"); | 
|  | 30 const {HitLogger, nonRequestTypes} = require("./hitLogger"); | 
|  | 31 | 
|  | 32 let panels = new Map(); | 
|  | 33 | 
|  | 34 function isActivePanel(panel) | 
|  | 35 { | 
|  | 36   return panel && !panel.reload && !panel.reloading; | 
|  | 37 } | 
|  | 38 | 
|  | 39 function getActivePanel(tabId) | 
|  | 40 { | 
|  | 41   let panel = panels.get(tabId); | 
|  | 42   if (isActivePanel(panel)) | 
|  | 43     return panel; | 
|  | 44   return null; | 
|  | 45 } | 
|  | 46 | 
|  | 47 function getFilterInfo(filter) | 
|  | 48 { | 
|  | 49   if (!filter) | 
|  | 50     return null; | 
|  | 51 | 
|  | 52   let userDefined = false; | 
|  | 53   let subscriptionTitle = null; | 
|  | 54 | 
|  | 55   for (let subscription of filter.subscriptions) | 
|  | 56   { | 
|  | 57     if (!subscription.disabled) | 
|  | 58     { | 
|  | 59       if (subscription instanceof SpecialSubscription) | 
|  | 60         userDefined = true; | 
|  | 61       else | 
|  | 62         subscriptionTitle = subscription.title; | 
|  | 63     } | 
|  | 64   } | 
|  | 65 | 
|  | 66   return { | 
|  | 67     text: filter.text, | 
|  | 68     whitelisted: filter instanceof WhitelistFilter, | 
|  | 69     userDefined, | 
|  | 70     subscription: subscriptionTitle | 
|  | 71   }; | 
|  | 72 } | 
|  | 73 | 
|  | 74 function hasRecord(panel, request, filter) | 
|  | 75 { | 
|  | 76   return panel.records.some(record => | 
|  | 77     record.request.url == request.url && | 
|  | 78     record.request.docDomain == request.docDomain && | 
|  | 79 | 
|  | 80     // Ignore partial (e.g. ELEMHIDE) whitelisting if there is already | 
|  | 81     // a DOCUMENT exception which disables all means of blocking. | 
|  | 82     (record.request.type == "DOCUMENT" ? | 
|  | 83        nonRequestTypes.includes(request.type) : | 
|  | 84        record.request.type == request.type) && | 
|  | 85 | 
|  | 86     // Matched element hiding filters don't relate to a particular request, | 
|  | 87     // so we have to compare the selector in order to avoid duplicates. | 
|  | 88     (record.filter && record.filter.selector) == (filter && filter.selector) | 
|  | 89   ); | 
|  | 90 } | 
|  | 91 | 
|  | 92 function addRecord(panel, request, filter) | 
|  | 93 { | 
|  | 94   if (!hasRecord(panel, request, filter)) | 
|  | 95   { | 
|  | 96     panel.port.postMessage({ | 
|  | 97       type: "add-record", | 
|  | 98       request, | 
|  | 99       filter: getFilterInfo(filter) | 
|  | 100     }); | 
|  | 101 | 
|  | 102     panel.records.push({request, filter}); | 
|  | 103   } | 
|  | 104 } | 
|  | 105 | 
|  | 106 function matchRequest(request) | 
|  | 107 { | 
|  | 108   return defaultMatcher.matchesAny( | 
|  | 109     request.url, | 
|  | 110     RegExpFilter.typeMap[request.type], | 
|  | 111     request.docDomain, | 
|  | 112     request.thirdParty, | 
|  | 113     request.sitekey, | 
|  | 114     request.specificOnly | 
|  | 115   ); | 
|  | 116 } | 
|  | 117 | 
|  | 118 function onBeforeRequest(details) | 
|  | 119 { | 
|  | 120   let panel = panels.get(details.tabId); | 
|  | 121 | 
|  | 122   // Clear the devtools panel and reload the inspected tab without caching | 
|  | 123   // when a new request is issued. However, make sure that we don't end up | 
|  | 124   // in an infinite recursion if we already triggered a reload. | 
|  | 125   if (panel.reloading) | 
|  | 126   { | 
|  | 127     panel.reloading = false; | 
|  | 128   } | 
|  | 129   else | 
|  | 130   { | 
|  | 131     panel.records = []; | 
|  | 132     panel.port.postMessage({type: "reset"}); | 
|  | 133 | 
|  | 134     // We can't repeat the request if it isn't a GET request. Chrome would | 
|  | 135     // prompt the user to confirm reloading the page, and POST requests are | 
|  | 136     // known to cause issues on many websites if repeated. | 
|  | 137     if (details.method == "GET") | 
|  | 138       panel.reload = true; | 
|  | 139   } | 
|  | 140 } | 
|  | 141 | 
|  | 142 function onLoading(page) | 
|  | 143 { | 
|  | 144   let tabId = page.id; | 
|  | 145   let panel = panels.get(tabId); | 
|  | 146 | 
|  | 147   // Reloading the tab is the only way that allows bypassing all caches, in | 
|  | 148   // order to see all requests in the devtools panel. Reloading must not be | 
|  | 149   // performed before the tab changes to "loading", otherwise it will load the | 
|  | 150   // previous URL. | 
|  | 151   if (panel && panel.reload) | 
|  | 152   { | 
|  | 153     browser.tabs.reload(tabId, {bypassCache: true}); | 
|  | 154 | 
|  | 155     panel.reload = false; | 
|  | 156     panel.reloading = true; | 
|  | 157   } | 
|  | 158 } | 
|  | 159 | 
|  | 160 function updateFilters(filters, added) | 
|  | 161 { | 
|  | 162   for (let panel of panels.values()) | 
|  | 163   { | 
|  | 164     for (let i = 0; i < panel.records.length; i++) | 
|  | 165     { | 
|  | 166       let record = panel.records[i]; | 
|  | 167 | 
|  | 168       // If an added filter matches a request shown in the devtools panel, | 
|  | 169       // update that record to show the new filter. Ignore filters that aren't | 
|  | 170       // associated with any sub-resource request. There is no record for these | 
|  | 171       // if they don't already match. In particular, in case of element hiding | 
|  | 172       // filters, we also wouldn't know if any new element matches. | 
|  | 173       if (added) | 
|  | 174       { | 
|  | 175         if (nonRequestTypes.includes(record.request.type)) | 
|  | 176           continue; | 
|  | 177 | 
|  | 178         let filter = matchRequest(record.request); | 
|  | 179         if (!filters.includes(filter)) | 
|  | 180           continue; | 
|  | 181 | 
|  | 182         record.filter = filter; | 
|  | 183       } | 
|  | 184 | 
|  | 185       // If a filter shown in the devtools panel got removed, update that | 
|  | 186       // record to show the filter that matches now, or none, instead. | 
|  | 187       // For filters that aren't associated with any sub-resource request, | 
|  | 188       // just remove the record. We wouldn't know whether another filter | 
|  | 189       // matches instead until the page is reloaded. | 
|  | 190       else | 
|  | 191       { | 
|  | 192         if (!filters.includes(record.filter)) | 
|  | 193           continue; | 
|  | 194 | 
|  | 195         if (nonRequestTypes.includes(record.request.type)) | 
|  | 196         { | 
|  | 197           panel.port.postMessage({ | 
|  | 198             type: "remove-record", | 
|  | 199             index: i | 
|  | 200           }); | 
|  | 201           panel.records.splice(i--, 1); | 
|  | 202           continue; | 
|  | 203         } | 
|  | 204 | 
|  | 205         record.filter = matchRequest(record.request); | 
|  | 206       } | 
|  | 207 | 
|  | 208       panel.port.postMessage({ | 
|  | 209         type: "update-record", | 
|  | 210         index: i, | 
|  | 211         request: record.request, | 
|  | 212         filter: getFilterInfo(record.filter) | 
|  | 213       }); | 
|  | 214     } | 
|  | 215   } | 
|  | 216 } | 
|  | 217 | 
|  | 218 function onFilterAdded(filter) | 
|  | 219 { | 
|  | 220   updateFilters([filter], true); | 
|  | 221 } | 
|  | 222 | 
|  | 223 function onFilterRemoved(filter) | 
|  | 224 { | 
|  | 225   updateFilters([filter], false); | 
|  | 226 } | 
|  | 227 | 
|  | 228 function onSubscriptionAdded(subscription) | 
|  | 229 { | 
|  | 230   if (subscription instanceof SpecialSubscription) | 
|  | 231     updateFilters(subscription.filters, true); | 
|  | 232 } | 
| 22 | 233 | 
| 23 browser.runtime.onConnect.addListener(newPort => | 234 browser.runtime.onConnect.addListener(newPort => | 
| 24 { | 235 { | 
| 25   let match = newPort.name.match(/^devtools-(\d+)$/); | 236   let match = newPort.name.match(/^devtools-(\d+)$/); | 
| 26   if (!match) | 237   if (!match) | 
| 27     return; | 238     return; | 
| 28 | 239 | 
| 29   let inspectedTabId = parseInt(match[1], 10); | 240   let inspectedTabId = parseInt(match[1], 10); | 
| 30   let onDevLogger = newPort.postMessage.bind(newPort); | 241   let localOnBeforeRequest = onBeforeRequest.bind(); | 
| 31 | 242   let panel = {port: newPort, records: []}; | 
| 32   DevLogger.on(inspectedTabId, onDevLogger); | 243   let hitListener = addRecord.bind(null, panel); | 
|  | 244 | 
|  | 245   browser.webRequest.onBeforeRequest.addListener( | 
|  | 246     localOnBeforeRequest, | 
|  | 247     { | 
|  | 248       urls: ["http://*/*", "https://*/*"], | 
|  | 249       types: ["main_frame"], | 
|  | 250       tabId: inspectedTabId | 
|  | 251     } | 
|  | 252   ); | 
|  | 253 | 
|  | 254   if (panels.size == 0) | 
|  | 255   { | 
|  | 256     ext.pages.onLoading.addListener(onLoading); | 
|  | 257     FilterNotifier.on("filter.added", onFilterAdded); | 
|  | 258     FilterNotifier.on("filter.removed", onFilterRemoved); | 
|  | 259     FilterNotifier.on("subscription.added", onSubscriptionAdded); | 
|  | 260   } | 
|  | 261 | 
| 33   newPort.onDisconnect.addListener(() => | 262   newPort.onDisconnect.addListener(() => | 
| 34   { | 263   { | 
| 35     DevLogger.off(inspectedTabId, onDevLogger); | 264     HitLogger.removeListener(inspectedTabId, hitListener); | 
|  | 265     panels.delete(inspectedTabId); | 
|  | 266     browser.webRequest.onBeforeRequest.removeListener(localOnBeforeRequest); | 
|  | 267 | 
|  | 268     if (panels.size == 0) | 
|  | 269     { | 
|  | 270       ext.pages.onLoading.removeListener(onLoading); | 
|  | 271       FilterNotifier.off("filter.added", onFilterAdded); | 
|  | 272       FilterNotifier.off("filter.removed", onFilterRemoved); | 
|  | 273       FilterNotifier.off("subscription.added", onSubscriptionAdded); | 
|  | 274     } | 
| 36   }); | 275   }); | 
|  | 276 | 
|  | 277   HitLogger.addListener(inspectedTabId, hitListener); | 
|  | 278   panels.set(inspectedTabId, panel); | 
| 37 }); | 279 }); | 
| LEFT | RIGHT | 
|---|