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 |