~ubuntu-branches/ubuntu/natty/adblock-plus/natty

« back to all changes in this revision

Viewing changes to modules/RequestNotifier.jsm

  • Committer: Bazaar Package Importer
  • Author(s): Benjamin Drung
  • Date: 2010-11-05 18:42:36 UTC
  • mto: (25.1.1 sid)
  • mto: This revision was merged to the branch mainline in revision 27.
  • Revision ID: james.westby@ubuntu.com-20101105184236-h7dnu8mbfjaoya62
Tags: upstream-1.3.1
ImportĀ upstreamĀ versionĀ 1.3.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* ***** BEGIN LICENSE BLOCK *****
 
2
 * Version: MPL 1.1
 
3
 *
 
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/
 
8
 *
 
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
 
12
 * License.
 
13
 *
 
14
 * The Original Code is Adblock Plus.
 
15
 *
 
16
 * The Initial Developer of the Original Code is
 
17
 * Wladimir Palant.
 
18
 * Portions created by the Initial Developer are Copyright (C) 2006-2010
 
19
 * the Initial Developer. All Rights Reserved.
 
20
 *
 
21
 * Contributor(s):
 
22
 *
 
23
 * ***** END LICENSE BLOCK ***** */
 
24
 
 
25
/**
 
26
 * @fileOverview Stores Adblock Plus data to be attached to a window.
 
27
 */
 
28
 
 
29
var EXPORTED_SYMBOLS = ["RequestNotifier"];
 
30
 
 
31
const Cc = Components.classes;
 
32
const Ci = Components.interfaces;
 
33
const Cr = Components.results;
 
34
const Cu = Components.utils;
 
35
 
 
36
let baseURL = Cc["@adblockplus.org/abp/private;1"].getService(Ci.nsIURI);
 
37
 
 
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
 
41
 
 
42
// Our properties should have randomized names
 
43
const dataSeed = Math.random();
 
44
const nodeDataProp = "abpNodeData" + dataSeed;
 
45
const wndStatProp = "abpWindowStats" + dataSeed;
 
46
 
 
47
/**
 
48
 * List of notifiers in use - these notifiers need to receive notifications on
 
49
 * new requests.
 
50
 * @type RequestNotifier[]
 
51
 */
 
52
let activeNotifiers = [];
 
53
 
 
54
let attachData;
 
55
let retrieveData;
 
56
if (Utils.versionComparator.compare(Utils.platformVersion, "1.9.2") >= 0)
 
57
{
 
58
  // Gecko 1.9.2 and higher - the sane branch
 
59
  attachData = function(node, prop, data)
 
60
  {
 
61
    node.setUserData(prop, data, null);
 
62
  }
 
63
  retrieveData = function(node, prop)
 
64
  {
 
65
    return node.getUserData(prop);
 
66
  }
 
67
}
 
68
else
 
