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

« back to all changes in this revision

Viewing changes to modules/AppIntegration.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 Application integration module, will keep track of application
 
27
 * windows and handle the necessary events.
 
28
 */
 
29
 
 
30
var EXPORTED_SYMBOLS = ["AppIntegration"];
 
31
 
 
32
const Cc = Components.classes;
 
33
const Ci = Components.interfaces;
 
34
const Cr = Components.results;
 
35
const Cu = Components.utils;
 
36
 
 
37
let baseURL = Cc["@adblockplus.org/abp/private;1"].getService(Ci.nsIURI);
 
38
 
 
39
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
40
Cu.import(baseURL.spec + "Utils.jsm");
 
41
Cu.import(baseURL.spec + "Prefs.jsm");
 
42
Cu.import(baseURL.spec + "ContentPolicy.jsm");
 
43
Cu.import(baseURL.spec + "FilterStorage.jsm");
 
44
Cu.import(baseURL.spec + "FilterClasses.jsm");
 
45
Cu.import(baseURL.spec + "SubscriptionClasses.jsm");
 
46
Cu.import(baseURL.spec + "RequestNotifier.jsm");
 
47
 
 
48
/**
 
49
 * Flag used to trigger special behavior for Fennec.
 
50
 * @type Boolean
 
51
 */
 
52
let isFennec = (Utils.appID == "{a23983c0-fd0e-11dc-95ff-0800200c9a66}");
 
53
if (isFennec)
 
54
  Cu.import(baseURL.spec + "AppIntegrationFennec.jsm");
 
55
 
 
56
/**
 
57
 * Wrappers for tracked application windows.
 
58
 * @type Array of WindowWrapper
 
59
 */
 
60
let wrappers = [];
 
61
 
 
62
/**
 
63
 * Stores the current value of showintoolbar preference (to detect changes).
 
64
 */
 
65
let currentlyShowingInToolbar = Prefs.showintoolbar;
 
66
 
 
67
/**
 
68
 * Initializes app integration module
 
69
 */
 
70
function init()
 
71
{
 
72
  // Process preferences
 
73
  reloadPrefs();
 
74
 
 
75
  // Listen for pref and filters changes
 
76
  Prefs.addListener(function(name)
 
77
  {
 
78
    if (name == "enabled" || name == "showintoolbar" || name == "showinstatusbar" || name == "defaulttoolbaraction" || name == "defaultstatusbaraction")
 
79
      reloadPrefs();
 
80
  });
 
81
  FilterStorage.addFilterObserver(reloadPrefs);
 
82
  FilterStorage.addSubscriptionObserver(reloadPrefs);
 
83
}
 
84
 
 
85
/**
 
86
 * Exported app integration functions.
 
87
 * @class
 
88
 */
 
89
var AppIntegration =
 
90
{
 
91
  /**
 
92
   * Adds an application window to the tracked list.
 
93
   */
 
94
  addWindow: function(/**Window*/ window)
 
95
  {
 
96
    let hooks = window.document.getElementById("abp-hooks");
 
97
    if (!hooks)
 
98
      return;
 
99
  
 
100
    // Execute first-run actions
 
101
    if (!("lastVersion" in Prefs))
 
102
    {
 
103
      Prefs.lastVersion = Prefs.currentVersion;
 
104
  
 
105
      // Show subscriptions dialog if the user doesn't have any subscriptions yet
 
106
      if (Prefs.currentVersion != Utils.addonVersion)
 
107
      {
 
108
        Prefs.currentVersion = Utils.addonVersion;
 
109
  
 
110
        if ("nsISessionStore" in Ci)
 
111
        {
 
112
          // Have to wait for session to be restored
 
113
          let observer =
 
114
          {
 
115
            QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
116
            observe: function(subject, topic, data)
 
117
            {
 
118
              observerService.removeObserver(observer, "sessionstore-windows-restored");
 
119
              timer.cancel();
 
120
              timer = null;
 
121
              showSubscriptions();
 
122
            }
 
123
          };
 
124
  
 
125
          let observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
 
126
          observerService.addObserver(observer, "sessionstore-windows-restored", false);
 
127
  
 
128
          // Just in case, don't wait more than a second
 
129
          let timer = Cc['@mozilla.org/timer;1'].createInstance(Ci.nsITimer);
 
130
          timer.init(observer, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
 
131
        }
 
132
        else
 
133
          Utils.runAsync(showSubscriptions);
 
134
      }
 
135
    }
 
136
 
 
137
    let wrapper = new WindowWrapper(window, hooks);
 
138
    wrappers.push(wrapper);
 
139
  },
 
140
 
 
141
  /**
 
142
   * Retrieves the wrapper object corresponding to a particular application window.
 
143
   */
 
144
  getWrapperForWindow: function(/**Window*/ wnd) /**WindowWrapper*/
 
145
  {
 
146
    for each (let wrapper in wrappers)
 
147
      if (wrapper.window == wnd)
 
148
        return wrapper;
 
149
 
 
150
    return null;
 
151
  },
 
152
 
 
153
  /**
 
154
   * Toggles the value of a boolean preference.
 
155
   */
 
156
  togglePref: function(/**String*/ pref)
 
157
  {
 
158
    Prefs[pref] = !Prefs[pref];
 
159
  },
 
160
  
 
161
  /**
 
162
   * If the given filter is already in user's list, removes it from the list. Otherwise adds it.
 
163
   */
 
164
  toggleFilter: function(/**Filter*/ filter)
 
165
  {
 
166
    if (filter.subscriptions.length)
 
167
    {
 
168
      if (filter.disabled || filter.subscriptions.some(function(subscription) !(subscription instanceof SpecialSubscription)))
 
169
      {
 
170
        filter.disabled = !filter.disabled;
 
171
        FilterStorage.triggerFilterObservers(filter.disabled ? "disable" : "enable", [filter]);
 
172
      }
 
173
      else
 
174
        FilterStorage.removeFilter(filter);
 
175
    }
 
176
    else
 
177
      FilterStorage.addFilter(filter);
 
178
    FilterStorage.saveToDisk();
 
179
  }
 
180
};
 
181
 
 
182
/**
 
183
 * Removes an application window from the tracked list.
 
184
 */
 
185
function removeWindow()
 
186
{
 
187
  let wnd = this;
 
188
 
 
189
  for (let i = 0; i < wrappers.length; i++)
 
190
    if (wrappers[i].window == wnd)
 
191
      wrappers.splice(i--, 1);
 
192
}
 
193
 
 
194
/**
 
195
 * Class providing various functions related to application windows.
 
196
 * @constructor
 
197
 */
 
198
function WindowWrapper(window, hooks)
 
199
{
 
200
  this.window = window;
 
201
 
 
202
  this.initializeHooks(hooks);
 
203
  if (!isFennec)
 
204
  {
 
205
    this.fixupMenus();
 
206
    this.configureKeys();
 
207
    this.initContextMenu();
 
208
    this.updateState();
 
209
 
 
210
    // Some people actually switch off browser.frames.enabled and are surprised
 
211
    // that things stop working...
 
212
    window.QueryInterface(Ci.nsIInterfaceRequestor)
 
213
          .getInterface(Ci.nsIWebNavigation)
 
214
          .QueryInterface(Ci.nsIDocShell)
 
215
          .allowSubframes = true;
 
216
  }
 
217
  this.registerEventListeners(!isFennec);
 
218
  this.executeFirstRunActions();
 
219
 
 
220
  // Custom initialization for Fennec
 
221
  if (isFennec)
 
222
    AppIntegrationFennec.initWindow(this);
 
223
}
 
224
WindowWrapper.prototype =
 
225
{
 
226
  /**
 
227
   * Application window this object belongs to.
 
228
   * @type Window
 
229
   */
 
230
  window: null,
 
231
 
 
232
  /**
 
233
   * Current state as displayed for this window.
 
234
   * @type String
 
235
   */
 
236
  state: null,
 
237
 
 
238
  /**
 
239
   * Methods that can be defined at attributes of the hooks element.
 
240
   * @type Array of String
 
241
   */
 
242
  customMethods: ["getBrowser", "addTab", "getContextMenu", "getToolbox", "getDefaultToolbar", "toolbarInsertBefore", "unhideToolbar"],
 
243
 
 
244
  /**
 
245
   * Progress listener used to watch for location changes, if any.
 
246
   * @type nsIProgressListener
 
247
   */
 
248
  progressListener: null,
 
249
 
 
250
  /**
 
251
   * Filter corresponding with "disable on site" menu item (set in fillPopup()).
 
252
   * @type Filter
 
253
   */
 
254
  siteWhitelist: null,
 
255
  /**
 
256
   * Filter corresponding with "disable on site" menu item (set in fillPopup()).
 
257
   * @type Filter
 
258
   */
 
259
  pageWhitelist: null,
 
260
 
 
261
  /**
 
262
   * Data associated with the node currently under mouse pointer (set in updateContextMenu()).
 
263
   * @type RequestEntry
 
264
   */
 
265
  nodeData: null,
 
266
  /**
 
267
   * The document node that nodeData belongs to.
 
268
   */
 
269
  currentNode: null,
 
270
  /**
 
271
   * Data associated with the background image currently under mouse pointer (set in updateContextMenu()).
 
272
   * @type RequestEntry
 
273
   */
 
274
  backgroundData: null,
 
275
  /**
 
276
   * Data associated with the frame currently under mouse pointer (set in updateContextMenu()).
 
277
   * @type RequestEntry
 
278
   */
 
279
  frameData: null,
 
280
  /**
 
281
   * The frame that frameData belongs to.
 
282
   */
 
283
  currentFrame: null,
 
284
 
 
285
  /**
 
286
   * Window of the detached list of blockable items (might be null or closed).
 
287
   * @type Window 
 
288
   */
 
289
  detachedSidebar: null,
 
290
 
 
291
  /**
 
292
   * Binds a function to the object, ensuring that "this" pointer is always set
 
293
   * correctly.
 
294
   */
 
295
  _bindMethod: function(/**Function*/ method) /**Function*/
 
296
  {
 
297
    let me = this;
 
298
    return function() method.apply(me, arguments);
 
299
  },
 
300
 
 
301
  /**
 
302
   * Retrieves an element by its ID.
 
303
   */
 
304
  E: function(/**String*/ id)
 
305
  {
 
306
    let doc = this.window.document;
 
307
    this.E = function(id) doc.getElementById(id);
 
308
    return this.E(id);
 
309
  },
 
310
 
 
311
  /**
 
312
   * Initializes abp-hooks element, converts any function attributes to actual
 
313
   * functions.
 
314
   */
 
315
  initializeHooks: function(hooks)
 
316
  {
 
317
    for each (let hook in this.customMethods)
 
318
    {
 
319
      let handler = hooks.getAttribute(hook);
 
320
      this[hook] = hooks[hook] = (handler ? this._bindMethod(new Function(handler)) : null);
 
321
    }
 
322
  },
 
323
 
 
324
  /**
 
325
   * Makes a copy of the ABP icon's context menu for the toolbar button.
 
326
   */
 
327
  fixupMenus: function()
 
328
  {
 
329
    function fixId(node)
 
330
    {
 
331
      if (node.nodeType == node.ELEMENT_NODE)
 
332
      {
 
333
        if (node.hasAttribute("id"))
 
334
          node.setAttribute("id", node.getAttribute("id").replace(/abp-status/, "abp-toolbar"));
 
335
    
 
336
        for (let i = 0, len = node.childNodes.length; i < len; i++)
 
337
          fixId(node.childNodes[i]);
 
338
      }
 
339
      return node;
 
340
    }
 
341
  
 
342
    let menuSource = this.E("abp-status-popup");
 
343
    let paletteButton = this.getPaletteButton();
 
344
    let toolbarButton = this.E("abp-toolbarbutton");
 
345
    if (toolbarButton)
 
346
      toolbarButton.appendChild(fixId(menuSource.cloneNode(true)));
 
347
    if (paletteButton && paletteButton != toolbarButton)
 
348
      paletteButton.appendChild(fixId(menuSource.cloneNode(true)));
 
349
  },
 
350
  
 
351
  /**
 
352
   * Attaches event listeners to a window represented by hooks element
 
353
   */
 
354
  registerEventListeners: function(/**Boolean*/ addProgressListener)
 
355
  {
 
356
    // Palette button elements aren't reachable by ID, create a lookup table
 
357
    let paletteButtonIDs = {};
 
358
    let paletteButton = this.getPaletteButton();
 
359
    if (paletteButton)
 
360
    {
 
361
      function getElementIds(element)
 
362
      {
 
363
        if (element.hasAttribute("id"))
 
364
          paletteButtonIDs[element.getAttribute("id")] = element;
 
365
  
 
366
        for (let child = element.firstChild; child; child = child.nextSibling)
 
367
          if (child.nodeType == Ci.nsIDOMNode.ELEMENT_NODE)
 
368
            getElementIds(child);
 
369
      }
 
370
      getElementIds(paletteButton);
 
371
    }
 
372
  
 
373
    // Go on and register listeners
 
374
    this.window.addEventListener("unload", removeWindow, false);
 
375
    for each (let [id, event, handler] in this.eventHandlers)
 
376
    {
 
377
      handler = this._bindMethod(handler);
 
378
 
 
379
      let element = this.E(id);
 
380
      if (element)
 
381
        element.addEventListener(event, handler, false);
 
382
  
 
383
      if (id in paletteButtonIDs)
 
384
        paletteButtonIDs[id].addEventListener(event, handler, false);
 
385
    }
 
386
  
 
387
    let browser = this.getBrowser();
 
388
    browser.addEventListener("click", this._bindMethod(this.handleLinkClick), true);
 
389
 
 
390
    // Register progress listener as well if requested
 
391
    if (addProgressListener)
 
392
    {
 
393
      let dummy = function() {};
 
394
      this.progressListener =
 
395
      {
 
396
        onLocationChange: this._bindMethod(this.updateState),
 
397
        onProgressChange: dummy,
 
398
        onSecurityChange: dummy,
 
399
        onStateChange: dummy,
 
400
        onStatusChange: dummy,
 
401
        QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener, Ci.nsISupportsWeakReference])
 
402
      };
 
403
      browser.addProgressListener(this.progressListener);
 
404
    }
 
405
  },
 
406
 
 
407
  /**
 
408
   * Retrieves the current location of the browser (might return null on failure).
 
409
   */
 
410
  getCurrentLocation: function() /**nsIURI*/
 
411
  {
 
412
    if ("currentHeaderData" in this.window && "content-base" in this.window.currentHeaderData)
 
413
    {
 
414
      // Thunderbird blog entry
 
415
      return Utils.unwrapURL(this.window.currentHeaderData["content-base"].headerValue);
 
416
    }
 
417
    else if ("currentHeaderData" in this.window && "from" in this.window.currentHeaderData)
 
418
    {
 
419
      // Thunderbird mail/newsgroup entry
 
420
      try
 
421
      {
 
422
        let headerParser = Cc["@mozilla.org/messenger/headerparser;1"].getService(Ci.nsIMsgHeaderParser);
 
423
        let emailAddress = headerParser.extractHeaderAddressMailboxes(this.window.currentHeaderData.from.headerValue);
 
424
        return Utils.makeURI("mailto:" + emailAddress.replace(/^[\s"]+/, "").replace(/[\s"]+$/, "").replace(/\s/g, "%20"));
 
425
      }
 
426
      catch(e)
 
427
      {
 
428
        return null;
 
429
      }
 
430
    }
 
431
    else
 
432
    {
 
433
      // Regular browser
 
434
      return Utils.unwrapURL(this.getBrowser().contentWindow.location.href);
 
435
    }
 
436
  },
 
437
 
 
438
  /**
 
439
   * Executes window-specific first-run actions if necessary.
 
440
   */
 
441
  executeFirstRunActions: function()
 
442
  {
 
443
    // Only execute first-run actions for this window once
 
444
    if ("doneFirstRunActions " + this.window.location.href in Prefs)
 
445
      return;
 
446
    Prefs["doneFirstRunActions " + this.window.location.href] = true;
 
447
 
 
448
    // Check version we previously executed first-run actions for;
 
449
    let hooks = this.E("abp-hooks");
 
450
    let lastVersion = hooks.getAttribute("currentVersion") || "0.0";
 
451
    if (lastVersion != Prefs.currentVersion)
 
452
    {
 
453
      hooks.setAttribute("currentVersion", Prefs.currentVersion);
 
454
      this.window.document.persist("abp-hooks", "currentVersion");
 
455
 
 
456
      let needInstall = (Utils.versionComparator.compare(lastVersion, "0.0") <= 0);
 
457
      if (!needInstall)
 
458
      {
 
459
        // Before version 1.1 we didn't add toolbar icon in SeaMonkey, do it now
 
460
        needInstall = Utils.appID == "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}" &&
 
461
                      Utils.versionComparator.compare(lastVersion, "1.1") < 0;
 
462
      }
 
463
 
 
464
      // Add ABP icon to toolbar if necessary
 
465
      if (needInstall)
 
466
        Utils.runAsync(this.installToolbarIcon, this);
 
467
    }
 
468
  },
 
469
 
 
470
  /**
 
471
   * Finds the toolbar button in the toolbar palette.
 
472
   */
 
473
  getPaletteButton: function()
 
474
  {
 
475
    let toolbox = (this.getToolbox ? this.getToolbox() : null);
 
476
    if (!toolbox || !("palette" in toolbox) || !toolbox.palette)
 
477
      return null;
 
478
  
 
479
    for (var child = toolbox.palette.firstChild; child; child = child.nextSibling)
 
480
      if (child.id == "abp-toolbarbutton")
 
481
        return child;
 
482
  
 
483
    return null;
 
484
  },
 
485
 
 
486
  /**
 
487
   * Updates displayed state for an application window.
 
488
   */
 
489
  updateState: function()
 
490
  {
 
491
    let state = (Prefs.enabled ? "active" : "disabled");
 
492
  
 
493
    if (state == "active")
 
494
    {
 
495
      let location = this.getCurrentLocation();
 
496
      if (location && Policy.isWhitelisted(location.spec))
 
497
        state = "whitelisted";
 
498
    }
 
499
    this.state = state;
 
500
  
 
501
    function updateElement(element)
 
502
    {
 
503
      if (!element)
 
504
        return;
 
505
  
 
506
      if (element.tagName == "statusbarpanel")
 
507
        element.hidden = !Prefs.showinstatusbar;
 
508
      else
 
509
        element.hidden = !Prefs.showintoolbar;
 
510
  
 
511
      // HACKHACK: Show status bar icon instead of toolbar icon if the application doesn't have a toolbar icon
 
512
      if (element.hidden && element.tagName == "statusbarpanel" && !this.getDefaultToolbar)
 
513
        element.hidden = !Prefs.showintoolbar;
 
514
  
 
515
      element.setAttribute("abpstate", state);
 
516
    };
 
517
  
 
518
    let status = this.E("abp-status");
 
519
    if (status)
 
520
    {
 
521
      updateElement.call(this, status);
 
522
      if (Prefs.defaultstatusbaraction == 0)
 
523
        status.setAttribute("popup", status.getAttribute("context"));
 
524
      else
 
525
        status.removeAttribute("popup");
 
526
    }
 
527
    
 
528
    let button = this.E("abp-toolbarbutton");
 
529
    if (button)
 
530
    {
 
531
      updateElement.call(this, button);
 
532
      if (button.hasAttribute("context") && Prefs.defaulttoolbaraction == 0)
 
533
        button.setAttribute("type", "menu");
 
534
      else
 
535
        button.setAttribute("type", "menu-button");
 
536
    }
 
537
  
 
538
    updateElement.call(this, this.getPaletteButton());
 
539
  },
 
540
 
 
541
  /**
 
542
   * Sets up hotkeys for the window.
 
543
   */
 
544
  configureKeys: function()
 
545
  {
 
546
    for (let pref in Prefs)
 
547
    {
 
548
      if (pref.match(/_key$/))
 
549
      {
 
550
        try
 
551
        {
 
552
          this.configureKey(RegExp.leftContext, Prefs[pref]);
 
553
        }
 
554
        catch (e)
 
555
        {
 
556
          Cu.reportError(e);
 
557
        }
 
558
      }
 
559
    }
 
560
  },
 
561
 
 
562
  /**
 
563
   * Sets a hotkey to the value defined in preferences.
 
564
   */
 
565
  configureKey: function(/**String*/ id, /**String*/ value)
 
566
  {
 
567
    let validModifiers =
 
568
    {
 
569
      accel: "accel",
 
570
      ctrl: "control",
 
571
      control: "control",
 
572
      shift: "shift",
 
573
      alt: "alt",
 
574
      meta: "meta"
 
575
    };
 
576
  
 
577
    let command = this.E("abp-command-" + id);
 
578
    if (!command)
 
579
      return;
 
580
  
 
581
    let modifiers = [];
 
582
    let keychar = null;
 
583
    let keycode = null;
 
584
    for each (let part in value.split(/\s+/))
 
585
    {
 
586
      if (part.toLowerCase() in validModifiers)
 
587
        modifiers.push(validModifiers[part.toLowerCase()]);
 
588
      else if (part.length == 1)
 
589
        keychar = part;
 
590
      else if ("DOM_VK_" + part.toUpperCase() in Ci.nsIDOMKeyEvent)
 
591
        keycode = "VK_" + part.toUpperCase();
 
592
    }
 
593
  
 
594
    if (keychar || keycode)
 
595
    {
 
596
      let element = this.window.document.createElement("key");
 
597
      element.setAttribute("id", "abp-key-" + id);
 
598
      element.setAttribute("command", "abp-command-" + id);
 
599
      if (keychar)
 
600
        element.setAttribute("key", keychar);
 
601
      else
 
602
        element.setAttribute("keycode", keycode);
 
603
      element.setAttribute("modifiers", modifiers.join(","));
 
604
  
 
605
      this.E("abp-keyset").appendChild(element);
 
606
    }
 
607
  },
 
608
 
 
609
  /**
 
610
   * Initializes window's context menu.
 
611
   */
 
612
  initContextMenu: function()
 
613
  {
 
614
    let contextMenu = this.getContextMenu();
 
615
    if (contextMenu)
 
616
    {
 
617
      contextMenu.addEventListener("popupshowing", this._bindMethod(this.updateContextMenu), false);
 
618
    
 
619
      // Make sure our context menu items are at the bottom
 
620
      contextMenu.appendChild(this.E("abp-removeWhitelist-menuitem"));
 
621
      contextMenu.appendChild(this.E("abp-frame-menuitem"));
 
622
      contextMenu.appendChild(this.E("abp-object-menuitem"));
 
623
      contextMenu.appendChild(this.E("abp-media-menuitem"));
 
624
      contextMenu.appendChild(this.E("abp-image-menuitem"));
 
625
    }
 
626
  },
 
627
 
 
628
  /**
 
629
   * Makes sure the toolbar button is displayed.
 
630
   */
 
631
  installToolbarIcon: function()
 
632
  {
 
633
    let tb = this.E("abp-toolbarbutton");
 
634
    if (tb && tb.parentNode.localName != "toolbarpalette")
 
635
      return;
 
636
 
 
637
    let toolbar = (this.getDefaultToolbar ? this.getDefaultToolbar() : null);
 
638
    if (!toolbar || typeof toolbar.insertItem != "function")
 
639
      return;
 
640
 
 
641
    let insertBefore = (this.toolbarInsertBefore ? this.toolbarInsertBefore() : null);
 
642
    if (insertBefore && insertBefore.parentNode != toolbar)
 
643
      insertBefore = null;
 
644
 
 
645
    toolbar.insertItem("abp-toolbarbutton", insertBefore, null, false);
 
646
 
 
647
    toolbar.setAttribute("currentset", toolbar.currentSet);
 
648
    this.window.document.persist(toolbar.id, "currentset");
 
649
 
 
650
    if (this.unhideToolbar && this.unhideToolbar())
 
651
    {
 
652
      toolbar.setAttribute("collapsed", "false");
 
653
      this.window.document.persist(toolbar.id, "collapsed");
 
654
    }
 
655
  },
 
656
 
 
657
  /**
 
658
   * Handles browser clicks to intercept clicks on abp: links.
 
659
   */
 
660
  handleLinkClick: function (/**Event*/ event)
 
