Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/devtools.js

Issue 29452181: Noissue - Merge current tip to Edge bookmark (Closed)
Patch Set: Created May 30, 2017, 3:49 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/csp.js ('k') | lib/filterComposer.js » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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-2016 Eyeo GmbH 3 * Copyright (C) 2006-2017 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 {RegExpFilter, WhitelistFilter, ElemHideFilter} = require("filterClasses") ; 20 const {RegExpFilter,
21 WhitelistFilter,
22 ElemHideFilter} = require("filterClasses");
21 const {SpecialSubscription} = require("subscriptionClasses"); 23 const {SpecialSubscription} = require("subscriptionClasses");
22 const {FilterStorage} = require("filterStorage"); 24 const {FilterStorage} = require("filterStorage");
23 const {defaultMatcher} = require("matcher"); 25 const {defaultMatcher} = require("matcher");
24 const {FilterNotifier} = require("filterNotifier"); 26 const {FilterNotifier} = require("filterNotifier");
25 const {extractHostFromFrame} = require("url"); 27 const {extractHostFromFrame} = require("url");
26 const {port} = require("messaging"); 28 const {port} = require("messaging");
27 29
28 const nonRequestTypes = ["DOCUMENT", "ELEMHIDE", "GENERICBLOCK", "GENERICHIDE"]; 30 const nonRequestTypes = ["DOCUMENT", "ELEMHIDE", "GENERICBLOCK", "GENERICHIDE"];
29 31
30 // Mapping of inspected tabs to their devpanel page 32 // Mapping of inspected tabs to their devpanel page
31 // and recorded items. We can't use a PageMap here, 33 // and recorded items. We can't use a PageMap here,
32 // because data must persist after navigation/reload. 34 // because data must persist after navigation/reload.
33 let panels = Object.create(null); 35 let panels = new Map();
34
35 function hasPanels()
36 {
37 return Object.keys(panels).length > 0;
38 }
39 36
40 function getActivePanel(page) 37 function getActivePanel(page)
41 { 38 {
42 let panel = panels[page.id]; 39 let panel = panels.get(page.id);
43 if(panel && !panel.reload && !panel.reloading) 40 if (panel && !panel.reload && !panel.reloading)
44 return panel; 41 return panel;
45 return null; 42 return null;
46 } 43 }
47 44
48 function getFilterInfo(filter) 45 function getFilterInfo(filter)
49 { 46 {
50 if (!filter) 47 if (!filter)
51 return null; 48 return null;
52 49
53 let userDefined = false; 50 let userDefined = false;
54 let subscriptionTitle = null; 51 let subscriptionTitle = null;
55 52
56 for (let subscription of filter.subscriptions) 53 for (let subscription of filter.subscriptions)
57 { 54 {
58 if (!subscription.disabled) 55 if (!subscription.disabled)
59 { 56 {
60 if (subscription instanceof SpecialSubscription) 57 if (subscription instanceof SpecialSubscription)
61 userDefined = true; 58 userDefined = true;
62 else 59 else
63 subscriptionTitle = subscription.title; 60 subscriptionTitle = subscription.title;
64 } 61 }
65 } 62 }
66 63
67 return { 64 return {
68 text: filter.text, 65 text: filter.text,
69 whitelisted: filter instanceof WhitelistFilter, 66 whitelisted: filter instanceof WhitelistFilter,
70 userDefined: userDefined, 67 userDefined,
71 subscription: subscriptionTitle 68 subscription: subscriptionTitle
72 }; 69 };
73 } 70 }
74 71
75 function hasRecord(panel, request, filter) 72 function hasRecord(panel, request, filter)
76 { 73 {
77 return panel.records.some(record => 74 return panel.records.some(record =>
78 record.request.url == request.url && 75 record.request.url == request.url &&
79 record.request.docDomain == request.docDomain && 76 record.request.docDomain == request.docDomain &&
80 77
81 // Ignore partial (e.g. ELEMHIDE) whitelisting if there is already 78 // Ignore partial (e.g. ELEMHIDE) whitelisting if there is already
82 // a DOCUMENT exception which disables all means of blocking. 79 // a DOCUMENT exception which disables all means of blocking.
83 (record.request.type == "DOCUMENT" ? nonRequestTypes.indexOf(request.type) ! = -1 80 (record.request.type == "DOCUMENT" ?
84 : record.request.type == request.type) && 81 nonRequestTypes.includes(request.type) :
82 record.request.type == request.type) &&
85 83
86 // Matched element hiding filters don't relate to a particular request, 84 // Matched element hiding filters don't relate to a particular request,
87 // so we also have to match the CSS selector in order to distinguish them. 85 // so we have to compare the selector in order to avoid duplicates.
88 (record.filter && record.filter.selector) == (filter && filter.selector) 86 (record.filter && record.filter.selector) == (filter && filter.selector)
89 ); 87 );
90 } 88 }
91 89
92 function addRecord(panel, request, filter) 90 function addRecord(panel, request, filter)
93 { 91 {
94 if (!hasRecord(panel, request, filter)) 92 if (!hasRecord(panel, request, filter))
95 { 93 {
96 panel.port.postMessage({ 94 panel.port.postMessage({
97 type: "add-record", 95 type: "add-record",
98 request: request, 96 request,
99 filter: getFilterInfo(filter) 97 filter: getFilterInfo(filter)
100 }); 98 });
101 99
102 panel.records.push({ 100 panel.records.push({request, filter});
103 request: request,
104 filter: filter
105 });
106 } 101 }
107 } 102 }
108 103
109 function matchRequest(request) 104 function matchRequest(request)
110 { 105 {
111 return defaultMatcher.matchesAny( 106 return defaultMatcher.matchesAny(
112 request.url, 107 request.url,
113 RegExpFilter.typeMap[request.type], 108 RegExpFilter.typeMap[request.type],
114 request.docDomain, 109 request.docDomain,
115 request.thirdParty, 110 request.thirdParty,
116 request.sitekey, 111 request.sitekey,
117 request.specificOnly 112 request.specificOnly
118 ); 113 );
119 } 114 }
120 115
121 /** 116 /**
122 * Logs a request to the devtools panel. 117 * Logs a request to the devtools panel.
123 * 118 *
124 * @param {Page} page The page the request occured on 119 * @param {Page} page The page the request occured on
125 * @param {string} url The URL of the request 120 * @param {string} url The URL of the request
126 * @param {string} type The request type 121 * @param {string} type The request type
127 * @param {string} docDomain The IDN-decoded hostname of the document 122 * @param {string} docDomain The IDN-decoded hostname of the document
128 * @param {boolean} thirdParty Whether the origin of the request and documen t differs 123 * @param {boolean} thirdParty Whether the origin of the request and
124 * document differs
129 * @param {?string} sitekey The active sitekey if there is any 125 * @param {?string} sitekey The active sitekey if there is any
130 * @param {?boolean} specificOnly Whether generic filters should be ignored 126 * @param {?boolean} specificOnly Whether generic filters should be ignored
131 * @param {?BlockingFilter} filter The matched filter or null if there is no mat ch 127 * @param {?BlockingFilter} filter The matched filter or null if there is no
128 * match
132 */ 129 */
133 exports.logRequest = function(page, url, type, docDomain, 130 exports.logRequest = function(page, url, type, docDomain,
134 thirdParty, sitekey, 131 thirdParty, sitekey,
135 specificOnly, filter) 132 specificOnly, filter)
136 { 133 {
137 let panel = getActivePanel(page); 134 let panel = getActivePanel(page);
138 if (panel) 135 if (panel)
139 { 136 {
140 let request = { 137 let request = {url, type, docDomain, thirdParty, sitekey, specificOnly};
141 url: url,
142 type: type,
143 docDomain: docDomain,
144 thirdParty: thirdParty,
145 sitekey: sitekey,
146 specificOnly: specificOnly
147 };
148
149 addRecord(panel, request, filter); 138 addRecord(panel, request, filter);
150 } 139 }
151 }; 140 };
152 141
153 /** 142 /**
154 * Logs active element hiding filters to the devtools panel. 143 * Logs active element hiding filters to the devtools panel.
155 * 144 *
156 * @param {Page} page The page the elements were hidden on 145 * @param {Page} page The page the elements were hidden on
157 * @param {string[]} selectors The CSS selectors of active elemhide filters 146 * @param {string[]} selectors The selectors of applied ElemHideFilters
147 * @param {string[]} filters The text of applied ElemHideEmulationFilters
158 * @param {string} docDomain The IDN-decoded hostname of the document 148 * @param {string} docDomain The IDN-decoded hostname of the document
159 */ 149 */
160 function logHiddenElements(page, selectors, docDomain) 150 function logHiddenElements(page, selectors, filters, docDomain)
161 { 151 {
162 let panel = getActivePanel(page); 152 let panel = getActivePanel(page);
153 if (panel)
163 { 154 {
164 for (let subscription of FilterStorage.subscriptions) 155 for (let subscription of FilterStorage.subscriptions)
165 { 156 {
166 if (subscription.disabled) 157 if (subscription.disabled)
167 continue; 158 continue;
168 159
169 for (let filter of subscription.filters) 160 for (let filter of subscription.filters)
170 { 161 {
171 if (!(filter instanceof ElemHideFilter)) 162 // We only know the exact filter in case of element hiding emulation.
172 continue; 163 // For regular element hiding filters, the content script only knows
173 if (selectors.indexOf(filter.selector) == -1) 164 // the selector, so we have to find a filter that has an identical
174 continue; 165 // selector and is active on the domain the match was reported from.
175 if (!filter.isActiveOnDomain(docDomain)) 166 let isActiveElemHideFilter = filter instanceof ElemHideFilter &&
176 continue; 167 selectors.includes(filter.selector) &&
168 filter.isActiveOnDomain(docDomain);
177 169
178 addRecord(panel, {type: "ELEMHIDE", docDomain: docDomain}, filter); 170 if (isActiveElemHideFilter || filters.includes(filter.text))
171 addRecord(panel, {type: "ELEMHIDE", docDomain}, filter);
179 } 172 }
180 } 173 }
181 } 174 }
182 }; 175 }
183 176
184 /** 177 /**
185 * Logs a whitelisting filter, that disables (some kind of) 178 * Logs a whitelisting filter, that disables (some kind of)
186 * blocking for a particular document, to the devtools panel. 179 * blocking for a particular document, to the devtools panel.
187 * 180 *
188 * @param {Page} page The page the whitelisting is active on 181 * @param {Page} page The page the whitelisting is active on
189 * @param {string} url The url of the whitelisted document 182 * @param {string} url The url of the whitelisted document
190 * @param {number} typeMask The bit mask of whitelisting types checked fo r 183 * @param {number} typeMask The bit mask of whitelisting types checked
191 * @param {string} docDomain The IDN-decoded hostname of the parent docume nt 184 * for
185 * @param {string} docDomain The IDN-decoded hostname of the parent
186 * document
192 * @param {WhitelistFilter} filter The matched whitelisting filter 187 * @param {WhitelistFilter} filter The matched whitelisting filter
193 */ 188 */
194 exports.logWhitelistedDocument = function(page, url, typeMask, docDomain, filter ) 189 exports.logWhitelistedDocument = function(page, url, typeMask, docDomain,
190 filter)
195 { 191 {
196 let panel = getActivePanel(page); 192 let panel = getActivePanel(page);
197 if (panel) 193 if (panel)
198 { 194 {
199 for (let type of nonRequestTypes) 195 for (let type of nonRequestTypes)
200 { 196 {
201 if (typeMask & filter.contentType & RegExpFilter.typeMap[type]) 197 if (typeMask & filter.contentType & RegExpFilter.typeMap[type])
202 addRecord(panel, {url: url, type: type, docDomain: docDomain}, filter); 198 addRecord(panel, {url, type, docDomain}, filter);
203 } 199 }
204 } 200 }
205 }; 201 };
206 202
207 /** 203 /**
208 * Checks whether a page is inspected by the devtools panel. 204 * Checks whether a page is inspected by the devtools panel.
209 * 205 *
210 * @param {Page} page 206 * @param {Page} page
211 * @return {boolean} 207 * @return {boolean}
212 */ 208 */
213 exports.hasPanel = function(page) 209 exports.hasPanel = function(page)
214 { 210 {
215 return page.id in panels; 211 return panels.has(page.id);
216 }; 212 };
217 213
218 function onBeforeRequest(details) 214 function onBeforeRequest(details)
219 { 215 {
220 let panel = panels[details.tabId]; 216 let panel = panels.get(details.tabId);
221 217
222 // Clear the devtools panel and reload the inspected tab without caching 218 // Clear the devtools panel and reload the inspected tab without caching
223 // when a new request is issued. However, make sure that we don't end up 219 // when a new request is issued. However, make sure that we don't end up
224 // in an infinite recursion if we already triggered a reload. 220 // in an infinite recursion if we already triggered a reload.
225 if (panel.reloading) 221 if (panel.reloading)
226 { 222 {
227 panel.reloading = false; 223 panel.reloading = false;
228 } 224 }
229 else 225 else
230 { 226 {
231 panel.records = []; 227 panel.records = [];
232 panel.port.postMessage({type: "reset"}); 228 panel.port.postMessage({type: "reset"});
233 229
234 // We can't repeat the request if it isn't a GET request. Chrome would 230 // We can't repeat the request if it isn't a GET request. Chrome would
235 // prompt the user to confirm reloading the page, and POST requests are 231 // prompt the user to confirm reloading the page, and POST requests are
236 // known to cause issues on many websites if repeated. 232 // known to cause issues on many websites if repeated.
237 if (details.method == "GET") 233 if (details.method == "GET")
238 panel.reload = true; 234 panel.reload = true;
239 } 235 }
240 } 236 }
241 237
242 function onLoading(page) 238 function onLoading(page)
243 { 239 {
244 let tabId = page.id; 240 let tabId = page.id;
245 let panel = panels[tabId]; 241 let panel = panels.get(tabId);
246 242
247 // Reloading the tab is the only way that allows bypassing all caches, in 243 // Reloading the tab is the only way that allows bypassing all caches, in
248 // order to see all requests in the devtools panel. Reloading must not be 244 // order to see all requests in the devtools panel. Reloading must not be
249 // performed before the tab changes to "loading", otherwise it will load the 245 // performed before the tab changes to "loading", otherwise it will load the
250 // previous URL. 246 // previous URL.
251 if (panel && panel.reload) 247 if (panel && panel.reload)
252 { 248 {
253 chrome.tabs.reload(tabId, {bypassCache: true}); 249 chrome.tabs.reload(tabId, {bypassCache: true});
254 250
255 panel.reload = false; 251 panel.reload = false;
256 panel.reloading = true; 252 panel.reloading = true;
257 } 253 }
258 } 254 }
259 255
260 function updateFilters(filters, added) 256 function updateFilters(filters, added)
261 { 257 {
262 for (let tabId in panels) 258 for (let panel of panels.values())
263 { 259 {
264 let panel = panels[tabId];
265
266 for (let i = 0; i < panel.records.length; i++) 260 for (let i = 0; i < panel.records.length; i++)
267 { 261 {
268 let record = panel.records[i]; 262 let record = panel.records[i];
269 263
270 // If an added filter matches a request shown in the devtools panel, 264 // If an added filter matches a request shown in the devtools panel,
271 // update that record to show the new filter. Ignore filters that aren't 265 // update that record to show the new filter. Ignore filters that aren't
272 // associated with any sub-resource request. There is no record for these 266 // associated with any sub-resource request. There is no record for these
273 // if they don't already match. In particular, in case of element hiding 267 // if they don't already match. In particular, in case of element hiding
274 // filters, we also wouldn't know if any new element matches. 268 // filters, we also wouldn't know if any new element matches.
275 if (added) 269 if (added)
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
326 { 320 {
327 updateFilters([filter], false); 321 updateFilters([filter], false);
328 } 322 }
329 323
330 function onSubscriptionAdded(subscription) 324 function onSubscriptionAdded(subscription)
331 { 325 {
332 if (subscription instanceof SpecialSubscription) 326 if (subscription instanceof SpecialSubscription)
333 updateFilters(subscription.filters, true); 327 updateFilters(subscription.filters, true);
334 } 328 }
335 329
336 chrome.runtime.onConnect.addListener(port => 330 chrome.runtime.onConnect.addListener(newPort =>
337 { 331 {
338 let match = port.name.match(/^devtools-(\d+)$/); 332 let match = newPort.name.match(/^devtools-(\d+)$/);
339 if (!match) 333 if (!match)
340 return; 334 return;
341 335
342 let inspectedTabId = parseInt(match[1], 10); 336 let inspectedTabId = parseInt(match[1], 10);
343 let localOnBeforeRequest = onBeforeRequest.bind(); 337 let localOnBeforeRequest = onBeforeRequest.bind();
344 338
345 chrome.webRequest.onBeforeRequest.addListener( 339 chrome.webRequest.onBeforeRequest.addListener(
346 localOnBeforeRequest, 340 localOnBeforeRequest,
347 { 341 {
348 urls: ["<all_urls>"], 342 urls: ["http://*/*", "https://*/*"],
349 types: ["main_frame"], 343 types: ["main_frame"],
350 tabId: inspectedTabId 344 tabId: inspectedTabId
351 } 345 }
352 ); 346 );
353 347
354 if (!hasPanels()) 348 if (panels.size == 0)
355 { 349 {
356 ext.pages.onLoading.addListener(onLoading); 350 ext.pages.onLoading.addListener(onLoading);
357 FilterNotifier.on("filter.added", onFilterAdded); 351 FilterNotifier.on("filter.added", onFilterAdded);
358 FilterNotifier.on("filter.removed", onFilterRemoved); 352 FilterNotifier.on("filter.removed", onFilterRemoved);
359 FilterNotifier.on("subscription.added", onSubscriptionAdded); 353 FilterNotifier.on("subscription.added", onSubscriptionAdded);
360 } 354 }
361 355
362 port.onDisconnect.addListener(() => 356 newPort.onDisconnect.addListener(() =>
363 { 357 {
364 delete panels[inspectedTabId]; 358 panels.delete(inspectedTabId);
365 chrome.webRequest.onBeforeRequest.removeListener(localOnBeforeRequest); 359 chrome.webRequest.onBeforeRequest.removeListener(localOnBeforeRequest);
366 360
367 if (!hasPanels()) 361 if (panels.size == 0)
368 { 362 {
369 ext.pages.onLoading.removeListener(onLoading); 363 ext.pages.onLoading.removeListener(onLoading);
370 FilterNotifier.off("filter.added", onFilterAdded); 364 FilterNotifier.off("filter.added", onFilterAdded);
371 FilterNotifier.off("filter.removed", onFilterRemoved); 365 FilterNotifier.off("filter.removed", onFilterRemoved);
372 FilterNotifier.off("subscription.added", onSubscriptionAdded); 366 FilterNotifier.off("subscription.added", onSubscriptionAdded);
373 } 367 }
374 }); 368 });
375 369
376 panels[inspectedTabId] = {port: port, records: []}; 370 panels.set(inspectedTabId, {port: newPort, records: []});
377 }); 371 });
378 372
379 port.on("devtools.traceElemHide", (message, sender) => 373 port.on("devtools.traceElemHide", (message, sender) =>
380 { 374 {
381 logHiddenElements( 375 logHiddenElements(
382 sender.page, message.selectors, 376 sender.page, message.selectors, message.filters,
383 extractHostFromFrame(sender.frame) 377 extractHostFromFrame(sender.frame)
384 ); 378 );
385 }); 379 });
OLDNEW
« no previous file with comments | « lib/csp.js ('k') | lib/filterComposer.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld