1
/* ***** BEGIN LICENSE BLOCK *****
4
* The contents of this file are subject to the Mozilla Public License Version
5
* 1.1 (the "License"); you may not use this file except in compliance with
6
* the License. You may obtain a copy of the License at
7
* http://www.mozilla.org/MPL/
9
* Software distributed under the License is distributed on an "AS IS" basis,
10
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
11
* for the specific language governing rights and limitations under the
14
* The Original Code is Adblock Plus.
16
* The Initial Developer of the Original Code is
18
* Portions created by the Initial Developer are Copyright (C) 2006-2010
19
* the Initial Developer. All Rights Reserved.
23
* ***** END LICENSE BLOCK ***** */
26
* @fileOverview Stores Adblock Plus data to be attached to a window.
29
var EXPORTED_SYMBOLS = ["RequestNotifier"];
31
const Cc = Components.classes;
32
const Ci = Components.interfaces;
33
const Cr = Components.results;
34
const Cu = Components.utils;
36
let baseURL = Cc["@adblockplus.org/abp/private;1"].getService(Ci.nsIURI);
38
Cu.import(baseURL.spec + "Utils.jsm");
39
Cu.import(baseURL.spec + "FilterClasses.jsm");
40
Utils.runAsync(Cu.import, Cu, baseURL.spec + "ContentPolicy.jsm"); // delay to avoid circular imports
42
// Our properties should have randomized names
43
const dataSeed = Math.random();
44
const nodeDataProp = "abpNodeData" + dataSeed;
45
const wndStatProp = "abpWindowStats" + dataSeed;
48
* List of notifiers in use - these notifiers need to receive notifications on
50
* @type RequestNotifier[]
52
let activeNotifiers = [];
56
if (Utils.versionComparator.compare(Utils.platformVersion, "1.9.2") >= 0)
58
// Gecko 1.9.2 and higher - the sane branch
59
attachData = function(node, prop, data)
61
node.setUserData(prop, data, null);
63
retrieveData = function(node, prop)
65
return node.getUserData(prop);
70
// Gecko 1.9.0/1.9.1 - the insane branch. User data will fail to save anything
71
// more complicated than a string, due to wrappers/permissions issues. Have
72
// to use event handlers :-(
74
attachData = function(node, prop, data)
76
node.setUserData(prop, true, null);
77
node.addEventListener(prop, function()
83
retrieveData = function(node, prop)
85
if (!node.getUserData(prop))
88
let doc = (node.nodeType == node.DOCUMENT_NODE ? node : node.ownerDocument);
92
let event = doc.createEvent("Events");
93
event.initEvent(prop, false, false);
94
node.dispatchEvent(event);
96
let result = tempData;
103
* Creates a notifier object for a particular window. After creation the window
104
* will first be scanned for previously saved requests. Once that scan is
105
* complete only new requests for this window will be reported.
106
* @param {Window} wnd window to attach the notifier to
107
* @param {Function} listener listener to be called whenever a new request is found
108
* @param {Object} [listenerObj] "this" pointer to be used when calling the listener
110
function RequestNotifier(wnd, listener, listenerObj)
113
this.listener = listener;
114
this.listenerObj = listenerObj || null;
115
activeNotifiers.push(this);
119
this.scanComplete = true;
121
RequestNotifier.prototype =
124
* The window this notifier is associated with.
130
* The listener to be called when a new request is found.
136
* "this" pointer to be used when calling the listener.
142
* Will be set to true once the initial window scan is complete.
148
* Shuts down the notifier once it is no longer used. The listener
149
* will no longer be called after that.
154
delete this.listener;
155
delete this.listenerObj;
157
for (let i = activeNotifiers.length - 1; i >= 0; i--)
158
if (activeNotifiers[i] == this)
159
activeNotifiers.splice(i, 1);
163
* Notifies listener about a new request.
165
notifyListener: function(/**Window*/ wnd, /**Node*/ node, /**RequestEntry*/ entry)
167
this.listener.call(this.listenerObj, wnd, node, entry, this.scanComplete);
171
* Number of currently posted scan events (will be 0 when the scan finishes
177
* Starts the initial scan of the window (will recurse into frames).
178
* @param {Window} wnd the window to be scanned
180
startScan: function(wnd)
182
let currentThread = Utils.threadManager.currentThread;
184
let doc = wnd.document;
185
let walker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, null, false);
193
if (!this.notifier.listener)
196
let node = walker.currentNode;
197
let data = retrieveData(node, nodeDataProp);
199
for (let i = data.length - 1; i >= 0; i--)
200
this.notifier.notifyListener(wnd, node, data[i]);
202
if (walker.nextNode())
203
currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
206
// Done with the current window, start the scan for its frames
207
for (let i = 0; i < wnd.frames.length; i++)
208
this.notifier.startScan(wnd.frames[i]);
210
this.notifier.eventsPosted--;
211
if (!this.notifier.eventsPosted)
213
this.notifier.scanComplete = true;
214
this.notifier.notifyListener(wnd, null, null);
217
this.notifier = null;
221
runnable.notifier = this;
223
// Process each node in a separate event on current thread to allow other
226
currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
230
RequestNotifier.getDataSeed = function() dataSeed;
233
* Attaches request data to a DOM node.
234
* @param {Node} node node to attach data to
235
* @param {Window} topWnd top-level window the node belongs to
236
* @param {Integer} contentType request type, one of the Policy.type.* constants
237
* @param {String} docDomain domain of the document that initiated the request
238
* @param {Boolean} thirdParty will be true if a third-party server has been requested
239
* @param {String} location the address that has been requested
240
* @param {Filter} filter filter applied to the request or null if none
242
RequestNotifier.addNodeData = function(/**Node*/ node, /**Window*/ topWnd, /**Integer*/ contentType, /**String*/ docDomain, /**Boolean*/ thirdParty, /**String*/ location, /**Filter*/ filter)
244
return new RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter);
248
* Retrieves the statistics for a window.
249
* @result {Object} Object with the properties items, blocked, whitelisted, hidden, filters containing statistics for the window (might be null)
251
RequestNotifier.getWindowStatistics = function(/**Window*/ wnd)
253
return retrieveData(wnd.document, wndStatProp);
257
* Retrieves the request entry associated with a DOM node.
259
* @param {Boolean} noParent if missing or false, the search will extend to the parent nodes until one is found that has data associated with it
260
* @param {Integer} [type] request type to be looking for
261
* @param {String} [location] request location to be looking for
262
* @result {[Node, RequestEntry]}
265
RequestNotifier.getDataForNode = function(node, noParent, type, location)
269
let data = retrieveData(node, nodeDataProp);
272
// Look for matching entry starting at the end of the list (most recent first)
273
for (let i = data.length - 1; i >= 0; i--)
276
if ((typeof type == "undefined" || entry.type == type) &&
277
(typeof location == "undefined" || entry.location == location))
279
return [node, entry];
284
// If we don't have any match on this node then maybe its parent will do
285
if ((typeof noParent != "boolean" || !noParent) &&
286
node.parentNode instanceof Ci.nsIDOMElement)
288
node = node.parentNode;
299
function RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter)
301
this.type = contentType;
302
this.docDomain = docDomain;
303
this.thirdParty = thirdParty;
304
this.location = location;
305
this.filter = filter;
307
this.attachToNode(node);
309
// Update window statistics
310
let windowStats = retrieveData(topWnd.document, wndStatProp);
321
attachData(topWnd.document, wndStatProp, windowStats);
324
if (filter && filter instanceof ElemHideFilter)
325
windowStats.hidden++;
330
if (filter instanceof BlockingFilter)
331
windowStats.blocked++;
332
else if (filter instanceof WhitelistFilter)
333
windowStats.whitelisted++;
335
if (filter.text in windowStats.filters)
336
windowStats.filters[filter.text]++;
338
windowStats.filters[filter.text] = 1;
342
for each (let notifier in activeNotifiers)
343
if (!notifier.window || notifier.window == topWnd)
344
notifier.notifyListener(topWnd, node, this);
346
RequestEntry.prototype =
349
* Content type of the request (one of the nsIContentPolicy constants)
354
* Domain name of the requesting document
359
* True if the request goes to a different domain than the domain of the containing document
364
* Address being requested
369
* Filter that was applied to this request (if any)
374
* String representation of the content type, e.g. "subdocument"
377
get typeDescr() Policy.typeDescr[this.type],
379
* User-visible localized representation of the content type, e.g. "frame"
382
get localizedDescr() Policy.localizedDescr[this.type],
385
* Attaches this request object to a DOM node.
387
attachToNode: function(/**Node*/ node)
389
let existingData = retrieveData(node, nodeDataProp);
392
// Add the new entry to the existing data
393
existingData.push(this);
397
// Associate the node with a new array
398
attachData(node, nodeDataProp, [this]);