661
  {
 
662
    // Ignore right-clicks
 
663
    if (event.button == 2)
 
664
      return;
 
665
  
 
666
    // Search the link associated with the click
 
667
    let link = event.target;
 
668
    while (link && !(link instanceof Ci.nsIDOMHTMLAnchorElement))
 
669
      link = link.parentNode;
 
670
  
 
671
    if (!link || !/^abp:\/*subscribe\/*\?(.*)/i.test(link.href))  /**/
 
672
      return;
 
673
  
 
674
    // This is our link - make sure the browser doesn't handle it
 
675
    event.preventDefault();
 
676
    event.stopPropagation();
 
677
  
 
678
    // Decode URL parameters
 
679
    let title = null;
 
680
    let url = null;
 
681
    let mainSubscriptionTitle = null;
 
682
    let mainSubscriptionURL = null;
 
683
    for each (let param in RegExp.$1.split('&'))
 
684
    {
 
685
      let parts = param.split("=", 2);
 
686
      if (parts.length != 2 || !/\S/.test(parts[1]))
 
687
        continue;
 
688
      switch (parts[0])
 
689
      {
 
690
        case "title":
 
691
          title = decodeURIComponent(parts[1]);
 
692
          break;
 
693
        case "location":
 
694
          url = decodeURIComponent(parts[1]);
 
695
          break;
 
696
        case "requiresTitle":
 
697
          mainSubscriptionTitle = decodeURIComponent(parts[1]);
 
698
          break;
 
699
        case "requiresLocation":
 
700
          mainSubscriptionURL = decodeURIComponent(parts[1]);
 
701
          break;
 
702
      }
 
703
    }
 
704
    if (!url)
 
705
      return;
 
706
  
 
707
    // Default title to the URL
 
708
    if (!title)
 
709
      title = url;
 
710
  
 
711
    // Main subscription needs both title and URL
 
712
    if (mainSubscriptionTitle && !mainSubscriptionURL)
 
713
      mainSubscriptionTitle = null;
 
714
    if (mainSubscriptionURL && !mainSubscriptionTitle)
 
715
      mainSubscriptionURL = null;
 
716
  
 
717
    // Trim spaces in title and URL
 
718
    title = title.replace(/^\s+/, "").replace(/\s+$/, "");
 
719
    url = url.replace(/^\s+/, "").replace(/\s+$/, "");
 
720
    if (mainSubscriptionURL)
 
721
    {
 
722
      mainSubscriptionTitle = mainSubscriptionTitle.replace(/^\s+/, "").replace(/\s+$/, "");
 
723
      mainSubscriptionURL = mainSubscriptionURL.replace(/^\s+/, "").replace(/\s+$/, "");
 
724
    }
 
725
  
 
726
    // Verify that the URL is valid
 
727
    url = Utils.makeURI(url);
 
728
    if (!url || (url.scheme != "http" && url.scheme != "https" && url.scheme != "ftp"))
 
729
      return;
 
730
    url = url.spec;
 
731
  
 
732
    if (mainSubscriptionURL)
 
733
    {
 
734
      mainSubscriptionURL = Utils.makeURI(mainSubscriptionURL);
 
735
      if (!mainSubscriptionURL || (mainSubscriptionURL.scheme != "http" && mainSubscriptionURL.scheme != "https" && mainSubscriptionURL.scheme != "ftp"))
 
736
        mainSubscriptionURL = mainSubscriptionTitle = null;
 
737
      else
 
738
        mainSubscriptionURL = mainSubscriptionURL.spec;
 
739
    }
 
740
  
 
741
    // Open dialog
 
742
    if (!isFennec)
 
743
    {
 
744
      let subscription = {url: url, title: title, disabled: false, external: false, autoDownload: true,
 
745
                          mainSubscriptionTitle: mainSubscriptionTitle, mainSubscriptionURL: mainSubscriptionURL};
 
746
      this.window.openDialog("chrome://adblockplus/content/ui/subscriptionSelection.xul", "_blank",
 
747
                             "chrome,centerscreen,resizable,dialog=no", subscription, null);
 
748
    }
 
749
    else
 
750
    {
 
751
      // Special handling for Fennec
 
752
      AppIntegrationFennec.openFennecSubscriptionDialog(this, url, title);
 
753
    }
 
754
  },
 
755
 
 
756
  /**
 
757
   * Updates state of the icon tooltip.
 
758
   */
 
759
  fillTooltip: function(/**Event*/ event)
 
760
  {
 
761
    let node = this.window.document.tooltipNode;
 
762
    if (!node || !node.hasAttribute("tooltip"))
 
763
    {
 
764
      event.preventDefault();
 
765
      return;
 
766
    }
 
767
  
 
768
    let type = (node.id == "abp-toolbarbutton" ? "toolbar" : "statusbar");
 
769
    let action = parseInt(Prefs["default" + type + "action"]);
 
770
    if (isNaN(action))
 
771
      action = -1;
 
772
  
 
773
    let actionDescr = this.E("abp-tooltip-action");
 
774
    actionDescr.hidden = (action < 0 || action > 3);
 
775
    if (!actionDescr.hidden)
 
776
      actionDescr.setAttribute("value", Utils.getString("action" + action + "_tooltip"));
 
777
  
 
778
    let statusDescr = this.E("abp-tooltip-status");
 
779
    let statusStr = Utils.getString(this.state + "_tooltip");
 
780
    if (this.state == "active")
 
781
    {
 
782
      let [activeSubscriptions, activeFilters] = FilterStorage.subscriptions.reduce(function([subscriptions, filters], current)
 
783
      {
 
784
        if (current instanceof SpecialSubscription)
 
785
          return [subscriptions, filters + current.filters.filter(function(filter) !filter.disabled).length];
 
786
        else if (!current.disabled)
 
787
          return [subscriptions + 1, filters];
 
788
        else
 
789
          return [subscriptions, filters]
 
790
      }, [0, 0]);
 
791
  
 
792
      statusStr = statusStr.replace(/\?1\?/, activeSubscriptions).replace(/\?2\?/, activeFilters);
 
793
    }
 
794
    statusDescr.setAttribute("value", statusStr);
 
795
  
 
796
    let activeFilters = [];
 
797
    this.E("abp-tooltip-blocked-label").hidden = (this.state != "active");
 
798
    this.E("abp-tooltip-blocked").hidden = (this.state != "active");
 
799
    if (this.state == "active")
 
800
    {
 
801
      let stats = RequestNotifier.getWindowStatistics(this.getBrowser().contentWindow);
 
802
  
 
803
      let blockedStr = Utils.getString("blocked_count_tooltip");
 
804
      blockedStr = blockedStr.replace(/\?1\?/, stats ? stats.blocked : 0).replace(/\?2\?/, stats ? stats.items : 0);
 
805
  
 
806
      if (stats && stats.whitelisted + stats.hidden)
 
807
      {
 
808
        blockedStr += " " + Utils.getString("blocked_count_addendum");
 
809
        blockedStr = blockedStr.replace(/\?1\?/, stats.whitelisted).replace(/\?2\?/, stats.hidden);
 
810
      }
 
811
  
 
812
      this.E("abp-tooltip-blocked").setAttribute("value", blockedStr);
 
813
 
 
814
      if (stats)
 
815
      {
 
816
        let filterSort = function(a, b)
 
817
        {
 
818
          return stats.filters[b] - stats.filters[a];
 
819
        };
 
820
        for (let filter in stats.filters)
 
821
          activeFilters.push(filter);
 
822
        activeFilters = activeFilters.sort(filterSort);
 
823
      }
 
824
  
 
825
      if (activeFilters.length > 0)
 
826
      {
 
827
        let filtersContainer = this.E("abp-tooltip-filters");
 
828
        while (filtersContainer.firstChild)
 
829
          filtersContainer.removeChild(filtersContainer.firstChild);
 
830
    
 
831
        for (let i = 0; i < activeFilters.length && i < 3; i++)
 
832
        {
 
833
          let descr = filtersContainer.ownerDocument.createElement("description");
 
834
          descr.setAttribute("value", activeFilters[i] + " (" + stats.filters[activeFilters[i]] + ")");
 
835
          filtersContainer.appendChild(descr);
 
836
        }
 
837
      }
 
838
    }
 
839
  
 
840
    this.E("abp-tooltip-filters-label").hidden = (activeFilters.length == 0);
 
841
    this.E("abp-tooltip-filters").hidden = (activeFilters.length == 0);
 
842
    this.E("abp-tooltip-more-filters").hidden = (activeFilters.length <= 3);
 
843
  },
 
844
 
 
845
  /**
 
846
   * Updates state of the icon context menu.
 
847
   */
 
848
  fillPopup: function(/**Event*/ event)
 
849
  {
 
850
    let popup = event.target;
 
851
  
 
852
    // Submenu being opened - ignore
 
853
    if (!/^(abp-(?:toolbar|status)-)popup$/.test(popup.getAttribute("id")))
 
854
      return;
 
855
    let prefix = RegExp.$1;
 
856
  
 
857
    let sidebarOpen = this.isSidebarOpen();
 
858
    this.E(prefix + "opensidebar").hidden = sidebarOpen;
 
859
    this.E(prefix + "closesidebar").hidden = !sidebarOpen;
 
860
  
 
861
    let whitelistItemSite = this.E(prefix + "whitelistsite");
 
862
    let whitelistItemPage = this.E(prefix + "whitelistpage");
 
863
    whitelistItemSite.hidden = whitelistItemPage.hidden = true;
 
864
  
 
865
    let whitelistSeparator = whitelistItemPage.nextSibling;
 
866
    while (whitelistSeparator.nodeType != whitelistSeparator.ELEMENT_NODE)
 
867
      whitelistSeparator = whitelistSeparator.nextSibling;
 
868
  
 
869
    let location = this.getCurrentLocation();
 
870
    if (location && Policy.isBlockableScheme(location))
 
871
    {
 
872
      let host = null;
 
873
      try
 
874
      {
 
875
        host = location.host.replace(/^www\./, "");
 
876
      } catch (e) {}
 
877
  
 
878
      if (host)
 
879
      {
 
880
        let ending = "|";
 
881
        if (location instanceof Ci.nsIURL && location.ref)
 
882
          location.ref = "";
 
883
        if (location instanceof Ci.nsIURL && location.query)
 
884
        {
 
885
          location.query = "";
 
886
          ending = "?";
 
887
        }
 
888
  
 
889
        this.siteWhitelist = Filter.fromText("@@||" + host + "^$document");
 
890
        whitelistItemSite.setAttribute("checked", this.siteWhitelist.subscriptions.length && !this.siteWhitelist.disabled);
 
891
        whitelistItemSite.setAttribute("label", whitelistItemSite.getAttribute("labeltempl").replace(/\?1\?/, host));
 
892
        whitelistItemSite.hidden = false;
 
893
  
 
894
        this.pageWhitelist = Filter.fromText("@@|" + location.spec + ending + "$document");
 
895
        whitelistItemPage.setAttribute("checked", this.pageWhitelist.subscriptions.length && !this.pageWhitelist.disabled);
 
896
        whitelistItemPage.hidden = false;
 
897
      }
 
898
      else
 
899
      {
 
900
        this.siteWhitelist = Filter.fromText("@@|" + location.spec + "|");
 
901
        whitelistItemSite.setAttribute("checked", this.siteWhitelist.subscriptions.length && !this.siteWhitelist.disabled);
 
902
        whitelistItemSite.setAttribute("label", whitelistItemSite.getAttribute("labeltempl").replace(/\?1\?/, location.spec.replace(/^mailto:/, "")));
 
903
        whitelistItemSite.hidden = false;
 
904
      }
 
905
    }
 
906
    whitelistSeparator.hidden = whitelistItemSite.hidden && whitelistItemPage.hidden;
 
907
  
 
908
    this.E(prefix + "enabled").setAttribute("checked", Prefs.enabled);
 
909
    this.E(prefix + "frameobjects").setAttribute("checked", Prefs.frameobjects);
 
910
    this.E(prefix + "slowcollapse").setAttribute("checked", !Prefs.fastcollapse);
 
911
    this.E(prefix + "showintoolbar").setAttribute("checked", Prefs.showintoolbar);
 
912
    this.E(prefix + "showinstatusbar").setAttribute("checked", Prefs.showinstatusbar);
 
913
  
 
914
    let defAction = (prefix == "abp-toolbar-" || this.window.document.popupNode.id == "abp-toolbarbutton" ?
 
915
                     Prefs.defaulttoolbaraction :
 
916
                     Prefs.defaultstatusbaraction);
 
917
    this.E(prefix + "opensidebar").setAttribute("default", defAction == 1);
 
918
    this.E(prefix + "closesidebar").setAttribute("default", defAction == 1);
 
919
    this.E(prefix + "settings").setAttribute("default", defAction == 2);
 
920
    this.E(prefix + "enabled").setAttribute("default", defAction == 3);
 
921
  },
 
922
 
 
923
  /**
 
924
   * Opens report wizard for the current page.
 
925
   */
 
926
  openReportDialog: function()
 
927
  {
 
928
    let wnd = Utils.windowMediator.getMostRecentWindow("abp:sendReport");
 
929
    if (wnd)
 
930
      wnd.focus();
 
931
    else
 
932
      this.window.openDialog("chrome://adblockplus/content/ui/sendReport.xul", "_blank", "chrome,centerscreen,resizable=no", this.window.content);
 
933
  },
 
934
 
 
935
  /**
 
936
   * Tests whether blockable items list is currently open.
 
937
   */
 
938
  isSidebarOpen: function() /**Boolean*/
 
939
  {
 
940
    if (this.detachedSidebar && !this.detachedSidebar.closed)
 
941
      return true;
 
942
  
 
943
    let sidebar = this.E("abp-sidebar");
 
944
    return (sidebar ? !sidebar.hidden : false);
 
945
  },
 
946
 
 
947
  /**
 
948
   * Toggles open/closed state of the blockable items list.
 
949
   */
 
950
  toggleSidebar: function()
 
951
  {
 
952
    if (this.detachedSidebar && !this.detachedSidebar.closed)
 
953
    {
 
954
      this.detachedSidebar.close();
 
955
      this.detachedSidebar = null;
 
956
    }
 
957
    else
 
958
    {
 
959
      let sidebar = this.E("abp-sidebar");
 
960
      if (sidebar && (!Prefs.detachsidebar || !sidebar.hidden))
 
961
      {
 
962
        this.E("abp-sidebar-splitter").hidden = !sidebar.hidden;
 
963
        this.E("abp-sidebar-browser").setAttribute("src", sidebar.hidden ? "chrome://adblockplus/content/ui/sidebar.xul" : "about:blank");
 
964
        sidebar.hidden = !sidebar.hidden;
 
965
        if (sidebar.hidden)
 
966
          this.getBrowser().contentWindow.focus();
 
967
      }
 
968
      else
 
969
        this.detachedSidebar = this.window.openDialog("chrome://adblockplus/content/ui/sidebarDetached.xul", "_blank", "chrome,resizable,dependent,dialog=no");
 
970
    }
 
971
  
 
972
    let menuItem = this.E("abp-blockableitems");
 
973
    if (menuItem)
 
974
      menuItem.setAttribute("checked", this.isSidebarOpen());
 
975
  },
 
976
 
 
977
  /**
 
978
   * Removes/disables the exception rule applying for the current page.
 
979
   */
 
980
  removeWhitelist: function()
 
981
  {
 
982
    let location = this.getCurrentLocation();
 
983
    let filter = null;
 
984
    if (location)
 
985
      filter = Policy.isWhitelisted(location.spec);
 
986
    if (filter && filter.subscriptions.length && !filter.disabled)
 
987
      AppIntegration.toggleFilter(filter);
 
988
  },
 
989
 
 
990
  /**
 
991
   * Handles command events on toolbar icon.
 
992
   */
 
993
  handleToolbarCommand: function(event)
 
994
  {
 
995
    if (event.eventPhase != event.AT_TARGET)
 
996
      return;
 
997
 
 
998
    if (Prefs.defaulttoolbaraction == 0)
 
999
      event.target.open = true;
 
1000
    else
 
1001
      this.executeAction(Prefs.defaulttoolbaraction);
 
1002
  },
 
1003
 
 
1004
  /**
 
1005
   * Handles click events on toolbar icon.
 
1006
   */
 
1007
  handleToolbarClick: function(/**Event*/ event)
 
1008
  {
 
1009
    if (event.eventPhase != event.AT_TARGET)
 
1010
      return;
 
1011
 
 
1012
    if (event.button == 1)
 
1013
      AppIntegration.togglePref("enabled"); 
 
1014
  },
 
1015
 
 
1016
  /**
 
1017
   * Handles click events on status bar icon.
 
1018
   */
 
1019
  handleStatusClick: function(/**Event*/ event)
 
1020
  {
 
1021
    if (event.eventPhase != event.AT_TARGET)
 
1022
      return;
 
1023
 
 
1024
    if (event.button == 0)
 
1025
      this.executeAction(Prefs.defaultstatusbaraction);
 
1026
    else if (event.button == 1)
 
1027
      AppIntegration.togglePref("enabled"); 
 
1028
  },
 
1029
 
 
1030
  // Executes default action for statusbar/toolbar by its number
 
1031
  executeAction: function (action)
 
1032
  {
 
1033
    if (action == 1)
 
1034
      this.toggleSidebar();
 
1035
    else if (action == 2)
 
1036
      Utils.openSettingsDialog();
 
1037
    else if (action == 3)
 
1038
      AppIntegration.togglePref("enabled");
 
1039
  },
 
1040
 
 
1041
  /**
 
1042
   * Updates context menu, in particularly controls the visibility of context
 
1043
   * menu items like "Block image".
 
1044
   */
 
1045
  updateContextMenu: function(event)
 
1046
  {
 
1047
    if (event.eventPhase != event.AT_TARGET)
 
1048
      return;
 
1049
 
 
1050
    let contextMenu = this.getContextMenu();
 
1051
    let target = this.window.document.popupNode;
 
1052
    if (target instanceof Ci.nsIDOMHTMLMapElement || target instanceof Ci.nsIDOMHTMLAreaElement)
 
1053
    {
 
1054
      // HTML image maps will usually receive events when the mouse pointer is
 
1055
      // over a different element, get the real event target.
 
1056
      let rect = target.getClientRects()[0];
 
1057
      target = target.ownerDocument.elementFromPoint(Math.max(rect.left, 0), Math.max(rect.top, 0));
 
1058
    }
 
1059
 
 
1060
    let nodeType = null;
 
1061
    this.nodeData = null;
 
1062
    this.currentNode = null;
 
1063
    this.backgroundData = null;
 
1064
    this.frameData = null;
 
1065
    this.currentFrame = null;
 
1066
    if (target)
 
1067
    {
 
1068
      // Lookup the node in our stored data
 
1069
      let data = RequestNotifier.getDataForNode(target);
 
1070
      if (data && !data[1].filter)
 
1071
      {
 
1072
        [this.currentNode, this.nodeData] = data;
 
1073
        nodeType = this.nodeData.typeDescr;
 
1074
      }
 
1075
  
 
1076
      let wnd = Utils.getWindow(target);
 
1077
 
 
1078
      if (wnd.frameElement)
 
1079
      {
 
1080
        let data = RequestNotifier.getDataForNode(wnd.frameElement, true);
 
1081
        if (data && !data[1].filter)
 
1082
          [this.currentFrame, this.frameData] = data;
 
1083
      }
 
1084
 
 
1085
      if (nodeType != "IMAGE")
 
1086
      {
 
1087
        // Look for a background image
 
1088
        let imageNode = target;
 
1089
        while (imageNode)
 
1090
        {
 
1091
          if (imageNode.nodeType == imageNode.ELEMENT_NODE)
 
1092
          {
 
1093
            let style = wnd.getComputedStyle(imageNode, "");
 
1094
            let bgImage = extractImageURL(style, "background-image") || extractImageURL(style, "list-style-image");
 
1095
            if (bgImage)
 
1096
            {
 
1097
              let data = RequestNotifier.getDataForNode(wnd.document, true, Policy.type.IMAGE, bgImage);
 
1098
              if (data && !data[1].filter)
 
1099
              {
 
1100
                this.backgroundData = data[1];
 
1101
                break;
 
1102
              }
 
1103
            }
 
1104
          }
 
1105
 
 
1106
          imageNode = imageNode.parentNode;
 
1107
        }
 
1108
      }
 
1109
  
 
1110
      // Hide "Block Images from ..." if hideimagemanager pref is true and the image manager isn't already blocking something
 
1111
      let imgManagerContext = this.E("context-blockimage");
 
1112
      if (imgManagerContext && shouldHideImageManager())
 
1113
      {
 
1114
        // Don't use "hidden" attribute - it might be overridden by the default popupshowing handler
 
1115
        imgManagerContext.collapsed = true;
 
1116
      }
 
1117
    }
 
1118
  
 
1119
    this.E("abp-image-menuitem").hidden = (nodeType != "IMAGE" && this.backgroundData == null);
 
1120
    this.E("abp-object-menuitem").hidden = (nodeType != "OBJECT");
 
1121
    this.E("abp-media-menuitem").hidden = (nodeType != "MEDIA");
 
1122
    this.E("abp-frame-menuitem").hidden = (this.frameData == null);
 
1123
  
 
1124
    let location = this.getCurrentLocation();
 
1125
    this.E("abp-removeWhitelist-menuitem").hidden = (!location || !Policy.isWhitelisted(location.spec));
 
1126
  },
 
1127
 
 
1128
  /**
 
1129
   * Brings up the filter composer dialog to block an item.
 
1130
   */
 
1131
  blockItem: function(/**Node*/ node, /**RequestEntry*/ item)
 
1132
  {
 
1133
    if (!item)
 
1134
      return;
 
1135
 
 
1136
    this.window.openDialog("chrome://adblockplus/content/ui/composer.xul", "_blank", "chrome,centerscreen,resizable,dialog=no,dependent", [node], item);
 
1137
  }
 
1138
};
 
