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

Side by Side Diff: lib/devtools.js

Issue 6393086494113792: Issue 154 - Added devtools panel showing blocked and blockable items (Closed)
Patch Set: Fixed issues in content script breaking element hide tracing Created Jan. 30, 2016, 6:19 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
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH
4 *
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
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
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/>.
16 */
17
18 "use strict";
19
20 let {RegExpFilter, WhitelistFilter, ElemHideFilter} = require("filterClasses");
21 let {SpecialSubscription} = require("subscriptionClasses");
22 let {FilterStorage} = require("filterStorage");
23 let {defaultMatcher} = require("matcher");
24 let {FilterNotifier} = require("filterNotifier");
25
26 // Mapping of inspected tabs to their devpanel page
27 // and recorded items. We can't use a PageMap here,
28 // because data must persist after navigation/reload.
29 let panels = Object.create(null);
30
31 function hasPanels()
32 {
33 return Object.getOwnPropertyNames(panels).length > 0;
34 }
35
36 function getRequestInfo(request)
37 {
38 return {
39 url: request.url,
40 type: request.type,
41 docDomain: request.docDomain
42 };
43 }
44
45 function getFilterInfo(filter)
46 {
47 if (!filter)
48 return null;
49
50 let userDefined = false;
51 let subscriptionTitle = null;
52
53 for (let subscription of filter.subscriptions)
54 {
55 if (!subscription.disabled)
56 {
57 if (subscription instanceof SpecialSubscription)
58 userDefined = true;
59 else
60 subscriptionTitle = subscription.title;
61 }
62 }
63
64 return {
65 text: filter.text,
66 whitelisted: filter instanceof WhitelistFilter,
67 userDefined: userDefined,
68 subscription: subscriptionTitle
69 };
70 }
71
72 function hasRecord(panel, request, filter)
73 {
74 return panel.records.some(record =>
75 record.request.type == request.type &&
76 record.request.url == request.url &&
77 record.request.docDomain == request.docDomain &&
78
79 // Matched element hiding filters don't relate to a particular request,
80 // so we also have to match the CSS selector in order to distinguish them.
81 (request.type != "ELEMHIDE" || record.filter.selector == filter.selector)
82 );
83 }
84
85 function addRecord(panel, request, filter)
86 {
87 if (!hasRecord(panel, request, filter))
88 {
89 panel.panel.sendMessage({
90 type: "add-record",
91 request: getRequestInfo(request),
92 filter: getFilterInfo(filter)
93 });
94
95 panel.records.push({
96 request: request,
97 filter: filter
98 });
99 }
100 }
101
102 function matchRequest(request)
103 {
104 return defaultMatcher.matchesAny(
105 request.url,
106 RegExpFilter.typeMap[request.type],
107 request.docDomain,
108 request.thirdParty,
109 request.sitekey,
110 request.specificOnly
111 );
112 }
113
114 /**
115 * Logs a request in the devtools panel.
116 *
117 * @param {Page} page The page the request occured on
118 * @param {string] url The URL of the request
119 * @param {string} type The request type
120 * @param {string} docDomain The IDN-decoded hostname of the document
121 * @param {boolean} thirdParty Whether the origin of the request and documen t differs
122 * @param {string} [sitekey] The active sitekey if there is any
123 * @param {boolean} [specificOnly] Whether generic filters should be ignored
124 * @param {filter} [filter] The matched filter or null if there is no mat ch
125 */
126 exports.logRequest = function(page, url, type, docDomain, thirdParty, sitekey, s pecificOnly, filter)
kzar 2016/01/31 13:33:52 Nit: Mind wrapping this?
Sebastian Noack 2016/02/02 10:39:53 Done.
127 {
128 let panel = panels[page._id];
129 if (panel && !panel.reload && !panel.reloading)
130 {
131 let request = {
132 url: url,
133 type: type,
134 docDomain: docDomain,
135 thirdParty: thirdParty,
136 sitekey: sitekey,
137 specificOnly: specificOnly
138 };
139
140 addRecord(panel, request, filter);
141 }
142 };
143
144 /**
145 * Logs active element hiding filters in the devtools panel.
146 *
147 * @param {Page} page The page the elements were hidden on
148 * @param {string[]} selectors The CSS selectors of active elemhide filters
149 * @param {string} docDomain The IDN-decoded hostname of the document
150 */
151 exports.logHiddenElements = function(page, selectors, docDomain)
152 {
153 let panel = panels[page._id];
154 if (panel && !panel.reload && !panel.reloading)
155 {
156 for (let subscription of FilterStorage.subscriptions)
157 {
158 if (subscription.disabled)
159 continue;
160
161 for (let filter of subscription.filters)
162 {
163 if (!(filter instanceof ElemHideFilter))
164 continue;
165 if (selectors.indexOf(filter.selector) == -1)
166 continue;
167 if (!filter.isActiveOnDomain(docDomain))
168 continue;
169
170 addRecord(panel, {type: "ELEMHIDE", docDomain: docDomain}, filter);
171 }
172 }
173 }
174 };
175
176 /**
177 * Checks whether a page is inspected by the devtools panel.
178 *
179 * @param {Page} page
180 * @return {boolean}
181 */
182 exports.hasPanel = function(page)
183 {
184 return page._id in panels;
185 };
186
187 function onBeforeRequest(details)
188 {
189 let panel = panels[details.tabId];
190
191 // Clear the devtools panel and reload the inspected tab without caching
192 // when a new request is issued. However, make sure that we don't end up
193 // in an infinite recursion if we already triggered a reload.
194 if (panel.reloading)
195 {
196 panel.reloading = false;
197 }
198 else
199 {
200 panel.records = [];
201 panel.panel.sendMessage({type: "reset"});
202
203 // We can't repeat the request if it isn't a GET request. Chrome would
204 // prompt the user to confirm reloading the page, and POST requests are
205 // known to cause issues on many websites if repeated.
206 if (details.method == "GET")
207 panel.reload = true;
208 }
209 }
210
211 function onLoading(page)
212 {
213 let tabId = page._id;
214 let panel = panels[tabId];
215
216 // Reloading the tab is the only way that allows bypassing all caches, in
217 // order to see all requests in the devtools panel. Reloading must not be
218 // performed before the tab changes to "loading", otherwise it will load the
219 // previous URL.
220 if (panel && panel.reload)
221 {
222 chrome.tabs.reload(tabId, {bypassCache: true});
223
224 panel.reload = false;
225 panel.reloading = true;
226 }
227 }
228
229 function onFilterChange(action, arg)
230 {
231 let added, filters;
232 switch (action)
233 {
234 case "filter.added":
235 added = true;
236 filters = [arg];
237 break;
238
239 case "filter.removed":
240 added = false;
241 filters = [arg];
242 break;
243
244 // When there haven't ever been any user filters before, the subscription is
245 // added, triggering a "subscription.added" instead of a "filter.added" even t.
246 case "subscription.added":
247 if (arg instanceof SpecialSubscription)
248 {
249 added = true;
250 filters = arg.filters;
251 break;
252 }
253
254 default:
255 return;
256 }
257
258 for (let tabId in panels)
259 {
260 let panel = panels[tabId];
261
262 for (let i = 0; i < panel.records.length; i++)
263 {
264 let record = panel.records[i];
265
266 // If an added filter matches a request shown in the devtools panel,
267 // update that record to show the new filter. Ignore element hiding
268 // filters since there are only records for element hiding filters that
269 // already match, and also we don't know if any new element matches.
270 if (added)
271 {
272 if (record.request.type == "ELEMHIDE")
273 continue;
274
275 let filter = matchRequest(record.request);
276 if (filters.indexOf(filter) == -1)
277 continue;
278
279 record.filter = filter;
280 }
281
282 // If a filter shown in the devtools panel got removed, update that
283 // record to show the filter (if any) that matches now instead.
284 // However, for element hiding, just remove the record, since we don't
285 // know whether another filter would match until the page is reloaded.
286 else
287 {
288 if (filters.indexOf(record.filter) == -1)
289 continue;
290
291 if (record.request.type == "ELEMHIDE")
292 {
293 panel.panel.sendMessage({
294 type: "remove-record",
295 index: i
296 });
297 panel.records.splice(i--, 1);
298 continue;
299 }
300
301 record.filter = matchRequest(record.request);
302 }
303
304 panel.panel.sendMessage({
305 type: "update-record",
306 index: i,
307 request: getRequestInfo(record.request),
308 filter: getFilterInfo(record.filter)
309 });
310 }
311 }
312 }
313
314 ext.devtools.onCreated.addListener(function(panel)
kzar 2016/01/31 13:33:52 I guess I'm undecided about the ext.devtools abstr
Sebastian Noack 2016/02/02 10:39:53 Yes, I would much prefer that as well. But note th
Sebastian Noack 2016/02/02 11:22:11 OK, I looked into it. And indeed, as we are in the
315 {
316 let inspectedTabId = panel.inspectedTabId;
317 let localOnBeforeRequest = onBeforeRequest.bind();
318
319 chrome.webRequest.onBeforeRequest.addListener(
320 localOnBeforeRequest,
321 {
322 urls: ["<all_urls>"],
323 types: ["main_frame"],
324 tabId: inspectedTabId
325 }
326 );
327
328 if (!hasPanels())
329 {
330 ext.pages.onLoading.addListener(onLoading);
331 FilterNotifier.addListener(onFilterChange);
332 }
333
334 panel.onRemoved.addListener(function()
335 {
336 delete panels[inspectedTabId];
337 chrome.webRequest.onBeforeRequest.removeListener(localOnBeforeRequest);
338
339 if (!hasPanels())
340 {
341 FilterNotifier.removeListener(onFilterChange);
342 ext.pages.onLoading.removeListener(onLoading);
343 }
344 });
345
346 panels[inspectedTabId] = {panel: panel, records: []};
347 });
OLDNEW

Powered by Google App Engine
This is Rietveld