69
{
 
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 :-(
 
73
  var tempData = null;
 
74
  attachData = function(node, prop, data)
 
75
  {
 
76
    node.setUserData(prop, true, null);
 
77
    node.addEventListener(prop, function()
 
78
    {
 
79
      tempData = data;
 
80
    }, true);
 
81
    node = null;
 
82
  }
 
83
  retrieveData = function(node, prop)
 
84
  {
 
85
    if (!node.getUserData(prop))
 
86
      return null;
 
87
 
 
88
    let doc = (node.nodeType == node.DOCUMENT_NODE ? node : node.ownerDocument);
 
89
    if (!doc)
 
90
      return null;
 
91
 
 
92
    let event = doc.createEvent("Events");
 
93
    event.initEvent(prop, false, false);
 
94
    node.dispatchEvent(event);
 
95
 
 
96
    let result = tempData;
 
97
    tempData = null;
 
98
    return result;
 
99
  }
 
100
}
 
101
 
 
102
/**
 
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
 
109
 */
 
110
function RequestNotifier(wnd, listener, listenerObj)
 
111
{
 
112
  this.window = wnd;
 
113
  this.listener = listener;
 
114
  this.listenerObj = listenerObj || null;
 
115
  activeNotifiers.push(this);
 
116
  if (wnd)
 
117
    this.startScan(wnd);
 
118
  else
 
119
    this.scanComplete = true;
 
120
}
 
121
RequestNotifier.prototype =
 
122
{
 
123
  /**
 
124
   * The window this notifier is associated with.
 
125
   * @type Window
 
126
   */
 
127
  window: null,
 
128
 
 
129
  /**
 
130
   * The listener to be called when a new request is found.
 
131
   * @type Function
 
132
   */
 
133
  listener: null,
 
134
 
 
135
  /**
 
136
   * "this" pointer to be used when calling the listener.
 
137
   * @type Object
 
138
   */
 
139
  listenerObj: null,
 
140
 
 
141
  /**
 
142
   * Will be set to true once the initial window scan is complete.
 
143
   * @type Boolean
 
144
   */
 
145
  scanComplete: false,
 
146
 
 
147
  /**
 
148
   * Shuts down the notifier once it is no longer used. The listener
 
149
   * will no longer be called after that.
 
150
   */
 
151
  shutdown: function()
 
152
  {
 
153
    delete this.window;
 
154
    delete this.listener;
 
155
    delete this.listenerObj;
 
156
 
 
157
    for (let i = activeNotifiers.length - 1; i >= 0; i--)
 
158
      if (activeNotifiers[i] == this)
 
159
        activeNotifiers.splice(i, 1);
 
160
  },
 
161
 
 
162
  /**
 
163
   * Notifies listener about a new request.
 
164
   */
 
165
  notifyListener: function(/**Window*/ wnd, /**Node*/ node, /**RequestEntry*/ entry)
 
166
  {
 
167
    this.listener.call(this.listenerObj, wnd, node, entry, this.scanComplete);
 
168
  },
 
169
 
 
170
  /**
 
171
   * Number of currently posted scan events (will be 0 when the scan finishes
 
172
   * running).
 
173
   */
 
174
  eventsPosted: 0,
 
175
 
 
176
  /**
 
177
   * Starts the initial scan of the window (will recurse into frames).
 
178
   * @param {Window} wnd  the window to be scanned
 
179
   */
 
180
  startScan: function(wnd)
 
181
  {
 
182
    let currentThread = Utils.threadManager.currentThread;
 
183
 
 
184
    let doc = wnd.document;
 
185
    let walker = doc.createTreeWalker(doc, Ci.nsIDOMNodeFilter.SHOW_ELEMENT, null, false);
 
186
 
 
187
    let runnable =
 
188
    {
 
189
      notifier: null,
 
190
 
 
191
      run: function()
 
192
      {
 
193
        if (!this.notifier.listener)
 
194
          return;
 
195
 
 
196
        let node = walker.currentNode;
 
197
        let data = retrieveData(node, nodeDataProp);
 
198
        if (data)
 
199
          for (let i = data.length - 1; i >= 0; i--)
 
200
            this.notifier.notifyListener(wnd, node, data[i]);
 
201
 
 
202
        if (walker.nextNode())
 
203
          currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
 
204
        else
 
205
        {
 
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]);
 
209
 
 
210
          this.notifier.eventsPosted--;
 
211
          if (!this.notifier.eventsPosted)
 
212
          {
 
213
            this.notifier.scanComplete = true;
 
214
            this.notifier.notifyListener(wnd, null, null);
 
215
          }
 
216
 
 
217
          this.notifier = null;
 
218
        }
 
219
      }
 
220
    };
 
221
    runnable.notifier = this;
 
222
 
 
223
    // Process each node in a separate event on current thread to allow other
 
224
    // events to process
 
225
    this.eventsPosted++;
 
226
    currentThread.dispatch(runnable, Ci.nsIEventTarget.DISPATCH_NORMAL);
 
227
  }
 
228
};
 
229
 
 
230
RequestNotifier.getDataSeed = function() dataSeed;
 
231
 
 
232
/**
 
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
 
241
 */
 
242
RequestNotifier.addNodeData = function(/**Node*/ node, /**Window*/ topWnd, /**Integer*/ contentType, /**String*/ docDomain, /**Boolean*/ thirdParty, /**String*/ location, /**Filter*/ filter)
 
243
{
 
244
  return new RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter);
 
245
}
 
246
 
 
247
/**
 
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)
 
250
 */
 
251
RequestNotifier.getWindowStatistics = function(/**Window*/ wnd)
 
252
{
 
253
  return retrieveData(wnd.document, wndStatProp);
 
254
}
 
255
 
 
256
/**
 
257
 * Retrieves the request entry associated with a DOM node.
 
258
 * @param {Node} 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]}
 
263
 * @static
 
264
 */
 