1139
 
 
1140
/**
 
1141
 * List of event handers to be registered. For each event handler the element ID,
 
1142
 * event and the actual event handler are listed.
 
1143
 * @type Array
 
1144
 */
 
1145
WindowWrapper.prototype.eventHandlers = [
 
1146
  ["abp-tooltip", "popupshowing", WindowWrapper.prototype.fillTooltip],
 
1147
  ["abp-status-popup", "popupshowing", WindowWrapper.prototype.fillPopup],
 
1148
  ["abp-toolbar-popup", "popupshowing", WindowWrapper.prototype.fillPopup],
 
1149
  ["abp-command-sendReport", "command", WindowWrapper.prototype.openReportDialog],
 
1150
  ["abp-command-settings", "command", function() {Utils.openSettingsDialog();}],
 
1151
  ["abp-command-sidebar", "command", WindowWrapper.prototype.toggleSidebar],
 
1152
  ["abp-command-togglesitewhitelist", "command", function() { AppIntegration.toggleFilter(this.siteWhitelist); }],
 
1153
  ["abp-command-togglepagewhitelist", "command", function() { AppIntegration.toggleFilter(this.pageWhitelist); }],
 
1154
  ["abp-command-toggleobjtabs", "command", function() { AppIntegration.togglePref("frameobjects"); }],
 
1155
  ["abp-command-togglecollapse", "command", function() { AppIntegration.togglePref("fastcollapse"); }],
 
1156
  ["abp-command-toggleshowintoolbar", "command", function() { AppIntegration.togglePref("showintoolbar"); }],
 
1157
  ["abp-command-toggleshowinstatusbar", "command", function() { AppIntegration.togglePref("showinstatusbar"); }],
 
1158
  ["abp-command-enable", "command", function() { AppIntegration.togglePref("enabled"); }],
 
1159
  ["abp-toolbarbutton", "command", WindowWrapper.prototype.handleToolbarCommand],
 
1160
  ["abp-toolbarbutton", "click", WindowWrapper.prototype.handleToolbarClick],
 
1161
  ["abp-status", "click", WindowWrapper.prototype.handleStatusClick],
 
1162
  ["abp-image-menuitem", "command", function() { this.backgroundData ? this.blockItem(null, this.backgroundData) : this.blockItem(this.currentNode, this.nodeData); }],
 
1163
  ["abp-object-menuitem", "command", function() { this.blockItem(this.currentNode, this.nodeData); }],
 
1164
  ["abp-media-menuitem", "command", function() { this.blockItem(this.currentNode, this.nodeData); }],
 
1165
  ["abp-frame-menuitem", "command", function() { this.blockItem(this.currentFrame, this.frameData); }],
 
1166
  ["abp-removeWhitelist-menuitem", "command", WindowWrapper.prototype.removeWhitelist]
 
1167
];
 
