-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathtabkit.js
More file actions
6239 lines (5439 loc) · 249 KB
/
tabkit.js
File metadata and controls
6239 lines (5439 loc) · 249 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/* Tab Kit 2nd Edition(Tab Kit 2 for short) - http://code.google.com/p/tabkit-2nd-edition/
*
* Copyright (c) 2007-2010 John Mellor
* Copyright (c) 2011-2012 Leung Ho Kuen <pikachuexe@gmail.com>
*
* This file is part of Tab Kit 2.
* Tab Kit is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* TabKit 2 is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
/* Note on the source code
* -----------------------
* This code is split into sections, separated by "//|##########################".
*
* The earlier sections, whose name is preceded by "//{### " provide services or abstraction
* functions for later sections.
*
* The later sections, starting with "//{>>> " or "//{=== ", are where the important code is
* (especially the >>> ones). These code sections are designed to be largely independent of
* each other, and could often be removed without affecting the rest of the code.
*
* n.b. If you edit this in SciTE the //{ and //} cause the code to fold nicely.
*/
/* Changelog
* ---------
* v0.6 (2010-09-26)
* - Add tab search bar
* - Re-enable tabs on bottom (as the main bug has been fixed)
* - Dropped compatibility with Fx3
* v0.5.12 (2010-07-02)
* - Use tab instead of modal dialog for First Run Wizard
* - Fix: don't rely on old extensions manager interface which has been removed from Fx 4
* v0.5.11 (2010-06-30)
* - Fix: hovering over multi-row tab bar would cause rows to change height.
* v0.5.10 (2010-06-29)
* - Support Firefox 3.6
* - Fix issue #16: Sidebar border shrinks/expands on mouseover => can't widen the sidebar by dragging
* - Fix issue #94: Tab drop indicator not showing while setting tab bar position at top edge
* - Fix Open Selected Links in Fx 3.6
* - Fix gaps appearing when dragging groups around horizontal tab bar
* v0.5.8 (2009-08-02)
* - Fix issue #2: Dropping a tab from a 2 tab group onto itself causes weird behaviour
* - Fix issue #11: "Use scrollbars instead of arrows on the Bookmarks and All Tabs menus" messes up various menus
* - Fix issue #8: When grouping disabled tab color still set when use "Open X Links in New Tabs", or "Open All In Tabs" on bookmark folder
* v0.5.7 (2009-07-06)
* - Added de (German) locale (by Tom Fichtner)
* - Fix bug closing the last tab when browser.tabs.closeWindowWithLastTab is false
* v0.5.6 (2009-05-24)
* - Updated zh-CN locale (by Renda)
* - Changed Close Subtree to more versatile Close Children
* - Tweaked Switch Tabs on Hover: now has delay even if your tabs are vertical
* - Improved compatibility with Personas by making tabs more opaque (as group colours are important) and making Personas' background repeat vertically (to fill the vertical tab bar)
* - Fix: Uncollapsing the tab drop indicator sometimes caused the tab bar to scroll
* - Fix: Multiple tabs (like multiple homepages) open in the same place the equivalent single tab would have opened
* - Added compatibility with Snap Links Plus (so tabs it opens are grouped)
* v0.5.5 (2009-04-27)
* - Fixed typo affecting closing tabs in Firefox 3.5b4
* v0.5.4 (2009-04-27)
* - Added activation delay to switch tabs on hover functionality (by fixing typo)
* v0.5.3 (2009-04-26)
* - Fixed multi-row tabs on Firefox 3
* - Made the tab bar remember scroll position if collapsed while vertical
* v0.5.2 (2009-04-23)
* - Improved Ubuntu compatibility
* - Made First Run Wizard fit smaller screens
* v0.5.1 (2009-04-22)
* - Fixed a bug that prevented 0.5 from working on most systems
* v0.5 (2009-04-22)
* - Made compatible with latest Firefox 3.5 betas; dropped compatibility with Firefox 2
* - Added First Run Wizard to help users choose between tab tree, multi-row tabs, or just normal tab positioning
* - Groups no longer expand on single-click - this just confused people. You now have to click the plus button, or double-click them, as before
* - Option to make bookmarks/history open in new tabs by default (under Advanced)
* - Option to switch tabs by hovering over them (under Controls)
* - Turned "Emphasize current tab in black" off by default for aesthetic reasons
* - Improved Open Selected Text Link url matching (now ignores closing brackets and punctuation)
* - Added Chinese Traditional (zh-TW) locale, thanks to Hugo Chen (though needs updating slightly)
* - Added Russian (ru) locale, thanks to Timur Timirkhanov (though needs updating slightly)
* - Now compatible with extensions like Tab Clicking Options (https://addons.mozilla.org/en-US/firefox/addon/260) that replace double-click functionality
* - Now detects tabs opened by the Mouseless Browsing extension
* - Performance: no longer loads main Javascript file in non-browser windows
* - When switching tabs, will no longer expand collapsed groups
* - Fix: Ctrl-clicked bookmarks now open in the right place
* - Fix: Prevent first group randomly collapsing and/or losing indents when restarting
* - Fix: Using Group Tabs From Here To Current in the middle of a group now always causes inner group to be ejected from outer group
* - Miscellaneous tweaks and fixes
* v0.4.3 (2008-08-02)
* - "Protect Tab" menuitem lets you mark tabs as protected, preventing them from being closed
* - Options to make the address bar and/or search bar open into new tabs by default (press Alt to open in current tab)
* - Collapsed groups now show a plus icon, and auto-expand when clicked on. Also, when hovering over them the tooltip shows all tab titles (one per line), instead of just the visible tab
* - Added Chinese (zh-CN) locale, thanks to Renda
* v0.4.2 (2008-07-16)
* - Collapsed vertical tab bar will expand on hover
* - When grouping tabs by domain, if you navigate to a new url in an ungrouped about:blank tab, that tab will now get grouped by domain
* - In Firefox 3, Tab Kit now uses the Effective TLD Service when grouping tabs by domain, rather than my approximation
* - Fix: URLs opened in new tabs using Alt+enter in the address bar are now correctly grouped by domain even if you miss off "http://" etc
* v0.4.1 (2008-07-09)
* - Much better theme compatibility
* - Customisable saturation and lightness ranges for tab group colours
* - Warns users about incompatibility with Tab Mix Plus
* - Fix: Infinite loop possible when changing "color tabs not labels" option
* v0.4 (2008-05-19)
* - Added Open Selected Links (including text links) feature, see Tab Kit Options under Tabs for details
* - Collapsed groups now always drag together as a group
* - Various minor tweaks
* v0.4pre (2008-05-07):
* - Made compatible with Firefox 3. There may still be one or two odd behaviours. Note that while Tab Kit is largely unchanged, everything will be much faster due to improvements in Firefox.
* - Vertical tabs splitter now allows the tabar to be temporarily collapsed by clicking the splitter
* - Fix: with dark themes, you could sometimes end up with unreadable black text on black tabs
* v0.3 (2007-11-06):
* - Automatically picks group colors which are different from those of nearby groups
* - Option to automatically collapse inactive groups
* - Reworked tab dragging.
* - o Shift-drag will drag a whole group together, even across windows
* - o Ctrl-drag copies dragged tabs, and Ctrl-Shift-drag copies a group (n.b. Cmd instead of Ctrl on Mac)
* - o Dragged tabs now gain the appropriate tree indentation (instead of resetting it) when in indented tree mode
* - o There is now an option to make Shift-drag move subtrees instead of groups (when in indented tree mode)
* - o Fix: Tab drop indicator no longer flashes (and sometimes prevents a drag) in vertical tab bar mode (unless you drag directly over the arrow - this is almost inevitable)
* - New "Group Tabs From Here To Current" command will group tabs between the selected tab and the right-clicked tab (this replaces the broken and long-winded "Create New Group From Consecutive Tabs")
* - Added "Close Subtree" command to close a tab and its child tabs
* - Ctrl-middleclick on a tab group closes it, or alternatively Ctrl-click a tab's close button (n.b. Cmd instead of Ctrl on Mac)
* - Similarly Ctrl-Shift-middleclick on a tab closes the subtree it is parent of, or alternatively Ctrl-Shift-click the close button
* - Replaced "Close Other Tabs" with "Close Tabs Before" and "Close Tabs After" (optionally)
* - Added Options button to Tab Kit tab context submenu for quick access
* - Fix: Double-clicking tab close buttons (when closing several tabs in a row) now closes the tab instead of collapsing its group
* - Fix: The splitter now hides if the tab bar is hidden (only one tab)
* v0.2.1 (2007-08-07):
* - Close buttons now show on tabs (if enabled) when the tab bar is vertical, and tab text is cropped appropriately.
* - Vertical or multi-row tab bar will now autoscroll to make sure new (background) tabs are onscreen
* - Fix: Context menu searches are now correctly grouped
* - Fix: Tab bar and sidebar positions are now remembered even if they are on the bottom and right respectively
* v0.2 (2007-08-02):
* - First public version.
*/
/* Rough Todo List
* ---------------
* I keep todo notes in the form TODO=Px where x is a priority betweeen 1 (highest) and 5 (lowest).
* Though I normally finish all the P1 and most of the P2 ones before making a release.
* There are more todos in the source itself, search for: TODO=P
* These todos are now being transitioned to two places, http://tabkit.uservoice.com/ for major enhancements, and
http://code.google.com/p/tabkit/issues for bug reports, tasks, and small tweaks. Issues marked UVOICE have been
moved to the former, issues marked GCODE are being moved to the latter. Issues marked ??? are undetermined,
issues marked TJS will remain in this source code file for now, and issues marked N/A are no longer relevant.
* TODO=P3: GCODE#1 Upload Tab Kit's Mercurial repository to Google Code
* TODO=P3: GCODE Move these TODOs to the issue tracker
* TODO=P2: GCODE#2 Bug: Drag child tab of parent-child group onto bottom half of parent tab (such that it wouldn't move!), and it'll lose its indent and the parent will be degrouped (but not the dragged tab!)
* TODO=P3: GCODE Strongly discourage using together with Tree Style Tab (don't necessarily auto-disable, but at least show a first-run-tab)
* TODO=P3: GCODE Recommend using with TabGroups Manager and/or TooManyTabs (until I implement workspaces), and Ctrl-Tab, Session Manager and Tab Clicking Options
* TODO=P3: GCODE Fx3.5: Occasional bugs with subtree dragging
* TODO=P4: GCODE Use, and hook, Firefox's new duplicateTab method (esp. reset tabid and remove gid) [partially done in sortgroup_onSSTabRestoring]
* TODO=P4: GCODE _onDrop's 'document.getBindingParent(aEvent.originalTarget).localName != "tab"' should be 'aEvent.target.localName != "tab"' ?!
Groups as persistent selections:
* TODO=P3: UVOICE Make Ctrl+Click tab add any tab to the current group (moving it adjacent to the group if necessary, and creating a new group if the current tab was ungrouped), unless the clicked tab was already in the current group, in which case it is removed from the group (and moved out of the group if not already on the edge).
* TODO=P3: UVOICE Make Shift+Click tab make a group from all tabs between the current and clicked tab inclusive (if the current tab was already in a group, any group tabs that aren't between the current and clicked tabs will stay in their old group instead of joining the new group). Then allow removing Group Tabs From Here To Current menuitem.
* TODO=P3: ??? Document both the above in First Run Wizard? Nah, just show shortcuts on menuitems and people will pick them up?
* TODO=P3: GCODE Refactor context menu. Move Global Actions into Tools, This Tab items into top level context menu, and This Group can stay as the submenu.
* TODO=P3: GCODE Make context menu New Tab become New Tab Here, replacing that option, except when right-clicking empty parts of tab bar.
* TODO=P3: GCODE Add Tab Bar Position to Global Actions (now in Tools), and other extremely common options. Also menuitem for Help and/or re-run First Run Wizard.
* TODO=P3: UVOICE Add Move group to window >> Title 1 / Title 2 / Title 3 / [New Window] to This Group submenu. To workspaces instead?
* TODO=P4: GCODE Add Close Other Tabs (not in this group) to This Group submenu (how does this interact with Close Left/Right?)
* TODO=P3: UVOICE Workspaces [[[adding a dropdown button with: Store Away Current Tab/Group <sep> Store Away Current Window <sep> <list of saved tab/groups> (clicking opens then removes entry) <sep> Recently restored entries >> <sep> (gray comment:) Shift+click to delete an entry (without opening it). Auto-suggest title from TLDs, date & tab count. Sort by most recent and/or alphabetic (if alphabetic default put date at beginning of title suggestion)]]].
* TODO=P3: UVOICE Idle Tabs functionality (possibly as separate extension) - make idle tabs (or startup tabs) be sessionstore stubs that only (re)load once viewed and/or clicked in
* TODO=P3: GCODE Scroll up/down when tab dragging so can drag to anywhere rather than having to do it in bits
* TODO=P3: GCODE Fx3+: Improve Fullscreen (F11) animation with vertical tab bar (c.f. bug 423014). Tree Style Tab does this well...
* TODO=P3: GCODE Expand groups hovered over (for a while) during tab drags, so can drag into them (then make auto-collapse always collapse, even if select ungrouped tab). In the long run, am planning to show collapsed groups as favicon list, which you could drag straight into.
* TODO=P3: UVOICE Allow dropping onto middle of tabs to make the dropped tab a child of the target tab, like Tree Style Tabs
* TODO=P3: GCODE Optmisation: Use _tabContainer.getElementsByAttribute in many of the cases where I currently iterate through _tabs
* TODO=P3: GCODE Shrink First Run Wizard image filesizes (use JPEGs if necessary)
* TODO=P3: UVOICE Option which will prevent you from opening the same url twice (or tell you that you have this url already opened) [info bar?]
* TODO=P3: UVOICE Search within all tabs' text c.f. Design Challenge Plans in Evernote
* TODO=P3: UVOICE Only mark tab as read after ~1s delay, to avoid doing so while flicking through
* TODO=P3: UVOICE Add Shortcuts dialog or options tab, with a 3/4 column table letting you 1) toggle whether things show in the tab context menu 2) allow setting keyboard shortcuts (with defaults of some kind (perhaps Alt+Shift ones) 3) ideally allow customisation of tab clicking options (assumes that context menu options correspond with possible commands). This could also take over letting people show Close Other Tabs and/or Close Left/Right tabs.
* TODO=P3: UVOICE Reset background tabs as unread when their title changes (due to a load, or incoming Gmail message)
* TODO=P3: GCODE Add "Tab Kit Options" button to Firefox Options -> Tabs, like Tab Mix Plus does (less important once Global Actions moved to Tools)
* TODO=P3: UVOICE Protect (/Pin) Tab could save tabs across sessions, like PermaTabs did
* TODO=P4: ??? Protect tab could lock navigation (no back/forward and links open in new tabs)
* TODO=P3: ??? Make grouping bookmark groups optional?
* TODO=P3: GCODE Colorpickers for unread/current/protected tab highlights, as in PermaTabs. Instead, could just make these prettier...
* TODO=P3: GCODE Document the fact that Close buttons: 'Show on all tabs' depends on tab clip width
* TODO=P3: GCODE Add double-click to close tab option (less important now no longer conflicts with Tab Clicking Options)
* TODO=P3: GCODE Use preventChangeOfAttributes to set vertical tabbar increment (though not pageincrement)
* TODO=P3: GCODE Fx3+: Update Sorting & Grouping method hooks
* TODO=P3: GCODE Investigate http://piro.sakura.ne.jp/xul/_treestyletab.html.en
* TODO=P3: UVOICE Collapse/expand any subtree, not just entire groups?
* TODO=P3: UVOICE Slick arrows for collapsed/expanded
* TODO=P3: ~UVOICE count showing # of hidden child tabs
* TODO=P3: UVOICE better auto-hide tab bar
o Can you make it so its just like that of the "Tree Style Tab" where you can pick the exact size-width of the tab-bar by dragging it to the width of your liking, and also when and where you want it to pop out when you hover your mouse over to it 0-100Px from left etc
o Also instead of moving the whole webpage over to the right to make room for the unhidden tab-bar (when its in vertical mode on the left), can you make it so the tab-bar just overlays on top of the webpage please..Like Tree Style Tab does..
o And also have the tab-bar already in auto-hide mode every time Firefox starts up and also when the tab-bar first appears when a new tab is opened, have it autohide itself then too.
* TODO=P3: GCODE Investigate http://paranoid-androids.com/tabgroups/
* TODO=P3: GCODE Check compatibility with https://addons.mozilla.org/en-US/firefox/addon/3726 (Tab Overflow Scrollbar)
* TODO=P3: UVOICE Preferences Wizard on first run offering sensible settings for multi-row / tab tree, etc.
* TODO=P3: GCODE Automatic conflict checkings, e.g.
* Disable gestures if FireGestures is installed - https://addons.mozilla.org/en-US/firefox/addon/6366
* Investigate compatibility with Tab Mix Plus (for the minor features like progress bars on tabs & tab clicking options)
* TreeStyleTab, tabgroups, etc.
* New Tab Button on Tab Right is apparently incompatible
* TODO=P3: UVOICE More flexible/intuitive tree drag&drop, letting you arbitrarily assign parents etc, and also make the indents etc more robust
* TODO=P3: UVOICE Window/workspace merging
* TODO=P3: GCODE .tabs-bottom color doesn't work in Fx3+ (and was never updated when closing a tab group)
* TODO=P3: GCODE Fx3+: Bottom row of multirow tabs is 1px too tall
* TODO=P4: GCODE Fx2: Can't drag scrollbar slider on bookmarks menu without closing menu (works in Fx3+)
* TODO=P4: GCODE Look into possibility of displaying the sidebar beneath a vertical tab bar, so they share one column
* TODO=P3: GCODE Implement lite version of LastTab Ctrl-Tab stack switching? Probably not since Ctrl-Tab is supposedly going to be incorporated into Fx3.6
* TODO=P3: ??? Multi-row on hover (for more than ~1 second)
* TODO=P3: ??? Multi-row: vertical splitter to adjust [max] no. of rows?
* TODO=P3: GCODE BabelZilla
* TODO=P3: UVOICE Fade old tabs with age, like Dao's Aging Tabs (https://addons.mozilla.org/en-US/firefox/addon/3542), or be compatible(!)
* TODO=P3: GCODE Collapsed group underline is invisible for the active tab when emphasizecurrent is on
* TODO=P3: GCODE Make All Tabs scroll to current tab (preferably vertically centered)
* TODO=P3: GCODE Back forward rocker: "Any chance something was left out? I've found a bug, but I don't know if it occurs in Tab Kit or only in the snippet. Activating a rocker gesture while hovering over a link usually does not work. Rather, the left-click takes precedence. From some testing, it appears that the gesture does work when the previous/next page is already in the fastback cache. (Edit: Thus, it seems the problem is that normal left-click still occurs in addition to, and right after, the gesture.) I hope this helps track down the issue. Thanks." http://forums.mozillazine.org/viewtopic.php?p=3746475#p3746475
* TODO=P3: UVOICE Under "When Closing Tabs", is it possible to add a "Last Selected Tab"?
* TODO=P3: ~UVOICE Make collapsed groups more obvious, e.g. "(+N)" right-aligned text showing hidden count, heavy border (arguably expanded ones should have a minus too, but need to think about how that ties into the tree)
* TODO=P4: GCODE Make collapsed group plus symbol work in Mac theme
* TODO=P4: N/A Should auto-expanded collapsed groups recollapse when you leave (assuming auto-collapse inactive is off)?
* TODO=P4: GCODE Option to hide All Tabs button
* TODO=P4: GCODE Groups change colour when dragged (probably only when shift-drag subtrees is enabled)
* TODO=P4: GCODE Disable Close Tabs Above/Below on first/last tab respectively
* TODO=P4: GCODE Use existing tab duplication code in Fx3+ rather than reimplementing
* TODO=P4: UVOICE Fisheye vertical tabs, c.f. https://addons.mozilla.org/en-US/firefox/addon/4845 (horizontal fisheye tabs)
* TODO=P4: GCODE Fix mouse rocker back/forward on linux (where context menu is onmousedown)
* TODO=P4: GCODE Make group start/end more obvious, e.g. with /--|---|--\ for colorblind people
* TODO=P4: GCODE Fx2: Scrollbar on bookmarks menu used to cause artifacts (wheelscroll even worse), check this is fixed
* TODO=P4: GCODE Check that shift-dragging a group and/or subtree into subtree never causes following tabs to reset indent
*/
(function (window) {
window.tabkit = new function _tabkit() { // Primarily just a 'namespace' to hide our stuff in
//|##########################
//{### Basic Constants
//|##########################
/// Private globals:
const tk = this; // Functions passed as parameters lose their this, as do nested functions, and tabkit is a bit long(!), so store it in 'tk'
const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
const Cc = Components.classes;
const Ci = Components.interfaces;
const PREF_BRANCH = "extensions.tabkit.";
const TAB_MIN_WIDTH = 50;
//}##########################
//{### Services
//|##########################
// Make sure we can use gPrefService from now on (even if this isn't a browser window!)
if (typeof gPrefService == "undefined" || !gPrefService)
var gPrefService = Cc["@mozilla.org/preferences-service;1"].
getService(Ci.nsIPrefBranch);
/// Private globals:
var _console = Cc["@mozilla.org/consoleservice;1"]
.getService(Ci.nsIConsoleService);
var _ds = Cc["@mozilla.org/file/directory_service;1"]
.getService(Ci.nsIProperties);
var _em = null;
if ("@mozilla.org/extensions/manager;1" in Cc)
_em = Cc["@mozilla.org/extensions/manager;1"]
.getService(Ci.nsIExtensionManager);
var _ios = Cc["@mozilla.org/network/io-service;1"]
.getService(Ci.nsIIOService);
var _os = Cc["@mozilla.org/observer-service;1"]
.getService(Ci.nsIObserverService);
var _prefs = Cc["@mozilla.org/preferences-service;1"]
.getService(Ci.nsIPrefService)
.getBranch(PREF_BRANCH);
this.localPrefService = _prefs;
var _ps = Cc["@mozilla.org/embedcomp/prompt-service;1"]
.getService(Ci.nsIPromptService);
var _sm = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
var _ss = null;
if ("@mozilla.org/browser/sessionstore;1" in Cc)
_ss = Cc["@mozilla.org/browser/sessionstore;1"]
.getService(Ci.nsISessionStore);
else
var _winvars = {}; // For tk.get/setWindowValue // TODO=P1: Remove if redundant
var _sound = Cc["@mozilla.org/sound;1"]
.getService(Ci.nsISound);
var _wm = Cc["@mozilla.org/appshell/window-mediator;1"]
.getService(Ci.nsIWindowMediator);
//}##########################
//{### Utility Functions
//|##########################
// The property "hidden" of a tab is read-only, therefore assigning to this property does not work. See Firefox's source code, browser/base/content/tabbrowser.xml
this.tabSetHidden = function tabSetHidden(tab, hidden) {
if (hidden)
gBrowser.hideTab(tab);
else
gBrowser.showTab(tab);
};
// A log of all reported errors is kept, in case the Error Console loses them!
this.logs = {
dump: [],
log: [],
debug: []
};
// For errors or warnings, with automatic line numbers, call stack, etc.
this.dump = function dump(error, actualException) {
try {
if (_prefs.getBoolPref("debug")) {
var scriptError = Cc["@mozilla.org/scripterror;1"].
createInstance(Ci.nsIScriptError);
if (!actualException && typeof error == "object")
actualException = error;
var haveException = actualException ? true : false;
if (haveException && actualException.stack) {
var stack = actualException.stack;
}
else {
var stack = new Error().stack; // Get call stack (could use Components.stack.caller instead)
stack = stack.substring(stack.indexOf("\n", stack.indexOf("\n")+1)+1); // Remove the two lines due to calling this
}
var message = 'TK Error: "' + error + '"\nat:\u00A0' + stack.replace("\n@:0", "").replace(/\n/g, "\n "); // \u00A0 is a non-breaking space
var sourceName = (haveException && "fileName" in actualException && actualException.fileName) ? actualException.fileName : Components.stack.caller.filename;
var sourceLine = (haveException && "sourceLine" in actualException && actualException.sourceLine) ? actualException.sourceLine : Components.stack.caller.sourceLine; // Unfortunately this is probably null
var lineNumber = (haveException && "lineNumber" in actualException && actualException.lineNumber) ? actualException.lineNumber : Components.stack.caller.lineNumber; // error.lineNumber isn't always accurate, unfortunately - sometimes might be better to just ignore it
var columnNumber = (haveException && "columnNumber" in actualException && actualException.columnNumber) ? actualException.columnNumber : 0;
var flags = haveException ? scriptError.errorFlag : scriptError.warningFlag;
var category = "JavaScript error"; // TODO-P6: TJS Check this
scriptError.init(message, sourceName, sourceLine, lineNumber, columnNumber, flags, category);
tk.logs.dump.push(scriptError);
_console.logMessage(scriptError);
}
else {
tk.logs.dump.push(String(error) + "\n" + tk.quickStack());
}
}
catch (ex) {
}
};
// For logging information (no line numbers, call stack, etc.)
this.log = function log(message) {
try {
tk.logs.log.push(message);
if (_prefs.getBoolPref("debug")) {
var msg = "TK: " + message;
_console.logStringMessage(msg);
}
}
catch (ex) {
}
};
// For minor/normal information that could still be interesting
this.debug = function debug(message) {
try {
tk.logs.debug.push(message);
if (_prefs.getBoolPref("debug") && _prefs.getBoolPref("debugMinorToo")) {
var msg = "TK Debug: " + message;
_console.logStringMessage(msg);
}
}
catch (ex) {
}
};
/* USAGE:
* tk.assert('true != false', function(e) eval(e), "True should not equal false");
*/
this.assert = function assert(condition, localEval, message) {
if (!_prefs.getBoolPref("debug") || localEval(condition))
return;
var title = "Assert Failed: '" + condition + "' in " + Components.stack.caller.name + "(";
// Append arguments to title
if (arguments.callee.caller.arguments.length > 0)
title += uneval(arguments.callee.caller.arguments[0]);
for (var i = 1; i < arguments.callee.caller.arguments.length; i++)
title += ", " + uneval(arguments.callee.caller.arguments[i]);
title += ")";
var msg = (message ? message + "\n\n" : "") + "Stacktrace:\n" + tk.quickStack();
tk.dump(title + "\n\n" + msg);
// quickprompt requires my (currently unreleased) QuickPrompt extension
if ("quickprompt" in window)
quickprompt(localEval, title, msg, "help()");
};
this.startsWith = function startsWith(str, start) {
return str.indexOf(start) == 0;
};
this.endsWith = function endsWith(str, end) {
var startPos = str.length - end.length;
if (startPos < 0)
return false;
return str.lastIndexOf(end, startPos) == startPos;
};
this.rawMD5 = function rawMD5(str) {
var converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
createInstance(Ci.nsIScriptableUnicodeConverter);
converter.charset = "UTF-8";
// result is an out parameter, result.value will contain the array length
var result = {};
// data is an array of bytes
var data = converter.convertToByteArray(str, result);
var ch = Cc["@mozilla.org/security/hash;1"].
createInstance(Ci.nsICryptoHash);
ch.init(ch.MD5);
ch.update(data, data.length);
return ch.finish(false);
};
// Returns a random integer between min and max
// Using Math.round() will give you a non-uniform distribution!
// Thanks to http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Math:random
this.randInt = function randInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
};
this.getWindowValue = function getWindowValue(aKey) {
if (_ss)
return _ss.getWindowValue(window, aKey);
else
return (aKey in _winvars ? _winvars[aKey] : "");
};
this.setWindowValue = function setWindowValue(aKey, aStringValue) {
if (_ss)
_ss.setWindowValue(window, aKey, aStringValue);
else
_winvars[aKey] = aStringValue;
};
this.deleteWindowValue = function removeWindowValue(aKey) {
if (_ss)
_ss.deleteWindowValue(window, aKey);
else
delete _winvars[aKey];
};
this.addDelayedEventListener = function addDelayedEventListener(target, eventType, listener) {
if (typeof listener == "object") {
target.addEventListener(eventType, function __delayedEventListener(event) {
window.setTimeout(function(listener) { listener.handleEvent(event); }, 0, listener);
}, false);
}
else {
target.addEventListener(eventType, function __delayedEventListener(event) {
window.setTimeout(function(listener) { listener(event); }, 0, listener);
}, false);
}
};
// TODO=P4: GCODE scrollOneExtra should also apply with a single-row horizontal tab bar
// TODO=P3: GCODE Could always keep selected tab in centre of tabbar instead (whether horizontal or vertical?)
this.scrollToElement = function scrollToElement(overflowPane, targetItem) { // TODO-P6: TJS cleanup code? [based on toomanytabs]
var scrollbar = overflowPane.mVerticalScrollbar;
if (!scrollbar) {
/*
// Alternative way to scroll things (can only scroll within an <xul:scrollbox> though)
if (overflowPane.localName != "scrollbox")
overflowPane = overflowPane.parentNode;
if (overflowPane.localName == "scrollbox") {
var nsIScrollBoxObject = overflowPane.boxObject.QueryInterface(Ci.nsIScrollBoxObject);
nsIScrollBoxObject.ensureElementIsVisible(element);
}
//TODO: Make sure overflowPane is never scrolled halfway across elements at both the top and bottom
//TODO: _prefs.getBoolPref("scrollOneExtra")
*/
return;
}
var container = targetItem.parentNode;
// Sometimes it is null, Don't know what happen
// Just ignore it at he moment
if (container == null) return;
var firstChild = container.firstChild;
// tk.log("scrollToElement "+firstChild.nodeName);
while (firstChild.hidden) // visibility of a tab
firstChild = firstChild.nextSibling;
var lastChild = container.lastChild;
while (lastChild.hidden) // visibility of a tab
lastChild = lastChild.previousSibling;
var curpos = parseInt(scrollbar.getAttribute("curpos"));
if (isNaN(curpos)) {
tk.debug("curpos was NaN");
curpos = 0;
}
var firstY = firstChild.boxObject.y;
var targetY = targetItem.boxObject.y;
var lastY = lastChild.boxObject.y;
var height = targetItem.boxObject.height;
var relativeY = targetY - firstY;
var paneHeight = overflowPane.boxObject.height;
// Make sure overflowPane is never scrolled halfway across elements at both the top and bottom
if ((lastY - firstY) % height == 0 && curpos % height != 0 && (curpos + paneHeight + firstY - lastY) % height != 0) {
curpos = height * Math.round(curpos / height);
}
var minpos = relativeY;
if (_prefs.getBoolPref("scrollOneExtra") && minpos > 0 && lastY - firstY > height) {
minpos -= height;
}
if (minpos < curpos) {
curpos = minpos; // Set it to minpos
}
else {
var maxpos = relativeY + height - paneHeight;
// tk.debug("relativeY = "+relativeY);
// tk.debug("height = "+height);
// tk.debug("paneHeight = "+paneHeight);
// tk.debug("maxpos = "+maxpos);
// tk.debug("curpos = "+curpos);
if (_prefs.getBoolPref("scrollOneExtra") && targetY < lastY && lastY - firstY > height) {
maxpos += height;
}
if (maxpos > curpos) {
curpos = maxpos; // Set it to maxpos
}
}
scrollbar.setAttribute("curpos", curpos);
};
this.moveBefore = function moveBefore(tabToMove, target) {
try {
var newIndex = target._tPos;
if (newIndex > tabToMove._tPos)
newIndex--;
if (newIndex != tabToMove._tPos)
gBrowser.moveTabTo(tabToMove, newIndex);
}
catch (ex) {
tk.dump(ex);
}
};
this.moveAfter = function moveAfter(tabToMove, target) {
try {
var newIndex = target._tPos + 1;
if (newIndex > tabToMove._tPos)
newIndex--;
if (newIndex != tabToMove._tPos)
gBrowser.moveTabTo(tabToMove, newIndex);
}
catch (ex) {
tk.dump(ex);
}
};
this.quickStack = function quickStack() {
// Intended mainly for outputting to the console
var func = arguments.callee.caller.caller;
var stack = "";
for (var i = 1; func && i < 8; i++) {
stack += " " + i + ". " + func.name;
func = func.caller;
}
return stack;
};
this.beep = function beep() {
_sound.beep();
};
this.getTabId = function (tab, forceUpdate) {
if (forceUpdate == null) {
forceUpdate = false;
}
if (forceUpdate || !tab.hasAttribute('tabid')) {
tab.setAttribute("tabid", tk.generateId());
}
return tab.getAttribute('tabid');
};
this.TabBar = this.TabBar || {};
this.TabBar.Mode = this.TabBar.Mode || {};
// @return [Boolean] if vertical mode, true
this.TabBar.Mode.getIsVerticalMode = function getIsVerticalMode () {
return gBrowser.hasAttribute("vertitabbar");
};
this.DomUtility = this.DomUtility || {};
this.DomUtility.insertBefore = function insertBefore(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode);
};
this.DomUtility.insertAfter = function insertAfter(referenceNode, newNode) {
referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
};
//}##########################
//{### Initialisation
//|##########################
// USAGE: this.*InitListeners.push(this.*Init*);
/// Globals:
this.preInitListeners = [
];
this.initListeners = [
];
this.postInitListeners = [
];
/// Private Globals:
/// Methods:
this.tryListener = function tryListener(type, listener, event) {
try {
listener(event);
}
catch (ex) {
var listenerString = "name" in listener ? listener.name : listener.toSource().substring(0, 78);
tk.dump(type + " listener '" + listenerString + "' failed with exception:\n" + ex, ex);
}
};
/// Event Listeners:
// This gets called for new browser windows, once the DOM tree is loaded
this.onDOMContentLoaded = function onDOMContentLoaded(event) {
if (event.originalTarget != document)
return; // Sometimes in Fx3+ there's a random HTMLDocument that fires a DOMContentLoaded before the main window does
window.removeEventListener("DOMContentLoaded", tk.onDOMContentLoaded, false);
// Find what version of Firefox we're using TODO=P4: TJS+GCODE Do this in a less hacky way. Or better still, just drop support for Fx2
// Check compatibility with existing addons (only in Fx3+, as extensions.enabledItems doesn't exist before that, and not in Fx4+ since _em is unavailable)
if (_prefs.getBoolPref("checkCompatibility")
&& _em != null
&& gPrefService.getPrefType("extensions.enabledItems") == gPrefService.PREF_STRING)
{
// TODO=P3: GCODE Only check compatibility on first loaded window; future windows should follow what the first window did
var incompatible = [
{ id: "{dc572301-7619-498c-a57d-39143191b318}", name: "Tab Mix Plus" }
// TODO: TJS Before adding more extensions here, change the neverCheckCompatibility pref so it's per extension instead of being global
];
var enabledAddons = gPrefService.getCharPref("extensions.enabledItems");
var needsRestart = false;
for each (var addon in incompatible) {
if (enabledAddons.indexOf(addon.id) != -1) {
// Focus the window before prompting.
// This will raise any minimized window, which will
// make it obvious which window the prompt is for and will
// solve the problem of windows "obscuring" the prompt.
// See bug #350299 for more details
window.focus();
var check = { value: false };
var strings = document.getElementById("bundle_tabkit");
var flags = _ps.BUTTON_POS_0 * _ps.BUTTON_TITLE_IS_STRING
+ _ps.BUTTON_POS_1 * _ps.BUTTON_TITLE_IS_STRING
+ _ps.BUTTON_POS_2 * _ps.BUTTON_TITLE_IS_STRING;
var button = _ps.confirmEx(
window, //aParent
strings.getString("tab_kit"), //aDialogTitle
strings.getFormattedString("incompatible_warning", [ addon.name ]), //aText
flags, // aButtonFlags
strings.getFormattedString("incompatible_disable", [ addon.name ]), //aButton0Title
strings.getString("incompatible_ignore"), //aButton1Title // This has to be button 1 due to Bug 345067 - Issues with prompt service's confirmEx - confirmEx always returns 1 when user closes dialog window using the X button in titlebar
strings.getFormattedString("incompatible_disable", [ strings.getString("tab_kit") ]), //aButton2Title
strings.getString("incompatible_dont_ask_again"), //aCheckMsg
check //aCheckState
);
if (button == 0) { // Disable addon
_em.disableItem(addon.id);
needsRestart = true;
}
else if (button == 2) { // Disable Tab Kit
_em.disableItem("tabkit@jomel.me.uk");
window.removeEventListener("load", tk.onLoad, false);
return; // Cancel load
}
else { // Ignore
if (check.value)
_prefs.setBoolPref("checkCompatibility", false);
}
}
}
if (needsRestart) {
// TODO=P3: GCODE Try to disable incompatible extensions by disabling onLoad methods etc to avoid restarting / allow selective disabling
window.focus();
var strings = document.getElementById("bundle_tabkit"); // This line is technically redundant, but it's clearer like this
var flags = _ps.BUTTON_POS_0 * _ps.BUTTON_TITLE_IS_STRING
+ _ps.BUTTON_POS_1 * _ps.BUTTON_TITLE_IS_STRING;
var button = _ps.confirmEx(
window,
strings.getString("tab_kit"),
strings.getString("incompatible_restart"),
flags,
strings.getString("incompatible_restart_now"),
strings.getString("incompatible_restart_later"),
"", // No third button
null, // No checkbox
{value: false} // No checkbox
);
if (button == 0) {
// Notify all windows that an application quit has been requested.
var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"]
.createInstance(Components.interfaces.nsISupportsPRBool);
_os.notifyObservers(cancelQuit, "quit-application-requested", "restart");
if (!cancelQuit.data) { // Quit unless we were told not to
Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup)
.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
}
}
window.removeEventListener("load", tk.onLoad, false);
return; // Cancel load if we haven't restarted
}
}
// Run First Run Wizard if appropriate
if (!_prefs.getBoolPref("firstRunWizardDone")) {
window.setTimeout(function __startfirstRunWizard() {
gBrowser.selectedTab = gBrowser.addTab("chrome://tabkit/content/firstRunWizard.xul");
}, 1500);
}
// Run module early initialisation code (before any init* listeners, and before most extensions):
for each (var listener in tk.preInitListeners) {
tk.tryListener("DOMContentLoaded", listener, event);
}
};
// This gets called for new browser windows, once they've completely finished loading
this.onLoad = function onLoad(event) {
if (event.originalTarget != document)
return;
window.removeEventListener("load", tk.onLoad, false);
// Run module specific initialisation code, such as registering event listeners:
for each (var listener in tk.initListeners) {
tk.tryListener("load", listener, event);
}
window.setTimeout(function __runPostInitListeners() {
// Run module specific late initialisation code (after all init* listeners, and after most extensions):
for each (var listener in tk.postInitListeners) {
listener(event);
}
}, 0);
};
//}##########################
//{### CSS
//|##########################
this.UAStyleSheets = [ "chrome://tabkit/content/ua.css" ];
this.preInitUAStyleSheets = function preInitUAStyleSheets(event) {
// [Fx3only] it seems
var sss = Cc["@mozilla.org/content/style-sheet-service;1"].getService(Ci.nsIStyleSheetService);
var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
for each (var s in tk.UAStyleSheets) {
var uri = ios.newURI(s, null, null);
if (!sss.sheetRegistered(uri, sss.AGENT_SHEET))
sss.loadAndRegisterSheet(uri, sss.AGENT_SHEET);
}
};
this.preInitListeners.push(this.preInitUAStyleSheets);
// Return (or delete) a style rule object by selector
// Based on http://www.hunlock.com/blogs/Totally_Pwn_CSS_with_Javascript
this.getCSSRule = function getCSSRule(ruleName, deleteIt) {
ruleName = ruleName.toLowerCase();
for (var i = 0; i < document.styleSheets.length; i++) {
var styleSheet = document.styleSheets[i];
for (var j = 0; j < styleSheet.cssRules.length; j++) {
var cssRule = styleSheet.cssRules[j];
if ("selectorText" in cssRule && cssRule.selectorText && cssRule.selectorText.toLowerCase() == ruleName) {
if (deleteIt) {
styleSheet.deleteRule(j);
return true;
}
return cssRule;
}
}
}
return false;
};
//}##########################
//{### Useful shortcuts
//|##########################
/*
Warning on using shortcuts!!!!
PikachuEXE:
There are so many minor bugs are caused by these shortcuts
I am not talking about not using them at all
But avoid using them when you call something dynamic
For example, if a scrollbar only appears (created) when there are too many tabs,
you should avoid calling its parent through shortcuts, since the "shortcut" seems to be "outdated"
2012-05-14: _tabstrip cause Multirow buggy, hence shortcut removed (_tabContainer seems fine though)
*/
/// Private Globals:
var _tabContainer;
//var _tabstrip;
var _tabInnerBox;
var _tabs;
var _tabBar;
/// Initialisation:
this.preInitShortcuts = function preInitShortcuts(event) {
//tk.assert('window.location == "chrome://browser/content/browser.xul"', function(e) eval(e), "preInitShortcuts should only be run in browser windows, as tabkit.js is only loaded into browser.xul");
if(window.location != "chrome://browser/content/browser.xul")
tk.dump("preInitShortcuts should only be run in browser windows, as tabkit.js is only loaded into browser.xul");
// Make sure we can use gBrowser from now on if this is a browser window
getBrowser();
//tk.assert('gBrowser', function(e) eval(e), "gBrowser must not be null after preInitShortcuts!");
if(!gBrowser)
tk.dump("gBrowser must not be null after preInitShortcuts!");
_tabContainer = gBrowser.tabContainer;
//_tabstrip = _tabContainer.mTabstrip;
_tabInnerBox = document.getAnonymousElementByAttribute(_tabContainer.mTabstrip._scrollbox, "class", "box-inherit scrollbox-innerbox");
_tabs = gBrowser.tabs;
_tabBar = document.getElementById("TabsToolbar");
};
this.preInitListeners.push(this.preInitShortcuts);
//}##########################
//{### Prefs Observers
//|##########################
/// Private globals:
var _globalPrefObservers = {};
var _localPrefListeners = {};
/// Initialisation:
this.preInitPrefsObservers = function preInitPrefsObservers(event) {
// Make sure we can use addObserver on this
gPrefService.QueryInterface(Ci.nsIPrefBranch);
// Do this in preInit just in case something expects their init prefListener to work 'instantly'
tk.addGlobalPrefListener(PREF_BRANCH, tk.localPrefsListener);
};
this.preInitListeners.push(this.preInitPrefsObservers);
/// Pref Listeners:
// This listener checks all changes to the extension's pref branch, and delegates them to their registered listeners
// Presumeably more efficient than simply adding a global observer for each one...
this.localPrefsListener = function localPrefsListener(changedPref) {
changedPref = changedPref.substring(PREF_BRANCH.length); // Remove prefix for these local prefs
for (var prefName in _localPrefListeners) {
if (changedPref.substring(0, prefName.length) == prefName) {
for each (var listener in _localPrefListeners[prefName]) {
listener(changedPref);
}
}
}
};
/// Methods:
this.addGlobalPrefListener = function addGlobalPrefListener(prefString, prefListener) {
if (!_globalPrefObservers[prefString]) {
_globalPrefObservers[prefString] = {
listeners: [],
observe: function(aSubject, aTopic, aData) {
if (aTopic != "nsPref:changed") return;
// aSubject is the nsIPrefBranch we're observing (after appropriate QI)
// aData is the name of the pref that's been changed (relative to aSubject)
for each (var listener in this.listeners) {
listener(aData);
}
}
};
gPrefService.addObserver(prefString, _globalPrefObservers[prefString], false);
window.addEventListener("unload", function() { gPrefService.removeObserver(prefString, _globalPrefObservers[prefString]); }, false);
}
_globalPrefObservers[prefString].listeners.push(prefListener);
};
this.addPrefListener = function addPrefListener(prefName, listener) {
if (!_localPrefListeners[prefName]) {
_localPrefListeners[prefName] = [];
}
_localPrefListeners[prefName].push(listener);
};
//}##########################
//{### Pref-attribute Mapping
//|##########################
this.mapPrefToAttribute = function mapPrefToAttribute(prefName, test, node, attributeName) {
var listener = function() {
var value = test(prefName);
if (value !== undefined) {
node.setAttribute(attributeName, value);
}
else {
node.removeAttribute(attributeName);
}
};
tk.addPrefListener(prefName, listener);
// Call it once on start
listener();
};
this.mapBoolPrefToAttribute = function mapBoolPrefToAttribute(prefName, node, attributeName) {
tk.mapPrefToAttribute(prefName, function() { return _prefs.getBoolPref(prefName) ? "true" : undefined; }, node, attributeName);
};
//}##########################
//{### Method Hooks
//|##########################
// USAGE: this.*MethodHooks.push([<original method>, <where to backup>, <search>, <replacement>]);
// e.g. this.lateMethodHooks.push(['gBrowser.addTab', 'gBrowser._doAddTab', 't._tPos = position;', 't._tPos = position; alert("hi!");']);
// Warning: if you make a backup of the original method and wish to call it, you must save it onto the same object as the original!