265
RequestNotifier.getDataForNode = function(node, noParent, type, location)
 
266
{
 
267
  while (node)
 
268
  {
 
269
    let data = retrieveData(node, nodeDataProp);
 
270
    if (data)
 
271
    {
 
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--)
 
274
      {
 
275
        let entry = data[i];
 
276
        if ((typeof type == "undefined" || entry.type == type) &&
 
277
            (typeof location == "undefined" || entry.location == location))
 
278
        {
 
279
          return [node, entry];
 
280
        }
 
281
      }
 
282
    }
 
283
 
 
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)
 
287
    {
 
288
      node = node.parentNode;
 
289
    }
 
290
    else
 
291
    {
 
292
      node = null;
 
293
    }
 
294
  }
 
295
 
 
296
  return null;
 
297
};
 
298
 
 
299
function RequestEntry(node, topWnd, contentType, docDomain, thirdParty, location, filter)
 
300
{
 
301
  this.type = contentType;
 
302
  this.docDomain = docDomain;
 
303
  this.thirdParty = thirdParty;
 
304
  this.location = location;
 
305
  this.filter = filter;
 
306
 
 
307
  this.attachToNode(node);
 
308
 
 
309
  // Update window statistics
 
310
  let windowStats = retrieveData(topWnd.document, wndStatProp);
 
311
  if (!windowStats)
 
312
  {
 
313
    windowStats = {
 
314
      items: 0,
 
315
      hidden: 0,
 
316
      blocked: 0,
 
317
      whitelisted: 0,
 
318
      filters: {}
 
319
    };
 
320
 
 
321
    attachData(topWnd.document, wndStatProp, windowStats);
 
322
  }
 
323
 
 
324
  if (filter && filter instanceof ElemHideFilter)
 
325
    windowStats.hidden++;
 
326
  else
 
327
    windowStats.items++;
 
328
  if (filter)
 
329
  {
 
330
    if (filter instanceof BlockingFilter)
 
331
      windowStats.blocked++;
 
332
    else if (filter instanceof WhitelistFilter)
 
333
      windowStats.whitelisted++;
 
334
 
 
335
    if (filter.text in windowStats.filters)
 
336
      windowStats.filters[filter.text]++;
 
337
    else
 
338
      windowStats.filters[filter.text] = 1;
 
339
  }
 
340
 
 
341
  // Notify listeners
 
342
  for each (let notifier in activeNotifiers)
 
343
    if (!notifier.window || notifier.window == topWnd)
 
344
      notifier.notifyListener(topWnd, node, this);
 
345
}
 
346
RequestEntry.prototype =
 
347
{
 
348
  /**
 
349
   * Content type of the request (one of the nsIContentPolicy constants)
 
350
   * @type Integer
 
351
   */
 
352
  type: null,
 
353
  /**
 
354
   * Domain name of the requesting document
 
355
   * @type String
 
356
   */
 
357
  docDomain: null,
 
358
  /**
 
359
   * True if the request goes to a different domain than the domain of the containing document
 
360
   * @type Boolean
 
361
   */
 
362
  thirdParty: false,
 
363
  /**
 
364
   * Address being requested
 
365
   * @type String
 
366
   */
 
367
  location: null,
 
368
  /**
 
369
   * Filter that was applied to this request (if any)
 
370
   * @type Filter
 
371
   */
 
372
  filter: null,
 
373
  /**
 
374
   * String representation of the content type, e.g. "subdocument"
 
375
   * @type String
 
376
   */
 
377
  get typeDescr() Policy.typeDescr[this.type],
 
378
  /**
 
379
   * User-visible localized representation of the content type, e.g. "frame"
 
380
   * @type String
 
381
   */
 
382
  get localizedDescr() Policy.localizedDescr[this.type],
 
383
 
 
384
  /**
 
385
   * Attaches this request object to a DOM node.
 
386
   */
 
387
  attachToNode: function(/**Node*/ node)
 
388
  {
 
389
    let existingData = retrieveData(node, nodeDataProp);
 
390
    if (existingData)
 
391
    {
 
392
      // Add the new entry to the existing data
 
393
      existingData.push(this);
 
394
    }
 
395
    else
 
396
    {
 
397
      // Associate the node with a new array
 
398
      attachData(node, nodeDataProp, [this]);
 
399
    }
 
400
  }
 
401
};