1168
 
 
1169
/**
 
1170
 * Updates displayed status for all application windows (on prefs or filters
 
1171
 * change).
 
1172
 */
 
1173
function reloadPrefs()
 
1174
{
 
1175
  if (currentlyShowingInToolbar != Prefs.showintoolbar)
 
1176
  {
 
1177
    currentlyShowingInToolbar = Prefs.showintoolbar;
 
1178
    if (Prefs.showintoolbar)
 
1179
      for each (let wrapper in wrappers)
 
1180
        wrapper.installToolbarIcon();
 
1181
  }
 
1182
 
 
1183
  for each (let wrapper in wrappers)
 
1184
    wrapper.updateState();
 
1185
}
 
1186
 
 
1187
/**
 
1188
 * Tests whether image manager context menu entry should be hidden with user's current preferences.
 
1189
 * @return Boolean
 
1190
 */
 
1191
function shouldHideImageManager()
 
1192
{
 
1193
  let result = false;
 
1194
  if (Prefs.hideimagemanager && "@mozilla.org/permissionmanager;1" in Cc)
 
1195
  {
 
1196
    try
 
1197
    {
 
1198
      result = true;
 
1199
      let enumerator = Cc["@mozilla.org/permissionmanager;1"].getService(Ci.nsIPermissionManager).enumerator;
 
1200
      while (enumerator.hasMoreElements())
 
1201
      {
 
1202
        let item = enumerator.getNext().QueryInterface(Ci.nsIPermission);
 
1203
        if (item.type == "image" && item.capability == Ci.nsIPermissionManager.DENY_ACTION)
 
1204
        {
 
1205
          result = false;
 
1206
          break;
 
1207
        }
 
1208
      }
 
1209
    }
 
1210
    catch(e)
 
1211
    {
 
1212
      result = false;
 
1213
    }
 
1214
  }
 
1215
 
 
1216
  shouldHideImageManager = function() result;
 
1217
  return result;
 
1218
}
 
1219
 
 
1220
/**
 
1221
 * Executed on first run, presents the user with a list of filter subscriptions
 
1222
 * and allows choosing one.
 
1223
 */
 
1224
function showSubscriptions()
 
1225
{
 
1226
  let wrapper = (wrappers.length ? wrappers[0] : null);
 
1227
 
 
1228
  // Don't annoy the user if he has a subscription already
 
1229
  let hasSubscriptions = FilterStorage.subscriptions.some(function(subscription) subscription instanceof DownloadableSubscription);
 
1230
  if (hasSubscriptions)
 
1231
    return;
 
1232
 
 
1233
  // Only show the list if this is the first run or the user has no filters
 
1234
  let hasFilters = FilterStorage.subscriptions.some(function(subscription) subscription.filters.length);
 
1235
  if (hasFilters && Utils.versionComparator.compare(Prefs.lastVersion, "0.0") > 0)
 
1236
    return;
 
1237
 
 
1238
  if (wrapper && wrapper.addTab)
 
1239
  {
 
1240
    wrapper.addTab("chrome://adblockplus/content/ui/subscriptionSelection.xul");
 
1241
  }
 
1242
  else
 
1243
  {
 
1244
    Utils.windowWatcher.openWindow(wrapper ? wrapper.window : null,
 
1245
                                   "chrome://adblockplus/content/ui/subscriptionSelection.xul",
 
1246
                                   "_blank", "chrome,centerscreen,resizable,dialog=no", null);
 
1247
  }
 
1248
}
 
1249
 
 
1250
/**
 
1251
 * Extracts the URL of the image from a CSS property.
 
1252
 */
 
1253
function extractImageURL(/**CSSStyleDeclaration*/ computedStyle, /**String*/ property)
 
1254
{
 
1255
  let value = computedStyle.getPropertyCSSValue(property);
 
1256
  if (value instanceof Ci.nsIDOMCSSValueList && value.length >= 1)
 
1257
    value = value[0];
 
1258
  if (value instanceof Ci.nsIDOMCSSPrimitiveValue && value.primitiveType == Ci.nsIDOMCSSPrimitiveValue.CSS_URI)
 
1259
    return Utils.unwrapURL(value.getStringValue()).spec;
 
1260
 
 
1261
  return null;
 
1262
}
 
1263
 
 
1264
init();